From 5e89371ecdba4eb706841a6776b174d1194acff4 Mon Sep 17 00:00:00 2001 From: grorp Date: Tue, 25 Feb 2025 13:19:44 -0500 Subject: [PATCH 001/284] TouchControls: touch_use_crosshair, dig/place simulation refactoring (#15800) - get rid of simulated mouse events for digging/placing, use keyboard events instead - consistent with other simulated events, less code, no need for a pointer position - more correct: touch controls no longer break if you have custom dig/place keybindings set - move reading of "touch_use_crosshair" setting from Game to TouchControls --- src/client/game.cpp | 24 +++++---------- src/gui/touchcontrols.cpp | 56 ++++++++++++----------------------- src/gui/touchcontrols.h | 18 ++++++----- src/gui/touchscreenlayout.cpp | 12 +++++++- src/gui/touchscreenlayout.h | 6 +++- 5 files changed, 53 insertions(+), 63 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 12a39e6ee..b66716036 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -772,9 +772,10 @@ private: bool m_is_paused = false; bool m_touch_simulate_aux1 = false; - bool m_touch_use_crosshair; - inline bool isTouchCrosshairDisabled() { - return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST; + inline bool isTouchShootlineUsed() + { + return g_touchcontrols && g_touchcontrols->isShootlineAvailable() && + camera->getCameraMode() == CAMERA_MODE_FIRST; } #ifdef __ANDROID__ bool m_android_chat_open; @@ -823,8 +824,6 @@ Game::Game() : &settingChangedCallback, this); g_settings->registerChangedCallback("pause_on_lost_focus", &settingChangedCallback, this); - g_settings->registerChangedCallback("touch_use_crosshair", - &settingChangedCallback, this); readSettings(); } @@ -1380,10 +1379,8 @@ bool Game::initGui() gui_chat_console = make_irr(guienv, guienv->getRootGUIElement(), -1, chat_backend, client, &g_menumgr); - if (shouldShowTouchControls()) { + if (shouldShowTouchControls()) g_touchcontrols = new TouchControls(device, texture_src); - g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); - } return true; } @@ -2980,9 +2977,6 @@ void Game::updateCameraMode() if (player->allowed_camera_mode != CAMERA_MODE_ANY) camera->setCameraMode(player->allowed_camera_mode); - if (g_touchcontrols) - g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); - GenericCAO *playercao = player->getCAO(); if (playercao) { // Make the player visible depending on camera mode. @@ -3086,7 +3080,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) } shootline.end = shootline.start + camera_direction * BS * d; - if (g_touchcontrols && isTouchCrosshairDisabled()) { + if (isTouchShootlineUsed()) { shootline = g_touchcontrols->getShootline(); // Scale shootline to the acual distance the player can reach shootline.end = shootline.start + @@ -4059,7 +4053,7 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats) (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && (this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); - if (g_touchcontrols && isTouchCrosshairDisabled()) + if (isTouchShootlineUsed()) draw_crosshair = false; this->m_rendering_engine->draw_scene(sky_color, this->m_game_ui->m_flags.show_hud, @@ -4139,10 +4133,6 @@ void Game::readSettings() m_invert_hotbar_mouse_wheel = g_settings->getBool("invert_hotbar_mouse_wheel"); m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus"); - - m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair"); - if (g_touchcontrols) - g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); } /****************************************************************************/ diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index 8a829d90e..7dd837d9e 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -142,6 +142,12 @@ static EKEY_CODE id_to_keycode(touch_gui_button_id id) std::string key = ""; switch (id) { + case dig_id: + key = "dig"; + break; + case place_id: + key = "place"; + break; case jump_id: key = "jump"; break; @@ -204,6 +210,7 @@ static EKEY_CODE id_to_keycode(touch_gui_button_id id) static const char *setting_names[] = { + "touch_use_crosshair", "touchscreen_threshold", "touch_long_tap_delay", "fixed_virtual_joystick", "virtual_joystick_triggers_aux1", "touch_layout", @@ -230,6 +237,7 @@ void TouchControls::settingChangedCallback(const std::string &name, void *data) void TouchControls::readSettings() { + m_use_crosshair = g_settings->getBool("touch_use_crosshair"); m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold"); m_long_tap_delay = g_settings->getU16("touch_long_tap_delay"); m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick"); @@ -542,10 +550,11 @@ void TouchControls::translateEvent(const SEvent &event) m_move_has_really_moved = false; m_move_downtime = porting::getTimeMs(); m_move_pos = touch_pos; - // DON'T reset m_tap_state here, otherwise many short taps - // will be ignored if you tap very fast. m_had_move_id = true; m_move_prevent_short_tap = prevent_short_tap; + + // DON'T reset m_tap_state here, otherwise many short taps + // will be ignored if you tap very fast. } } } @@ -663,15 +672,13 @@ void TouchControls::step(float dtime) // Since not only the pointer position, but also the player position and // thus the camera position can change, it doesn't suffice to update the // shootline when a touch event occurs. - // Note that the shootline isn't used if touch_use_crosshair is enabled. // Only updating when m_has_move_id means that the shootline will stay at // it's last in-world position when the player doesn't need it. - if (!m_draw_crosshair && (m_has_move_id || m_had_move_id)) { - v2s32 pointer_pos = getPointerPos(); + if (!m_use_crosshair && (m_has_move_id || m_had_move_id)) { m_shootline = m_device ->getSceneManager() ->getSceneCollisionManager() - ->getRayFromScreenCoordinates(pointer_pos); + ->getRayFromScreenCoordinates(m_move_pos); } m_had_move_id = false; } @@ -734,11 +741,11 @@ void TouchControls::releaseAll() // Release those manually too since the change initiated by // handleReleaseEvent will only be applied later by applyContextControls. if (m_dig_pressed) { - emitMouseEvent(EMIE_LMOUSE_LEFT_UP); + emitKeyboardEvent(id_to_keycode(dig_id), false); m_dig_pressed = false; } if (m_place_pressed) { - emitMouseEvent(EMIE_RMOUSE_LEFT_UP); + emitKeyboardEvent(id_to_keycode(place_id), false); m_place_pressed = false; } } @@ -753,31 +760,6 @@ void TouchControls::show() setVisible(true); } -v2s32 TouchControls::getPointerPos() -{ - if (m_draw_crosshair) - return v2s32(m_screensize.X / 2, m_screensize.Y / 2); - // We can't just use m_pointer_pos[m_move_id] because applyContextControls - // may emit release events after m_pointer_pos[m_move_id] is erased. - return m_move_pos; -} - -void TouchControls::emitMouseEvent(EMOUSE_INPUT_EVENT type) -{ - v2s32 pointer_pos = getPointerPos(); - - SEvent event{}; - event.EventType = EET_MOUSE_INPUT_EVENT; - event.MouseInput.X = pointer_pos.X; - event.MouseInput.Y = pointer_pos.Y; - event.MouseInput.Shift = false; - event.MouseInput.Control = false; - event.MouseInput.ButtonStates = 0; - event.MouseInput.Event = type; - event.MouseInput.Simulated = true; - m_receiver->OnEvent(event); -} - void TouchControls::applyContextControls(const TouchInteractionMode &mode) { // Since the pointed thing has already been determined when this function @@ -844,20 +826,20 @@ void TouchControls::applyContextControls(const TouchInteractionMode &mode) target_place_pressed |= now < m_place_pressed_until; if (target_dig_pressed && !m_dig_pressed) { - emitMouseEvent(EMIE_LMOUSE_PRESSED_DOWN); + emitKeyboardEvent(id_to_keycode(dig_id), true); m_dig_pressed = true; } else if (!target_dig_pressed && m_dig_pressed) { - emitMouseEvent(EMIE_LMOUSE_LEFT_UP); + emitKeyboardEvent(id_to_keycode(dig_id), false); m_dig_pressed = false; } if (target_place_pressed && !m_place_pressed) { - emitMouseEvent(EMIE_RMOUSE_PRESSED_DOWN); + emitKeyboardEvent(id_to_keycode(place_id), true); m_place_pressed = true; } else if (!target_place_pressed && m_place_pressed) { - emitMouseEvent(EMIE_RMOUSE_LEFT_UP); + emitKeyboardEvent(id_to_keycode(place_id), false); m_place_pressed = false; } } diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index ea71e2c86..9241e3252 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -93,6 +93,8 @@ public: return res; } + bool isShootlineAvailable() { return !m_use_crosshair; } + /** * Returns a line which describes what the player is pointing at. * The starting point and looking direction are significant, @@ -100,6 +102,9 @@ public: * the player can reach. * The line starts at the camera and ends on the camera's far plane. * The coordinates do not contain the camera offset. + * + * This may only be used if isShootlineAvailable returns true. + * Otherwise, the normal crosshair must be used. */ line3d getShootline() { return m_shootline; } @@ -107,7 +112,6 @@ public: float getJoystickSpeed() { return m_joystick_speed; } void step(float dtime); - inline void setUseCrosshair(bool use_crosshair) { m_draw_crosshair = use_crosshair; } void setVisible(bool visible); void hide(); @@ -132,6 +136,7 @@ private: s32 m_button_size; // cached settings + bool m_use_crosshair; double m_touchscreen_threshold; u16 m_long_tap_delay; bool m_fixed_joystick; @@ -143,9 +148,6 @@ private: ButtonLayout m_layout; void applyLayout(const ButtonLayout &layout); - // not read from a setting, but set by Game via setUseCrosshair - bool m_draw_crosshair = false; - std::unordered_map m_hotbar_rects; std::optional m_hotbar_selection = std::nullopt; @@ -157,6 +159,8 @@ private: * A line starting at the camera and pointing towards the selected object. * The line ends on the camera's far plane. * The coordinates do not contain the camera offset. + * + * Only valid if !m_use_crosshair */ line3d m_shootline; @@ -164,7 +168,9 @@ private: size_t m_move_id; bool m_move_has_really_moved = false; u64 m_move_downtime = 0; - // m_move_pos stays valid even after m_move_id has been released. + // m_move_pos stays valid even after the m_move_id pointer has been + // released and m_pointer_pos[m_move_id] has been erased + // (or even overwritten by a new pointer reusing the same id). v2s32 m_move_pos; // This is needed so that we don't miss if m_has_move_id is true for less // than one client step, i.e. press and release happen in the same step. @@ -236,8 +242,6 @@ private: // map to store the IDs and positions of currently pressed pointers std::unordered_map m_pointer_pos; - v2s32 getPointerPos(); - void emitMouseEvent(EMOUSE_INPUT_EVENT type); TouchInteractionMode m_last_mode = TouchInteractionMode_END; TapState m_tap_state = TapState::None; diff --git a/src/gui/touchscreenlayout.cpp b/src/gui/touchscreenlayout.cpp index 0a88f5dd5..e0ad5b723 100644 --- a/src/gui/touchscreenlayout.cpp +++ b/src/gui/touchscreenlayout.cpp @@ -14,6 +14,9 @@ #include "IGUIStaticText.h" const char *button_names[] = { + "dig", + "place", + "jump", "sneak", "zoom", @@ -41,6 +44,9 @@ const char *button_names[] = { // compare with GUIKeyChangeMenu::init_keys const char *button_titles[] = { + N_("Dig/punch/use"), + N_("Place/use"), + N_("Jump"), N_("Sneak"), N_("Zoom"), @@ -67,6 +73,9 @@ const char *button_titles[] = { }; const char *button_image_names[] = { + "", + "", + "jump_btn.png", "down.png", "zoom.png", @@ -123,7 +132,8 @@ void ButtonMeta::setPos(v2s32 pos, v2u32 screensize, s32 button_size) bool ButtonLayout::isButtonAllowed(touch_gui_button_id id) { - return id != joystick_off_id && id != joystick_bg_id && id != joystick_center_id && + return id != dig_id && id != place_id && + id != joystick_off_id && id != joystick_bg_id && id != joystick_center_id && id != touch_gui_button_id_END; } diff --git a/src/gui/touchscreenlayout.h b/src/gui/touchscreenlayout.h index 9c7d72fbf..fe0d99b9d 100644 --- a/src/gui/touchscreenlayout.h +++ b/src/gui/touchscreenlayout.h @@ -22,7 +22,11 @@ namespace irr::video enum touch_gui_button_id : u8 { - jump_id = 0, + // these two are no actual buttons ... yet + dig_id = 0, + place_id, + + jump_id, sneak_id, zoom_id, aux1_id, From ee9258cefda49e2b33eaac45de47c74adab0d6a9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 22 Feb 2025 16:37:45 +0100 Subject: [PATCH 002/284] Clean up some packet-related code --- src/client/client.cpp | 14 +-- src/client/client.h | 5 - src/mapnode.cpp | 2 +- src/mapnode.h | 2 +- src/network/clientpackethandler.cpp | 94 +++++------------- src/network/networkpacket.cpp | 33 ++---- src/network/networkpacket.h | 18 ++-- src/network/serverpackethandler.cpp | 149 ++++++++-------------------- src/server.cpp | 6 +- src/unittest/test_connection.cpp | 4 +- src/util/serialize.cpp | 6 +- 11 files changed, 94 insertions(+), 239 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 7a370b34d..9d06f3055 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -398,11 +398,7 @@ void Client::step(float dtime) if (dtime > DTIME_LIMIT) dtime = DTIME_LIMIT; - m_animation_time += dtime; - if(m_animation_time > 60.0) - m_animation_time -= 60.0; - - m_time_of_day_update_timer += dtime; + m_animation_time = fmodf(m_animation_time + dtime, 60.0f); ReceiveAll(); @@ -873,14 +869,6 @@ void Client::deletingPeer(con::IPeer *peer, bool timeout) m_access_denied_reason = gettext("Connection aborted (protocol error?)."); } -/* - u16 command - u16 number of files requested - for each file { - u16 length of name - string name - } -*/ void Client::request_media(const std::vector &file_requests) { std::ostringstream os(std::ios_base::binary); diff --git a/src/client/client.h b/src/client/client.h index 7a183a4fe..ce753ebdf 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -538,11 +538,6 @@ private: // Pending downloads of dynamic media (key: token) std::vector>> m_pending_media_downloads; - // time_of_day speed approximation for old protocol - bool m_time_of_day_set = false; - float m_last_time_of_day_f = -1.0f; - float m_time_of_day_update_timer = 0.0f; - // An interval for generally sending object positions and stuff float m_recommended_send_interval = 0.1f; diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 22fc36086..a82cbe041 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -561,7 +561,7 @@ void MapNode::serialize(u8 *dest, u8 version) const writeU8(dest+2, param1); writeU8(dest+3, param2); } -void MapNode::deSerialize(u8 *source, u8 version) +void MapNode::deSerialize(const u8 *source, u8 version) { if (!ser_ver_supported_read(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); diff --git a/src/mapnode.h b/src/mapnode.h index c0f1f4f10..5366caea2 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -296,7 +296,7 @@ struct alignas(u32) MapNode static u32 serializedLength(u8 version); void serialize(u8 *dest, u8 version) const; - void deSerialize(u8 *source, u8 version); + void deSerialize(const u8 *source, u8 version); // Serializes or deserializes a list of nodes in bulk format (first the // content of all nodes, then the param1 of all nodes, then the param2 diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 3bceb2bbf..05ef689b0 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -132,17 +132,10 @@ void Client::handleCommand_AuthAccept(NetworkPacket* pkt) { deleteAuthData(); - v3f playerpos; - *pkt >> playerpos >> m_map_seed >> m_recommended_send_interval + v3f unused; + *pkt >> unused >> m_map_seed >> m_recommended_send_interval >> m_sudo_auth_methods; - playerpos -= v3f(0, BS / 2, 0); - - // Set player position - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player != NULL); - player->setPosition(playerpos); - infostream << "Client: received map seed: " << m_map_seed << std::endl; infostream << "Client: received recommended send interval " << m_recommended_send_interval<getCommand() != TOCLIENT_ACCESS_DENIED) { - // Legacy code from 0.4.12 and older but is still used - // in some places of the server code + // Servers older than 5.6 still send TOCLIENT_ACCESS_DENIED_LEGACY sometimes. + // see commit a65f6f07f3a5601207b790edcc8cc945133112f7 if (pkt->getSize() >= 2) { std::wstring wide_reason; *pkt >> wide_reason; @@ -231,9 +225,6 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) void Client::handleCommand_RemoveNode(NetworkPacket* pkt) { - if (pkt->getSize() < 6) - return; - v3s16 p; *pkt >> p; removeNode(p); @@ -241,20 +232,17 @@ void Client::handleCommand_RemoveNode(NetworkPacket* pkt) void Client::handleCommand_AddNode(NetworkPacket* pkt) { - if (pkt->getSize() < 6 + MapNode::serializedLength(m_server_ser_ver)) - return; - v3s16 p; *pkt >> p; - MapNode n; - n.deSerialize(pkt->getU8Ptr(6), m_server_ser_ver); + auto *ptr = reinterpret_cast(pkt->getRemainingString()); + pkt->skip(MapNode::serializedLength(m_server_ser_ver)); // performs length check - bool remove_metadata = true; - u32 index = 6 + MapNode::serializedLength(m_server_ser_ver); - if ((pkt->getSize() >= index + 1) && pkt->getU8(index)) { - remove_metadata = false; - } + MapNode n; + n.deSerialize(ptr, m_server_ser_ver); + + bool remove_metadata; + *pkt >> remove_metadata; addNode(p, n, remove_metadata); } @@ -272,7 +260,7 @@ void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) meta_updates_list.deSerialize(sstr, m_itemdef, true); Map &map = m_env.getMap(); - for (NodeMetadataMap::const_iterator i = meta_updates_list.begin(); + for (auto i = meta_updates_list.begin(); i != meta_updates_list.end(); ++i) { v3s16 pos = i->first; @@ -294,7 +282,7 @@ void Client::handleCommand_BlockData(NetworkPacket* pkt) v3s16 p; *pkt >> p; - std::string datastring(pkt->getString(6), pkt->getSize() - 6); + std::string datastring(pkt->getRemainingString(), pkt->getRemainingBytes()); std::istringstream istr(datastring, std::ios_base::binary); MapSector *sector; @@ -358,46 +346,15 @@ void Client::handleCommand_TimeOfDay(NetworkPacket* pkt) return; u16 time_of_day; - *pkt >> time_of_day; - time_of_day = time_of_day % 24000; - float time_speed = 0; - if (pkt->getSize() >= 2 + 4) { - *pkt >> time_speed; - } - 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; - - if (time_of_day_f < 0.2 && m_last_time_of_day_f > 0.8) - tod_diff_f = time_of_day_f - m_last_time_of_day_f + 1.0f; - 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_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; - } - } + float time_speed; + *pkt >> time_speed; // Update environment m_env.setTimeOfDay(time_of_day); m_env.setTimeOfDaySpeed(time_speed); - m_time_of_day_set = true; - - //u32 dr = m_env.getDayNightRatio(); - //infostream << "Client: time_of_day=" << time_of_day - // << " time_speed=" << time_speed - // << " dr=" << dr << std::endl; } void Client::handleCommand_ChatMessage(NetworkPacket *pkt) @@ -851,6 +808,8 @@ void Client::handleCommand_PlaySound(NetworkPacket* pkt) pos = cao->getPosition() * (1.0f/BS); vel = cao->getVelocity() * (1.0f/BS); } + // Note that the server sends 'pos' correctly even for attached sounds, + // so this fallback path is not a mistake. m_sound->playSoundAt(client_id, spec, pos, vel); break; } @@ -883,7 +842,7 @@ void Client::handleCommand_StopSound(NetworkPacket* pkt) *pkt >> server_id; - std::unordered_map::iterator i = m_sounds_server_to_client.find(server_id); + auto 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); @@ -898,9 +857,7 @@ void Client::handleCommand_FadeSound(NetworkPacket *pkt) *pkt >> sound_id >> step >> gain; - std::unordered_map::const_iterator i = - m_sounds_server_to_client.find(sound_id); - + auto i = m_sounds_server_to_client.find(sound_id); if (i != m_sounds_server_to_client.end()) m_sound->fadeSound(i->second, step, gain); } @@ -958,8 +915,8 @@ void Client::handleCommand_DetachedInventory(NetworkPacket* pkt) inv = inv_it->second; } - u16 ignore; - *pkt >> ignore; // this used to be the length of the following string, ignore it + // this used to be the length of the following string, ignore it + pkt->skip(2); std::string contents(pkt->getRemainingString(), pkt->getRemainingBytes()); std::istringstream is(contents, std::ios::binary); @@ -1009,7 +966,7 @@ void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) p.amount = readU16(is); p.time = readF32(is); if (p.time < 0) - throw SerializationError("particle spawner time < 0"); + throw PacketError("particle spawner time < 0"); bool missing_end_values = false; if (m_proto_ver >= 42) { @@ -1324,10 +1281,7 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt) for (size_t i = 0; i < count; i++) skybox.textures.emplace_back(deSerializeString16(is)); - skybox.clouds = true; - try { - skybox.clouds = readU8(is); - } catch (...) {} + skybox.clouds = readU8(is) != 0; // Use default skybox settings: SunParams sun = SkyboxDefaults::getSunDefaults(); diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index 7c82b2199..f736d877b 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -49,7 +49,13 @@ const char* NetworkPacket::getString(u32 from_offset) const { checkReadOffset(from_offset, 0); - return (char*)&m_data[from_offset]; + return reinterpret_cast(&m_data[from_offset]); +} + +void NetworkPacket::skip(u32 count) +{ + checkReadOffset(m_read_offset, count); + m_read_offset += count; } void NetworkPacket::putRawString(const char* src, u32 len) @@ -311,24 +317,6 @@ NetworkPacket& NetworkPacket::operator>>(u8& dst) return *this; } -u8 NetworkPacket::getU8(u32 offset) -{ - checkReadOffset(offset, 1); - - return readU8(&m_data[offset]); -} - -u8* NetworkPacket::getU8Ptr(u32 from_offset) -{ - if (m_datasize == 0) { - return NULL; - } - - checkReadOffset(from_offset, 1); - - return &m_data[from_offset]; -} - NetworkPacket& NetworkPacket::operator>>(u16& dst) { checkReadOffset(m_read_offset, 2); @@ -339,13 +327,6 @@ NetworkPacket& NetworkPacket::operator>>(u16& dst) return *this; } -u16 NetworkPacket::getU16(u32 from_offset) -{ - checkReadOffset(from_offset, 2); - - return readU16(&m_data[from_offset]); -} - NetworkPacket& NetworkPacket::operator>>(u32& dst) { checkReadOffset(m_read_offset, 4); diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index 4e4fd78bb..32d378f47 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -35,12 +35,16 @@ public: session_t getPeerId() const { return m_peer_id; } u16 getCommand() const { return m_command; } u32 getRemainingBytes() const { return m_datasize - m_read_offset; } - const char *getRemainingString() { return getString(m_read_offset); } - // Returns a c-string without copying. + // Returns a pointer to buffer data. // A better name for this would be getRawString() const char *getString(u32 from_offset) const; - // major difference to putCString(): doesn't write len into the buffer + const char *getRemainingString() const { return getString(m_read_offset); } + + // Perform length check and skip ahead by `count` bytes. + void skip(u32 count); + + // Appends bytes from string buffer to packet void putRawString(const char *src, u32 len); void putRawString(std::string_view src) { @@ -63,14 +67,9 @@ public: NetworkPacket &operator>>(bool &dst); NetworkPacket &operator<<(bool src); - u8 getU8(u32 offset); - NetworkPacket &operator>>(u8 &dst); NetworkPacket &operator<<(u8 src); - u8 *getU8Ptr(u32 offset); - - u16 getU16(u32 from_offset); NetworkPacket &operator>>(u16 &dst); NetworkPacket &operator<<(u16 src); @@ -114,6 +113,7 @@ public: private: void checkReadOffset(u32 from_offset, u32 field_size) const; + // resize data buffer for writing inline void checkDataSize(u32 field_size) { if (m_read_offset + field_size > m_datasize) { @@ -124,7 +124,7 @@ private: std::vector m_data; u32 m_datasize = 0; - u32 m_read_offset = 0; + u32 m_read_offset = 0; // read and write offset u16 m_command = 0; session_t m_peer_id = 0; }; diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 4e2998101..17ba8ca21 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -325,13 +325,6 @@ void Server::handleCommand_Init2(NetworkPacket* pkt) SendTimeOfDay(peer_id, time, time_speed); SendCSMRestrictionFlags(peer_id); - - // 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!")); - } } void Server::handleCommand_RequestMedia(NetworkPacket* pkt) @@ -421,11 +414,6 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) u8 count; *pkt >> count; - if ((s16)pkt->getSize() < 1 + (int)count * 6) { - throw con::InvalidIncomingDataException - ("GOTBLOCKS length is too short"); - } - ClientInterface::AutoLock lock(m_clients); RemoteClient *client = m_clients.lockedGetClientNoEx(pkt->getPeerId()); @@ -511,20 +499,14 @@ 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; - DisconnectPeer(peer_id); + if (!player) { + warningstream << FUNCTION_NAME << ": player is null" << std::endl; return; } PlayerSAO *playersao = player->getPlayerSAO(); - if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; - DisconnectPeer(peer_id); + if (!playersao) { + warningstream << FUNCTION_NAME << ": player SAO is null" << std::endl; return; } @@ -554,12 +536,8 @@ void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) u8 count; *pkt >> count; - RemoteClient *client = getClient(pkt->getPeerId()); - - if ((s16)pkt->getSize() < 1 + (int)count * 6) { - throw con::InvalidIncomingDataException - ("DELETEDBLOCKS length is too short"); - } + ClientInterface::AutoLock lock(m_clients); + RemoteClient *client = m_clients.lockedGetClientNoEx(pkt->getPeerId()); for (u16 i = 0; i < count; i++) { v3s16 p; @@ -572,28 +550,19 @@ 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; - DisconnectPeer(peer_id); + if (!player) { + warningstream << FUNCTION_NAME << ": player is null" << std::endl; return; } PlayerSAO *playersao = player->getPlayerSAO(); - if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; - DisconnectPeer(peer_id); + if (!playersao) { + warningstream << FUNCTION_NAME << ": player SAO is null" << std::endl; return; } // Strip command and create a stream std::string datastring(pkt->getString(0), pkt->getSize()); - verbosestream << "TOSERVER_INVENTORY_ACTION: data=" << datastring - << std::endl; std::istringstream is(datastring, std::ios_base::binary); // Create an action std::unique_ptr a(InventoryAction::deSerialize(is)); @@ -777,15 +746,12 @@ 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; - DisconnectPeer(peer_id); + if (!player) { + warningstream << FUNCTION_NAME << ": player is null" << std::endl; return; } - std::string name = player->getName(); + const auto &name = player->getName(); std::wstring answer_to_sender = handleChat(name, message, true, player); if (!answer_to_sender.empty()) { @@ -803,29 +769,22 @@ void Server::handleCommand_Damage(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; - DisconnectPeer(peer_id); + if (!player) { + warningstream << FUNCTION_NAME << ": player is null" << std::endl; return; } PlayerSAO *playersao = player->getPlayerSAO(); - if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; - DisconnectPeer(peer_id); + if (!playersao) { + warningstream << FUNCTION_NAME << ": player SAO is null" << std::endl; return; } if (!playersao->isImmortal()) { if (playersao->isDead()) { - verbosestream << "Server::ProcessData(): Info: " + verbosestream << "Server: " "Ignoring damage as player " << player->getName() - << " is already dead." << std::endl; + << " is already dead" << std::endl; return; } @@ -845,21 +804,14 @@ void Server::handleCommand_PlayerItem(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; - DisconnectPeer(peer_id); + if (!player) { + warningstream << FUNCTION_NAME << ": player is null" << std::endl; return; } PlayerSAO *playersao = player->getPlayerSAO(); - if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; - DisconnectPeer(peer_id); + if (!playersao) { + warningstream << FUNCTION_NAME << ": player SAO is null" << std::endl; return; } @@ -868,7 +820,7 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) *pkt >> item; if (item >= player->getMaxHotbarItemcount()) { - actionstream << "Player: " << player->getName() + actionstream << "Player " << player->getName() << " tried to access item=" << item << " out of hotbar_itemcount=" << player->getMaxHotbarItemcount() @@ -933,21 +885,14 @@ void Server::handleCommand_Interact(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; - DisconnectPeer(peer_id); + if (!player) { + warningstream << FUNCTION_NAME << ": player is null" << std::endl; return; } PlayerSAO *playersao = player->getPlayerSAO(); - if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; - DisconnectPeer(peer_id); + if (!playersao) { + warningstream << FUNCTION_NAME << ": player SAO is null" << std::endl; return; } @@ -972,7 +917,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Update wielded item if (item_i >= player->getMaxHotbarItemcount()) { - actionstream << "Player: " << player->getName() + actionstream << "Player " << player->getName() << " tried to access item=" << item_i << " out of hotbar_itemcount=" << player->getMaxHotbarItemcount() @@ -1363,21 +1308,14 @@ void Server::handleCommand_NodeMetaFields(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; - DisconnectPeer(peer_id); + if (!player) { + warningstream << FUNCTION_NAME << ": player is null" << std::endl; return; } PlayerSAO *playersao = player->getPlayerSAO(); - if (playersao == NULL) { - errorstream << - "Server::ProcessData(): Canceling: No player object for peer_id=" << - peer_id << " disconnecting peer!" << std::endl; - DisconnectPeer(peer_id); + if (!playersao) { + warningstream << FUNCTION_NAME << ": player SAO is null" << std::endl; return; } @@ -1450,25 +1388,26 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt) } // verify that we displayed the formspec to the user - const auto peer_state_iterator = m_formspec_state_data.find(peer_id); - if (peer_state_iterator != m_formspec_state_data.end()) { - const std::string &server_formspec_name = peer_state_iterator->second; + const auto it = m_formspec_state_data.find(peer_id); + if (it != m_formspec_state_data.end()) { + const auto &server_formspec_name = it->second; if (client_formspec_name == server_formspec_name) { - auto it = fields.find("quit"); - if (it != fields.end() && it->second == "true") - m_formspec_state_data.erase(peer_state_iterator); + // delete state if formspec was closed + auto it2 = fields.find("quit"); + if (it2 != fields.end() && it2->second == "true") + m_formspec_state_data.erase(it); m_script->on_playerReceiveFields(playersao, client_formspec_name, fields); return; } - actionstream << "'" << player->getName() - << "' submitted formspec ('" << client_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 + actionstream << player->getName() + << " submitted formspec ('" << client_formspec_name << "') but server hasn't sent formspec to client"; } actionstream << ", possible exploitation attempt" << std::endl; diff --git a/src/server.cpp b/src/server.cpp index 2f59b7443..dd5041a56 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1559,10 +1559,6 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) << static_cast(message.timestamp); if (peer_id != PEER_ID_INEXISTENT) { - RemotePlayer *player = m_env->getPlayer(peer_id); - if (!player) - return; - Send(&pkt); } else { m_clients.sendToAll(&pkt); @@ -2921,7 +2917,7 @@ void Server::acceptAuth(session_t peer_id, bool forSudoMode) NetworkPacket resp_pkt(TOCLIENT_AUTH_ACCEPT, 1 + 6 + 8 + 4, peer_id); - resp_pkt << v3f(0,0,0) << (u64) m_env->getServerMap().getSeed() + resp_pkt << v3f() << (u64) m_env->getServerMap().getSeed() << g_settings->getFloat("dedicated_server_step") << client->allowed_auth_mechs; diff --git a/src/unittest/test_connection.cpp b/src/unittest/test_connection.cpp index 654d6f463..298d259aa 100644 --- a/src/unittest/test_connection.cpp +++ b/src/unittest/test_connection.cpp @@ -273,7 +273,7 @@ void TestConnection::testConnectSendReceive() UASSERT(server.ReceiveTimeoutMs(&recvpacket, timeout_ms)); infostream << "** Server received: peer_id=" << pkt.getPeerId() << ", size=" << pkt.getSize() - << ", data=" << (const char*)pkt.getU8Ptr(0) + << ", data=" << pkt.getString(0) << std::endl; auto recvdata = pkt.oldForgePacket(); @@ -298,7 +298,7 @@ void TestConnection::testConnectSendReceive() infostream << " "; char buf[10]; porting::mt_snprintf(buf, sizeof(buf), "%.2X", - ((int)((const char *)pkt.getU8Ptr(0))[i]) & 0xff); + ((int)(pkt.getString(0))[i]) & 0xff); infostream< 20) diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 3ca415ddb..257991008 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -24,6 +24,7 @@ std::string serializeString16(std::string_view plain) std::string s; char buf[2]; + static_assert(STRING_MAX_LEN <= U16_MAX); if (plain.size() > STRING_MAX_LEN) throw SerializationError("String too long for serializeString16"); s.reserve(2 + plain.size()); @@ -51,7 +52,7 @@ std::string deSerializeString16(std::istream &is) s.resize(s_size); is.read(&s[0], s_size); if (is.gcount() != s_size) - throw SerializationError("deSerializeString16: couldn't read all chars"); + throw SerializationError("deSerializeString16: truncated"); return s; } @@ -66,6 +67,7 @@ std::string serializeString32(std::string_view plain) std::string s; char buf[4]; + static_assert(LONG_STRING_MAX_LEN <= U32_MAX); if (plain.size() > LONG_STRING_MAX_LEN) throw SerializationError("String too long for serializeLongString"); s.reserve(4 + plain.size()); @@ -98,7 +100,7 @@ std::string deSerializeString32(std::istream &is) s.resize(s_size); is.read(&s[0], s_size); if ((u32)is.gcount() != s_size) - throw SerializationError("deSerializeLongString: couldn't read all chars"); + throw SerializationError("deSerializeLongString: truncated"); return s; } From fc8c6742c477a81e61bd6ef5628e88b2b8c1f95e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 22 Feb 2025 17:20:42 +0100 Subject: [PATCH 003/284] Update Wireshark dissector --- util/wireshark/minetest.lua | 215 +++++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 92 deletions(-) diff --git a/util/wireshark/minetest.lua b/util/wireshark/minetest.lua index 837ea2963..bc9909dc1 100644 --- a/util/wireshark/minetest.lua +++ b/util/wireshark/minetest.lua @@ -1,32 +1,16 @@ -- minetest.lua --- Packet dissector for the UDP-based Minetest protocol +-- Packet dissector for the UDP-based Luanti protocol -- Copy this to $HOME/.wireshark/plugins/ - --- --- Minetest +-- Luanti +-- SPDX-License-Identifier: LGPL-2.1-or-later -- Copyright (C) 2011 celeron55, Perttu Ahola --- --- This program is free software; you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation; either version 2 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 General Public License for more details. --- --- You should have received a copy of the GNU 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. --- -- Wireshark documentation: --- https://web.archive.org/web/20170711121726/https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Proto.html --- https://web.archive.org/web/20170711121844/https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Tree.html --- https://web.archive.org/web/20170711121917/https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Tvb.html +-- https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Proto.html +-- https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Tree.html +-- https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Tvb.html -- Table of Contents: @@ -51,7 +35,8 @@ function minetest_field_helper(lentype, name, abbr) return f_textlen, f_text end - +-- global reference to 'minetest.peer' field (set later) +local minetest_peer_field -------------------------------------------- @@ -97,7 +82,7 @@ end -- TOSERVER_INIT_LEGACY (obsolete) -minetest_client_commands[0x10] = { "INIT_LEGACY", 53 } +minetest_client_commands[0x10] = { "INIT_LEGACY", 2 } minetest_client_obsolete[0x10] = true -- TOSERVER_INIT2 @@ -332,7 +317,7 @@ end -- TOSERVER_PASSWORD (obsolete) -minetest_client_commands[0x36] = { "CLICK_ACTIVEOBJECT", 2 } +minetest_client_commands[0x36] = { "PASSWORD", 2 } minetest_client_obsolete[0x36] = true -- TOSERVER_PLAYERITEM @@ -349,9 +334,9 @@ do } end --- TOSERVER_RESPAWN +-- TOSERVER_RESPAWN_LEGACY -minetest_client_commands[0x38] = { "RESPAWN", 2 } +minetest_client_commands[0x38] = { "RESPAWN_LEGACY", 2 } -- TOSERVER_INTERACT @@ -482,8 +467,7 @@ end minetest_client_commands[0x50] = { "FIRST_SRP", 2 } minetest_client_commands[0x51] = { "SRP_BYTES_A", 2 } minetest_client_commands[0x52] = { "SRP_BYTES_M", 2 } - - +minetest_client_commands[0x53] = { "UPDATE_CLIENT_INFO", 2 } -------------------------------------------- -- Part 3 -- @@ -669,7 +653,7 @@ do local f_time_speed = ProtoField.float("minetest.server.time_speed", "Time Speed", base.DEC) minetest_server_commands[0x29] = { - "TIME_OF_DAY", 4, + "TIME_OF_DAY", 8, { f_time, f_time_speed }, function(buffer, pinfo, tree, t) t:add(f_time, buffer(2,2)) @@ -678,13 +662,11 @@ do } end --- TOCLIENT_CSM_RESTRICTION_FLAGS +-- ... minetest_server_commands[0x2a] = { "CSM_RESTRICTION_FLAGS", 2 } - --- TOCLIENT_PLAYER_SPEED - minetest_server_commands[0x2b] = { "PLAYER_SPEED", 2 } +minetest_server_commands[0x2c] = { "MEDIA_PUSH", 2 } -- TOCLIENT_CHAT_MESSAGE @@ -911,18 +893,15 @@ end -- TOCLIENT_ACCESS_DENIED_LEGACY do - local f_reason_length = ProtoField.uint16("minetest.server.access_denied_reason_length", "Reason length", base.DEC) - local f_reason = ProtoField.string("minetest.server.access_denied_reason", "Reason") + local f_reasonlen, f_reason = minetest_field_helper("uint16", + "minetest.server.access_denied_reason", "Reason") minetest_server_commands[0x35] = { "ACCESS_DENIED_LEGACY", 4, - { f_reason_length, f_reason }, + { f_reasonlen, f_reason }, function(buffer, pinfo, tree, t) - t:add(f_reason_length, buffer(2,2)) - local reason_length = buffer(2,2):uint() - if minetest_check_length(buffer, 4 + reason_length * 2, t) then - t:add(f_reason, minetest_convert_utf16(buffer(4, reason_length * 2), "Converted reason message")) - end + local off = 2 + minetest_decode_helper_utf16(buffer, t, "uint16", off, f_reasonlen, f_reason) end } end @@ -931,34 +910,9 @@ end minetest_server_commands[0x36] = { "FOV", 2 } --- TOCLIENT_DEATHSCREEN +-- TOCLIENT_DEATHSCREEN_LEGACY -do - local f_set_camera_point_target = ProtoField.bool( - "minetest.server.deathscreen_set_camera_point_target", - "Set camera point target") - local f_camera_point_target_x = ProtoField.int32( - "minetest.server.deathscreen_camera_point_target_x", - "Camera point target X", base.DEC) - local f_camera_point_target_y = ProtoField.int32( - "minetest.server.deathscreen_camera_point_target_y", - "Camera point target Y", base.DEC) - local f_camera_point_target_z = ProtoField.int32( - "minetest.server.deathscreen_camera_point_target_z", - "Camera point target Z", base.DEC) - - minetest_server_commands[0x37] = { - "DEATHSCREEN", 15, - { f_set_camera_point_target, f_camera_point_target_x, - f_camera_point_target_y, f_camera_point_target_z}, - function(buffer, pinfo, tree, t) - t:add(f_set_camera_point_target, buffer(2,1)) - t:add(f_camera_point_target_x, buffer(3,4)) - t:add(f_camera_point_target_y, buffer(7,4)) - t:add(f_camera_point_target_z, buffer(11,4)) - end - } -end +minetest_server_commands[0x37] = { "DEATHSCREEN_LEGACY", 2 } -- TOCLIENT_MEDIA @@ -990,18 +944,77 @@ minetest_server_commands[0x43] = {"DETACHED_INVENTORY", 2} minetest_server_commands[0x44] = {"SHOW_FORMSPEC", 2} minetest_server_commands[0x45] = {"MOVEMENT", 2} minetest_server_commands[0x46] = {"SPAWN_PARTICLE", 2} -minetest_server_commands[0x47] = {"ADD_PARTICLE_SPAWNER", 2} +minetest_server_commands[0x47] = {"ADD_PARTICLESPAWNER", 2} +minetest_server_commands[0x48] = {"CAMERA", 2} +minetest_server_commands[0x49] = {"HUDADD", 2} +minetest_server_commands[0x4a] = {"HUDRM", 2} --- TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY (obsolete) +-- TOCLIENT_HUDCHANGE -minetest_server_commands[0x48] = {"DELETE_PARTICLESPAWNER_LEGACY", 2} -minetest_server_obsolete[0x48] = true +do + local abbr = "minetest.server.hudchange_" + local vs_stat = { + [0] = "pos", + [1] = "name", + [2] = "scale", + [3] = "text", + [4] = "number", + [5] = "item", + [6] = "dir", + [7] = "align", + [8] = "offset", + [9] = "world_pos", + [10] = "size", + [11] = "z_index", + [12] = "text2", + [13] = "style", + } + local uses_v2f = { + [0] = true, [2] = true, [7] = true, [8] = true, + } + local uses_string = { + [1] = true, [3] = true, [12] = true, + } + + local f_id = ProtoField.uint32(abbr.."id", "HUD ID", base.DEC) + local f_stat = ProtoField.uint8(abbr.."stat", "HUD Type", base.DEC, vs_stat) + local f_x = ProtoField.float(abbr.."x", "Position X") + local f_y = ProtoField.float(abbr.."y", "Position Y") + local f_z = ProtoField.float(abbr.."z", "Position Z") + local f_str = ProtoField.string(abbr.."string", "String data") + local f_sx = ProtoField.int32(abbr.."sy", "Size X", base.DEC) + local f_sy = ProtoField.int32(abbr.."sz", "Size Y", base.DEC) + local f_int = ProtoField.uint32(abbr.."int", "Integer data", base.DEC) + + minetest_server_commands[0x4b] = { + "HUDCHANGE", 7, + { f_id, f_stat, f_x, f_y, f_z, f_str, f_sx, f_sy, f_int, }, + function(buffer, pinfo, tree, t) + t:add(f_id, buffer(2,4)) + t:add(f_stat, buffer(6,1)) + local stat = buffer(6,1):uint() + local off = 7 + if uses_v2f[stat] then + t:add(f_x, buffer(off,4)) + t:add(f_y, buffer(off+4,4)) + elseif uses_string[stat] then + minetest_decode_helper_ascii(buffer, t, "uint16", off, nil, f_str) + elseif stat == 9 then -- v3f + t:add(f_x, buffer(off,4)) + t:add(f_y, buffer(off+4,4)) + t:add(f_z, buffer(off+8,4)) + elseif stat == 10 then -- v2s32 + t:add(f_sx, buffer(off,4)) + t:add(f_sy, buffer(off+8,4)) + else + t:add(f_int, buffer(off,4)) + end + end + } +end -- ... -minetest_server_commands[0x49] = {"HUDADD", 2} -minetest_server_commands[0x4a] = {"HUDRM", 2} -minetest_server_commands[0x4b] = {"HUDCHANGE", 2} minetest_server_commands[0x4c] = {"HUD_SET_FLAGS", 2} minetest_server_commands[0x4d] = {"HUD_SET_PARAM", 2} minetest_server_commands[0x4e] = {"BREATH", 2} @@ -1057,8 +1070,11 @@ minetest_server_commands[0x59] = {"NODEMETA_CHANGED", 2} minetest_server_commands[0x5a] = {"SET_SUN", 2} minetest_server_commands[0x5b] = {"SET_MOON", 2} minetest_server_commands[0x5c] = {"SET_STARS", 2} +minetest_server_commands[0x5d] = {"MOVE_PLAYER_REL", 2} minetest_server_commands[0x60] = {"SRP_BYTES_S_B", 2} minetest_server_commands[0x61] = {"FORMSPEC_PREPEND", 2} +minetest_server_commands[0x62] = {"MINIMAP_MODES", 2} +minetest_server_commands[0x63] = {"SET_LIGHTING", 2} ------------------------------------ @@ -1069,13 +1085,13 @@ minetest_server_commands[0x61] = {"FORMSPEC_PREPEND", 2} -- minetest.control dissector do - local p_control = Proto("minetest.control", "Minetest Control") + local p_control = Proto("minetest.control", "Luanti Control") local vs_control_type = { [0] = "Ack", [1] = "Set Peer ID", [2] = "Ping", - [3] = "Disco" + [3] = "Disconnect" } local f_control_type = ProtoField.uint8("minetest.control.type", "Control Type", base.DEC, vs_control_type) @@ -1105,7 +1121,7 @@ do elseif buffer(0,1):uint() == 2 then pinfo.cols.info = "Ping" elseif buffer(0,1):uint() == 3 then - pinfo.cols.info = "Disco" + pinfo.cols.info = "Disconnect" end data_dissector:call(buffer(pos):tvb(), pinfo, tree) @@ -1143,7 +1159,7 @@ function minetest_define_client_or_server_proto(is_client) end -- Create the protocol object. - local proto = Proto(proto_name, "Minetest " .. this_peer .. " to " .. other_peer) + local proto = Proto(proto_name, "Luanti " .. this_peer .. " to " .. other_peer) -- Create a table vs_command that maps command codes to command names. local vs_command = {} @@ -1173,7 +1189,10 @@ function minetest_define_client_or_server_proto(is_client) function proto.dissector(buffer, pinfo, tree) local t = tree:add(proto, buffer) - pinfo.cols.info = this_peer + -- If we're nested, don't reset + if string.find(tostring(pinfo.cols.info), this_peer, 1, true) ~= 1 then + pinfo.cols.info = this_peer + end if buffer:len() == 0 then -- Empty message. @@ -1195,8 +1214,8 @@ function minetest_define_client_or_server_proto(is_client) local command_min_length = command_info[2] local command_fields = command_info[3] local command_dissector = command_info[4] + pinfo.cols.info:append(": " .. command_name) if minetest_check_length(buffer, command_min_length, t) then - pinfo.cols.info:append(": " .. command_name) if command_dissector ~= nil then command_dissector(buffer, pinfo, tree, t) end @@ -1215,7 +1234,7 @@ minetest_define_client_or_server_proto(false) -- minetest.server -- minetest.split dissector do - local p_split = Proto("minetest.split", "Minetest Split Message") + local p_split = Proto("minetest.split", "Luanti Split Message") local f_split_seq = ProtoField.uint16("minetest.split.seq", "Sequence number", base.DEC) local f_split_chunkcount = ProtoField.uint16("minetest.split.chunkcount", "Chunk count", base.DEC) @@ -1230,6 +1249,17 @@ do t:add(f_split_chunknum, buffer(4,2)) t:add(f_split_data, buffer(6)) pinfo.cols.info:append(" " .. buffer(0,2):uint() .. " chunk " .. buffer(4,2):uint() .. "/" .. buffer(2,2):uint()) + -- If it's the first chunk, read the peer id from the upper layer and + -- pass the data that we have to the right dissector. + -- this provides at least a partial decoding + if buffer(4,2):uint() == 0 then + local peer_id = minetest_peer_field() + if peer_id.value and peer_id.value == 1 then + Dissector.get("minetest.server"):call(buffer(6):tvb(), pinfo, tree) + elseif peer_id.value then + Dissector.get("minetest.client"):call(buffer(6):tvb(), pinfo, tree) + end + end end end @@ -1241,10 +1271,8 @@ end -- Wrapper protocol main dissector -- ------------------------------------- --- minetest dissector - do - local p_minetest = Proto("minetest", "Minetest") + local p_minetest = Proto("minetest", "Luanti") local minetest_id = 0x4f457403 local vs_id = { @@ -1263,7 +1291,7 @@ do [3] = "Reliable" } - local f_id = ProtoField.uint32("minetest.id", "ID", base.HEX, vs_id) + local f_id = ProtoField.uint32("minetest.id", "Protocol ID", base.HEX, vs_id) local f_peer = ProtoField.uint16("minetest.peer", "Peer", base.DEC, vs_peer) local f_channel = ProtoField.uint8("minetest.channel", "Channel", base.DEC) local f_type = ProtoField.uint8("minetest.type", "Type", base.DEC, vs_type) @@ -1290,14 +1318,15 @@ do t:add(f_id, buffer(0,4)) -- ID is valid, so replace packet's shown protocol - pinfo.cols.protocol = "Minetest" - pinfo.cols.info = "Minetest" + pinfo.cols.protocol = "Luanti" + pinfo.cols.info = "Luanti" -- Set the other header fields + local peer_id = buffer(4,2):uint() t:add(f_peer, buffer(4,2)) t:add(f_channel, buffer(6,1)) t:add(f_type, buffer(7,1)) - t:set_text("Minetest, Peer: " .. buffer(4,2):uint() .. ", Channel: " .. buffer(6,1):uint()) + t:set_text("Luanti, Peer: " .. peer_id .. ", Channel: " .. buffer(6,1):uint()) local reliability_info local pos @@ -1319,14 +1348,14 @@ do control_dissector:call(buffer(pos+1):tvb(), pinfo, tree) elseif buffer(pos,1):uint() == 1 then -- Original message, possibly reliable - if buffer(4,2):uint() == 1 then + if peer_id == 1 then server_dissector:call(buffer(pos+1):tvb(), pinfo, tree) else client_dissector:call(buffer(pos+1):tvb(), pinfo, tree) end elseif buffer(pos,1):uint() == 2 then -- Split message, possibly reliable - if buffer(4,2):uint() == 1 then + if peer_id == 1 then pinfo.cols.info = "Server: Split message" else pinfo.cols.info = "Client: Split message" @@ -1355,6 +1384,8 @@ end -- Utility functions part 2 -- ------------------------------ +minetest_peer_field = Field.new("minetest.peer") + -- Checks if a (sub-)Tvb is long enough to be further dissected. -- If it is long enough, sets the dissector tree item length to min_len -- and returns true. If it is not long enough, adds expert info to the From 42a35cec83a5b3b1e6d6ab1845cce9c175c07359 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 23 Feb 2025 16:18:27 +0100 Subject: [PATCH 004/284] Allow looking straight up or down --- src/client/game.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index b66716036..8ff7050cd 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2435,7 +2435,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; } - cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); + cam->camera_pitch = rangelim(cam->camera_pitch, -90, 90); } From 22c81e5292b8a1b9b209346cb30bec5547845ac4 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 23 Feb 2025 17:08:43 +0100 Subject: [PATCH 005/284] Print if sdl2-compat is in use --- irr/src/CIrrDeviceSDL.cpp | 32 ++++++++++++++++++-------------- irr/src/CIrrDeviceSDL.h | 1 - 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index 2775858f3..f0f0db577 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -308,8 +308,6 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) : if (SDL_Init(flags) < 0) { os::Printer::log("Unable to initialize SDL", SDL_GetError(), ELL_ERROR); Close = true; - } else { - os::Printer::log("SDL initialized", ELL_INFORMATION); } } @@ -324,21 +322,27 @@ CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters ¶m) : } } - SDL_VERSION(&Info.version); + core::stringc sdlver = "SDL "; + { + SDL_version v{}; + SDL_GetVersion(&v); + sdlver += v.major; + sdlver += "."; + sdlver += v.minor; + sdlver += "."; + sdlver += v.patch; + // the SDL team seems to intentionally number sdl2-compat this way: + // + if (v.patch >= 50) + sdlver += " (compat)"; -#ifndef _IRR_EMSCRIPTEN_PLATFORM_ - SDL_GetWindowWMInfo(Window, &Info); -#endif //_IRR_EMSCRIPTEN_PLATFORM_ - core::stringc sdlversion = "SDL Version "; - sdlversion += Info.version.major; - sdlversion += "."; - sdlversion += Info.version.minor; - sdlversion += "."; - sdlversion += Info.version.patch; + sdlver += " on "; + sdlver += SDL_GetPlatform(); + } - Operator = new COSOperator(sdlversion); + Operator = new COSOperator(sdlver); if (SDLDeviceInstances == 1) { - os::Printer::log(sdlversion.c_str(), ELL_INFORMATION); + os::Printer::log(sdlver.c_str(), ELL_INFORMATION); } // create cursor control diff --git a/irr/src/CIrrDeviceSDL.h b/irr/src/CIrrDeviceSDL.h index eb2250f2a..fcf4608be 100644 --- a/irr/src/CIrrDeviceSDL.h +++ b/irr/src/CIrrDeviceSDL.h @@ -340,7 +340,6 @@ private: }; core::array KeyMap; - SDL_SysWMinfo Info; s32 CurrentTouchCount; bool IsInBackground; From eb8b44981754ba5f13be3caa0de63b18937554b4 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Feb 2025 18:45:23 +0100 Subject: [PATCH 006/284] Fix shadow performance regression due to force update broken by: b861f0c5c59546b64e937fdf384634175df4a372 --- src/client/shadows/dynamicshadows.h | 1 + src/client/shadows/dynamicshadowsrender.cpp | 18 ++++++------------ src/client/shadows/dynamicshadowsrender.h | 1 + 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h index 640a5479f..70135ea69 100644 --- a/src/client/shadows/dynamicshadows.h +++ b/src/client/shadows/dynamicshadows.h @@ -85,6 +85,7 @@ public: return mapRes; } + /// If true, shadow map needs to be invalidated due to frustum change bool should_update_map_shadow{true}; void commitFrustum(); diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index b213cf12a..9898b08e6 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -254,17 +254,14 @@ void ShadowRenderer::updateSMTextures() if (!m_shadow_node_array.empty()) { bool reset_sm_texture = false; - // detect if SM should be regenerated + // clear texture if requested for (DirectionalLight &light : m_light_list) { - if (light.should_update_map_shadow) - m_force_update_shadow_map = true; + reset_sm_texture |= light.should_update_map_shadow; light.should_update_map_shadow = false; } - if (m_force_update_shadow_map) { + if (reset_sm_texture || m_force_update_shadow_map) m_current_frame = 0; - reset_sm_texture = true; - } video::ITexture* shadowMapTargetTexture = shadowMapClientMapFuture; if (shadowMapTargetTexture == nullptr) @@ -273,7 +270,7 @@ void ShadowRenderer::updateSMTextures() // Update SM incrementally: for (DirectionalLight &light : m_light_list) { // Static shader values. - for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb}) + for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb}) { if (cb) { cb->MapRes = (f32)m_shadow_map_texture_size; cb->MaxFar = (f32)m_shadow_map_max_distance * BS; @@ -281,12 +278,9 @@ void ShadowRenderer::updateSMTextures() cb->PerspectiveBiasZ = getPerspectiveBiasZ(); cb->CameraPos = light.getFuturePlayerPos(); } + } - // set the Render Target - // right now we can only render in usual RTT, not - // Depth texture is available in irrlicth maybe we - // should put some gl* fn here - + // Note that force_update means we're drawing everything one go. if (m_current_frame < m_map_shadow_update_frames || m_force_update_shadow_map) { m_driver->setRenderTarget(shadowMapTargetTexture, reset_sm_texture, true, diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index 9bc4f6b94..5854dc4ed 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -68,6 +68,7 @@ public: void removeNodeFromShadowList(scene::ISceneNode *node); void update(video::ITexture *outputTarget = nullptr); + /// Force shadow map to be re-drawn in one go next frame void setForceUpdateShadowMap() { m_force_update_shadow_map = true; } void drawDebug(); From 8654e16725f56879e2d76915e5b06fe6cf40c140 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Feb 2025 18:52:58 +0100 Subject: [PATCH 007/284] Disable shadow force updates with performance_tradeoffs --- src/client/client.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 9d06f3055..48dc61984 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -640,9 +640,11 @@ void Client::step(float dtime) if (num_processed_meshes > 0) g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); - auto shadow_renderer = RenderingEngine::get_shadow_renderer(); - if (shadow_renderer && force_update_shadows) - shadow_renderer->setForceUpdateShadowMap(); + if (force_update_shadows && !g_settings->getFlag("performance_tradeoffs")) { + auto shadow = RenderingEngine::get_shadow_renderer(); + if (shadow) + shadow->setForceUpdateShadowMap(); + }; } /* From 415e96184d41a1bda614726aaf3aebf9d05b4c3e Mon Sep 17 00:00:00 2001 From: guinea7pig <145406121+guinea7pig@users.noreply.github.com> Date: Wed, 26 Feb 2025 06:22:19 -0500 Subject: [PATCH 008/284] Update copyright date in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ff436419..3fe4fe856 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Luanti (formerly Minetest) Luanti is a free open-source voxel game engine with easy modding and game creation. -Copyright (C) 2010-2024 Perttu Ahola +Copyright (C) 2010-2025 Perttu Ahola and contributors (see source file comments and the version control log) Table of Contents From 58ad604a4bf96836fd9ea2342c6cb075681e7cab Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Thu, 27 Feb 2025 12:30:55 +0100 Subject: [PATCH 009/284] Note that `core.hash_node_position` is not a hash function --- doc/lua_api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 7b2a8945e..7f4d69021 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -7456,7 +7456,8 @@ Misc. * This function can be overridden by mods to change the leave message. * `core.hash_node_position(pos)`: returns a 48-bit integer * `pos`: table {x=number, y=number, z=number}, - * Gives a unique hash number for a node position (16+16+16=48bit) + * Gives a unique numeric encoding for a node position (16+16+16=48bit) + * Despite the name, this is not a hash function (so it doesn't mix or produce collisions). * `core.get_position_from_hash(hash)`: returns a position * Inverse transform of `core.hash_node_position` * `core.get_item_group(name, group)`: returns a rating From 0e8636632478607829a070adc7ba3cff32eb5815 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Wed, 15 Jan 2025 18:18:54 +0100 Subject: [PATCH 010/284] Add test for matrix4::getRotationDegrees --- src/unittest/test_irr_matrix4.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/unittest/test_irr_matrix4.cpp b/src/unittest/test_irr_matrix4.cpp index 764b0a5b2..c7f065649 100644 --- a/src/unittest/test_irr_matrix4.cpp +++ b/src/unittest/test_irr_matrix4.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "catch.h" +#include "catch_amalgamated.hpp" #include "irrMath.h" #include "matrix4.h" #include "irr_v3d.h" @@ -83,4 +84,29 @@ SECTION("getScale") { } } +SECTION("getRotationDegrees") { + auto test_rotation_degrees = [](v3f deg) { + matrix4 S; + Catch::Generators::RandomFloatingGenerator gen(0.1f, 10, Catch::getSeed()); + S.setScale({gen.get(), gen.get(), gen.get()}); + + matrix4 R; + R.setRotationDegrees(deg); + v3f rot = (R * S).getRotationDegrees(); + matrix4 B; + B.setRotationDegrees(rot); + CHECK(matrix_equals(R, B)); + }; + SECTION("returns a rotation equivalent to the original rotation") { + test_rotation_degrees({100, 200, 300}); + Catch::Generators::RandomFloatingGenerator gen(0, 360, Catch::getSeed()); + for (int i = 0; i < 1000; ++i) + test_rotation_degrees(v3f{gen.get(), gen.get(), gen.get()}); + for (f32 i = 0; i < 360; i += 90) + for (f32 j = 0; j < 360; j += 90) + for (f32 k = 0; k < 360; k += 90) + test_rotation_degrees({i, j, k}); + } +} + } From 3ae1fd459abada44f42b6669e3a7d98e05d682f5 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Wed, 15 Jan 2025 18:26:34 +0100 Subject: [PATCH 011/284] Add quaternion conversion unit tests --- src/unittest/CMakeLists.txt | 3 ++- src/unittest/test_irr_quaternion.cpp | 40 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/unittest/test_irr_quaternion.cpp diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 8a878cdd7..fb3e1f97a 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -13,6 +13,8 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_filesys.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_quaternion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_lua.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map.cpp @@ -54,7 +56,6 @@ set (UNITTEST_CLIENT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_gltf_mesh_loader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp PARENT_SCOPE) diff --git a/src/unittest/test_irr_quaternion.cpp b/src/unittest/test_irr_quaternion.cpp new file mode 100644 index 000000000..1bb0c3cc9 --- /dev/null +++ b/src/unittest/test_irr_quaternion.cpp @@ -0,0 +1,40 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "catch.h" +#include "irrMath.h" +#include "matrix4.h" +#include "quaternion.h" +#include "irr_v3d.h" + +using matrix4 = core::matrix4; + +static bool matrix_equals(const matrix4 &a, const matrix4 &b) { + return a.equals(b, 0.00001f); +} + +TEST_CASE("quaternion") { + +// Make sure that the conventions are consistent +SECTION("equivalence to euler rotations") { + auto test_rotation = [](v3f rad) { + matrix4 R; + R.setRotationRadians(rad); + v3f rad2; + core::quaternion(rad).toEuler(rad2); + matrix4 R2; + R2.setRotationRadians(rad2); + CHECK(matrix_equals(R, R2)); + }; + + test_rotation({100, 200, 300}); + Catch::Generators::RandomFloatingGenerator gen(0.0f, 2 * core::PI, Catch::getSeed()); + for (int i = 0; i < 1000; ++i) + test_rotation(v3f{gen.get(), gen.get(), gen.get()}); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + for (int k = 0; k < 4; k++) + test_rotation(core::PI / 4.0f * v3f(i, j, k)); +} + +} \ No newline at end of file From 1ceeea34f442d367af9b3357a50e3923a85b7813 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Wed, 15 Jan 2025 20:26:34 +0100 Subject: [PATCH 012/284] Extend quaternion tests --- src/unittest/test_irr_quaternion.cpp | 53 ++++++++++++++++++---------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/unittest/test_irr_quaternion.cpp b/src/unittest/test_irr_quaternion.cpp index 1bb0c3cc9..61ddcad7c 100644 --- a/src/unittest/test_irr_quaternion.cpp +++ b/src/unittest/test_irr_quaternion.cpp @@ -10,31 +10,48 @@ using matrix4 = core::matrix4; static bool matrix_equals(const matrix4 &a, const matrix4 &b) { - return a.equals(b, 0.00001f); + return a.equals(b, 0.00001f); } TEST_CASE("quaternion") { // Make sure that the conventions are consistent SECTION("equivalence to euler rotations") { - auto test_rotation = [](v3f rad) { - matrix4 R; - R.setRotationRadians(rad); - v3f rad2; - core::quaternion(rad).toEuler(rad2); - matrix4 R2; - R2.setRotationRadians(rad2); - CHECK(matrix_equals(R, R2)); - }; + auto test_rotation = [](v3f rad) { + matrix4 R; + R.setRotationRadians(rad); + v3f rad2; + core::quaternion(rad).toEuler(rad2); + matrix4 R2; + R2.setRotationRadians(rad2); + CHECK(matrix_equals(R, R2)); + }; - test_rotation({100, 200, 300}); - Catch::Generators::RandomFloatingGenerator gen(0.0f, 2 * core::PI, Catch::getSeed()); - for (int i = 0; i < 1000; ++i) - test_rotation(v3f{gen.get(), gen.get(), gen.get()}); - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - for (int k = 0; k < 4; k++) - test_rotation(core::PI / 4.0f * v3f(i, j, k)); + Catch::Generators::RandomFloatingGenerator gen(0.0f, 2 * core::PI, Catch::getSeed()); + for (int i = 0; i < 1000; ++i) + test_rotation(v3f{gen.get(), gen.get(), gen.get()}); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + for (int k = 0; k < 4; k++) + test_rotation(core::PI / 4.0f * v3f(i, j, k)); +} + +SECTION("equivalence to rotation matrices") { + auto test_rotation = [](v3f rad) { + matrix4 R; + R.setRotationRadians(rad); + matrix4 R2; + core::quaternion(R).getMatrix(R2); + CHECK(matrix_equals(R, R2)); + }; + + Catch::Generators::RandomFloatingGenerator gen(0.0f, 2 * core::PI, Catch::getSeed()); + for (int i = 0; i < 1000; ++i) + test_rotation(v3f{gen.get(), gen.get(), gen.get()}); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + for (int k = 0; k < 4; k++) + test_rotation(core::PI / 4.0f * v3f(i, j, k)); } } \ No newline at end of file From 5abf2209792a87ebe1a81bfdf77c675c4f81e880 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Thu, 30 Jan 2025 13:17:13 +0100 Subject: [PATCH 013/284] Fix random usage in matrix4 tests --- src/unittest/test_irr_matrix4.cpp | 54 ++++++++++++++++++------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/unittest/test_irr_matrix4.cpp b/src/unittest/test_irr_matrix4.cpp index c7f065649..92938dfed 100644 --- a/src/unittest/test_irr_matrix4.cpp +++ b/src/unittest/test_irr_matrix4.cpp @@ -85,28 +85,38 @@ SECTION("getScale") { } SECTION("getRotationDegrees") { - auto test_rotation_degrees = [](v3f deg) { - matrix4 S; - Catch::Generators::RandomFloatingGenerator gen(0.1f, 10, Catch::getSeed()); - S.setScale({gen.get(), gen.get(), gen.get()}); - - matrix4 R; - R.setRotationDegrees(deg); - v3f rot = (R * S).getRotationDegrees(); - matrix4 B; - B.setRotationDegrees(rot); - CHECK(matrix_equals(R, B)); - }; - SECTION("returns a rotation equivalent to the original rotation") { - test_rotation_degrees({100, 200, 300}); - Catch::Generators::RandomFloatingGenerator gen(0, 360, Catch::getSeed()); - for (int i = 0; i < 1000; ++i) - test_rotation_degrees(v3f{gen.get(), gen.get(), gen.get()}); - for (f32 i = 0; i < 360; i += 90) - for (f32 j = 0; j < 360; j += 90) - for (f32 k = 0; k < 360; k += 90) - test_rotation_degrees({i, j, k}); - } + auto test_rotation_degrees = [](v3f deg, v3f scale) { + matrix4 S; + S.setScale(scale); + matrix4 R; + R.setRotationDegrees(deg); + v3f rot = (R * S).getRotationDegrees(); + matrix4 B; + B.setRotationDegrees(rot); + CHECK(matrix_equals(R, B)); + }; + SECTION("returns a rotation equivalent to the original rotation") { + test_rotation_degrees({100, 200, 300}, v3f(1)); + Catch::Generators::RandomFloatingGenerator gen_angle(0, 360, Catch::getSeed()); + Catch::Generators::RandomFloatingGenerator gen_scale(0.1f, 10, Catch::getSeed()); + auto draw = [](auto gen) { + f32 f = gen.get(); + gen.next(); + return f; + }; + auto draw_v3f = [&](auto gen) { + return v3f{draw(gen), draw(gen), draw(gen)}; + }; + for (int i = 0; i < 1000; ++i) + test_rotation_degrees(draw_v3f(gen_angle), draw_v3f(gen_scale)); + for (f32 i = 0; i < 360; i += 90) + for (f32 j = 0; j < 360; j += 90) + for (f32 k = 0; k < 360; k += 90) { + for (int l = 0; l < 100; ++l) { + test_rotation_degrees({i, j, k}, draw_v3f(gen_scale)); + } + } + } } } From c261c26456a30f5d30126b6a018424975b48eee5 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Thu, 30 Jan 2025 13:27:59 +0100 Subject: [PATCH 014/284] Add Irrlicht rotation consistency unit tests --- src/unittest/CMakeLists.txt | 2 +- src/unittest/test_irr_quaternion.cpp | 57 -------------- src/unittest/test_irr_rotation.cpp | 109 +++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 58 deletions(-) delete mode 100644 src/unittest/test_irr_quaternion.cpp create mode 100644 src/unittest/test_irr_rotation.cpp diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index fb3e1f97a..8a635c8e8 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -14,7 +14,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_quaternion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_rotation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_lua.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map.cpp diff --git a/src/unittest/test_irr_quaternion.cpp b/src/unittest/test_irr_quaternion.cpp deleted file mode 100644 index 61ddcad7c..000000000 --- a/src/unittest/test_irr_quaternion.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Luanti -// SPDX-License-Identifier: LGPL-2.1-or-later - -#include "catch.h" -#include "irrMath.h" -#include "matrix4.h" -#include "quaternion.h" -#include "irr_v3d.h" - -using matrix4 = core::matrix4; - -static bool matrix_equals(const matrix4 &a, const matrix4 &b) { - return a.equals(b, 0.00001f); -} - -TEST_CASE("quaternion") { - -// Make sure that the conventions are consistent -SECTION("equivalence to euler rotations") { - auto test_rotation = [](v3f rad) { - matrix4 R; - R.setRotationRadians(rad); - v3f rad2; - core::quaternion(rad).toEuler(rad2); - matrix4 R2; - R2.setRotationRadians(rad2); - CHECK(matrix_equals(R, R2)); - }; - - Catch::Generators::RandomFloatingGenerator gen(0.0f, 2 * core::PI, Catch::getSeed()); - for (int i = 0; i < 1000; ++i) - test_rotation(v3f{gen.get(), gen.get(), gen.get()}); - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - for (int k = 0; k < 4; k++) - test_rotation(core::PI / 4.0f * v3f(i, j, k)); -} - -SECTION("equivalence to rotation matrices") { - auto test_rotation = [](v3f rad) { - matrix4 R; - R.setRotationRadians(rad); - matrix4 R2; - core::quaternion(R).getMatrix(R2); - CHECK(matrix_equals(R, R2)); - }; - - Catch::Generators::RandomFloatingGenerator gen(0.0f, 2 * core::PI, Catch::getSeed()); - for (int i = 0; i < 1000; ++i) - test_rotation(v3f{gen.get(), gen.get(), gen.get()}); - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - for (int k = 0; k < 4; k++) - test_rotation(core::PI / 4.0f * v3f(i, j, k)); -} - -} \ No newline at end of file diff --git a/src/unittest/test_irr_rotation.cpp b/src/unittest/test_irr_rotation.cpp new file mode 100644 index 000000000..1da0462fe --- /dev/null +++ b/src/unittest/test_irr_rotation.cpp @@ -0,0 +1,109 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "catch.h" +#include "catch_amalgamated.hpp" +#include "irrMath.h" +#include "matrix4.h" +#include "irrMath.h" +#include "matrix4.h" +#include "irr_v3d.h" +#include "quaternion.h" +#include + +// Irrlicht provides three different representations of rotations: +// - Euler angles in radians (or degrees, but that doesn't matter much); +// - Quaternions; +// - Rotation matrices. +// These tests ensure that converting between these representations is rotation-preserving. + +using matrix4 = core::matrix4; +using quaternion = core::quaternion; + +// Despite the internal usage of doubles, matrix4::setRotationRadians +// simply incurs component-wise errors of the order 1e-3. +const f32 tolerance = 1e-2f; + +static bool matrix_equals(const matrix4 &mat, const matrix4 &mat2) +{ + return mat.equals(mat2, tolerance); +} + +static bool euler_angles_equiv(v3f rad, v3f rad2) +{ + matrix4 mat, mat2; + mat.setRotationRadians(rad); + mat2.setRotationRadians(rad2); + return matrix_equals(mat, mat2); +} + +static void test_euler_angles_rad(const std::function &test_euler_radians) +{ + Catch::Generators::RandomFloatingGenerator gen(0.0f, 2 * core::PI, Catch::getSeed()); + auto random_angle = [&gen]() { + f32 f = gen.get(); + gen.next(); + return f; + }; + for (int i = 0; i < 1000; ++i) + test_euler_radians(v3f{random_angle(), random_angle(), random_angle()}); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + for (int k = 0; k < 4; k++) { + v3f rad = core::PI / 4.0f * v3f(i, j, k); + test_euler_radians(rad); + // Test very slightly nudged, "almost-perfect" rotations to make sure + // that the conversions are relatively stable at extremal points + for (int l = 0; l < 10; ++l) { + v3f jitter = v3f{random_angle(), random_angle(), random_angle()} * 0.001f; + test_euler_radians(rad + jitter); + } + } +} + +TEST_CASE("rotations") { + +SECTION("euler-to-quaternion conversion") { + test_euler_angles_rad([](v3f rad) { + core::matrix4 rot, rot_quat; + rot.setRotationRadians(rad); + quaternion q(rad); + q.getMatrix(rot_quat); + // Check equivalence of the rotations via matrices + CHECK(matrix_equals(rot, rot_quat)); + }); +} + +// Now that we've already tested the conversion to quaternions, +// this essentially primarily tests the quaternion to euler conversion +SECTION("quaternion-euler roundtrip") { + test_euler_angles_rad([](v3f rad) { + quaternion q(rad); + v3f rad2; + q.toEuler(rad2); + CHECK(euler_angles_equiv(rad, rad2)); + }); +} + +SECTION("matrix-quaternion roundtrip") { + test_euler_angles_rad([](v3f rad) { + matrix4 mat; + mat.setRotationRadians(rad); + quaternion q(mat); + matrix4 mat2; + q.getMatrix(mat2); + CHECK(matrix_equals(mat, mat2)); + }); +} + +SECTION("matrix-euler roundtrip") { + test_euler_angles_rad([](v3f rad) { + matrix4 mat, mat2; + mat.setRotationRadians(rad); + v3f rad2 = mat.getRotationDegrees() * core::DEGTORAD; + mat2.setRotationRadians(rad2); + CHECK(matrix_equals(mat, mat2)); + }); +} + +} From b6c71b2379cf6a510c070ea7630b93fcd7bfa89d Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Thu, 30 Jan 2025 13:32:10 +0100 Subject: [PATCH 015/284] Improve matrix4::getRotationDegrees a bit, radians --- irr/include/matrix4.h | 88 +++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/irr/include/matrix4.h b/irr/include/matrix4.h index efb727098..40fb41e57 100644 --- a/irr/include/matrix4.h +++ b/irr/include/matrix4.h @@ -179,9 +179,7 @@ public: CMatrix4 &setRotationDegrees(const vector3d &rotation); //! Get the rotation, as set by setRotation() when you already know the scale used to create the matrix - /** NOTE: The scale needs to be the correct one used to create this matrix. - You can _not_ use the result of getScale(), but have to save your scale - variable in another place (like ISceneNode does). + /** NOTE: No scale value can be 0 or the result is undefined. NOTE: It does not necessarily return the *same* Euler angles as those set by setRotationDegrees(), but the rotation will be equivalent, i.e. will have the same result when used to rotate a vector or node. @@ -189,15 +187,17 @@ public: WARNING: There have been troubles with this function over the years and we may still have missed some corner cases. It's generally safer to keep the rotation and scale you used to create the matrix around and work with those. */ - vector3d getRotationDegrees(const vector3d &scale) const; + vector3d getRotationRadians(const vector3d &scale) const; //! Returns the rotation, as set by setRotation(). /** NOTE: You will have the same end-rotation as used in setRotation, but it might not use the same axis values. - NOTE: This only works correct if no other matrix operations have been done on the inner 3x3 matrix besides - setting rotation (so no scale/shear). Thought it (probably) works as long as scale doesn't flip handedness. + NOTE: This only works correctly for TRS matrix products where S is a positive, component-wise scaling (see setScale). NOTE: It does not necessarily return the *same* Euler angles as those set by setRotationDegrees(), - but the rotation will be equivalent, i.e. will have the same result when used to rotate a vector or node. + but the rotation will be equivalent, i.e. will have the same result when used to rotate a vector or node. */ + vector3d getRotationRadians() const; + + //! Same as getRotationRadians, but returns degrees. vector3d getRotationDegrees() const; //! Make a rotation matrix from angle and axis, assuming left handed rotation. @@ -425,6 +425,9 @@ public: bool equals(const CMatrix4 &other, const T tolerance = (T)ROUNDING_ERROR_f64) const; private: + template + vector3d getRotation(const vector3d &scale) const; + //! Matrix data, stored in row-major order T M[16]; }; @@ -779,63 +782,60 @@ inline CMatrix4 &CMatrix4::setRotationRadians(const vector3d &rotation) return *this; } -//! Returns a rotation which (mostly) works in combination with the given scale -/** -This code was originally written by by Chev (assuming no scaling back then, -we can be blamed for all problems added by regarding scale) -*/ template -inline vector3d CMatrix4::getRotationDegrees(const vector3d &scale_) const +template +inline vector3d CMatrix4::getRotation(const vector3d &scale_) const { + // Based on code by Chev const CMatrix4 &mat = *this; const vector3d scale(iszero(scale_.X) ? FLT_MAX : scale_.X, iszero(scale_.Y) ? FLT_MAX : scale_.Y, iszero(scale_.Z) ? FLT_MAX : scale_.Z); const vector3d invScale(reciprocal(scale.X), reciprocal(scale.Y), reciprocal(scale.Z)); - f64 Y = -asin(clamp(mat[2] * invScale.X, -1.0, 1.0)); - const f64 C = cos(Y); - Y *= RADTODEG64; + f64 a = clamp(mat[2] * invScale.X, -1.0, 1.0); + f64 Y = -asin(a); f64 rotx, roty, X, Z; - if (!iszero((T)C)) { - const f64 invC = reciprocal(C); - rotx = mat[10] * invC * invScale.Z; - roty = mat[6] * invC * invScale.Y; - X = atan2(roty, rotx) * RADTODEG64; - rotx = mat[0] * invC * invScale.X; - roty = mat[1] * invC * invScale.X; - Z = atan2(roty, rotx) * RADTODEG64; + if (!core::equals(std::abs(a), 1.0)) { + // abs(a) = abs(sin(Y)) = 1 <=> cos(Y) = 0 + rotx = mat[10] * invScale.Z; + roty = mat[6] * invScale.Y; + X = atan2(roty, rotx); + rotx = mat[0] * invScale.X; + roty = mat[1] * invScale.X; + Z = atan2(roty, rotx); } else { X = 0.0; - rotx = mat[5] * invScale.Y; - roty = -mat[4] * invScale.Y; - Z = atan2(roty, rotx) * RADTODEG64; + rotx = mat[5]; + roty = -mat[4]; + Z = atan2(roty, rotx); } - // fix values that get below zero - if (X < 0.0) - X += 360.0; - if (Y < 0.0) - Y += 360.0; - if (Z < 0.0) - Z += 360.0; + if (degrees) { + X *= core::RADTODEG64; + Y *= core::RADTODEG64; + Z *= core::RADTODEG64; + } return vector3d((T)X, (T)Y, (T)Z); } -//! Returns a rotation that is equivalent to that set by setRotationDegrees(). +template +inline vector3d CMatrix4::getRotationRadians(const vector3d &scale) const +{ + return getRotation(scale); +} + +template +inline vector3d CMatrix4::getRotationRadians() const +{ + return getRotationRadians(getScale()); +} + template inline vector3d CMatrix4::getRotationDegrees() const { - // Note: Using getScale() here make it look like it could do matrix decomposition. - // It can't! It works (or should work) as long as rotation doesn't flip the handedness - // aka scale swapping 1 or 3 axes. (I think we could catch that as well by comparing - // crossproduct of first 2 axes to direction of third axis, but TODO) - // And maybe it should also offer the solution for the simple calculation - // without regarding scaling as Irrlicht did before 1.7 - vector3d scale(getScale()); - - return getRotationDegrees(scale); + return getRotation(getScale()); } //! Sets matrix to rotation matrix defined by axis and angle, assuming LH rotation From d74af2f1a741386f646c8522eb1644fb6c554b0e Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Thu, 30 Jan 2025 14:11:16 +0100 Subject: [PATCH 016/284] Use matrix4::getRotationRadians --- irr/src/CAnimatedMeshSceneNode.cpp | 2 +- irr/src/CXMeshFileLoader.cpp | 2 -- src/client/clientenvironment.cpp | 4 ++-- src/client/game.cpp | 7 ++++--- src/client/hud.cpp | 2 +- src/client/hud.h | 12 +++++++++--- src/raycast.cpp | 6 +++--- src/server/unit_sao.h | 2 +- src/unittest/test_irr_matrix4.cpp | 23 ++++++++++++----------- src/unittest/test_irr_rotation.cpp | 2 +- 10 files changed, 34 insertions(+), 28 deletions(-) diff --git a/irr/src/CAnimatedMeshSceneNode.cpp b/irr/src/CAnimatedMeshSceneNode.cpp index 6facfcd06..09d83038b 100644 --- a/irr/src/CAnimatedMeshSceneNode.cpp +++ b/irr/src/CAnimatedMeshSceneNode.cpp @@ -619,7 +619,7 @@ void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions) // Code is slow, needs to be fixed up - const core::quaternion RotationStart(PretransitingSave[n].getRotationDegrees() * core::DEGTORAD); + const core::quaternion RotationStart(PretransitingSave[n].getRotationRadians()); const core::quaternion RotationEnd(JointChildSceneNodes[n]->getRotation() * core::DEGTORAD); core::quaternion QRotation; diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index c09f2e481..ed8c18350 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -1526,8 +1526,6 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint) os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } - // core::vector3df rotation = mat.getRotationDegrees(); - AnimatedMesh->addRotationKey(joint, time, core::quaternion(mat.getTransposed())); AnimatedMesh->addPositionKey(joint, time, mat.getTranslation()); diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 42a31ae8c..ed9a23ce3 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -441,8 +441,8 @@ void ClientEnvironment::getSelectedActiveObjects( GenericCAO* gcao = dynamic_cast(obj); if (gcao != nullptr && gcao->getProperties().rotate_selectionbox) { gcao->getSceneNode()->updateAbsolutePosition(); - const v3f deg = obj->getSceneNode()->getAbsoluteTransformation().getRotationDegrees(); - collision = boxLineCollision(selection_box, deg, + const v3f rad = obj->getSceneNode()->getAbsoluteTransformation().getRotationRadians(); + collision = boxLineCollision(selection_box, rad, rel_pos, line_vector, ¤t_intersection, ¤t_normal, ¤t_raw_normal); } else { collision = boxLineCollision(selection_box, rel_pos, line_vector, diff --git a/src/client/game.cpp b/src/client/game.cpp index 8ff7050cd..968cddf01 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3239,9 +3239,10 @@ PointedThing Game::updatePointedThing( hud->setSelectionPos(pos, camera_offset); GenericCAO* gcao = dynamic_cast(runData.selected_object); if (gcao != nullptr && gcao->getProperties().rotate_selectionbox) - hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees()); + hud->setSelectionRotationRadians(gcao->getSceneNode() + ->getAbsoluteTransformation().getRotationRadians()); else - hud->setSelectionRotation(v3f()); + hud->setSelectionRotationRadians(v3f()); } hud->setSelectedFaceNormal(result.raw_intersection_normal); } else if (result.type == POINTEDTHING_NODE) { @@ -3261,7 +3262,7 @@ PointedThing Game::updatePointedThing( } hud->setSelectionPos(intToFloat(result.node_undersurface, BS), camera_offset); - hud->setSelectionRotation(v3f()); + hud->setSelectionRotationRadians(v3f()); hud->setSelectedFaceNormal(result.intersection_normal); } diff --git a/src/client/hud.cpp b/src/client/hud.cpp index e0b4e83fc..aa9544486 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -880,7 +880,7 @@ void Hud::drawSelectionMesh() core::matrix4 translate; translate.setTranslation(m_selection_pos_with_offset); core::matrix4 rotation; - rotation.setRotationDegrees(m_selection_rotation); + rotation.setRotationRadians(m_selection_rotation_radians); driver->setTransform(video::ETS_WORLD, translate * rotation); if (m_mode == HIGHLIGHT_BOX) { diff --git a/src/client/hud.h b/src/client/hud.h index aa4d53cda..b42435f25 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -74,9 +74,15 @@ public: v3f getSelectionPos() const { return m_selection_pos; } - void setSelectionRotation(v3f rotation) { m_selection_rotation = rotation; } + void setSelectionRotationRadians(v3f rotation) + { + m_selection_rotation_radians = rotation; + } - v3f getSelectionRotation() const { return m_selection_rotation; } + v3f getSelectionRotationRadians() const + { + return m_selection_rotation_radians; + } void setSelectionMeshColor(const video::SColor &color) { @@ -129,7 +135,7 @@ private: std::vector m_halo_boxes; v3f m_selection_pos; v3f m_selection_pos_with_offset; - v3f m_selection_rotation; + v3f m_selection_rotation_radians; scene::IMesh *m_selection_mesh = nullptr; video::SColor m_selection_mesh_color; diff --git a/src/raycast.cpp b/src/raycast.cpp index c84cb01e7..fa6ad9cbd 100644 --- a/src/raycast.cpp +++ b/src/raycast.cpp @@ -124,13 +124,13 @@ bool boxLineCollision(const aabb3f &box, const v3f start, return false; } -bool boxLineCollision(const aabb3f &box, const v3f rotation, - const v3f start, const v3f dir, +bool boxLineCollision(const aabb3f &box, v3f rotation_radians, + v3f start, v3f dir, v3f *collision_point, v3f *collision_normal, v3f *raw_collision_normal) { // Inversely transform the ray rather than rotating the box faces; // this allows us to continue using a simple ray - AABB intersection - core::quaternion rot(rotation * core::DEGTORAD); + core::quaternion rot(rotation_radians); rot.makeInverse(); bool collision = boxLineCollision(box, rot * start, rot * dir, collision_point, collision_normal); diff --git a/src/server/unit_sao.h b/src/server/unit_sao.h index 8cc27c967..6930cd583 100644 --- a/src/server/unit_sao.h +++ b/src/server/unit_sao.h @@ -30,7 +30,7 @@ public: v3f res; // First rotate by m_rotation, then rotate by the automatic rotate yaw (core::quaternion(v3f(0, -m_rotation_add_yaw * core::DEGTORAD, 0)) - * core::quaternion(rot.getRotationDegrees() * core::DEGTORAD)) + * core::quaternion(rot.getRotationRadians())) .toEuler(res); return res * core::RADTODEG; } diff --git a/src/unittest/test_irr_matrix4.cpp b/src/unittest/test_irr_matrix4.cpp index 92938dfed..be6c6aa08 100644 --- a/src/unittest/test_irr_matrix4.cpp +++ b/src/unittest/test_irr_matrix4.cpp @@ -84,20 +84,20 @@ SECTION("getScale") { } } -SECTION("getRotationDegrees") { - auto test_rotation_degrees = [](v3f deg, v3f scale) { +SECTION("getRotationRadians") { + auto test_rotation_degrees = [](v3f rad, v3f scale) { matrix4 S; S.setScale(scale); matrix4 R; - R.setRotationDegrees(deg); - v3f rot = (R * S).getRotationDegrees(); + R.setRotationRadians(rad); + v3f rot = (R * S).getRotationRadians(); matrix4 B; - B.setRotationDegrees(rot); + B.setRotationRadians(rot); CHECK(matrix_equals(R, B)); }; SECTION("returns a rotation equivalent to the original rotation") { - test_rotation_degrees({100, 200, 300}, v3f(1)); - Catch::Generators::RandomFloatingGenerator gen_angle(0, 360, Catch::getSeed()); + test_rotation_degrees({1.0f, 2.0f, 3.0f}, v3f(1)); + Catch::Generators::RandomFloatingGenerator gen_angle(0.0f, 2 * core::PI, Catch::getSeed()); Catch::Generators::RandomFloatingGenerator gen_scale(0.1f, 10, Catch::getSeed()); auto draw = [](auto gen) { f32 f = gen.get(); @@ -109,11 +109,12 @@ SECTION("getRotationDegrees") { }; for (int i = 0; i < 1000; ++i) test_rotation_degrees(draw_v3f(gen_angle), draw_v3f(gen_scale)); - for (f32 i = 0; i < 360; i += 90) - for (f32 j = 0; j < 360; j += 90) - for (f32 k = 0; k < 360; k += 90) { + for (f32 i = 0; i < 4; ++i) + for (f32 j = 0; j < 4; ++j) + for (f32 k = 0; k < 4; ++k) { + v3f rad = core::PI / 4.0f * v3f(i, j, k); for (int l = 0; l < 100; ++l) { - test_rotation_degrees({i, j, k}, draw_v3f(gen_scale)); + test_rotation_degrees(rad, draw_v3f(gen_scale)); } } } diff --git a/src/unittest/test_irr_rotation.cpp b/src/unittest/test_irr_rotation.cpp index 1da0462fe..0aa9c9573 100644 --- a/src/unittest/test_irr_rotation.cpp +++ b/src/unittest/test_irr_rotation.cpp @@ -100,7 +100,7 @@ SECTION("matrix-euler roundtrip") { test_euler_angles_rad([](v3f rad) { matrix4 mat, mat2; mat.setRotationRadians(rad); - v3f rad2 = mat.getRotationDegrees() * core::DEGTORAD; + v3f rad2 = mat.getRotationRadians(); mat2.setRotationRadians(rad2); CHECK(matrix_equals(mat, mat2)); }); From 90121dc66f02242437f6db908c3360a482cfd0b9 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Thu, 30 Jan 2025 14:12:44 +0100 Subject: [PATCH 017/284] Fix & improve glTF loader matrix decomposition --- irr/src/CGLTFMeshFileLoader.cpp | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 4e653fbf8..b32ba5692 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -546,16 +546,6 @@ void SelfType::MeshExtractor::deferAddMesh( }); } -// Base transformation between left & right handed coordinate systems. -// This just inverts the Z axis. -static const core::matrix4 leftToRight = core::matrix4( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, -1, 0, - 0, 0, 0, 1 -); -static const core::matrix4 rightToLeft = leftToRight; - static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMesh::SJoint *joint) { // Note: Under the hood, this casts these doubles to floats. @@ -570,14 +560,7 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMes auto scale = mat.getScale(); joint->Animatedscale = scale; - core::matrix4 inverseScale; - inverseScale.setScale(core::vector3df( - scale.X == 0 ? 0 : 1 / scale.X, - scale.Y == 0 ? 0 : 1 / scale.Y, - scale.Z == 0 ? 0 : 1 / scale.Z)); - - core::matrix4 axisNormalizedMat = inverseScale * mat; - joint->Animatedrotation = axisNormalizedMat.getRotationDegrees(); + joint->Animatedrotation = mat.getRotationRadians(scale); // Invert the rotation because it is applied using `getMatrix_transposed`, // which again inverts. joint->Animatedrotation.makeInverse(); From a11b25f3f533a125cbf47e8e0f74501a5dd5e6d8 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Fri, 28 Feb 2025 10:49:52 +0100 Subject: [PATCH 018/284] Use fallback font correctly for fonts provided by the server --- src/client/fontengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index 965b4ab15..a8c6d4b3e 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -278,7 +278,7 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) }; auto it = m_media_faces.find(media_name); - if (it != m_media_faces.end()) { + if (spec.mode != _FM_Fallback && it != m_media_faces.end()) { auto *face = it->second.get(); if (auto *font = createFont(face)) return font; From 8d822d82315f153a9d8d5c2f4fcd2e7fab1dfec2 Mon Sep 17 00:00:00 2001 From: Joshua Gerrish Date: Sat, 1 Mar 2025 06:26:33 -0500 Subject: [PATCH 019/284] Fix compile error with MSVC: string is not a member of std --- src/script/common/c_converter.h | 1 + src/script/common/c_internal.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 39f6d3f7a..b55f1c9c9 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -12,6 +12,7 @@ #pragma once #include +#include #include #include "irrlichttypes_bloated.h" diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index e5b33f9eb..401b2115e 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -11,6 +11,7 @@ #pragma once +#include #include extern "C" { From c0328e5363872fd1d538be2bf33f1647c606394b Mon Sep 17 00:00:00 2001 From: millennIumAMbiguity <37588844+millennIumAMbiguity@users.noreply.github.com> Date: Sat, 1 Mar 2025 12:27:43 +0100 Subject: [PATCH 020/284] Centered title in README.md and added icon --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3fe4fe856..ed149687c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ -Luanti (formerly Minetest) -========================== - -![Build Status](https://github.com/luanti-org/luanti/workflows/build/badge.svg) -[![Translation status](https://hosted.weblate.org/widgets/minetest/-/svg-badge.svg)](https://hosted.weblate.org/engage/minetest/?utm_source=widget) -[![License](https://img.shields.io/badge/license-LGPLv2.1%2B-blue.svg)](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) +
+ +

Luanti (formerly Minetest)

+ Build Status + Translation status + License +
+

Luanti is a free open-source voxel game engine with easy modding and game creation. From eb79a76742f52f5b1e579ad91b00dfae545203f8 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 1 Mar 2025 18:27:46 +0100 Subject: [PATCH 021/284] Android: update SDL support code (#15853) --- android/SDL_Java_RMB_fix.patch | 3 +-- .../app/src/main/java/org/libsdl/app/SDLActivity.java | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/android/SDL_Java_RMB_fix.patch b/android/SDL_Java_RMB_fix.patch index cc0eb4772..ef8840dca 100644 --- a/android/SDL_Java_RMB_fix.patch +++ b/android/SDL_Java_RMB_fix.patch @@ -1,5 +1,4 @@ diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java -index fd5a056e3..83e3cf657 100644 --- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -1345,7 +1345,12 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh @@ -9,7 +8,7 @@ index fd5a056e3..83e3cf657 100644 - if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE || + /* -+ * CUSTOM ADDITION FOR MINETEST ++ * CUSTOM ADDITION FOR LUANTI + * should be upstreamed + */ + (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) { diff --git a/android/app/src/main/java/org/libsdl/app/SDLActivity.java b/android/app/src/main/java/org/libsdl/app/SDLActivity.java index 393347d43..919640040 100644 --- a/android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -60,8 +60,8 @@ import java.util.Locale; public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 2; - private static final int SDL_MINOR_VERSION = 30; - private static final int SDL_MICRO_VERSION = 8; + private static final int SDL_MINOR_VERSION = 32; + private static final int SDL_MICRO_VERSION = 0; /* // Display InputType.SOURCE/CLASS of events and devices // @@ -790,6 +790,9 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); SDLActivity.mFullscreenModeActive = false; } + if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) { + window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } } } else { Log.e(TAG, "error handling message, getContext() returned no Activity"); @@ -1347,7 +1350,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE || /* - * CUSTOM ADDITION FOR MINETEST + * CUSTOM ADDITION FOR LUANTI * should be upstreamed */ (source & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE) { From 24c1230c7b2389d3cdf1e801d4a9b2a1d77c8fef Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 1 Mar 2025 21:05:15 +0100 Subject: [PATCH 022/284] Client: fix disappearing node inventories on older servers ee9258ce introduced a logic error, which caused clients to lose node metadata when they should not and vice-versa. See also: server.cpp / Server::sendAddNode --- src/network/clientpackethandler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 05ef689b0..f589396c6 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -241,10 +241,10 @@ void Client::handleCommand_AddNode(NetworkPacket* pkt) MapNode n; n.deSerialize(ptr, m_server_ser_ver); - bool remove_metadata; - *pkt >> remove_metadata; + bool keep_metadata; + *pkt >> keep_metadata; - addNode(p, n, remove_metadata); + addNode(p, n, !keep_metadata); } void Client::handleCommand_NodemetaChanged(NetworkPacket *pkt) From 062207e696cfbe382157ece4746e104977b7e2cd Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 1 Mar 2025 00:16:18 +0100 Subject: [PATCH 023/284] Enforce minimum client_mapblock_limit depending on view range --- builtin/settingtypes.txt | 4 +++- src/client/client.cpp | 39 +++++++++++++++++++++++++++++++-------- src/client/client.h | 7 +------ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 11aac1d66..2fad0cdef 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1998,7 +1998,9 @@ max_out_chat_queue_size (Maximum size of the outgoing chat queue) int 20 -1 3276 client_unload_unused_data_timeout (Mapblock unload timeout) float 600.0 0.0 # Maximum number of mapblocks for client to be kept in memory. -# Set to -1 for unlimited amount. +# Note that there is an internal dynamic minimum number of blocks that +# won't be deleted, depending on the current view range. +# Set to -1 for no limit. client_mapblock_limit (Mapblock limit) int 7500 -1 2147483647 # Maximum number of blocks that are simultaneously sent per client. diff --git a/src/client/client.cpp b/src/client/client.cpp index 48dc61984..3c7716cbd 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -446,20 +446,43 @@ void Client::step(float dtime) /* Run Map's timers and unload unused data */ - const float map_timer_and_unload_dtime = 5.25; + constexpr float map_timer_and_unload_dtime = 5.25f; + constexpr s32 mapblock_limit_enforce_distance = 200; if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { std::vector deleted_blocks; + + // Determine actual block limit to use + const s32 configured_limit = g_settings->getS32("client_mapblock_limit"); + s32 mapblock_limit; + if (configured_limit < 0) { + mapblock_limit = -1; + } else { + s32 view_range = g_settings->getS16("viewing_range"); + // Up to a certain limit we want to guarantee that the client can keep + // a full 360° view loaded in memory without blocks vanishing behind + // the players back. + // We use a sphere volume to approximate this. In practice far less + // blocks will be needed due to occlusion/culling. + float blocks_range = ceilf(std::min(mapblock_limit_enforce_distance, view_range) + / (float) MAP_BLOCKSIZE); + mapblock_limit = (4.f/3.f) * M_PI * powf(blocks_range, 3); + assert(mapblock_limit > 0); + mapblock_limit = std::max(mapblock_limit, configured_limit); + if (mapblock_limit > std::max(configured_limit, m_mapblock_limit_logged)) { + infostream << "Client: using block limit of " << mapblock_limit + << " rather than configured " << configured_limit + << " due to view range." << std::endl; + m_mapblock_limit_logged = mapblock_limit; + } + } + m_env.getMap().timerUpdate(map_timer_and_unload_dtime, std::max(g_settings->getFloat("client_unload_unused_data_timeout"), 0.0f), - g_settings->getS32("client_mapblock_limit"), - &deleted_blocks); + mapblock_limit, &deleted_blocks); - /* - Send info to server - NOTE: This loop is intentionally iterated the way it is. - */ + // Send info to server - std::vector::iterator i = deleted_blocks.begin(); + auto i = deleted_blocks.begin(); std::vector sendlist; for(;;) { if(sendlist.size() == 255 || i == deleted_blocks.end()) { diff --git a/src/client/client.h b/src/client/client.h index ce753ebdf..237dd93b6 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -486,23 +486,18 @@ private: u8 m_server_ser_ver; // Used version of the protocol with server - // Values smaller than 25 only mean they are smaller than 25, - // and aren't accurate. We simply just don't know, because - // the server didn't send the version back then. // If 0, server init hasn't been received yet. u16 m_proto_ver = 0; bool m_update_wielded_item = false; Inventory *m_inventory_from_server = nullptr; float m_inventory_from_server_age = 0.0f; + s32 m_mapblock_limit_logged = 0; PacketCounter m_packetcounter; // Block mesh animation parameters float m_animation_time = 0.0f; int m_crack_level = -1; v3s16 m_crack_pos; - // 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT - //s32 m_daynight_i; - //u32 m_daynight_ratio; std::queue m_out_chat_queue; u32 m_last_chat_message_sent; float m_chat_message_allowance = 5.0f; From c3477a4d08d26f710d944ff5adb4bf697c51aff1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 1 Mar 2025 00:32:38 +0100 Subject: [PATCH 024/284] Adjust Android default view range and mapblock limit --- src/defaultsettings.cpp | 8 +++++--- src/emerge.cpp | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 022c71071..f81995c76 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -112,7 +112,7 @@ void set_default_settings() settings->setDefault("screenshot_format", "png"); settings->setDefault("screenshot_quality", "0"); settings->setDefault("client_unload_unused_data_timeout", "600"); - settings->setDefault("client_mapblock_limit", "7500"); + settings->setDefault("client_mapblock_limit", "7500"); // about 120 MB settings->setDefault("enable_build_where_you_stand", "false"); settings->setDefault("curl_timeout", "20000"); settings->setDefault("curl_parallel_limit", "8"); @@ -547,6 +547,7 @@ void set_default_settings() settings->setDefault("virtual_joystick_triggers_aux1", "false"); settings->setDefault("touch_punch_gesture", "short_tap"); settings->setDefault("clickable_chat_weblinks", "true"); + // Altered settings for Android #ifdef __ANDROID__ settings->setDefault("screen_w", "0"); @@ -558,9 +559,9 @@ void set_default_settings() settings->setDefault("max_block_generate_distance", "5"); settings->setDefault("sqlite_synchronous", "1"); settings->setDefault("server_map_save_interval", "15"); - settings->setDefault("client_mapblock_limit", "1000"); + settings->setDefault("client_mapblock_limit", "1500"); settings->setDefault("active_block_range", "2"); - settings->setDefault("viewing_range", "50"); + settings->setDefault("viewing_range", "70"); settings->setDefault("leaves_style", "simple"); // Note: OpenGL ES 2.0 is not guaranteed to provide depth textures, // which we would need for PP. @@ -568,6 +569,7 @@ void set_default_settings() // still set these two settings in case someone wants to enable it settings->setDefault("debanding", "false"); settings->setDefault("post_processing_texture_bits", "8"); + // We don't have working certificate verification... settings->setDefault("curl_verify_cert", "false"); // Apply settings according to screen size diff --git a/src/emerge.cpp b/src/emerge.cpp index f1980579b..be323f7a9 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -106,9 +106,9 @@ EmergeManager::EmergeManager(Server *server, MetricsBackend *mb) m_qlimit_generate = nthreads + 1; // don't trust user input for something very important like this - m_qlimit_total = rangelim(m_qlimit_total, 1, 1000000); - m_qlimit_diskonly = rangelim(m_qlimit_diskonly, 1, 1000000); + m_qlimit_diskonly = rangelim(m_qlimit_diskonly, 2, 1000000); m_qlimit_generate = rangelim(m_qlimit_generate, 1, 1000000); + m_qlimit_total = std::max(m_qlimit_diskonly, m_qlimit_generate); for (s16 i = 0; i < nthreads; i++) m_threads.push_back(new EmergeThread(server, i)); From 08fad862aa5f1a40425cfa151a92fa9c77bf5b75 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Sat, 1 Mar 2025 17:11:23 +0100 Subject: [PATCH 025/284] Code cleanups. Function does not return deco count. --- src/mapgen/mg_decoration.cpp | 35 ++++++++++------------------------- src/mapgen/mg_decoration.h | 4 ++-- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index e8f381ec6..825391663 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -36,21 +36,17 @@ DecorationManager::DecorationManager(IGameDef *gamedef) : } -size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed, +void DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) { - size_t nplaced = 0; - for (size_t i = 0; i != m_objects.size(); i++) { Decoration *deco = (Decoration *)m_objects[i]; if (!deco) continue; - nplaced += deco->placeDeco(mg, blockseed, nmin, nmax); + deco->placeDeco(mg, blockseed, nmin, nmax); blockseed++; } - - return nplaced; } DecorationManager *DecorationManager::clone() const @@ -128,38 +124,27 @@ bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) } -size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) +void Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) { PcgRandom ps(blockseed + 53); int carea_size = nmax.X - nmin.X + 1; // Divide area into parts // If chunksize is changed it may no longer be divisable by sidelen - if (carea_size % sidelen) + if (carea_size % sidelen != 0) sidelen = carea_size; - s16 divlen = carea_size / sidelen; 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 z0 = 0; z0 < carea_size; z0 += sidelen) + for (s16 x0 = 0; x0 < carea_size; x0 += sidelen) { + v2s16 p2d_min(nmin.X + x0, nmin.Z + z0); + v2s16 p2d_max(nmin.X + x0 + sidelen - 1, nmin.Z + z0 + sidelen - 1); bool cover = false; // Amount of decorations float nval = (flags & DECO_USE_NOISE) ? - NoisePerlin2D(&np, p2d_center.X, p2d_center.Y, mapseed) : + NoisePerlin2D(&np, p2d_min.X + sidelen / 2, p2d_min.Y + sidelen / 2, mapseed) : fill_ratio; u32 deco_count = 0; @@ -262,7 +247,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) } } - return 0; + return; } diff --git a/src/mapgen/mg_decoration.h b/src/mapgen/mg_decoration.h index c8d187d2a..40829b689 100644 --- a/src/mapgen/mg_decoration.h +++ b/src/mapgen/mg_decoration.h @@ -44,7 +44,7 @@ public: virtual void resolveNodeNames(); bool canPlaceDecoration(MMVManip *vm, v3s16 p); - size_t placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); + void placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) = 0; @@ -135,7 +135,7 @@ public: } } - size_t placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); + void placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); private: DecorationManager() {}; From 6e995972bba107d1e3dfd5361fd7620d8ce748ab Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Sat, 1 Mar 2025 17:15:02 +0100 Subject: [PATCH 026/284] check y limits early --- src/mapgen/mg_decoration.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index 825391663..dcf32acb7 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -126,6 +126,10 @@ bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) void Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) { + // Skip if y ranges do not overlap + if (nmax.Y < y_min || y_max < nmin.Y) + return; + PcgRandom ps(blockseed + 53); int carea_size = nmax.X - nmin.X + 1; From 98048cb06d9a3b13f1d9ee70f03c2014ff73dbbe Mon Sep 17 00:00:00 2001 From: wrrrzr <161970349+wrrrzr@users.noreply.github.com> Date: Mon, 3 Mar 2025 22:33:19 +0300 Subject: [PATCH 027/284] Fix missing includes in skyparams.h --- src/skyparams.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/skyparams.h b/src/skyparams.h index 79a4d8024..c5cd574cd 100644 --- a/src/skyparams.h +++ b/src/skyparams.h @@ -4,6 +4,12 @@ #pragma once +#include +#include +#include "SColor.h" +#include "irr_v2d.h" + +using namespace irr; struct SkyColor { From 0eb047ca33b2c3708257d95f8857697fc0403c07 Mon Sep 17 00:00:00 2001 From: Medley <198984680+maplemedley@users.noreply.github.com> Date: Mon, 3 Mar 2025 20:33:42 +0100 Subject: [PATCH 028/284] Disable debug-breaking locale workaround when debugging (#15859) --- src/gettext.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/gettext.cpp b/src/gettext.cpp index 59ce2409c..7cb4a7763 100644 --- a/src/gettext.cpp +++ b/src/gettext.cpp @@ -171,8 +171,13 @@ void init_gettext(const char *path, const std::string &configured_language, #if CHECK_CLIENT_BUILD() // Hack to force gettext to see the right environment - if (current_language != configured_language) - MSVC_LocaleWorkaround(argc, argv); + if (current_language != configured_language) { + // Disabled when debugger is present as it can break debugging + if (!IsDebuggerPresent()) + MSVC_LocaleWorkaround(argc, argv); + else + actionstream << "Debugger detected. Skipping MSVC_LocaleWorkaround." << std::endl; + } #else errorstream << "*******************************************************" << std::endl; errorstream << "Can't apply locale workaround for server!" << std::endl; From 8449f5f6db8ef9ad9fbd10ae7ecb4e6cfcc665f9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Feb 2025 17:21:22 +0100 Subject: [PATCH 029/284] Make devtest grass use overlay tiles --- games/devtest/mods/basenodes/init.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/games/devtest/mods/basenodes/init.lua b/games/devtest/mods/basenodes/init.lua index a6cc680b4..532fca633 100644 --- a/games/devtest/mods/basenodes/init.lua +++ b/games/devtest/mods/basenodes/init.lua @@ -22,11 +22,15 @@ core.register_node("basenodes:desert_stone", { core.register_node("basenodes:dirt_with_grass", { description = "Dirt with Grass", - tiles ={"default_grass.png", + -- Using overlays here has no real merit here but we do it anyway so + -- overlay-related bugs become more apparent in devtest. + tiles = {"default_dirt.png"}, + overlay_tiles = { + "default_grass.png", -- a little dot on the bottom to distinguish it from dirt - "default_dirt.png^basenodes_dirt_with_grass_bottom.png", - {name = "default_dirt.png^default_grass_side.png", - tileable_vertical = false}}, + "basenodes_dirt_with_grass_bottom.png", + {name = "default_grass_side.png", tileable_vertical = false}, + }, groups = {crumbly=3, soil=1}, }) From bc430194672f67e9a336172cf99ef2e8439466ea Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Feb 2025 17:35:02 +0100 Subject: [PATCH 030/284] Fix TerminalChatConsole crash this setting was removed in #15633 --- src/terminal_chat_console.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/terminal_chat_console.cpp b/src/terminal_chat_console.cpp index 471409ee0..8c2967b01 100644 --- a/src/terminal_chat_console.cpp +++ b/src/terminal_chat_console.cpp @@ -332,12 +332,9 @@ void TerminalChatConsole::step(int ch) if (p.first > m_log_level) continue; - std::wstring error_message = utf8_to_wide(Logger::getLevelLabel(p.first)); - if (!g_settings->getBool("disable_escape_sequences")) { - error_message = std::wstring(L"\x1b(c@red)").append(error_message) - .append(L"\x1b(c@white)"); - } - m_chat_backend.addMessage(error_message, utf8_to_wide(p.second)); + auto label = std::string("\x1b(c@red)") + Logger::getLevelLabel(p.first) + + "\x1b(c@white)"; + m_chat_backend.addMessage(utf8_to_wide(label), utf8_to_wide(p.second)); } // handle input From 76023088351e698316c8898e6b799f7173a93592 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Feb 2025 17:55:31 +0100 Subject: [PATCH 031/284] Revert "Restrict relative mouse mode to Wayland users (#15697)" see #15761 SDL is the only device that supports relative mode and mouse input is actually somewhat broken if it's *not* enabled. This reverts commit 45c5ef87985bbaec14e8ccaa1d7ca4c9efe00260 and 88b007907a8f1a645daa850597083b796e89510e. --- irr/include/IrrlichtDevice.h | 3 --- irr/src/CIrrDeviceSDL.cpp | 9 --------- irr/src/CIrrDeviceSDL.h | 3 --- src/client/game.cpp | 16 ++-------------- 4 files changed, 2 insertions(+), 29 deletions(-) diff --git a/irr/include/IrrlichtDevice.h b/irr/include/IrrlichtDevice.h index 0c422ea89..edc6ead61 100644 --- a/irr/include/IrrlichtDevice.h +++ b/irr/include/IrrlichtDevice.h @@ -198,9 +198,6 @@ public: or similar. */ virtual bool supportsTouchEvents() const { return false; } - //! Checks whether windowing uses the Wayland protocol. - virtual bool isUsingWayland() const { return false; } - //! Get the current color format of the window /** \return Color format of the window. */ virtual video::ECOLOR_FORMAT getColorFormat() const = 0; diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index f0f0db577..6c6b2c00f 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -1256,15 +1256,6 @@ bool CIrrDeviceSDL::supportsTouchEvents() const return true; } -//! Checks whether windowing uses the Wayland protocol. -bool CIrrDeviceSDL::isUsingWayland() const -{ - if (!Window) - return false; - auto *name = SDL_GetCurrentVideoDriver(); - return name && !strcmp(name, "wayland"); -} - //! returns if window is active. if not, nothing need to be drawn bool CIrrDeviceSDL::isWindowActive() const { diff --git a/irr/src/CIrrDeviceSDL.h b/irr/src/CIrrDeviceSDL.h index fcf4608be..4e7a53d9c 100644 --- a/irr/src/CIrrDeviceSDL.h +++ b/irr/src/CIrrDeviceSDL.h @@ -96,9 +96,6 @@ public: //! Checks if the Irrlicht device supports touch events. bool supportsTouchEvents() const override; - //! Checks whether windowing uses the Wayland protocol. - bool isUsingWayland() const override; - //! Get the position of this window on screen core::position2di getWindowPosition() override; diff --git a/src/client/game.cpp b/src/client/game.cpp index 968cddf01..4ad93e1ca 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -756,7 +756,6 @@ private: f32 m_repeat_dig_time; f32 m_cache_cam_smoothing; - bool m_enable_relative_mode = false; bool m_invert_mouse; bool m_enable_hotbar_mouse_wheel; bool m_invert_hotbar_mouse_wheel; @@ -900,15 +899,6 @@ bool Game::startup(bool *kill, m_first_loop_after_window_activation = true; - // In principle we could always enable relative mouse mode, but it causes weird - // bugs on some setups (e.g. #14932), so we enable it only when it's required. - // That is: on Wayland or Android, because it does not support mouse repositioning -#ifdef __ANDROID__ - m_enable_relative_mode = true; -#else - m_enable_relative_mode = device->isUsingWayland(); -#endif - g_client_translations->clear(); // address can change if simple_singleplayer_mode @@ -2361,10 +2351,8 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) Since Minetest has its own code to synthesize mouse events from touch events, this results in duplicated input. To avoid that, we don't enable relative mouse mode if we're in touchscreen mode. */ - if (cur_control) { - cur_control->setRelativeMode(m_enable_relative_mode && - !g_touchcontrols && !isMenuActive()); - } + if (cur_control) + cur_control->setRelativeMode(!g_touchcontrols && !isMenuActive()); if ((device->isWindowActive() && device->isWindowFocused() && !isMenuActive()) || input->isRandom()) { From 27962835503ba1f16c30b2a5433e052ebb8b3b73 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Feb 2025 17:58:21 +0100 Subject: [PATCH 032/284] Remove broken fall bobbing --- builtin/common/settings/dlg_settings.lua | 1 - builtin/settingtypes.txt | 4 --- src/client/camera.cpp | 32 ++---------------------- src/client/camera.h | 3 --- src/defaultsettings.cpp | 1 - 5 files changed, 2 insertions(+), 39 deletions(-) diff --git a/builtin/common/settings/dlg_settings.lua b/builtin/common/settings/dlg_settings.lua index 894eaa596..570c01cd5 100644 --- a/builtin/common/settings/dlg_settings.lua +++ b/builtin/common/settings/dlg_settings.lua @@ -159,7 +159,6 @@ local function load() { heading = fgettext_ne("Movement") }, "arm_inertia", "view_bobbing_amount", - "fall_bobbing_amount", }, }) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 2fad0cdef..a6b05496a 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -295,10 +295,6 @@ arm_inertia (Arm inertia) bool true # For example: 0 for no view bobbing; 1.0 for normal; 2.0 for double. view_bobbing_amount (View bobbing factor) float 1.0 0.0 7.9 -# Multiplier for fall bobbing. -# For example: 0 for no view bobbing; 1.0 for normal; 2.0 for double. -fall_bobbing_amount (Fall bobbing factor) float 0.03 0.0 100.0 - [**Camera] # Field of view in degrees. diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 1eb5bc34d..3f8b4d51f 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -34,7 +34,7 @@ static constexpr f32 CAMERA_OFFSET_STEP = 200; #define WIELDMESH_AMPLITUDE_Y 10.0f static const char *setting_names[] = { - "fall_bobbing_amount", "view_bobbing_amount", "fov", "arm_inertia", + "view_bobbing_amount", "fov", "arm_inertia", "show_nametag_backgrounds", }; @@ -78,7 +78,6 @@ void Camera::readSettings() * (as opposed to the this local caching). This can be addressed in * a later release. */ - m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount", 0.0f, 100.0f); m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount", 0.0f, 7.9f); // 45 degrees is the lowest FOV that doesn't cause the server to treat this // as a zoom FOV and load world beyond the set server limits. @@ -130,13 +129,6 @@ inline f32 my_modf(f32 x) void Camera::step(f32 dtime) { - if(m_view_bobbing_fall > 0) - { - m_view_bobbing_fall -= 3 * dtime; - if(m_view_bobbing_fall <= 0) - m_view_bobbing_fall = -1; // Mark the effect as finished - } - bool was_under_zero = m_wield_change_timer < 0; m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125); @@ -351,26 +343,6 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) // Get camera tilt timer (hurt animation) float cameratilt = fabs(fabs(player->hurt_tilt_timer-0.75)-0.75); - // Fall bobbing animation - float fall_bobbing = 0; - if(player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD) - { - if(m_view_bobbing_fall == -1) // Effect took place and has finished - player->camera_impact = m_view_bobbing_fall = 0; - else if(m_view_bobbing_fall == 0) // Initialize effect - m_view_bobbing_fall = 1; - - // Convert 0 -> 1 to 0 -> 1 -> 0 - fall_bobbing = m_view_bobbing_fall < 0.5 ? m_view_bobbing_fall * 2 : -(m_view_bobbing_fall - 0.5) * 2 + 1; - // Smoothen and invert the above - fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1; - // Amplify according to the intensity of the impact - if (player->camera_impact > 0.0f) - fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5; - - fall_bobbing *= m_cache_fall_bobbing_amount; - } - // Calculate and translate the head SceneNode offsets { v3f eye_offset = player->getEyeOffset(); @@ -392,7 +364,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) } // Set head node transformation - eye_offset.Y += cameratilt * -player->hurt_tilt_strength + fall_bobbing; + eye_offset.Y += cameratilt * -player->hurt_tilt_strength; m_headnode->setPosition(eye_offset); m_headnode->setRotation(v3f(pitch, 0, cameratilt * player->hurt_tilt_strength)); diff --git a/src/client/camera.h b/src/client/camera.h index e23618258..49e07d4ee 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -256,8 +256,6 @@ private: s32 m_view_bobbing_state = 0; // Speed of view bobbing animation f32 m_view_bobbing_speed = 0.0f; - // Fall view bobbing - f32 m_view_bobbing_fall = 0.0f; // Digging animation frame (0 <= m_digging_anim < 1) f32 m_digging_anim = 0.0f; @@ -272,7 +270,6 @@ private: CameraMode m_camera_mode = CAMERA_MODE_FIRST; - f32 m_cache_fall_bobbing_amount; f32 m_cache_view_bobbing_amount; bool m_arm_inertia; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index f81995c76..f9f0c41f0 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -270,7 +270,6 @@ void set_default_settings() settings->setDefault("camera_smoothing", "0.0"); settings->setDefault("cinematic_camera_smoothing", "0.7"); settings->setDefault("view_bobbing_amount", "1.0"); - settings->setDefault("fall_bobbing_amount", "0.03"); settings->setDefault("enable_3d_clouds", "true"); settings->setDefault("soft_clouds", "false"); settings->setDefault("cloud_radius", "12"); From 7abaa8d4cdf3c856676a7cfc1e6593319c8cb445 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Feb 2025 18:06:53 +0100 Subject: [PATCH 033/284] Make Irrlicht identity material const --- irr/include/ISceneNode.h | 6 +++++- irr/include/SMaterial.h | 2 +- irr/src/Irrlicht.cpp | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/irr/include/ISceneNode.h b/irr/include/ISceneNode.h index 897cfb350..c80ff4b48 100644 --- a/irr/include/ISceneNode.h +++ b/irr/include/ISceneNode.h @@ -310,7 +310,11 @@ public: \return The material at that index. */ virtual video::SMaterial &getMaterial(u32 num) { - return video::IdentityMaterial; + // We return a default material since a reference can't be null, + // but note that writing to this is a mistake either by a child class + // or the caller, because getMaterialCount() is zero. + // Doing so will helpfully cause a segfault. + return const_cast(video::IdentityMaterial); } //! Get amount of materials used by this scene node. diff --git a/irr/include/SMaterial.h b/irr/include/SMaterial.h index 3bbc6e946..d48328a31 100644 --- a/irr/include/SMaterial.h +++ b/irr/include/SMaterial.h @@ -472,7 +472,7 @@ public: }; //! global const identity Material -IRRLICHT_API extern SMaterial IdentityMaterial; +IRRLICHT_API extern const SMaterial IdentityMaterial; } // end namespace video } // end namespace irr diff --git a/irr/src/Irrlicht.cpp b/irr/src/Irrlicht.cpp index b1818eb55..2e80088ab 100644 --- a/irr/src/Irrlicht.cpp +++ b/irr/src/Irrlicht.cpp @@ -88,7 +88,7 @@ const matrix4 IdentityMatrix(matrix4::EM4CONST_IDENTITY); namespace video { -SMaterial IdentityMaterial; +const SMaterial IdentityMaterial; extern "C" IRRLICHT_API bool IRRCALLCONV isDriverSupported(E_DRIVER_TYPE driver) { From d54646d34225cfeb45add05d58447c41b5bb5ea9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Feb 2025 18:50:41 +0100 Subject: [PATCH 034/284] Improve error handling of map database creation --- src/database/database-postgresql.cpp | 2 +- src/database/database-postgresql.h | 33 +++++++++++++++++----------- src/database/database-sqlite3.h | 31 +++++++++++++++++--------- src/database/database.h | 5 +++++ src/emerge.cpp | 2 ++ src/server.cpp | 11 ++++++++-- src/servermap.cpp | 19 +++++++++++----- 7 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index 2d10f0db9..20d5482f9 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -97,7 +97,7 @@ void Database_PostgreSQL::ping() bool Database_PostgreSQL::initialized() const { - return (PQstatus(m_conn) == CONNECTION_OK); + return m_conn && PQstatus(m_conn) == CONNECTION_OK; } PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear) diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h index 61e443b11..eb73960f3 100644 --- a/src/database/database-postgresql.h +++ b/src/database/database-postgresql.h @@ -9,20 +9,20 @@ #include "database.h" #include "util/basic_macros.h" -class Settings; - -class Database_PostgreSQL: public Database +// Template class for PostgreSQL based data storage +class Database_PostgreSQL : public Database { public: Database_PostgreSQL(const std::string &connect_string, const char *type); ~Database_PostgreSQL(); - void beginSave(); - void endSave(); + void beginSave() override; + void endSave() override; void rollback(); - bool initialized() const; + bool initialized() const override; + void verifyDatabase() override; protected: // Conversion helpers @@ -73,7 +73,6 @@ protected: } void createTableIfNotExists(const std::string &table_name, const std::string &definition); - void verifyDatabase(); // Database initialization void connectToDatabase(); @@ -99,6 +98,12 @@ private: int m_pgversion = 0; }; +// Not sure why why we have to do this. can't C++ figure it out on its own? +#define PARENT_CLASS_FUNCS \ + void beginSave() { Database_PostgreSQL::beginSave(); } \ + void endSave() { Database_PostgreSQL::endSave(); } \ + void verifyDatabase() { Database_PostgreSQL::verifyDatabase(); } + class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase { public: @@ -110,8 +115,7 @@ public: bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); - void beginSave() { Database_PostgreSQL::beginSave(); } - void endSave() { Database_PostgreSQL::endSave(); } + PARENT_CLASS_FUNCS protected: virtual void createDatabase(); @@ -129,6 +133,8 @@ public: bool removePlayer(const std::string &name); void listPlayers(std::vector &res); + PARENT_CLASS_FUNCS + protected: virtual void createDatabase(); virtual void initStatements(); @@ -143,8 +149,6 @@ public: AuthDatabasePostgreSQL(const std::string &connect_string); virtual ~AuthDatabasePostgreSQL() = default; - virtual void verifyDatabase() { Database_PostgreSQL::verifyDatabase(); } - virtual bool getAuth(const std::string &name, AuthEntry &res); virtual bool saveAuth(const AuthEntry &authEntry); virtual bool createAuth(AuthEntry &authEntry); @@ -152,6 +156,8 @@ public: virtual void listNames(std::vector &res); virtual void reload(); + PARENT_CLASS_FUNCS + protected: virtual void createDatabase(); virtual void initStatements(); @@ -176,10 +182,11 @@ public: bool removeModEntries(const std::string &modname); void listMods(std::vector *res); - void beginSave() { Database_PostgreSQL::beginSave(); } - void endSave() { Database_PostgreSQL::endSave(); } + PARENT_CLASS_FUNCS protected: virtual void createDatabase(); virtual void initStatements(); }; + +#undef PARENT_CLASS_FUNCS diff --git a/src/database/database-sqlite3.h b/src/database/database-sqlite3.h index 0ebd0bbf4..2f9212c16 100644 --- a/src/database/database-sqlite3.h +++ b/src/database/database-sqlite3.h @@ -19,17 +19,17 @@ class Database_SQLite3 : public Database public: virtual ~Database_SQLite3(); - void beginSave(); - void endSave(); + void beginSave() override; + void endSave() override; - bool initialized() const { return m_initialized; } + bool initialized() const override { return m_initialized; } + + /// @note not thread-safe + void verifyDatabase() override; protected: Database_SQLite3(const std::string &savedir, const std::string &dbname); - // Open and initialize the database if needed (not thread-safe) - void verifyDatabase(); - // Check if a specific table exists bool checkTable(const char *table); @@ -160,6 +160,12 @@ private: static int busyHandler(void *data, int count); }; +// Not sure why why we have to do this. can't C++ figure it out on its own? +#define PARENT_CLASS_FUNCS \ + void beginSave() { Database_SQLite3::beginSave(); } \ + void endSave() { Database_SQLite3::endSave(); } \ + void verifyDatabase() { Database_SQLite3::verifyDatabase(); } + class MapDatabaseSQLite3 : private Database_SQLite3, public MapDatabase { public: @@ -171,8 +177,8 @@ public: bool deleteBlock(const v3s16 &pos); void listAllLoadableBlocks(std::vector &dst); - void beginSave() { Database_SQLite3::beginSave(); } - void endSave() { Database_SQLite3::endSave(); } + PARENT_CLASS_FUNCS + protected: virtual void createDatabase(); virtual void initStatements(); @@ -201,6 +207,8 @@ public: bool removePlayer(const std::string &name); void listPlayers(std::vector &res); + PARENT_CLASS_FUNCS + protected: virtual void createDatabase(); virtual void initStatements(); @@ -238,6 +246,8 @@ public: virtual void listNames(std::vector &res); virtual void reload(); + PARENT_CLASS_FUNCS + protected: virtual void createDatabase(); virtual void initStatements(); @@ -273,8 +283,7 @@ public: virtual bool removeModEntries(const std::string &modname); virtual void listMods(std::vector *res); - virtual void beginSave() { Database_SQLite3::beginSave(); } - virtual void endSave() { Database_SQLite3::endSave(); } + PARENT_CLASS_FUNCS protected: virtual void createDatabase(); @@ -289,3 +298,5 @@ private: sqlite3_stmt *m_stmt_remove = nullptr; sqlite3_stmt *m_stmt_remove_all = nullptr; }; + +#undef PARENT_CLASS_FUNCS diff --git a/src/database/database.h b/src/database/database.h index 8d6efdee8..4335ef4dc 100644 --- a/src/database/database.h +++ b/src/database/database.h @@ -16,7 +16,12 @@ class Database public: virtual void beginSave() = 0; virtual void endSave() = 0; + + /// @return true if database connection is open virtual bool initialized() const { return true; } + + /// Open and initialize the database if needed + virtual void verifyDatabase() {}; }; class MapDatabase : public Database diff --git a/src/emerge.cpp b/src/emerge.cpp index be323f7a9..2dfead5b4 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -707,6 +707,8 @@ void *EmergeThread::run() { ScopeProfiler sp(g_profiler, "EmergeThread: load block - async (sum)"); MutexAutoLock dblock(m_db.mutex); + // Note: this can throw an exception, but there isn't really + // a good, safe way to handle it. m_db.loadBlock(pos, databuf); } // actually load it, then decide again diff --git a/src/server.cpp b/src/server.cpp index dd5041a56..116bbfe77 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -473,8 +473,15 @@ void Server::init() EnvAutoLock envlock(this); // Create the Map (loads map_meta.txt, overriding configured mapgen params) - auto startup_server_map = std::make_unique(m_path_world, this, - m_emerge.get(), m_metrics_backend.get()); + std::unique_ptr startup_server_map; + try { + startup_server_map = std::make_unique(m_path_world, this, + m_emerge.get(), m_metrics_backend.get()); + } catch (DatabaseException &e) { + throw ServerError(std::string( + "Failed to initialize the map database. The world may be " + "corrupted or in an unsupported format.\n") + e.what()); + } // Initialize scripting infostream << "Server: Initializing Lua" << std::endl; diff --git a/src/servermap.cpp b/src/servermap.cpp index b72626255..87e886492 100644 --- a/src/servermap.cpp +++ b/src/servermap.cpp @@ -577,27 +577,34 @@ MapDatabase *ServerMap::createDatabase( const std::string &savedir, Settings &conf) { + MapDatabase *db = nullptr; + if (name == "sqlite3") - return new MapDatabaseSQLite3(savedir); + db = new MapDatabaseSQLite3(savedir); if (name == "dummy") - return new Database_Dummy(); + db = new Database_Dummy(); #if USE_LEVELDB if (name == "leveldb") - return new Database_LevelDB(savedir); + db = new Database_LevelDB(savedir); #endif #if USE_REDIS if (name == "redis") - return new Database_Redis(conf); + db = new Database_Redis(conf); #endif #if USE_POSTGRESQL if (name == "postgresql") { std::string connect_string; conf.getNoEx("pgsql_connection", connect_string); - return new MapDatabasePostgreSQL(connect_string); + db = new MapDatabasePostgreSQL(connect_string); } #endif - throw BaseException(std::string("Database backend ") + name + " not supported."); + if (!db) + throw BaseException(std::string("Database backend ") + name + " not supported."); + // Do this to get feedback about errors asap + db->verifyDatabase(); + assert(db->initialized()); + return db; } void ServerMap::beginSave() From 304ce4cd54870c8f95e515e750715a1bda0570c4 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Feb 2025 18:51:46 +0100 Subject: [PATCH 035/284] Fix syntax error in credits.json reported at As it happens this didn't affect most users as jsoncpp allows trailing commas by default since 2019. --- builtin/mainmenu/credits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/mainmenu/credits.json b/builtin/mainmenu/credits.json index cd2b15d78..bd4f0591f 100644 --- a/builtin/mainmenu/credits.json +++ b/builtin/mainmenu/credits.json @@ -57,7 +57,7 @@ "AFCMS", "siliconsniffer", "Wuzzy", - "Zemtzov7", + "Zemtzov7" ], "previous_contributors": [ "Ælla Chiana Moskopp (erle) [Logo]", From 47c000a2938b63b1b6c12b555d41e88f29f37faa Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Feb 2025 19:16:41 +0100 Subject: [PATCH 036/284] Add unittest that lints builtin JSON files --- games/devtest/mods/unittests/misc.lua | 23 +++++++++++++++++++++++ src/script/cpp_api/s_security.cpp | 7 +++++++ 2 files changed, 30 insertions(+) diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index d01eed17d..65dc3259e 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -189,6 +189,29 @@ local function test_write_json() end unittests.register("test_write_json", test_write_json) +local function lint_json_files() + -- Check that files we ship with Luanti are valid JSON + local stack = {core.get_builtin_path()} + local checked = 0 + while #stack > 0 do + local path = table.remove(stack) + for _, name in ipairs(core.get_dir_list(path, true)) do + stack[#stack+1] = path .. "/" .. name + end + for _, name in ipairs(core.get_dir_list(path, false)) do + if name:match("%.json$") then + local f = io.open(path .. "/" .. name, "rb") + print(path .. "/" .. name) + assert(core.parse_json(f:read("*all"), -1) ~= nil) + f:close() + checked = checked + 1 + end + end + end + assert(checked > 0, "no files found?!") +end +unittests.register("lint_json_files", lint_json_files) + local function test_game_info() local info = core.get_game_info() local game_conf = Settings(info.path .. "/game.conf") diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 685403b4b..834650fdc 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -659,6 +659,13 @@ bool ScriptApiSecurity::checkPathWithGamedef(lua_State *L, } } + // Allow read-only access to builtin + if (!write_required) { + str = fs::AbsolutePath(Server::getBuiltinLuaPath()); + if (!str.empty() && fs::PathStartsWith(abs_path, str)) + return true; + } + // Allow read-only access to game directory if (!write_required) { const SubgameSpec *game_spec = gamedef->getGameSpec(); From e84ac56e353dad9fe62f960b2e6f08bceb275ad9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 1 Mar 2025 00:58:33 +0100 Subject: [PATCH 037/284] Don't try to update uninitialized shadow frustum --- src/client/shadows/dynamicshadows.cpp | 4 +++- src/client/shadows/dynamicshadows.h | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/shadows/dynamicshadows.cpp b/src/client/shadows/dynamicshadows.cpp index 92aa9f893..826e87d82 100644 --- a/src/client/shadows/dynamicshadows.cpp +++ b/src/client/shadows/dynamicshadows.cpp @@ -32,6 +32,7 @@ void DirectionalLight::createSplitMatrices(const Camera *cam) // adjusted frustum boundaries float sfNear = future_frustum.zNear; float sfFar = adjustDist(future_frustum.zFar, cam->getFovY()); + assert(sfFar - sfNear > 0); // adjusted camera positions v3f cam_pos_world = cam->getPosition(); @@ -74,7 +75,6 @@ void DirectionalLight::createSplitMatrices(const Camera *cam) future_frustum.ViewMat.buildCameraLookAtMatrixLH(eye, center_scene, v3f(0.0f, 1.0f, 0.0f)); future_frustum.ProjOrthMat.buildProjectionMatrixOrthoLH(radius, radius, 0.0f, length, false); - future_frustum.camera_offset = cam->getOffset(); } DirectionalLight::DirectionalLight(const u32 shadowMapResolution, @@ -86,6 +86,8 @@ DirectionalLight::DirectionalLight(const u32 shadowMapResolution, void DirectionalLight::updateCameraOffset(const Camera *cam) { + if (future_frustum.zFar == 0.0f) // not initialized + return; createSplitMatrices(cam); should_update_map_shadow = true; dirty = true; diff --git a/src/client/shadows/dynamicshadows.h b/src/client/shadows/dynamicshadows.h index 70135ea69..1d741b0a3 100644 --- a/src/client/shadows/dynamicshadows.h +++ b/src/client/shadows/dynamicshadows.h @@ -22,7 +22,6 @@ struct shadowFrustum core::matrix4 ViewMat; v3f position; v3f player; - v3s16 camera_offset; }; class DirectionalLight From 68602b2eaf615204c96430d61638c49f2c9381a6 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 1 Mar 2025 11:00:58 +0100 Subject: [PATCH 038/284] Fix shadow flicker on camera offset update (take 2) The previous fix never did what it was supposed to, so let's do this. --- src/client/game.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 4ad93e1ca..fbe81ff66 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2989,8 +2989,13 @@ void Game::updateCameraOffset() if (!m_flags.disable_camera_update) { auto *shadow = RenderingEngine::get_shadow_renderer(); - if (shadow) + if (shadow) { shadow->getDirectionalLight().updateCameraOffset(camera); + // FIXME: I bet we can be smarter about this and don't need to redraw + // the shadow map at all, but this is for someone else to figure out. + if (!g_settings->getFlag("performance_tradeoffs")) + shadow->setForceUpdateShadowMap(); + } env.getClientMap().updateCamera(camera->getPosition(), camera->getDirection(), camera->getFovMax(), camera_offset, From 358658fa34b9907f03ab59c601e6777573525a04 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 1 Mar 2025 22:13:33 +0100 Subject: [PATCH 039/284] Fix cloud-related bugs First, this reverts 56123b2fbe1ad4e7878556f8d51c551d84fb92e7, which un-fixes #15031 but fixes #15798 and #15854. Then we disable culling for the cloud scene node which fixes #15031 again. --- src/client/clouds.cpp | 26 +++++++++++++++++++++++--- src/client/clouds.h | 7 +++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index b65970f95..18b1d281c 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -52,6 +52,13 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc, updateBox(); + // Neither EAC_BOX (the default) nor EAC_FRUSTUM_BOX will correctly cull + // the clouds. + // And yes, the bounding box is correct. You can check using the #if 0'd + // code in render() and see for yourself. + // So I give up and let's disable culling. + setAutomaticCulling(scene::EAC_OFF); + m_meshbuffer.reset(new scene::SMeshBuffer()); m_meshbuffer->setHardwareMappingHint(scene::EHM_DYNAMIC); } @@ -366,6 +373,19 @@ void Clouds::render() if (SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT) return; +#if 0 + { + video::SMaterial tmp; + tmp.Thickness = 1.f; + driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); + driver->setMaterial(tmp); + aabb3f tmpbox = m_box; + tmpbox.MinEdge.X = tmpbox.MinEdge.Z = -1000 * BS; + tmpbox.MaxEdge.X = tmpbox.MaxEdge.Z = 1000 * BS; + driver->draw3DBox(tmpbox, video::SColor(255, 255, 0x4d, 0)); + } +#endif + updateMesh(); // Update position @@ -425,14 +445,14 @@ void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse) // is the camera inside the cloud mesh? m_camera_pos = camera_p; - m_camera_inside_cloud = false; // default + m_camera_inside_cloud = false; if (is3D()) { float camera_height = camera_p.Y - BS * m_camera_offset.Y; if (camera_height >= m_box.MinEdge.Y && camera_height <= m_box.MaxEdge.Y) { v2f camera_in_noise; - camera_in_noise.X = floor((camera_p.X - m_origin.X) / cloud_size + 0.5); - camera_in_noise.Y = floor((camera_p.Z - m_origin.Y) / cloud_size + 0.5); + camera_in_noise.X = floorf((camera_p.X - m_origin.X) / cloud_size + 0.5f); + camera_in_noise.Y = floorf((camera_p.Z - m_origin.Y) / cloud_size + 0.5f); bool filled = gridFilled(camera_in_noise.X, camera_in_noise.Y); m_camera_inside_cloud = filled; } diff --git a/src/client/clouds.h b/src/client/clouds.h index d081a4853..ea5c18394 100644 --- a/src/client/clouds.h +++ b/src/client/clouds.h @@ -134,8 +134,11 @@ private: { float height_bs = m_params.height * BS; float thickness_bs = m_params.thickness * BS; - m_box = aabb3f(-BS * 1000000.0f, height_bs, -BS * 1000000.0f, - BS * 1000000.0f, height_bs + thickness_bs, BS * 1000000.0f); + float far_bs = 1000000.0f * BS; + m_box = aabb3f(-far_bs, height_bs, -far_bs, + far_bs, height_bs + thickness_bs, far_bs); + m_box.MinEdge -= v3f::from(m_camera_offset) * BS; + m_box.MaxEdge -= v3f::from(m_camera_offset) * BS; } void updateMesh(); From 7892541383ce69000da0d6643c5dfbcfc444b6b4 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 1 Mar 2025 11:53:37 +0100 Subject: [PATCH 040/284] Various random code cleanups --- irr/include/irrMath.h | 58 +++++++-------------- irr/include/vector3d.h | 6 +++ irr/src/CMakeLists.txt | 9 ++++ irr/src/CNullDriver.cpp | 4 +- src/client/client.cpp | 14 ++--- src/client/content_mapblock.cpp | 2 +- src/client/event_manager.h | 4 +- src/client/game.cpp | 9 ++-- src/client/hud.cpp | 4 +- src/client/imagefilters.cpp | 4 +- src/client/mesh_generator_thread.cpp | 7 ++- src/client/minimap.cpp | 12 ++--- src/client/particles.cpp | 4 +- src/client/shadows/dynamicshadowsrender.cpp | 15 +++--- src/client/shadows/dynamicshadowsrender.h | 3 +- src/client/wieldmesh.cpp | 3 +- src/database/database-dummy.cpp | 3 +- src/database/database-files.cpp | 3 +- src/emerge.cpp | 3 +- src/face_position_cache.cpp | 2 +- src/gui/guiFormSpecMenu.cpp | 9 ++-- src/gui/guiHyperText.cpp | 6 +-- src/gui/guiTable.cpp | 7 ++- src/gui/profilergraph.cpp | 2 +- src/main.cpp | 2 +- src/map.cpp | 14 ++--- src/map.h | 2 +- src/mapgen/mapgen.cpp | 4 +- src/mapgen/mapgen.h | 4 +- src/mapgen/mapgen_carpathian.cpp | 2 +- src/mapgen/mapgen_carpathian.h | 2 +- src/mapgen/mapgen_flat.cpp | 2 +- src/mapgen/mapgen_flat.h | 2 +- src/mapgen/mapgen_fractal.cpp | 2 +- src/mapgen/mapgen_fractal.h | 2 +- src/mapgen/mapgen_v5.cpp | 2 +- src/mapgen/mapgen_v5.h | 2 +- src/mapgen/mapgen_v6.cpp | 2 +- src/mapgen/mapgen_v6.h | 2 +- src/mapgen/mapgen_v7.cpp | 2 +- src/mapgen/mapgen_v7.h | 2 +- src/mapgen/mapgen_valleys.cpp | 2 +- src/mapgen/mapgen_valleys.h | 2 +- src/mapgen/mg_decoration.cpp | 2 +- src/mapgen/mg_decoration.h | 2 +- src/mapgen/mg_ore.cpp | 4 +- src/mapgen/mg_ore.h | 2 +- src/mapgen/mg_schematic.cpp | 5 +- src/network/clientpackethandler.cpp | 2 +- src/nodedef.cpp | 3 +- src/nodetimer.cpp | 2 +- src/nodetimer.h | 8 ++- src/noise.cpp | 4 +- src/noise.h | 2 +- src/pathfinder.cpp | 8 ++- src/script/common/c_content.cpp | 11 ++-- src/script/common/c_content.h | 10 ++-- src/script/lua_api/l_craft.cpp | 4 +- src/script/lua_api/l_mapgen.cpp | 2 +- src/script/lua_api/l_rollback.cpp | 5 +- src/server.cpp | 3 +- src/server/rollback.cpp | 12 ++--- src/serverenvironment.cpp | 7 ++- src/tileanimation.cpp | 4 +- src/unittest/test_random.cpp | 4 +- src/unittest/test_utilities.cpp | 4 +- src/unittest/test_voxelmanipulator.cpp | 6 +-- src/util/areastore.cpp | 4 +- src/util/areastore.h | 2 +- src/util/numeric.cpp | 22 +++----- src/util/numeric.h | 53 +++++++++---------- src/util/string.cpp | 41 +++++---------- src/util/thread.h | 16 +++--- 73 files changed, 216 insertions(+), 285 deletions(-) diff --git a/irr/include/irrMath.h b/irr/include/irrMath.h index 3a1471a02..e9c86156d 100644 --- a/irr/include/irrMath.h +++ b/irr/include/irrMath.h @@ -18,47 +18,38 @@ namespace core //! Rounding error constant often used when comparing f32 values. -const f32 ROUNDING_ERROR_f32 = 0.000001f; -const f64 ROUNDING_ERROR_f64 = 0.00000001; +constexpr f32 ROUNDING_ERROR_f32 = 0.000001f; +constexpr f64 ROUNDING_ERROR_f64 = 0.00000001; #ifdef PI // make sure we don't collide with a define #undef PI #endif //! Constant for PI. -const f32 PI = 3.14159265359f; - -//! Constant for reciprocal of PI. -const f32 RECIPROCAL_PI = 1.0f / PI; - -//! Constant for half of PI. -const f32 HALF_PI = PI / 2.0f; +constexpr f32 PI = M_PI; #ifdef PI64 // make sure we don't collide with a define #undef PI64 #endif //! Constant for 64bit PI. -const f64 PI64 = 3.1415926535897932384626433832795028841971693993751; - -//! Constant for 64bit reciprocal of PI. -const f64 RECIPROCAL_PI64 = 1.0 / PI64; +constexpr f64 PI64 = M_PI; //! 32bit Constant for converting from degrees to radians -const f32 DEGTORAD = PI / 180.0f; +constexpr f32 DEGTORAD = PI / 180.0f; //! 32bit constant for converting from radians to degrees (formally known as GRAD_PI) -const f32 RADTODEG = 180.0f / PI; +constexpr f32 RADTODEG = 180.0f / PI; //! 64bit constant for converting from degrees to radians (formally known as GRAD_PI2) -const f64 DEGTORAD64 = PI64 / 180.0; +constexpr f64 DEGTORAD64 = PI64 / 180.0; //! 64bit constant for converting from radians to degrees -const f64 RADTODEG64 = 180.0 / PI64; +constexpr f64 RADTODEG64 = 180.0 / PI64; //! Utility function to convert a radian value to degrees /** Provided as it can be clearer to write radToDeg(X) than RADTODEG * X \param radians The radians value to convert to degrees. */ -inline f32 radToDeg(f32 radians) +inline constexpr f32 radToDeg(f32 radians) { return RADTODEG * radians; } @@ -67,7 +58,7 @@ inline f32 radToDeg(f32 radians) /** Provided as it can be clearer to write radToDeg(X) than RADTODEG * X \param radians The radians value to convert to degrees. */ -inline f64 radToDeg(f64 radians) +inline constexpr f64 radToDeg(f64 radians) { return RADTODEG64 * radians; } @@ -76,7 +67,7 @@ inline f64 radToDeg(f64 radians) /** Provided as it can be clearer to write degToRad(X) than DEGTORAD * X \param degrees The degrees value to convert to radians. */ -inline f32 degToRad(f32 degrees) +inline constexpr f32 degToRad(f32 degrees) { return DEGTORAD * degrees; } @@ -85,44 +76,44 @@ inline f32 degToRad(f32 degrees) /** Provided as it can be clearer to write degToRad(X) than DEGTORAD * X \param degrees The degrees value to convert to radians. */ -inline f64 degToRad(f64 degrees) +inline constexpr f64 degToRad(f64 degrees) { return DEGTORAD64 * degrees; } -//! returns minimum of two values. Own implementation to get rid of the STL (VS6 problems) +//! returns minimum of two values. template inline const T &min_(const T &a, const T &b) { return a < b ? a : b; } -//! returns minimum of three values. Own implementation to get rid of the STL (VS6 problems) +//! returns minimum of three values. template inline const T &min_(const T &a, const T &b, const T &c) { return a < b ? min_(a, c) : min_(b, c); } -//! returns maximum of two values. Own implementation to get rid of the STL (VS6 problems) +//! returns maximum of two values. template inline const T &max_(const T &a, const T &b) { return a < b ? b : a; } -//! returns maximum of three values. Own implementation to get rid of the STL (VS6 problems) +//! returns maximum of three values. template inline const T &max_(const T &a, const T &b, const T &c) { return a < b ? max_(b, c) : max_(a, c); } -//! returns abs of two values. Own implementation to get rid of STL (VS6 problems) +//! returns abs of two values. template inline T abs_(const T &a) { - return a < (T)0 ? -a : a; + return std::abs(a); } //! returns linear interpolation of a and b with ratio t @@ -140,19 +131,6 @@ inline const T clamp(const T &value, const T &low, const T &high) return min_(max_(value, low), high); } -//! swaps the content of the passed parameters -// Note: We use the same trick as boost and use two template arguments to -// avoid ambiguity when swapping objects of an Irrlicht type that has not -// it's own swap overload. Otherwise we get conflicts with some compilers -// in combination with stl. -template -inline void swap(T1 &a, T2 &b) -{ - T1 c(a); - a = b; - b = c; -} - template inline T roundingError(); diff --git a/irr/include/vector3d.h b/irr/include/vector3d.h index a69f17e16..fd788e734 100644 --- a/irr/include/vector3d.h +++ b/irr/include/vector3d.h @@ -33,6 +33,12 @@ public: explicit constexpr vector3d(T n) : X(n), Y(n), Z(n) {} + template + constexpr static vector3d from(const vector3d &other) + { + return {static_cast(other.X), static_cast(other.Y), static_cast(other.Z)}; + } + // operators vector3d operator-() const { return vector3d(-X, -Y, -Z); } diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 5ac78a17e..820af4fe9 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -33,6 +33,15 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$") elseif(MSVC) string(APPEND CMAKE_CXX_STANDARD_LIBRARIES " msvcrt.lib") # ???? fuck off + add_compile_definitions( + # Suppress some useless warnings + _CRT_SECURE_NO_DEPRECATE + # Get M_PI to work + _USE_MATH_DEFINES + # Don't define min/max macros in minwindef.h + NOMINMAX + ) + add_compile_options(/Zl) # Enable SSE for floating point math on 32-bit x86 by default diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index c87d5ae93..6f44fc4e6 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -1455,9 +1455,9 @@ void CNullDriver::setMaterialRendererName(u32 idx, const char *name) void CNullDriver::swapMaterialRenderers(u32 idx1, u32 idx2, bool swapNames) { if (idx1 < MaterialRenderers.size() && idx2 < MaterialRenderers.size()) { - irr::core::swap(MaterialRenderers[idx1].Renderer, MaterialRenderers[idx2].Renderer); + std::swap(MaterialRenderers[idx1].Renderer, MaterialRenderers[idx2].Renderer); if (swapNames) - irr::core::swap(MaterialRenderers[idx1].Name, MaterialRenderers[idx2].Name); + std::swap(MaterialRenderers[idx1].Name, MaterialRenderers[idx2].Name); } } diff --git a/src/client/client.cpp b/src/client/client.cpp index 3c7716cbd..08fd2a215 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1044,8 +1044,8 @@ void Client::Send(NetworkPacket* pkt) // Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4 bytes void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt, bool camera_inverted) { - v3f pf = myplayer->getPosition() * 100; - v3f sf = myplayer->getSpeed() * 100; + v3s32 position = v3s32::from(myplayer->getPosition() * 100); + v3s32 speed = v3s32::from(myplayer->getSpeed() * 100); s32 pitch = myplayer->getPitch() * 100; s32 yaw = myplayer->getYaw() * 100; u32 keyPressed = myplayer->control.getKeysPressed(); @@ -1056,9 +1056,6 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * f32 movement_speed = myplayer->control.movement_speed; f32 movement_dir = myplayer->control.movement_direction; - v3s32 position(pf.X, pf.Y, pf.Z); - v3s32 speed(sf.X, sf.Y, sf.Z); - /* Format: [0] v3s32 position*100 @@ -1755,12 +1752,7 @@ void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent) { - { - v3s16 p = nodepos; - infostream<<"Client::addUpdateMeshTaskForNode(): " - <<"("<light_source && (normal != v3s16(0, 0, 0)); - v3f normal2(normal.X, normal.Y, normal.Z); + v3f normal2 = v3f::from(normal); for (int j = 0; j < 4; j++) { vertices[j].Pos = coords[j] + cur_node.origin; vertices[j].Normal = normal2; diff --git a/src/client/event_manager.h b/src/client/event_manager.h index 35b36adce..1fee6781c 100644 --- a/src/client/event_manager.h +++ b/src/client/event_manager.h @@ -33,7 +33,7 @@ public: void put(MtEvent *e) override { - std::map::iterator i = m_dest.find(e->getType()); + auto i = m_dest.find(e->getType()); if (i != m_dest.end()) { std::list &funcs = i->second.funcs; for (FuncSpec &func : funcs) { @@ -44,7 +44,7 @@ public: } void reg(MtEvent::Type type, event_receive_func f, void *data) override { - std::map::iterator i = m_dest.find(type); + auto i = m_dest.find(type); if (i != m_dest.end()) { i->second.funcs.emplace_back(f, data); } else { diff --git a/src/client/game.cpp b/src/client/game.cpp index fbe81ff66..7588fedc7 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3245,10 +3245,8 @@ PointedThing Game::updatePointedThing( n.getSelectionBoxes(nodedef, &boxes, n.getNeighbors(result.node_undersurface, &map)); - f32 d = 0.002 * BS; - for (std::vector::const_iterator i = boxes.begin(); - i != boxes.end(); ++i) { - aabb3f box = *i; + f32 d = 0.002f * BS; + for (aabb3f box : boxes) { box.MinEdge -= v3f(d, d, d); box.MaxEdge += v3f(d, d, d); selectionboxes->push_back(box); @@ -3450,9 +3448,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, u8 predicted_param2 = dir.Y < 0 ? 1 : 0; if (selected_def.wallmounted_rotate_vertical) { bool rotate90 = false; - v3f fnodepos = v3f(neighborpos.X, neighborpos.Y, neighborpos.Z); v3f ppos = client->getEnv().getLocalPlayer()->getPosition() / BS; - v3f pdir = fnodepos - ppos; + v3f pdir = v3f::from(neighborpos) - ppos; switch (predicted_f.drawtype) { case NDT_TORCHLIKE: { rotate90 = !((pdir.X < 0 && pdir.Z > 0) || diff --git a/src/client/hud.cpp b/src/client/hud.cpp index aa9544486..4ef64fedc 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -965,8 +965,8 @@ void Hud::drawBlockBounds() v3f pmax = v3f(x, y, 1 + radius) * MAP_BLOCKSIZE * BS; driver->draw3DLine( - base_corner + v3f(pmin.X, pmin.Y, pmin.Z), - base_corner + v3f(pmax.X, pmax.Y, pmax.Z), + base_corner + pmin, + base_corner + pmax, choose_color(block_pos.X, block_pos.Y) ); driver->draw3DLine( diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp index 09a1198ea..cd7205ff9 100644 --- a/src/client/imagefilters.cpp +++ b/src/client/imagefilters.cpp @@ -285,13 +285,13 @@ void imageScaleNNAA(video::IImage *src, const core::rect &srcrect, video::I maxsx = minsx + sw / dim.Width; maxsx = rangelim(maxsx, 0, sox + sw); if (minsx > maxsx) - SWAP(double, minsx, maxsx); + std::swap(minsx, maxsx); minsy = soy + (dy * sh / dim.Height); minsy = rangelim(minsy, 0, soy + sh); maxsy = minsy + sh / dim.Height; maxsy = rangelim(maxsy, 0, soy + sh); if (minsy > maxsy) - SWAP(double, minsy, maxsy); + std::swap(minsy, maxsy); // Total area, and integral of r, g, b values over that area, // initialized to zero, to be summed up in next loops. diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp index e511bc62c..712c785ce 100644 --- a/src/client/mesh_generator_thread.cpp +++ b/src/client/mesh_generator_thread.cpp @@ -145,8 +145,7 @@ QueuedMeshUpdate *MeshUpdateQueue::pop() MutexAutoLock lock(m_mutex); bool must_be_urgent = !m_urgents.empty(); - for (std::vector::iterator i = m_queue.begin(); - i != m_queue.end(); ++i) { + for (auto i = m_queue.begin(); i != m_queue.end(); ++i) { QueuedMeshUpdate *q = *i; if (must_be_urgent && m_urgents.count(q->p) == 0) continue; @@ -264,8 +263,8 @@ void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server, g_settings->getBool("smooth_lighting") && !g_settings->getFlag("performance_tradeoffs"); if (!m_queue_in.addBlock(map, p, ack_block_to_server, urgent)) { - warningstream << "Update requested for non-existent block at (" - << p.X << ", " << p.Y << ", " << p.Z << ")" << std::endl; + warningstream << "Update requested for non-existent block at " + << p << std::endl; return; } if (update_neighbors) { diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index a37790f71..e60608688 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -78,15 +78,13 @@ 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)); + auto result = m_blocks_cache.insert(std::make_pair(update.pos, update.data)); if (!result.second) { delete result.first->second; result.first->second = update.data; } } else { - std::map::iterator it; - it = m_blocks_cache.find(update.pos); + auto it = m_blocks_cache.find(update.pos); if (it != m_blocks_cache.end()) { delete it->second; m_blocks_cache.erase(it); @@ -124,8 +122,7 @@ void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height) for (blockpos.Z = blockpos_min.Z; blockpos.Z <= blockpos_max.Z; ++blockpos.Z) for (blockpos.Y = blockpos_min.Y; blockpos.Y <= blockpos_max.Y; ++blockpos.Y) for (blockpos.X = blockpos_min.X; blockpos.X <= blockpos_max.X; ++blockpos.X) { - std::map::const_iterator pblock = - m_blocks_cache.find(blockpos); + auto pblock = m_blocks_cache.find(blockpos); if (pblock == m_blocks_cache.end()) continue; const MinimapMapblock &block = *pblock->second; @@ -647,8 +644,7 @@ void Minimap::drawMinimap(core::rect rect) f32 sin_angle = std::sin(m_angle * core::DEGTORAD); f32 cos_angle = std::cos(m_angle * core::DEGTORAD); s32 marker_size2 = 0.025 * (float)rect.getWidth();; - for (std::list::const_iterator - i = m_active_markers.begin(); + for (auto i = m_active_markers.begin(); i != m_active_markers.end(); ++i) { v2f posf = *i; if (data->minimap_shape_round) { diff --git a/src/client/particles.cpp b/src/client/particles.cpp index bcb3cf207..179c9c465 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -193,7 +193,7 @@ void Particle::updateVertices(ClientEnvironment *env, video::SColor color) video::S3DVertex *vertices = m_buffer->getVertices(m_index); if (m_texture.tex != nullptr) - scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1)); + scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1f)); else scale = v2f(1.f, 1.f); @@ -203,7 +203,7 @@ void Particle::updateVertices(ClientEnvironment *env, video::SColor color) v2u32 framesize; texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame); m_p.animation.determineParams(texsize, NULL, NULL, &framesize); - framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y); + framesize_f = v2f::from(framesize) / v2f::from(texsize); tx0 = m_texpos.X + texcoord.X; tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X; diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 9898b08e6..835f995f1 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -177,14 +177,15 @@ void ShadowRenderer::removeNodeFromShadowList(scene::ISceneNode *node) node->forEachMaterial([] (auto &mat) { mat.setTexture(TEXTURE_LAYER_SHADOW, nullptr); }); - for (auto it = m_shadow_node_array.begin(); it != m_shadow_node_array.end();) { - if (it->node == node) { - it = m_shadow_node_array.erase(it); - break; - } else { - ++it; - } + + auto it = std::find(m_shadow_node_array.begin(), m_shadow_node_array.end(), node); + if (it == m_shadow_node_array.end()) { + infostream << "removeNodeFromShadowList: " << node << " not found" << std::endl; + return; } + // swap with last, then remove + *it = m_shadow_node_array.back(); + m_shadow_node_array.pop_back(); } void ShadowRenderer::updateSMTextures() diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index 5854dc4ed..1c7b6e482 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -28,7 +28,8 @@ struct NodeToApply E_SHADOW_MODE m = E_SHADOW_MODE::ESM_BOTH) : node(n), shadowMode(m){}; - bool operator<(const NodeToApply &other) const { return node < other.node; }; + + bool operator==(scene::ISceneNode *n) const { return node == n; } scene::ISceneNode *node; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 7b87f7bdf..9e8d72cf2 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -154,8 +154,7 @@ public: int maxdim = MYMAX(dim.Width, dim.Height); - std::map::iterator - it = m_extrusion_meshes.lower_bound(maxdim); + auto it = m_extrusion_meshes.lower_bound(maxdim); if (it == m_extrusion_meshes.end()) { // no viable resolution found; use largest one diff --git a/src/database/database-dummy.cpp b/src/database/database-dummy.cpp index f23734b6f..280745711 100644 --- a/src/database/database-dummy.cpp +++ b/src/database/database-dummy.cpp @@ -36,8 +36,7 @@ bool Database_Dummy::deleteBlock(const v3s16 &pos) void Database_Dummy::listAllLoadableBlocks(std::vector &dst) { dst.reserve(m_database.size()); - for (std::map::const_iterator x = m_database.begin(); - x != m_database.end(); ++x) { + for (auto x = m_database.begin(); x != m_database.end(); ++x) { dst.push_back(getIntegerAsBlock(x->first)); } } diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index 5001a2810..84684299d 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -234,8 +234,7 @@ 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 (auto it = files.begin(); it != files.end(); ++it) { // Ignore directories if (it->dir) continue; diff --git a/src/emerge.cpp b/src/emerge.cpp index 2dfead5b4..71e1ed7c5 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -375,8 +375,7 @@ bool EmergeManager::pushBlockEmergeData( } } - std::pair::iterator, bool> findres; - findres = m_blocks_enqueued.insert(std::make_pair(pos, BlockEmergeData())); + auto findres = m_blocks_enqueued.insert(std::make_pair(pos, BlockEmergeData())); BlockEmergeData &bedata = findres.first->second; *entry_already_exists = !findres.second; diff --git a/src/face_position_cache.cpp b/src/face_position_cache.cpp index 65a66a37c..b85ebb048 100644 --- a/src/face_position_cache.cpp +++ b/src/face_position_cache.cpp @@ -13,7 +13,7 @@ std::mutex FacePositionCache::cache_mutex; const std::vector &FacePositionCache::getFacePositions(u16 d) { MutexAutoLock lock(cache_mutex); - std::unordered_map>::const_iterator it = cache.find(d); + auto it = cache.find(d); if (it != cache.end()) return it->second; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 0595832a1..621eba1c2 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3194,7 +3194,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) pos_offset = v2f32(); // used for formspec versions < 3 - std::list::iterator legacy_sort_start = std::prev(Children.end()); // last element + auto legacy_sort_start = std::prev(Children.end()); // last element if (enable_prepends) { // Backup the coordinates so that prepends can use the coordinates of choice. @@ -3308,7 +3308,7 @@ void GUIFormSpecMenu::legacySortElements(std::list::iterator from else ++from; - std::list::iterator to = Children.end(); + auto to = Children.end(); // 1: Copy into a sortable container std::vector elements(from, to); @@ -4899,8 +4899,7 @@ 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); + auto it = field_close_on_enter.find(s.fname); if (it != field_close_on_enter.end()) close_on_enter = (*it).second; @@ -5085,7 +5084,7 @@ double GUIFormSpecMenu::calculateImgsize(const parserData &data) ((15.0 / 13.0) * (0.85 + data.invsize.Y)); } - double prefer_imgsize = getImgsize(v2u32(padded_screensize.X, padded_screensize.Y), + double prefer_imgsize = getImgsize(v2u32::from(padded_screensize), screen_dpi, gui_scaling); // Try to use the preferred imgsize, but if that's bigger than the maximum diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 5446038b0..0f887ec4e 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -734,7 +734,7 @@ void TextDrawer::place(const core::rect &dest_rect) ymargin = p.margin; // Place non floating stuff - std::vector::iterator el = p.elements.begin(); + auto el = p.elements.begin(); while (el != p.elements.end()) { // Determine line width and y pos @@ -807,8 +807,8 @@ void TextDrawer::place(const core::rect &dest_rect) el++; } - std::vector::iterator linestart = el; - std::vector::iterator lineend = p.elements.end(); + auto linestart = el; + auto lineend = p.elements.end(); // First pass, find elements fitting into line // (or at least one element) diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index 0c10a2b64..0bc480da7 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -362,8 +362,7 @@ void GUITable::setTable(const TableOptions &options, // Find content_index. Image indices are defined in // 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); + auto image_iter =active_image_indices.find(image_index); if (image_iter != active_image_indices.end()) row->content_index = image_iter->second; @@ -965,7 +964,7 @@ bool GUITable::OnEvent(const SEvent &event) s32 GUITable::allocString(const std::string &text) { - std::map::iterator it = m_alloc_strings.find(text); + auto it = m_alloc_strings.find(text); if (it == m_alloc_strings.end()) { s32 id = m_strings.size(); std::wstring wtext = utf8_to_wide(text); @@ -979,7 +978,7 @@ s32 GUITable::allocString(const std::string &text) s32 GUITable::allocImage(const std::string &imagename) { - std::map::iterator it = m_alloc_images.find(imagename); + auto it = m_alloc_images.find(imagename); if (it == m_alloc_images.end()) { s32 id = m_images.size(); m_images.push_back(m_tsrc->getTexture(imagename)); diff --git a/src/gui/profilergraph.cpp b/src/gui/profilergraph.cpp index 22e6608b8..4e633ea5d 100644 --- a/src/gui/profilergraph.cpp +++ b/src/gui/profilergraph.cpp @@ -27,7 +27,7 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, for (const auto &i : piece.values) { const std::string &id = i.first; const float &value = i.second; - std::map::iterator j = m_meta.find(id); + auto j = m_meta.find(id); if (j == m_meta.end()) { m_meta[id] = Meta(value); diff --git a/src/main.cpp b/src/main.cpp index d93803306..d3e698f33 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1223,7 +1223,7 @@ 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) { + for (auto it = blocks.begin(); it != blocks.end(); ++it) { if (kill) return false; std::string data; diff --git a/src/map.cpp b/src/map.cpp index e8ccac0cc..d88200846 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -292,12 +292,13 @@ void Map::timerUpdate(float dtime, float unload_timeout, s32 max_loaded_blocks, // If there is no practical limit, we spare creation of mapblock_queue if (max_loaded_blocks < 0) { + MapBlockVect blocks; for (auto §or_it : m_sectors) { MapSector *sector = sector_it.second; bool all_blocks_deleted = true; - MapBlockVect blocks; + blocks.clear(); sector->getBlocks(blocks); for (MapBlock *block : blocks) { @@ -336,10 +337,11 @@ void Map::timerUpdate(float dtime, float unload_timeout, s32 max_loaded_blocks, } } else { std::priority_queue mapblock_queue; + MapBlockVect blocks; for (auto §or_it : m_sectors) { MapSector *sector = sector_it.second; - MapBlockVect blocks; + blocks.clear(); sector->getBlocks(blocks); for (MapBlock *block : blocks) { @@ -417,16 +419,16 @@ void Map::timerUpdate(float dtime, float unload_timeout, s32 max_loaded_blocks, void Map::unloadUnreferencedBlocks(std::vector *unloaded_blocks) { - timerUpdate(0.0, -1.0, 0, unloaded_blocks); + timerUpdate(0, -1, 0, unloaded_blocks); } -void Map::deleteSectors(std::vector §orList) +void Map::deleteSectors(const std::vector §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) - m_sector_cache = NULL; + if (m_sector_cache == sector) + m_sector_cache = nullptr; // Remove from map and delete m_sectors.erase(j); delete sector; diff --git a/src/map.h b/src/map.h index a4f0e4524..db4d8be28 100644 --- a/src/map.h +++ b/src/map.h @@ -191,7 +191,7 @@ public: // Deletes sectors and their blocks from memory // Takes cache into account // If deleted sector is in sector cache, clears cache - void deleteSectors(std::vector &list); + void deleteSectors(const std::vector &list); // For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: " virtual void PrintInfo(std::ostream &out); diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index 1917c484c..83c56ee8d 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -37,7 +37,7 @@ #include "cavegen.h" #include "dungeongen.h" -FlagDesc flagdesc_mapgen[] = { +const FlagDesc flagdesc_mapgen[] = { {"caves", MG_CAVES}, {"dungeons", MG_DUNGEONS}, {"light", MG_LIGHT}, @@ -47,7 +47,7 @@ FlagDesc flagdesc_mapgen[] = { {NULL, 0} }; -FlagDesc flagdesc_gennotify[] = { +const FlagDesc flagdesc_gennotify[] = { {"dungeon", 1 << GENNOTIFY_DUNGEON}, {"temple", 1 << GENNOTIFY_TEMPLE}, {"cave_begin", 1 << GENNOTIFY_CAVE_BEGIN}, diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index a81b9a361..31313b35f 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -29,8 +29,8 @@ class Settings; class MMVManip; class NodeDefManager; -extern FlagDesc flagdesc_mapgen[]; -extern FlagDesc flagdesc_gennotify[]; +extern const FlagDesc flagdesc_mapgen[]; +extern const FlagDesc flagdesc_gennotify[]; class Biome; class BiomeGen; diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp index 46048b51f..5c7f9e1b1 100644 --- a/src/mapgen/mapgen_carpathian.cpp +++ b/src/mapgen/mapgen_carpathian.cpp @@ -24,7 +24,7 @@ #include "mapgen_carpathian.h" -FlagDesc flagdesc_mapgen_carpathian[] = { +const FlagDesc flagdesc_mapgen_carpathian[] = { {"caverns", MGCARPATHIAN_CAVERNS}, {"rivers", MGCARPATHIAN_RIVERS}, {NULL, 0} diff --git a/src/mapgen/mapgen_carpathian.h b/src/mapgen/mapgen_carpathian.h index c2c0d48fe..754634af8 100644 --- a/src/mapgen/mapgen_carpathian.h +++ b/src/mapgen/mapgen_carpathian.h @@ -12,7 +12,7 @@ class BiomeManager; -extern FlagDesc flagdesc_mapgen_carpathian[]; +extern const FlagDesc flagdesc_mapgen_carpathian[]; struct MapgenCarpathianParams : public MapgenParams diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index 56346432e..c0face7b9 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -23,7 +23,7 @@ #include "mapgen_flat.h" -FlagDesc flagdesc_mapgen_flat[] = { +const FlagDesc flagdesc_mapgen_flat[] = { {"lakes", MGFLAT_LAKES}, {"hills", MGFLAT_HILLS}, {"caverns", MGFLAT_CAVERNS}, diff --git a/src/mapgen/mapgen_flat.h b/src/mapgen/mapgen_flat.h index 46ff46154..6eb303765 100644 --- a/src/mapgen/mapgen_flat.h +++ b/src/mapgen/mapgen_flat.h @@ -14,7 +14,7 @@ class BiomeManager; -extern FlagDesc flagdesc_mapgen_flat[]; +extern const FlagDesc flagdesc_mapgen_flat[]; struct MapgenFlatParams : public MapgenParams { diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp index 3e28fbcac..0ac72ac08 100644 --- a/src/mapgen/mapgen_fractal.cpp +++ b/src/mapgen/mapgen_fractal.cpp @@ -24,7 +24,7 @@ #include "mapgen_fractal.h" -FlagDesc flagdesc_mapgen_fractal[] = { +const FlagDesc flagdesc_mapgen_fractal[] = { {"terrain", MGFRACTAL_TERRAIN}, {NULL, 0} }; diff --git a/src/mapgen/mapgen_fractal.h b/src/mapgen/mapgen_fractal.h index 0a71bd1d1..1070f7f25 100644 --- a/src/mapgen/mapgen_fractal.h +++ b/src/mapgen/mapgen_fractal.h @@ -12,7 +12,7 @@ class BiomeManager; -extern FlagDesc flagdesc_mapgen_fractal[]; +extern const FlagDesc flagdesc_mapgen_fractal[]; struct MapgenFractalParams : public MapgenParams diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp index 07d1abda7..9cfd0cf9d 100644 --- a/src/mapgen/mapgen_v5.cpp +++ b/src/mapgen/mapgen_v5.cpp @@ -23,7 +23,7 @@ #include "mapgen_v5.h" -FlagDesc flagdesc_mapgen_v5[] = { +const FlagDesc flagdesc_mapgen_v5[] = { {"caverns", MGV5_CAVERNS}, {NULL, 0} }; diff --git a/src/mapgen/mapgen_v5.h b/src/mapgen/mapgen_v5.h index 8c8c1c27f..7901347dc 100644 --- a/src/mapgen/mapgen_v5.h +++ b/src/mapgen/mapgen_v5.h @@ -12,7 +12,7 @@ class BiomeManager; -extern FlagDesc flagdesc_mapgen_v5[]; +extern const FlagDesc flagdesc_mapgen_v5[]; struct MapgenV5Params : public MapgenParams { diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index 44243618e..a698494cd 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -25,7 +25,7 @@ #include "mapgen_v6.h" -FlagDesc flagdesc_mapgen_v6[] = { +const FlagDesc flagdesc_mapgen_v6[] = { {"jungles", MGV6_JUNGLES}, {"biomeblend", MGV6_BIOMEBLEND}, {"mudflow", MGV6_MUDFLOW}, diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index 6d776665a..820577187 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -27,7 +27,7 @@ #define MGV6_TEMPLES 0x40 -extern FlagDesc flagdesc_mapgen_v6[]; +extern const FlagDesc flagdesc_mapgen_v6[]; enum BiomeV6Type diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp index fe052f3b7..8fc5b0c6f 100644 --- a/src/mapgen/mapgen_v7.cpp +++ b/src/mapgen/mapgen_v7.cpp @@ -24,7 +24,7 @@ #include "mapgen_v7.h" -FlagDesc flagdesc_mapgen_v7[] = { +const FlagDesc flagdesc_mapgen_v7[] = { {"mountains", MGV7_MOUNTAINS}, {"ridges", MGV7_RIDGES}, {"floatlands", MGV7_FLOATLANDS}, diff --git a/src/mapgen/mapgen_v7.h b/src/mapgen/mapgen_v7.h index 49e036b82..10a0aa12e 100644 --- a/src/mapgen/mapgen_v7.h +++ b/src/mapgen/mapgen_v7.h @@ -16,7 +16,7 @@ class BiomeManager; -extern FlagDesc flagdesc_mapgen_v7[]; +extern const FlagDesc flagdesc_mapgen_v7[]; struct MapgenV7Params : public MapgenParams { diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp index 55185c445..0d91765b6 100644 --- a/src/mapgen/mapgen_valleys.cpp +++ b/src/mapgen/mapgen_valleys.cpp @@ -32,7 +32,7 @@ Licensing changed by permission of Gael de Sailly. #include -FlagDesc flagdesc_mapgen_valleys[] = { +const FlagDesc flagdesc_mapgen_valleys[] = { {"altitude_chill", MGVALLEYS_ALT_CHILL}, {"humid_rivers", MGVALLEYS_HUMID_RIVERS}, {"vary_river_depth", MGVALLEYS_VARY_RIVER_DEPTH}, diff --git a/src/mapgen/mapgen_valleys.h b/src/mapgen/mapgen_valleys.h index c0e3bd129..7e318bb59 100644 --- a/src/mapgen/mapgen_valleys.h +++ b/src/mapgen/mapgen_valleys.h @@ -24,7 +24,7 @@ Licensing changed by permission of Gael de Sailly. class BiomeManager; class BiomeGenOriginal; -extern FlagDesc flagdesc_mapgen_valleys[]; +extern const FlagDesc flagdesc_mapgen_valleys[]; struct MapgenValleysParams : public MapgenParams { diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index dcf32acb7..9d6b73070 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -15,7 +15,7 @@ #include "mapgen/treegen.h" -FlagDesc flagdesc_deco[] = { +const FlagDesc flagdesc_deco[] = { {"place_center_x", DECO_PLACE_CENTER_X}, {"place_center_y", DECO_PLACE_CENTER_Y}, {"place_center_z", DECO_PLACE_CENTER_Z}, diff --git a/src/mapgen/mg_decoration.h b/src/mapgen/mg_decoration.h index 40829b689..15f38c394 100644 --- a/src/mapgen/mg_decoration.h +++ b/src/mapgen/mg_decoration.h @@ -33,7 +33,7 @@ enum DecorationType { #define DECO_ALL_FLOORS 0x40 #define DECO_ALL_CEILINGS 0x80 -extern FlagDesc flagdesc_deco[]; +extern const FlagDesc flagdesc_deco[]; class Decoration : public ObjDef, public NodeResolver { diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp index d5817f682..3ab908d75 100644 --- a/src/mapgen/mg_ore.cpp +++ b/src/mapgen/mg_ore.cpp @@ -13,7 +13,7 @@ #include -FlagDesc flagdesc_ore[] = { +const FlagDesc flagdesc_ore[] = { {"absheight", OREFLAG_ABSHEIGHT}, // Non-functional {"puff_cliffs", OREFLAG_PUFF_CLIFFS}, {"puff_additive_composition", OREFLAG_PUFF_ADDITIVE}, @@ -324,7 +324,7 @@ void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed, int y1 = ymid + ntop; if ((flags & OREFLAG_PUFF_ADDITIVE) && (y0 > y1)) - SWAP(int, y0, y1); + std::swap(y0, y1); for (int y = y0; y <= y1; y++) { u32 i = vm->m_area.index(x, y, z); diff --git a/src/mapgen/mg_ore.h b/src/mapgen/mg_ore.h index eed13ebfc..ae9faee11 100644 --- a/src/mapgen/mg_ore.h +++ b/src/mapgen/mg_ore.h @@ -33,7 +33,7 @@ enum OreType { ORE_STRATUM, }; -extern FlagDesc flagdesc_ore[]; +extern const FlagDesc flagdesc_ore[]; class Ore : public ObjDef, public NodeResolver { public: diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index 8caed8157..9e5c078c8 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -127,7 +127,7 @@ void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_pla i_start = sx - 1; i_step_x = zstride; i_step_z = -xstride; - SWAP(s16, sx, sz); + std::swap(sx, sz); break; case ROTATE_180: i_start = zstride * (sz - 1) + sx - 1; @@ -138,7 +138,7 @@ void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_pla i_start = zstride * (sz - 1); i_step_x = -zstride; i_step_z = xstride; - SWAP(s16, sx, sz); + std::swap(sx, sz); break; default: i_start = 0; @@ -222,7 +222,6 @@ void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place) { std::map modified_blocks; - std::map::iterator it; assert(map != NULL); assert(schemdata != NULL); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index f589396c6..fe490dd8c 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -568,7 +568,7 @@ void Client::handleCommand_MovePlayer(NetworkPacket* pkt) player->setPosition(pos); infostream << "Client got TOCLIENT_MOVE_PLAYER" - << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" + << " pos=" << pos << " pitch=" << pitch << " yaw=" << yaw << std::endl; diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 6994464ed..ffc5503b1 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -1067,8 +1067,7 @@ 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); + auto i = m_name_id_mapping_with_aliases.find(name); if(i == m_name_id_mapping_with_aliases.end()) return false; result = i->second; diff --git a/src/nodetimer.cpp b/src/nodetimer.cpp index 04ea3d5bc..bdf842a26 100644 --- a/src/nodetimer.cpp +++ b/src/nodetimer.cpp @@ -117,7 +117,7 @@ std::vector NodeTimerList::step(float dtime) if (m_next_trigger_time == -1. || m_time < m_next_trigger_time) { return elapsed_timers; } - std::multimap::iterator i = m_timers.begin(); + auto i = m_timers.begin(); // Process timers for (; i != m_timers.end() && i->first <= m_time; ++i) { NodeTimer t = i->second; diff --git a/src/nodetimer.h b/src/nodetimer.h index a61abf172..c2990c166 100644 --- a/src/nodetimer.h +++ b/src/nodetimer.h @@ -50,8 +50,7 @@ public: // Get timer NodeTimer get(const v3s16 &p) { - std::map::iterator>::iterator n = - m_iterators.find(p); + auto n = m_iterators.find(p); if (n == m_iterators.end()) return NodeTimer(); NodeTimer t = n->second->second; @@ -60,8 +59,7 @@ public: } // Deletes timer void remove(v3s16 p) { - std::map::iterator>::iterator n = - m_iterators.find(p); + auto n = m_iterators.find(p); if(n != m_iterators.end()) { double removed_time = n->second->first; m_timers.erase(n->second); @@ -81,7 +79,7 @@ public: void insert(const NodeTimer &timer) { v3s16 p = timer.position; double trigger_time = m_time + (double)(timer.timeout - timer.elapsed); - std::multimap::iterator it = m_timers.emplace(trigger_time, timer); + auto it = m_timers.emplace(trigger_time, timer); m_iterators.emplace(p, it); if (m_next_trigger_time == -1. || trigger_time < m_next_trigger_time) m_next_trigger_time = trigger_time; diff --git a/src/noise.cpp b/src/noise.cpp index bfd29e4ee..d81e0bbba 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -38,7 +38,9 @@ // Unsigned magic seed prevents undefined behavior. #define NOISE_MAGIC_SEED 1013U -FlagDesc flagdesc_noiseparams[] = { +#define myfloor(x) ((x) < 0 ? (int)(x) - 1 : (int)(x)) + +const FlagDesc flagdesc_noiseparams[] = { {"defaults", NOISE_FLAG_DEFAULTS}, {"eased", NOISE_FLAG_EASED}, {"absvalue", NOISE_FLAG_ABSVALUE}, diff --git a/src/noise.h b/src/noise.h index c864c8fbb..acd8d555d 100644 --- a/src/noise.h +++ b/src/noise.h @@ -36,7 +36,7 @@ #undef RANDOM_MAX #endif -extern FlagDesc flagdesc_noiseparams[]; +extern const FlagDesc flagdesc_noiseparams[]; // Note: this class is not polymorphic so that its high level of // optimizability may be preserved in the common use case diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp index 656abf901..9509ba88a 100644 --- a/src/pathfinder.cpp +++ b/src/pathfinder.cpp @@ -578,7 +578,7 @@ MapGridNodeContainer::MapGridNodeContainer(Pathfinder *pathf) PathGridnode &MapGridNodeContainer::access(v3s16 p) { - std::map::iterator it = m_nodes.find(p); + auto it = m_nodes.find(p); if (it != m_nodes.end()) { return it->second; } @@ -758,8 +758,7 @@ std::vector Pathfinder::getPath(v3s16 source, } //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) { + for (auto rit = index_path.rbegin(); rit != index_path.rend(); ++rit) { full_path.push_back(getIndexElement(*rit).pos); } //manually add true_destination to end of path, if needed @@ -1419,8 +1418,7 @@ std::string Pathfinder::dirToName(PathDirections dir) void Pathfinder::printPath(const std::vector &path) { unsigned int current = 0; - for (std::vector::iterator i = path.begin(); - i != path.end(); ++i) { + for (auto i = path.begin(); i != path.end(); ++i) { std::cout << std::setw(3) << current << ":" << *i << std::endl; current++; } diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index fb4762eec..7efbb7abf 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -1175,8 +1175,7 @@ void push_palette(lua_State *L, const std::vector *palette) lua_createtable(L, palette->size(), 0); int newTable = lua_gettop(L); int index = 1; - std::vector::const_iterator iter; - for (iter = palette->begin(); iter != palette->end(); ++iter) { + for (auto iter = palette->begin(); iter != palette->end(); ++iter) { push_ARGB8(L, (*iter)); lua_rawseti(L, newTable, index); index++; @@ -1829,7 +1828,7 @@ 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) + const FlagDesc *flagdesc, u32 *flags, u32 *flagmask) { lua_getfield(L, table, fieldname); @@ -1840,7 +1839,7 @@ bool getflagsfield(lua_State *L, int table, const char *fieldname, return success; } -bool read_flags(lua_State *L, int index, FlagDesc *flagdesc, +bool read_flags(lua_State *L, int index, const FlagDesc *flagdesc, u32 *flags, u32 *flagmask) { if (lua_isstring(L, index)) { @@ -1855,7 +1854,7 @@ bool read_flags(lua_State *L, int index, FlagDesc *flagdesc, return true; } -u32 read_flags_table(lua_State *L, int table, FlagDesc *flagdesc, u32 *flagmask) +u32 read_flags_table(lua_State *L, int table, const FlagDesc *flagdesc, u32 *flagmask) { u32 flags = 0, mask = 0; char fnamebuf[64] = "no"; @@ -1880,7 +1879,7 @@ u32 read_flags_table(lua_State *L, int table, FlagDesc *flagdesc, u32 *flagmask) return flags; } -void push_flags_string(lua_State *L, FlagDesc *flagdesc, u32 flags, u32 flagmask) +void push_flags_string(lua_State *L, const FlagDesc *flagdesc, u32 flags, u32 flagmask) { std::string flagstring = writeFlagString(flags, flagdesc, flagmask); lua_pushlstring(L, flagstring.c_str(), flagstring.size()); diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index f05a51f24..f57c15797 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -120,21 +120,21 @@ void read_groups(lua_State *L, int index, ItemGroupList &result); void push_groups(lua_State *L, const ItemGroupList &groups); -//TODO rename to "read_enum_field" +// TODO: rename to "read_enum_field" and replace with type-safe template 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); + const FlagDesc *flagdesc, u32 *flags, u32 *flagmask); -bool read_flags(lua_State *L, int index, FlagDesc *flagdesc, +bool read_flags(lua_State *L, int index, const FlagDesc *flagdesc, u32 *flags, u32 *flagmask); -void push_flags_string(lua_State *L, FlagDesc *flagdesc, +void push_flags_string(lua_State *L, const FlagDesc *flagdesc, u32 flags, u32 flagmask); u32 read_flags_table(lua_State *L, int table, - FlagDesc *flagdesc, u32 *flagmask); + const FlagDesc *flagdesc, u32 *flagmask); void push_items(lua_State *L, const std::vector &items); diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index be6eb4102..f3b6fd41c 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -411,7 +411,7 @@ static void push_craft_recipe(lua_State *L, IGameDef *gdef, CraftOutput output = recipe->getOutput(input, gdef); lua_newtable(L); // items - std::vector::const_iterator iter = input.items.begin(); + auto iter = input.items.begin(); for (u16 j = 1; iter != input.items.end(); ++iter, j++) { if (iter->empty()) continue; @@ -457,7 +457,7 @@ static void push_craft_recipes(lua_State *L, IGameDef *gdef, lua_createtable(L, recipes.size(), 0); - std::vector::const_iterator it = recipes.begin(); + auto it = recipes.begin(); for (unsigned i = 0; it != recipes.end(); ++it) { lua_newtable(L); push_craft_recipe(L, gdef, *it, output); diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 3b547f5da..8d3df7213 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -250,7 +250,7 @@ bool read_schematic_def(lua_State *L, int index, 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); + auto it = name_id_map.find(name); content_t name_index; if (it != name_id_map.end()) { name_index = it->second; diff --git a/src/script/lua_api/l_rollback.cpp b/src/script/lua_api/l_rollback.cpp index 93137263c..9a4a3ca64 100644 --- a/src/script/lua_api/l_rollback.cpp +++ b/src/script/lua_api/l_rollback.cpp @@ -36,7 +36,7 @@ int ModApiRollback::l_rollback_get_node_actions(lua_State *L) } std::list actions = rollback->getNodeActors(pos, range, seconds, limit); - std::list::iterator iter = actions.begin(); + auto iter = actions.begin(); lua_createtable(L, actions.size(), 0); for (unsigned int i = 1; iter != actions.end(); ++iter, ++i) { @@ -86,8 +86,7 @@ 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(auto 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/server.cpp b/src/server.cpp index 116bbfe77..16611843f 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1986,9 +1986,8 @@ void Server::SendMovePlayer(PlayerSAO *sao) pkt << sao->getBasePosition() << sao->getLookPitch() << sao->getRotation().Y; { - v3f pos = sao->getBasePosition(); verbosestream << "Server: Sending TOCLIENT_MOVE_PLAYER" - << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" + << " pos=" << sao->getBasePosition() << " pitch=" << sao->getLookPitch() << " yaw=" << sao->getRotation().Y << std::endl; diff --git a/src/server/rollback.cpp b/src/server/rollback.cpp index a20469aa5..43ca70bb3 100644 --- a/src/server/rollback.cpp +++ b/src/server/rollback.cpp @@ -121,7 +121,7 @@ void RollbackManager::registerNewNode(const int id, const std::string &name) int RollbackManager::getActorId(const std::string &name) { - for (std::vector::const_iterator iter = knownActors.begin(); + for (auto iter = knownActors.begin(); iter != knownActors.end(); ++iter) { if (iter->name == name) { return iter->id; @@ -141,7 +141,7 @@ int RollbackManager::getActorId(const std::string &name) int RollbackManager::getNodeId(const std::string &name) { - for (std::vector::const_iterator iter = knownNodes.begin(); + for (auto iter = knownNodes.begin(); iter != knownNodes.end(); ++iter) { if (iter->name == name) { return iter->id; @@ -161,7 +161,7 @@ int RollbackManager::getNodeId(const std::string &name) const char * RollbackManager::getActorName(const int id) { - for (std::vector::const_iterator iter = knownActors.begin(); + for (auto iter = knownActors.begin(); iter != knownActors.end(); ++iter) { if (iter->id == id) { return iter->name.c_str(); @@ -174,7 +174,7 @@ const char * RollbackManager::getActorName(const int id) const char * RollbackManager::getNodeName(const int id) { - for (std::vector::const_iterator iter = knownNodes.begin(); + for (auto iter = knownNodes.begin(); iter != knownNodes.end(); ++iter) { if (iter->id == id) { return iter->name.c_str(); @@ -771,9 +771,7 @@ void RollbackManager::flush() { sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); - std::list::const_iterator iter; - - for (iter = action_todisk_buffer.begin(); + for (auto iter = action_todisk_buffer.begin(); iter != action_todisk_buffer.end(); ++iter) { if (iter->actor.empty()) { diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 3de1f2168..bab243713 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -43,7 +43,7 @@ // A number that is much smaller than the timeout for particle spawners should/could ever be #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f -static constexpr s16 ACTIVE_OBJECT_RESAVE_DISTANCE_SQ = 3 * 3; +static constexpr s16 ACTIVE_OBJECT_RESAVE_DISTANCE_SQ = sqr(3); /* ABMWithState @@ -627,8 +627,7 @@ void ServerEnvironment::addPlayer(RemotePlayer *player) void ServerEnvironment::removePlayer(RemotePlayer *player) { - for (std::vector::iterator it = m_players.begin(); - it != m_players.end(); ++it) { + for (auto it = m_players.begin(); it != m_players.end(); ++it) { if ((*it) == player) { delete *it; m_players.erase(it); @@ -2412,7 +2411,7 @@ bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params, std::vector player_list; srcdb->listPlayers(player_list); - for (std::vector::const_iterator it = player_list.begin(); + for (auto it = player_list.begin(); it != player_list.end(); ++it) { actionstream << "Migrating player " << it->c_str() << std::endl; RemotePlayer player(it->c_str(), NULL); diff --git a/src/tileanimation.cpp b/src/tileanimation.cpp index 311fa4c84..a3dc6965d 100644 --- a/src/tileanimation.cpp +++ b/src/tileanimation.cpp @@ -57,7 +57,7 @@ void TileAnimationParams::determineParams(v2u32 texture_size, int *frame_count, if (frame_count) *frame_count = _frame_count; if (frame_length_ms) - *frame_length_ms = 1000.0 * vertical_frames.length / _frame_count; + *frame_length_ms = 1000 * vertical_frames.length / _frame_count; if (frame_size) *frame_size = v2u32(texture_size.X, frame_height); } else if (type == TAT_SHEET_2D) { @@ -104,5 +104,5 @@ v2f TileAnimationParams::getTextureCoords(v2u32 texture_size, int frame) const r = frame % sheet_2d.frames_w; ret = v2u32(r * frame_size.X, q * frame_size.Y); } - return v2f(ret.X / (float) texture_size.X, ret.Y / (float) texture_size.Y); + return v2f::from(ret) / v2f::from(texture_size); } diff --git a/src/unittest/test_random.cpp b/src/unittest/test_random.cpp index da5c2cf90..314192166 100644 --- a/src/unittest/test_random.cpp +++ b/src/unittest/test_random.cpp @@ -80,7 +80,7 @@ void TestRandom::testPseudoRandomRange() s32 min = (pr.next() % 3000) - 500; s32 max = (pr.next() % 3000) - 500; if (min > max) - SWAP(s32, min, max); + std::swap(min, max); s32 randval = pr.range(min, max); UASSERT(randval >= min); @@ -120,7 +120,7 @@ void TestRandom::testPcgRandomRange() s32 min = (pr.next() % 3000) - 500; s32 max = (pr.next() % 3000) - 500; if (min > max) - SWAP(s32, min, max); + std::swap(min, max); s32 randval = pr.range(min, max); UASSERT(randval >= min); diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index b7d4965f9..ac1f29b30 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -656,8 +656,6 @@ C apply_all(const C &co, F functor) return ret; } -#define cast_v3(T, other) T((other).X, (other).Y, (other).Z) - void TestUtilities::testIsBlockInSight() { const std::vector testdata1 = { @@ -674,7 +672,7 @@ void TestUtilities::testIsBlockInSight() auto test1 = [] (const std::vector &data) { float range = BS * MAP_BLOCKSIZE * 4; float fov = 72 * core::DEGTORAD; - v3f cam_pos = cast_v3(v3f, data[0]), cam_dir = cast_v3(v3f, data[1]); + v3f cam_pos = v3f::from(data[0]), cam_dir = v3f::from(data[1]); UASSERT( isBlockInSight(data[2], cam_pos, cam_dir, fov, range)); UASSERT(!isBlockInSight(data[3], cam_pos, cam_dir, fov, range)); UASSERT(!isBlockInSight(data[4], cam_pos, cam_dir, fov, range)); diff --git a/src/unittest/test_voxelmanipulator.cpp b/src/unittest/test_voxelmanipulator.cpp index abfb5a5ec..09d4b5781 100644 --- a/src/unittest/test_voxelmanipulator.cpp +++ b/src/unittest/test_voxelmanipulator.cpp @@ -52,13 +52,11 @@ void TestVoxelManipulator::testVoxelArea() UASSERT(aa.size() == results.size()); infostream<<"Result of diff:"<::const_iterator - it = aa.begin(); it != aa.end(); ++it) { + for (auto it = aa.begin(); it != aa.end(); ++it) { it->print(infostream); infostream << std::endl; - std::vector::iterator j; - j = std::find(results.begin(), results.end(), *it); + auto j = std::find(results.begin(), results.end(), *it); UASSERT(j != results.end()); results.erase(j); } diff --git a/src/util/areastore.cpp b/src/util/areastore.cpp index 8d5dfa7d4..67bf4113a 100644 --- a/src/util/areastore.cpp +++ b/src/util/areastore.cpp @@ -194,7 +194,7 @@ bool VectorAreaStore::removeArea(u32 id) if (it == areas_map.end()) return false; Area *a = &it->second; - for (std::vector::iterator v_it = m_areas.begin(); + for (auto v_it = m_areas.begin(); v_it != m_areas.end(); ++v_it) { if (*v_it == a) { m_areas.erase(v_it); @@ -259,7 +259,7 @@ bool SpatialAreaStore::insertArea(Area *a) bool SpatialAreaStore::removeArea(u32 id) { - std::map::iterator itr = areas_map.find(id); + auto itr = areas_map.find(id); if (itr != areas_map.end()) { Area *a = &itr->second; bool result = m_tree->deleteData(get_spatial_region(a->minedge, diff --git a/src/util/areastore.h b/src/util/areastore.h index 7f6c24815..c377c2ec9 100644 --- a/src/util/areastore.h +++ b/src/util/areastore.h @@ -162,7 +162,7 @@ private: { u32 id = in.getIdentifier(); - std::map::iterator itr = m_store->areas_map.find(id); + auto itr = m_store->areas_map.find(id); assert(itr != m_store->areas_map.end()); m_result->push_back(&itr->second); } diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 2ed351fbf..7dc3d4dea 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -7,7 +7,6 @@ #include "log.h" #include "constants.h" // BS, MAP_BLOCKSIZE #include "noise.h" // PseudoRandom, PcgRandom -#include "threading/mutex_auto_lock.h" #include #include @@ -73,15 +72,14 @@ u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed) h *= m; } - const unsigned char *data2 = (const unsigned char *)data; switch (len & 7) { - case 7: h ^= (u64)data2[6] << 48; [[fallthrough]]; - case 6: h ^= (u64)data2[5] << 40; [[fallthrough]]; - case 5: h ^= (u64)data2[4] << 32; [[fallthrough]]; - case 4: h ^= (u64)data2[3] << 24; [[fallthrough]]; - case 3: h ^= (u64)data2[2] << 16; [[fallthrough]]; - case 2: h ^= (u64)data2[1] << 8; [[fallthrough]]; - case 1: h ^= (u64)data2[0]; + case 7: h ^= (u64)data[6] << 48; [[fallthrough]]; + case 6: h ^= (u64)data[5] << 40; [[fallthrough]]; + case 5: h ^= (u64)data[4] << 32; [[fallthrough]]; + case 4: h ^= (u64)data[3] << 24; [[fallthrough]]; + case 3: h ^= (u64)data[2] << 16; [[fallthrough]]; + case 2: h ^= (u64)data[1] << 8; [[fallthrough]]; + case 1: h ^= (u64)data[0]; h *= m; } @@ -105,11 +103,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, v3s16 blockpos_nodes = blockpos_b * MAP_BLOCKSIZE; // Block center position - v3f blockpos( - ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS, - ((float)blockpos_nodes.Y + MAP_BLOCKSIZE/2) * BS, - ((float)blockpos_nodes.Z + MAP_BLOCKSIZE/2) * BS - ); + v3f blockpos = v3f::from(blockpos_nodes + MAP_BLOCKSIZE / 2) * BS; // Block position relative to camera v3f blockpos_relative = blockpos - camera_pos; diff --git a/src/util/numeric.h b/src/util/numeric.h index 3082adb31..5c0d2cebf 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -15,14 +15,16 @@ #include #include -#define rangelim(d, min, max) ((d) < (min) ? (min) : ((d) > (max) ? (max) : (d))) -#define myfloor(x) ((x) < 0.0 ? (int)(x) - 1 : (int)(x)) -// The naive swap performs better than the xor version -#define SWAP(t, x, y) do { \ - t temp = x; \ - x = y; \ - y = temp; \ -} while (0) +// Like std::clamp but allows mismatched types +template +inline constexpr T rangelim(const T &d, const T2 &min, const T3 &max) +{ + if (d < (T)min) + return (T)min; + if (d > (T)max) + return (T)max; + return d; +} // Maximum radius of a block. The magic number is // sqrt(3.0) / 2.0 in literal form. @@ -113,23 +115,24 @@ inline bool isInArea(v3s16 p, v3s16 d) ); } -inline void sortBoxVerticies(v3s16 &p1, v3s16 &p2) { +inline void sortBoxVerticies(v3s16 &p1, v3s16 &p2) +{ if (p1.X > p2.X) - SWAP(s16, p1.X, p2.X); + std::swap(p1.X, p2.X); if (p1.Y > p2.Y) - SWAP(s16, p1.Y, p2.Y); + std::swap(p1.Y, p2.Y); if (p1.Z > p2.Z) - SWAP(s16, p1.Z, p2.Z); + std::swap(p1.Z, p2.Z); } inline v3s16 componentwise_min(const v3s16 &a, const v3s16 &b) { - return v3s16(MYMIN(a.X, b.X), MYMIN(a.Y, b.Y), MYMIN(a.Z, b.Z)); + return v3s16(std::min(a.X, b.X), std::min(a.Y, b.Y), std::min(a.Z, b.Z)); } inline v3s16 componentwise_max(const v3s16 &a, const v3s16 &b) { - return v3s16(MYMAX(a.X, b.X), MYMAX(a.Y, b.Y), MYMAX(a.Z, b.Z)); + return v3s16(std::max(a.X, b.X), std::max(a.Y, b.Y), std::max(a.Z, b.Z)); } /// @brief Describes a grid with given step, oirginating at (0,0,0) @@ -290,7 +293,8 @@ inline s32 myround(f32 f) return (s32)(f < 0.f ? (f - 0.5f) : (f + 0.5f)); } -inline constexpr f32 sqr(f32 f) +template +inline constexpr T sqr(T f) { return f * f; } @@ -322,23 +326,15 @@ inline v3s16 doubleToInt(v3d p, double d) */ inline v3f intToFloat(v3s16 p, f32 d) { - return v3f( - (f32)p.X * d, - (f32)p.Y * d, - (f32)p.Z * d - ); + return v3f::from(p) * d; } // Random helper. Usually d=BS inline aabb3f getNodeBox(v3s16 p, float d) { return aabb3f( - (float)p.X * d - 0.5f * d, - (float)p.Y * d - 0.5f * d, - (float)p.Z * d - 0.5f * d, - (float)p.X * d + 0.5f * d, - (float)p.Y * d + 0.5f * d, - (float)p.Z * d + 0.5f * d + v3f::from(p) * d - 0.5f * d, + v3f::from(p) * d + 0.5f * d ); } @@ -410,14 +406,15 @@ inline float cycle_shift(float value, float by = 0, float max = 1) return value + by; } -inline bool is_power_of_two(u32 n) +constexpr inline bool is_power_of_two(u32 n) { return n != 0 && (n & (n - 1)) == 0; } // Compute next-higher power of 2 efficiently, e.g. for power-of-2 texture sizes. // Public Domain: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 -inline u32 npot2(u32 orig) { +constexpr inline u32 npot2(u32 orig) +{ orig--; orig |= orig >> 1; orig |= orig >> 2; diff --git a/src/util/string.cpp b/src/util/string.cpp index 0dbb9c0d3..c08421d55 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -139,16 +139,6 @@ std::string wide_to_utf8(std::wstring_view input) return out; } -void wide_add_codepoint(std::wstring &result, char32_t codepoint) -{ - if ((0xD800 <= codepoint && codepoint <= 0xDFFF) || (0x10FFFF < codepoint)) { - // Invalid codepoint, replace with unicode replacement character - result.push_back(0xFFFD); - return; - } - result.push_back(codepoint); -} - #else // _WIN32 std::wstring utf8_to_wide(std::string_view input) @@ -175,32 +165,25 @@ std::string wide_to_utf8(std::wstring_view input) return out; } +#endif // _WIN32 + void wide_add_codepoint(std::wstring &result, char32_t codepoint) { - if (codepoint < 0x10000) { - if (0xD800 <= codepoint && codepoint <= 0xDFFF) { - // Invalid codepoint, part of a surrogate pair - // Replace with unicode replacement character - result.push_back(0xFFFD); - return; - } - result.push_back((wchar_t) codepoint); - return; - } - codepoint -= 0x10000; - if (codepoint >= 0x100000) { - // original codepoint was above 0x10FFFF, so invalid - // replace with unicode replacement character + if ((0xD800 <= codepoint && codepoint <= 0xDFFF) || codepoint > 0x10FFFF) { + // Invalid codepoint, replace with unicode replacement character result.push_back(0xFFFD); return; } - result.push_back((wchar_t) ((codepoint >> 10) | 0xD800)); - result.push_back((wchar_t) ((codepoint & 0x3FF) | 0xDC00)); + if constexpr (sizeof(wchar_t) == 2) { // Surrogate encoding needed? + if (codepoint > 0xffff) { + result.push_back((wchar_t) ((codepoint >> 10) | 0xD800)); + result.push_back((wchar_t) ((codepoint & 0x3FF) | 0xDC00)); + return; + } + } + result.push_back((wchar_t) codepoint); } -#endif // _WIN32 - - std::string urlencode(std::string_view str) { // Encodes reserved URI characters by a percent sign diff --git a/src/util/thread.h b/src/util/thread.h index 1bc91e726..8b28bf72e 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -92,22 +92,19 @@ public: void add(const Key &key, Caller caller, CallerData callerdata, ResultQueue *dest) { - typename std::deque >::iterator i; - typename std::list >::iterator j; - { MutexAutoLock lock(m_queue.getMutex()); /* If the caller is already on the list, only update CallerData */ - for (i = m_queue.getQueue().begin(); i != m_queue.getQueue().end(); ++i) { - GetRequest &request = *i; + for (auto i = m_queue.getQueue().begin(); i != m_queue.getQueue().end(); ++i) { + auto &request = *i; if (request.key != key) continue; - for (j = request.callers.begin(); j != request.callers.end(); ++j) { - CallerInfo &ca = *j; + for (auto j = request.callers.begin(); j != request.callers.end(); ++j) { + auto &ca = *j; if (ca.caller == caller) { ca.data = callerdata; return; @@ -150,10 +147,9 @@ public: void pushResult(GetRequest req, T res) { - for (typename std::list >::iterator - i = req.callers.begin(); + for (auto i = req.callers.begin(); i != req.callers.end(); ++i) { - CallerInfo &ca = *i; + auto &ca = *i; GetResult result; From 63701de45f31a49c079d2b4d7e3c5e8cf9898b32 Mon Sep 17 00:00:00 2001 From: Medley <198984680+maplemedley@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:01:43 +0100 Subject: [PATCH 041/284] Make `Sneak` and `Aux1` optionally togglable (#15785) --- builtin/settingtypes.txt | 6 ++++++ src/client/game.cpp | 23 +++++++++++++++++++++-- src/defaultsettings.cpp | 2 ++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index a6b05496a..5b7de5ff2 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -111,6 +111,12 @@ doubletap_jump (Double tap jump for fly) bool false # enabled. always_fly_fast (Always fly fast) bool true +# If enabled, the "Sneak" key will toggle when pressed. +toggle_sneak_key (Toggle Sneak key) bool false + +# If enabled, the "Aux1" key will toggle when pressed. +toggle_aux1_key (Toggle Aux1 key) bool false + # The time in seconds it takes between repeated node placements when holding # the place button. # diff --git a/src/client/game.cpp b/src/client/game.cpp index 7588fedc7..a4ec79ffc 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -560,6 +560,7 @@ protected: void updateCameraDirection(CameraOrientation *cam, float dtime); void updateCameraOrientation(CameraOrientation *cam, float dtime); + bool getTogglableKeyState(GameKeyType key, bool toggling_enabled, bool prev_key_state); void updatePlayerControl(const CameraOrientation &cam); void updatePauseState(); void step(f32 dtime); @@ -746,6 +747,8 @@ private: * a later release. */ bool m_cache_doubletap_jump; + bool m_cache_toggle_sneak_key; + bool m_cache_toggle_aux1_key; bool m_cache_enable_joysticks; bool m_cache_enable_fog; bool m_cache_enable_noclip; @@ -791,6 +794,10 @@ Game::Game() : &settingChangedCallback, this); g_settings->registerChangedCallback("doubletap_jump", &settingChangedCallback, this); + g_settings->registerChangedCallback("toggle_sneak_key", + &settingChangedCallback, this); + g_settings->registerChangedCallback("toggle_aux1_key", + &settingChangedCallback, this); g_settings->registerChangedCallback("enable_joysticks", &settingChangedCallback, this); g_settings->registerChangedCallback("enable_fog", @@ -2427,6 +2434,16 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) } +// Get the state of an optionally togglable key +bool Game::getTogglableKeyState(GameKeyType key, bool toggling_enabled, bool prev_key_state) +{ + if (!toggling_enabled) + return isKeyDown(key); + else + return prev_key_state ^ wasKeyPressed(key); +} + + void Game::updatePlayerControl(const CameraOrientation &cam) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -2439,8 +2456,8 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::LEFT), isKeyDown(KeyType::RIGHT), isKeyDown(KeyType::JUMP) || player->getAutojump(), - isKeyDown(KeyType::AUX1), - isKeyDown(KeyType::SNEAK), + getTogglableKeyState(KeyType::AUX1, m_cache_toggle_aux1_key, player->control.aux1), + getTogglableKeyState(KeyType::SNEAK, m_cache_toggle_sneak_key, player->control.sneak), isKeyDown(KeyType::ZOOM), isKeyDown(KeyType::DIG), isKeyDown(KeyType::PLACE), @@ -4100,6 +4117,8 @@ void Game::readSettings() m_chat_log_buf.setLogLevel(chat_log_level); m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); + m_cache_toggle_sneak_key = g_settings->getBool("toggle_sneak_key"); + m_cache_toggle_aux1_key = g_settings->getBool("toggle_aux1_key"); m_cache_enable_joysticks = g_settings->getBool("enable_joysticks"); m_cache_enable_fog = g_settings->getBool("enable_fog"); m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index f9f0c41f0..b6d45b073 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -356,6 +356,8 @@ void set_default_settings() settings->setDefault("aux1_descends", "false"); settings->setDefault("doubletap_jump", "false"); settings->setDefault("always_fly_fast", "true"); + settings->setDefault("toggle_sneak_key", "false"); + settings->setDefault("toggle_aux1_key", "false"); settings->setDefault("autojump", bool_to_cstr(has_touch)); settings->setDefault("continuous_forward", "false"); settings->setDefault("enable_joysticks", "false"); From 18ac8b20fa86696164b9d09beb8c24e8637af43d Mon Sep 17 00:00:00 2001 From: cx384 Date: Thu, 6 Mar 2025 21:02:11 +0100 Subject: [PATCH 042/284] Replace object visual by enum (#15681) --- src/client/content_cao.cpp | 54 +++++++++++++++++++-------------- src/object_properties.cpp | 30 ++++++++++++++++-- src/object_properties.h | 18 ++++++++++- src/script/common/c_content.cpp | 11 +++++-- src/server/player_sao.cpp | 2 +- 5 files changed, 86 insertions(+), 29 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 988ef210e..cdea7d4fd 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -653,9 +653,12 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) if (!m_prop.is_visible) return; - infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl; + infostream << "GenericCAO::addToScene(): " << + enum_to_string(es_ObjectVisual, m_prop.visual)<< std::endl; - if (m_prop.visual != "node" && m_prop.visual != "wielditem" && m_prop.visual != "item") + if (m_prop.visual != OBJECTVISUAL_NODE && + m_prop.visual != OBJECTVISUAL_WIELDITEM && + m_prop.visual != OBJECTVISUAL_ITEM) { IShaderSource *shader_source = m_client->getShaderSource(); MaterialType material_type; @@ -691,7 +694,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) node->forEachMaterial(setMaterial); }; - if (m_prop.visual == "upright_sprite") { + switch(m_prop.visual) { + case OBJECTVISUAL_UPRIGHT_SPRITE: { auto mesh = make_irr(); f32 dx = BS * m_prop.visual_size.X / 2; f32 dy = BS * m_prop.visual_size.Y / 2; @@ -732,7 +736,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) mesh->recalculateBoundingBox(); m_meshnode = m_smgr->addMeshSceneNode(mesh.get(), m_matrixnode); m_meshnode->grab(); - } else if (m_prop.visual == "cube") { + break; + } case OBJECTVISUAL_CUBE: { scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS)); m_meshnode = m_smgr->addMeshSceneNode(mesh, m_matrixnode); m_meshnode->grab(); @@ -745,7 +750,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_meshnode->forEachMaterial([this] (auto &mat) { mat.BackfaceCulling = m_prop.backface_culling; }); - } else if (m_prop.visual == "mesh") { + break; + } case OBJECTVISUAL_MESH: { scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true); if (mesh) { if (!checkMeshNormals(mesh)) { @@ -771,7 +777,10 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) }); } else errorstream<<"GenericCAO::addToScene(): Could not load mesh "<setItem(item, m_client, - (m_prop.visual == "wielditem")); + (m_prop.visual == OBJECTVISUAL_WIELDITEM)); m_wield_meshnode->setScale(m_prop.visual_size / 2.0f); - } else if (m_prop.visual == "node") { + break; + } case OBJECTVISUAL_NODE: { auto *mesh = generateNodeMesh(m_client, m_prop.node, m_meshnode_animation); assert(mesh); @@ -803,7 +813,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) m_meshnode->setScale(m_prop.visual_size); setSceneNodeMaterials(m_meshnode); - } else { + break; + } default: m_spritenode = m_smgr->addBillboardSceneNode(m_matrixnode); m_spritenode->grab(); @@ -814,19 +825,18 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) setBillboardTextureMatrix(m_spritenode, 1, 1, 0, 0); // This also serves as fallback for unknown visual types - if (m_prop.visual != "sprite") { - infostream << "GenericCAO::addToScene(): \"" << m_prop.visual - << "\" not supported" << std::endl; + if (m_prop.visual != OBJECTVISUAL_SPRITE) { m_spritenode->getMaterial(0).setTexture(0, tsrc->getTextureForMesh("unknown_object.png")); } + break; } /* Set VBO hint */ // wieldmesh sets its own hint, no need to handle it if (m_meshnode || m_animated_meshnode) { // sprite uses vertex animation - if (m_meshnode && m_prop.visual != "upright_sprite") + if (m_meshnode && m_prop.visual != OBJECTVISUAL_UPRIGHT_SPRITE) m_meshnode->getMesh()->setHardwareMappingHint(scene::EHM_STATIC); if (m_animated_meshnode) { @@ -930,7 +940,7 @@ void GenericCAO::updateLight(u32 day_night_ratio) void GenericCAO::setNodeLight(const video::SColor &light_color) { - if (m_prop.visual == "wielditem" || m_prop.visual == "item") { + if (m_prop.visual == OBJECTVISUAL_WIELDITEM || m_prop.visual == OBJECTVISUAL_ITEM) { if (m_wield_meshnode) m_wield_meshnode->setNodeLightColor(light_color); return; @@ -1317,7 +1327,7 @@ void GenericCAO::updateTextureAnim() } else if (m_meshnode) { - if (m_prop.visual == "upright_sprite") { + if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) { int row = m_tx_basepos.Y; int col = m_tx_basepos.X; @@ -1334,7 +1344,7 @@ void GenericCAO::updateTextureAnim() auto mesh = m_meshnode->getMesh(); setMeshBufferTextureCoords(mesh->getMeshBuffer(0), t, 4); setMeshBufferTextureCoords(mesh->getMeshBuffer(1), t, 4); - } else if (m_prop.visual == "node") { + } else if (m_prop.visual == OBJECTVISUAL_NODE) { // same calculation as MapBlockMesh::animate() with a global timer const float time = m_client->getAnimationTime(); for (auto &it : m_meshnode_animation) { @@ -1368,7 +1378,7 @@ void GenericCAO::updateTextures(std::string mod) m_current_texture_modifier = mod; if (m_spritenode) { - if (m_prop.visual == "sprite") { + if (m_prop.visual == OBJECTVISUAL_SPRITE) { std::string texturestring = "no_texture.png"; if (!m_prop.textures.empty()) texturestring = m_prop.textures[0]; @@ -1386,7 +1396,7 @@ void GenericCAO::updateTextures(std::string mod) } else if (m_animated_meshnode) { - if (m_prop.visual == "mesh") { + if (m_prop.visual == OBJECTVISUAL_MESH) { for (u32 i = 0; i < m_animated_meshnode->getMaterialCount(); ++i) { const auto texture_idx = m_animated_meshnode->getMesh()->getTextureSlot(i); if (texture_idx >= m_prop.textures.size()) @@ -1423,7 +1433,7 @@ void GenericCAO::updateTextures(std::string mod) } else if (m_meshnode) { - if(m_prop.visual == "cube") + if(m_prop.visual == OBJECTVISUAL_CUBE) { for (u32 i = 0; i < 6; ++i) { @@ -1443,7 +1453,7 @@ void GenericCAO::updateTextures(std::string mod) use_anisotropic_filter); }); } - } else if (m_prop.visual == "upright_sprite") { + } else if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) { scene::IMesh *mesh = m_meshnode->getMesh(); { std::string tname = "no_texture.png"; @@ -1607,7 +1617,7 @@ bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const */ bool uses_legacy_texture = new_.wield_item.empty() && - (new_.visual == "wielditem" || new_.visual == "item"); + (new_.visual == OBJECTVISUAL_WIELDITEM || new_.visual == OBJECTVISUAL_ITEM); // Ordered to compare primitive types before std::vectors return old.backface_culling != new_.backface_culling || old.is_visible != new_.is_visible || @@ -1999,7 +2009,7 @@ void GenericCAO::updateMeshCulling() if (!node) return; - if (m_prop.visual == "upright_sprite") { + if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) { // upright sprite has no backface culling node->forEachMaterial([=] (auto &mat) { mat.FrontfaceCulling = hidden; diff --git a/src/object_properties.cpp b/src/object_properties.cpp index feef295ba..35bf2e63e 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -8,11 +8,25 @@ #include "exceptions.h" #include "log.h" #include "util/serialize.h" +#include "util/enum_string.h" #include #include static const video::SColor NULL_BGCOLOR{0, 1, 1, 1}; +const struct EnumString es_ObjectVisual[] = +{ + {OBJECTVISUAL_UNKNOWN, "unknown"}, + {OBJECTVISUAL_SPRITE, "sprite"}, + {OBJECTVISUAL_UPRIGHT_SPRITE, "upright_sprite"}, + {OBJECTVISUAL_CUBE, "cube"}, + {OBJECTVISUAL_MESH, "mesh"}, + {OBJECTVISUAL_ITEM, "item"}, + {OBJECTVISUAL_WIELDITEM, "wielditem"}, + {OBJECTVISUAL_NODE, "node"}, + {0, nullptr}, +}; + ObjectProperties::ObjectProperties() { textures.emplace_back("no_texture.png"); @@ -26,7 +40,7 @@ std::string ObjectProperties::dump() const os << ", physical=" << physical; os << ", collideWithObjects=" << collideWithObjects; os << ", collisionbox=" << collisionbox.MinEdge << "," << collisionbox.MaxEdge; - os << ", visual=" << visual; + os << ", visual=" << enum_to_string(es_ObjectVisual, visual); os << ", mesh=" << mesh; os << ", visual_size=" << visual_size; os << ", textures=["; @@ -137,7 +151,10 @@ void ObjectProperties::serialize(std::ostream &os) const writeV3F32(os, selectionbox.MinEdge); writeV3F32(os, selectionbox.MaxEdge); Pointabilities::serializePointabilityType(os, pointable); - os << serializeString16(visual); + + // Convert to string for compatibility + os << serializeString16(enum_to_string(es_ObjectVisual, visual)); + writeV3F32(os, visual_size); writeU16(os, textures.size()); for (const std::string &texture : textures) { @@ -222,7 +239,14 @@ void ObjectProperties::deSerialize(std::istream &is) selectionbox.MinEdge = readV3F32(is); selectionbox.MaxEdge = readV3F32(is); pointable = Pointabilities::deSerializePointabilityType(is); - visual = deSerializeString16(is); + + std::string visual_string{deSerializeString16(is)}; + if (!string_to_enum(es_ObjectVisual, visual, visual_string)) { + infostream << "ObjectProperties::deSerialize(): visual \"" << visual_string + << "\" not supported" << std::endl; + visual = OBJECTVISUAL_UNKNOWN; + } + visual_size = readV3F32(is); textures.clear(); u32 texture_count = readU16(is); diff --git a/src/object_properties.h b/src/object_properties.h index dfb081923..b898c982d 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -12,6 +12,22 @@ #include "util/pointabilities.h" #include "mapnode.h" +struct EnumString; + +enum ObjectVisual : u8 { + OBJECTVISUAL_UNKNOWN, + OBJECTVISUAL_SPRITE, + OBJECTVISUAL_UPRIGHT_SPRITE, + OBJECTVISUAL_CUBE, + OBJECTVISUAL_MESH, + OBJECTVISUAL_ITEM, + OBJECTVISUAL_WIELDITEM, + OBJECTVISUAL_NODE, +}; + +extern const EnumString es_ObjectVisual[]; + + struct ObjectProperties { /* member variables ordered roughly by size */ @@ -22,7 +38,7 @@ struct ObjectProperties aabb3f collisionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f); // Values are BS=1 aabb3f selectionbox = aabb3f(-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f); - std::string visual = "sprite"; + ObjectVisual visual = OBJECTVISUAL_SPRITE; std::string mesh; std::string damage_texture_modifier = "^[brighten"; std::string nametag; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 7efbb7abf..0e802d9c7 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -353,7 +353,14 @@ void read_object_properties(lua_State *L, int index, } lua_pop(L, 1); - getstringfield(L, -1, "visual", prop->visual); + // Don't set if nil + std::string visual; + if (getstringfield(L, -1, "visual", visual)) { + if (!string_to_enum(es_ObjectVisual, prop->visual, visual)) { + script_log_unique(L, "Unsupported ObjectVisual: " + visual, warningstream); + prop->visual = OBJECTVISUAL_UNKNOWN; + } + } getstringfield(L, -1, "mesh", prop->mesh); @@ -502,7 +509,7 @@ void push_object_properties(lua_State *L, const ObjectProperties *prop) lua_setfield(L, -2, "selectionbox"); push_pointability_type(L, prop->pointable); lua_setfield(L, -2, "pointable"); - lua_pushlstring(L, prop->visual.c_str(), prop->visual.size()); + lua_pushstring(L, enum_to_string(es_ObjectVisual, prop->visual)); lua_setfield(L, -2, "visual"); lua_pushlstring(L, prop->mesh.c_str(), prop->mesh.size()); lua_setfield(L, -2, "mesh"); diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 449dbc6cf..11fc15597 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -26,7 +26,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); m_prop.pointable = PointabilityType::POINTABLE; // Start of default appearance, this should be overwritten by Lua - m_prop.visual = "upright_sprite"; + m_prop.visual = OBJECTVISUAL_UPRIGHT_SPRITE; m_prop.visual_size = v3f(1, 2, 1); m_prop.textures.clear(); m_prop.textures.emplace_back("player.png"); From 017318f117d210298b7e7a346cc43c53e3cacd32 Mon Sep 17 00:00:00 2001 From: lhofhansl Date: Sat, 8 Mar 2025 12:42:50 -0800 Subject: [PATCH 043/284] Avoid touching all blocks in range every 0.2s (#15878) Instead touch these blocks every 4s. --- src/client/game.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index a4ec79ffc..c2513fd9b 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3907,7 +3907,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, runData.update_draw_list_timer += dtime; runData.touch_blocks_timer += dtime; - float update_draw_list_delta = 0.2f; + constexpr float update_draw_list_delta = 0.2f; + constexpr float touch_mapblock_delta = 4.0f; v3f camera_direction = camera->getDirection(); @@ -3920,7 +3921,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, runData.update_draw_list_timer = 0; client->getEnv().getClientMap().updateDrawList(); runData.update_draw_list_last_cam_dir = camera_direction; - } else if (runData.touch_blocks_timer > update_draw_list_delta) { + } else if (runData.touch_blocks_timer > touch_mapblock_delta) { client->getEnv().getClientMap().touchMapBlocks(); runData.touch_blocks_timer = 0; } else if (RenderingEngine::get_shadow_renderer()) { From dadd097f32a90a57498ca706841516cac607490c Mon Sep 17 00:00:00 2001 From: Alex <24834740+GreenXenith@users.noreply.github.com> Date: Tue, 11 Mar 2025 01:59:51 -0700 Subject: [PATCH 044/284] Echo DMs sent with /msg (#15887) --- builtin/game/chat.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index 69c657194..64f14cc40 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -1275,7 +1275,7 @@ core.register_chatcommand("msg", { core.log("action", "DM from " .. name .. " to " .. sendto .. ": " .. message) core.chat_send_player(sendto, S("DM from @1: @2", name, message)) - return true, S("Message sent.") + return true, S("DM sent to @1: @2", sendto, message) end, }) From b9ed4793ea0d228a23875325f698cef5cdd27783 Mon Sep 17 00:00:00 2001 From: cx384 Date: Tue, 11 Mar 2025 10:00:04 +0100 Subject: [PATCH 045/284] Move drawItemStack out of hud.h/cpp (#15868) --- src/client/hud.cpp | 295 +------------------------------ src/client/hud.h | 29 ---- src/gui/CMakeLists.txt | 1 + src/gui/drawItemStack.cpp | 307 +++++++++++++++++++++++++++++++++ src/gui/drawItemStack.h | 41 +++++ src/gui/guiButtonItemImage.cpp | 1 - src/gui/guiFormSpecMenu.cpp | 2 +- src/gui/guiHyperText.cpp | 2 +- src/gui/guiInventoryList.cpp | 2 +- src/gui/guiItemImage.cpp | 2 +- 10 files changed, 354 insertions(+), 328 deletions(-) create mode 100644 src/gui/drawItemStack.cpp create mode 100644 src/gui/drawItemStack.h diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 4ef64fedc..47ef56039 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -17,17 +17,16 @@ #include "client/tile.h" #include "localplayer.h" #include "camera.h" -#include "porting.h" #include "fontengine.h" #include "guiscalingfilter.h" #include "mesh.h" -#include "wieldmesh.h" #include "client/renderingengine.h" #include "client/minimap.h" #include "client/texturesource.h" #include "gui/touchcontrols.h" #include "util/enriched_string.h" #include "irrlicht_changes/CGUITTFont.h" +#include "gui/drawItemStack.h" #define OBJECT_CROSSHAIR_LINE_SIZE 8 #define CROSSHAIR_LINE_SIZE 10 @@ -1039,295 +1038,3 @@ void Hud::resizeHotbar() { m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2); } } - -struct MeshTimeInfo { - u64 time; - scene::IMesh *mesh = nullptr; -}; - -void drawItemStack( - video::IVideoDriver *driver, - gui::IGUIFont *font, - const ItemStack &item, - const core::rect &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]; - - if (item.empty()) { - if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) { - rotation_time_infos[rotation_kind].mesh = NULL; - } - return; - } - - const bool enable_animations = g_settings->getBool("inventory_items_animations"); - - auto *idef = client->idef(); - const ItemDefinition &def = item.getDefinition(idef); - - bool draw_overlay = false; - - const std::string inventory_image = item.getInventoryImage(idef); - const std::string inventory_overlay = item.getInventoryOverlay(idef); - - bool has_mesh = false; - ItemMesh *imesh; - - core::rect viewrect = rect; - if (clip != nullptr) - viewrect.clipAgainst(*clip); - - // Render as mesh if animated or no inventory image - if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) { - imesh = idef->getWieldMesh(item, client); - has_mesh = imesh && imesh->mesh; - } - if (has_mesh) { - scene::IMesh *mesh = imesh->mesh; - driver->clearBuffers(video::ECBF_DEPTH); - s32 delta = 0; - if (rotation_kind < IT_ROT_NONE) { - MeshTimeInfo &ti = rotation_time_infos[rotation_kind]; - if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) { - ti.mesh = mesh; - ti.time = porting::getTimeMs(); - } else { - delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000; - } - } - core::rect oldViewPort = driver->getViewPort(); - core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); - core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); - - core::matrix4 ProjMatrix; - ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f); - - core::matrix4 ViewMatrix; - ViewMatrix.buildProjectionMatrixOrthoLH( - 2.0f * viewrect.getWidth() / rect.getWidth(), - 2.0f * viewrect.getHeight() / rect.getHeight(), - -1.0f, - 100.0f); - ViewMatrix.setTranslation(core::vector3df( - 1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X - - viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) / - viewrect.getWidth(), - 1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y - - rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) / - viewrect.getHeight(), - 0.0f)); - - driver->setTransform(video::ETS_PROJECTION, ProjMatrix); - driver->setTransform(video::ETS_VIEW, ViewMatrix); - - core::matrix4 matrix; - matrix.makeIdentity(); - - if (enable_animations) { - float timer_f = (float) delta / 5000.f; - matrix.setRotationDegrees(v3f( - angle.X + rotation_speed.X * 3.60f * timer_f, - angle.Y + rotation_speed.Y * 3.60f * timer_f, - angle.Z + rotation_speed.Z * 3.60f * timer_f) - ); - } - - driver->setTransform(video::ETS_WORLD, matrix); - driver->setViewPort(viewrect); - - video::SColor basecolor = - client->idef()->getItemstackColor(item, client); - - const u32 mc = mesh->getMeshBufferCount(); - if (mc > imesh->buffer_colors.size()) - imesh->buffer_colors.resize(mc); - for (u32 j = 0; j < mc; ++j) { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - video::SColor c = basecolor; - - auto &p = imesh->buffer_colors[j]; - p.applyOverride(c); - - // TODO: could be moved to a shader - if (p.needColorize(c)) { - buf->setDirty(scene::EBT_VERTEX); - if (imesh->needs_shading) - colorizeMeshBuffer(buf, &c); - else - setMeshBufferColor(buf, c); - } - - video::SMaterial &material = buf->getMaterial(); - material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - driver->setMaterial(material); - driver->drawMeshBuffer(buf); - } - - driver->setTransform(video::ETS_VIEW, oldViewMat); - driver->setTransform(video::ETS_PROJECTION, oldProjMat); - driver->setViewPort(oldViewPort); - - draw_overlay = def.type == ITEM_NODE && inventory_image.empty(); - } else { // Otherwise just draw as 2D - video::ITexture *texture = client->idef()->getInventoryTexture(item, client); - video::SColor color; - if (texture) { - color = client->idef()->getItemstackColor(item, client); - } else { - color = video::SColor(255, 255, 255, 255); - ITextureSource *tsrc = client->getTextureSource(); - texture = tsrc->getTexture("no_texture.png"); - if (!texture) - return; - } - - const video::SColor colors[] = { color, color, color, color }; - - draw2DImageFilterScaled(driver, texture, rect, - core::rect({0, 0}, core::dimension2di(texture->getOriginalSize())), - clip, colors, true); - - draw_overlay = true; - } - - // draw the inventory_overlay - if (!inventory_overlay.empty() && draw_overlay) { - ITextureSource *tsrc = client->getTextureSource(); - video::ITexture *overlay_texture = tsrc->getTexture(inventory_overlay); - core::dimension2d dimens = overlay_texture->getOriginalSize(); - core::rect srcrect(0, 0, dimens.Width, dimens.Height); - draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true); - } - - if (def.type == ITEM_TOOL && item.wear != 0) { - // Draw a progressbar - float barheight = static_cast(rect.getHeight()) / 16; - float barpad_x = static_cast(rect.getWidth()) / 16; - float barpad_y = static_cast(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); - - // Shrink progressrect by amount of tool damage - float wear = item.wear / 65535.0f; - int progressmid = - wear * progressrect.UpperLeftCorner.X + - (1 - wear) * progressrect.LowerRightCorner.X; - - // Compute progressbar color - // default scheme: - // wear = 0.0: green - // wear = 0.5: yellow - // wear = 1.0: red - - video::SColor color; - auto barParams = item.getWearBarParams(client->idef()); - if (barParams.has_value()) { - f32 durabilityPercent = 1.0 - wear; - color = barParams->getWearBarColor(durabilityPercent); - } else { - color = video::SColor(255, 255, 255, 255); - int wear_i = MYMIN(std::floor(wear * 600), 511); - wear_i = MYMIN(wear_i + 10, 511); - - if (wear_i <= 255) - color.set(255, wear_i, 255, 0); - else - color.set(255, 255, 511 - wear_i, 0); - } - - core::rect progressrect2 = progressrect; - progressrect2.LowerRightCorner.X = progressmid; - driver->draw2DRectangle(color, progressrect2, clip); - - color = video::SColor(255, 0, 0, 0); - progressrect2 = progressrect; - progressrect2.UpperLeftCorner.X = progressmid; - driver->draw2DRectangle(color, progressrect2, clip); - } - - const std::string &count_text = item.metadata.getString("count_meta"); - if (font != nullptr && (item.count >= 2 || !count_text.empty())) { - // Get the item count as a string - std::string text = count_text.empty() ? itos(item.count) : count_text; - v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str()); - v2s32 sdim(dim.X, dim.Y); - - core::rect rect2( - rect.LowerRightCorner - sdim, - rect.LowerRightCorner - ); - - // get the count alignment - s32 count_alignment = stoi(item.metadata.getString("count_alignment")); - if (count_alignment != 0) { - s32 a_x = count_alignment & 3; - s32 a_y = (count_alignment >> 2) & 3; - - s32 x1, x2, y1, y2; - switch (a_x) { - case 1: // left - x1 = rect.UpperLeftCorner.X; - x2 = x1 + sdim.X; - break; - case 2: // middle - x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2; - x2 = x1 + sdim.X; - break; - case 3: // right - x2 = rect.LowerRightCorner.X; - x1 = x2 - sdim.X; - break; - default: // 0 = default - x1 = rect2.UpperLeftCorner.X; - x2 = rect2.LowerRightCorner.X; - break; - } - - switch (a_y) { - case 1: // up - y1 = rect.UpperLeftCorner.Y; - y2 = y1 + sdim.Y; - break; - case 2: // middle - y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2; - y2 = y1 + sdim.Y; - break; - case 3: // down - y2 = rect.LowerRightCorner.Y; - y1 = y2 - sdim.Y; - break; - default: // 0 = default - y1 = rect2.UpperLeftCorner.Y; - y2 = rect2.LowerRightCorner.Y; - break; - } - - rect2 = core::rect(x1, y1, x2, y2); - } - - video::SColor color(255, 255, 255, 255); - font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect); - } -} - -void drawItemStack( - video::IVideoDriver *driver, - gui::IGUIFont *font, - const ItemStack &item, - const core::rect &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)); -} diff --git a/src/client/hud.h b/src/client/hud.h index b42435f25..e2cf757de 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -153,32 +153,3 @@ private: HIGHLIGHT_NONE } m_mode; }; - -enum ItemRotationKind -{ - IT_ROT_SELECTED, - IT_ROT_HOVERED, - IT_ROT_DRAGGED, - IT_ROT_OTHER, - IT_ROT_NONE, // Must be last, also serves as number -}; - -void drawItemStack(video::IVideoDriver *driver, - gui::IGUIFont *font, - const ItemStack &item, - const core::rect &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, - const v3s16 &rotation_speed); - diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 876acab74..26fd5bfcf 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -27,5 +27,6 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/touchcontrols.cpp ${CMAKE_CURRENT_SOURCE_DIR}/touchscreenlayout.cpp ${CMAKE_CURRENT_SOURCE_DIR}/touchscreeneditor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/drawItemStack.cpp PARENT_SCOPE ) diff --git a/src/gui/drawItemStack.cpp b/src/gui/drawItemStack.cpp new file mode 100644 index 000000000..728f5fd11 --- /dev/null +++ b/src/gui/drawItemStack.cpp @@ -0,0 +1,307 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 cx384 + +#include "drawItemStack.h" + +#include +#include "settings.h" +#include "client/client.h" +#include "porting.h" +#include "inventory.h" +#include "client/mesh.h" +#include "client/wieldmesh.h" +#include "client/texturesource.h" +#include "client/guiscalingfilter.h" + +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, + const v3s16 &rotation_speed) +{ + static MeshTimeInfo rotation_time_infos[IT_ROT_NONE]; + + if (item.empty()) { + if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) { + rotation_time_infos[rotation_kind].mesh = NULL; + } + return; + } + + const bool enable_animations = g_settings->getBool("inventory_items_animations"); + + auto *idef = client->idef(); + const ItemDefinition &def = item.getDefinition(idef); + + bool draw_overlay = false; + + const std::string inventory_image = item.getInventoryImage(idef); + const std::string inventory_overlay = item.getInventoryOverlay(idef); + + bool has_mesh = false; + ItemMesh *imesh; + + core::rect viewrect = rect; + if (clip != nullptr) + viewrect.clipAgainst(*clip); + + // Render as mesh if animated or no inventory image + if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) { + imesh = idef->getWieldMesh(item, client); + has_mesh = imesh && imesh->mesh; + } + if (has_mesh) { + scene::IMesh *mesh = imesh->mesh; + driver->clearBuffers(video::ECBF_DEPTH); + s32 delta = 0; + if (rotation_kind < IT_ROT_NONE) { + MeshTimeInfo &ti = rotation_time_infos[rotation_kind]; + if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) { + ti.mesh = mesh; + ti.time = porting::getTimeMs(); + } else { + delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000; + } + } + core::rect oldViewPort = driver->getViewPort(); + core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); + core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); + + core::matrix4 ProjMatrix; + ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f); + + core::matrix4 ViewMatrix; + ViewMatrix.buildProjectionMatrixOrthoLH( + 2.0f * viewrect.getWidth() / rect.getWidth(), + 2.0f * viewrect.getHeight() / rect.getHeight(), + -1.0f, + 100.0f); + ViewMatrix.setTranslation(core::vector3df( + 1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X - + viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) / + viewrect.getWidth(), + 1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y - + rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) / + viewrect.getHeight(), + 0.0f)); + + driver->setTransform(video::ETS_PROJECTION, ProjMatrix); + driver->setTransform(video::ETS_VIEW, ViewMatrix); + + core::matrix4 matrix; + matrix.makeIdentity(); + + if (enable_animations) { + float timer_f = (float) delta / 5000.f; + matrix.setRotationDegrees(v3f( + angle.X + rotation_speed.X * 3.60f * timer_f, + angle.Y + rotation_speed.Y * 3.60f * timer_f, + angle.Z + rotation_speed.Z * 3.60f * timer_f) + ); + } + + driver->setTransform(video::ETS_WORLD, matrix); + driver->setViewPort(viewrect); + + video::SColor basecolor = + client->idef()->getItemstackColor(item, client); + + const u32 mc = mesh->getMeshBufferCount(); + if (mc > imesh->buffer_colors.size()) + imesh->buffer_colors.resize(mc); + for (u32 j = 0; j < mc; ++j) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + video::SColor c = basecolor; + + auto &p = imesh->buffer_colors[j]; + p.applyOverride(c); + + // TODO: could be moved to a shader + if (p.needColorize(c)) { + buf->setDirty(scene::EBT_VERTEX); + if (imesh->needs_shading) + colorizeMeshBuffer(buf, &c); + else + setMeshBufferColor(buf, c); + } + + video::SMaterial &material = buf->getMaterial(); + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + driver->setMaterial(material); + driver->drawMeshBuffer(buf); + } + + driver->setTransform(video::ETS_VIEW, oldViewMat); + driver->setTransform(video::ETS_PROJECTION, oldProjMat); + driver->setViewPort(oldViewPort); + + draw_overlay = def.type == ITEM_NODE && inventory_image.empty(); + } else { // Otherwise just draw as 2D + video::ITexture *texture = client->idef()->getInventoryTexture(item, client); + video::SColor color; + if (texture) { + color = client->idef()->getItemstackColor(item, client); + } else { + color = video::SColor(255, 255, 255, 255); + ITextureSource *tsrc = client->getTextureSource(); + texture = tsrc->getTexture("no_texture.png"); + if (!texture) + return; + } + + const video::SColor colors[] = { color, color, color, color }; + + draw2DImageFilterScaled(driver, texture, rect, + core::rect({0, 0}, core::dimension2di(texture->getOriginalSize())), + clip, colors, true); + + draw_overlay = true; + } + + // draw the inventory_overlay + if (!inventory_overlay.empty() && draw_overlay) { + ITextureSource *tsrc = client->getTextureSource(); + video::ITexture *overlay_texture = tsrc->getTexture(inventory_overlay); + core::dimension2d dimens = overlay_texture->getOriginalSize(); + core::rect srcrect(0, 0, dimens.Width, dimens.Height); + draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true); + } + + if (def.type == ITEM_TOOL && item.wear != 0) { + // Draw a progressbar + float barheight = static_cast(rect.getHeight()) / 16; + float barpad_x = static_cast(rect.getWidth()) / 16; + float barpad_y = static_cast(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); + + // Shrink progressrect by amount of tool damage + float wear = item.wear / 65535.0f; + int progressmid = + wear * progressrect.UpperLeftCorner.X + + (1 - wear) * progressrect.LowerRightCorner.X; + + // Compute progressbar color + // default scheme: + // wear = 0.0: green + // wear = 0.5: yellow + // wear = 1.0: red + + video::SColor color; + auto barParams = item.getWearBarParams(client->idef()); + if (barParams.has_value()) { + f32 durabilityPercent = 1.0 - wear; + color = barParams->getWearBarColor(durabilityPercent); + } else { + color = video::SColor(255, 255, 255, 255); + int wear_i = MYMIN(std::floor(wear * 600), 511); + wear_i = MYMIN(wear_i + 10, 511); + + if (wear_i <= 255) + color.set(255, wear_i, 255, 0); + else + color.set(255, 255, 511 - wear_i, 0); + } + + core::rect progressrect2 = progressrect; + progressrect2.LowerRightCorner.X = progressmid; + driver->draw2DRectangle(color, progressrect2, clip); + + color = video::SColor(255, 0, 0, 0); + progressrect2 = progressrect; + progressrect2.UpperLeftCorner.X = progressmid; + driver->draw2DRectangle(color, progressrect2, clip); + } + + const std::string &count_text = item.metadata.getString("count_meta"); + if (font != nullptr && (item.count >= 2 || !count_text.empty())) { + // Get the item count as a string + std::string text = count_text.empty() ? itos(item.count) : count_text; + v2u32 dim = font->getDimension(utf8_to_wide(unescape_enriched(text)).c_str()); + v2s32 sdim(dim.X, dim.Y); + + core::rect rect2( + rect.LowerRightCorner - sdim, + rect.LowerRightCorner + ); + + // get the count alignment + s32 count_alignment = stoi(item.metadata.getString("count_alignment")); + if (count_alignment != 0) { + s32 a_x = count_alignment & 3; + s32 a_y = (count_alignment >> 2) & 3; + + s32 x1, x2, y1, y2; + switch (a_x) { + case 1: // left + x1 = rect.UpperLeftCorner.X; + x2 = x1 + sdim.X; + break; + case 2: // middle + x1 = (rect.UpperLeftCorner.X + rect.LowerRightCorner.X - sdim.X) / 2; + x2 = x1 + sdim.X; + break; + case 3: // right + x2 = rect.LowerRightCorner.X; + x1 = x2 - sdim.X; + break; + default: // 0 = default + x1 = rect2.UpperLeftCorner.X; + x2 = rect2.LowerRightCorner.X; + break; + } + + switch (a_y) { + case 1: // up + y1 = rect.UpperLeftCorner.Y; + y2 = y1 + sdim.Y; + break; + case 2: // middle + y1 = (rect.UpperLeftCorner.Y + rect.LowerRightCorner.Y - sdim.Y) / 2; + y2 = y1 + sdim.Y; + break; + case 3: // down + y2 = rect.LowerRightCorner.Y; + y1 = y2 - sdim.Y; + break; + default: // 0 = default + y1 = rect2.UpperLeftCorner.Y; + y2 = rect2.LowerRightCorner.Y; + break; + } + + rect2 = core::rect(x1, y1, x2, y2); + } + + video::SColor color(255, 255, 255, 255); + font->draw(utf8_to_wide(text).c_str(), rect2, color, false, false, &viewrect); + } +} + +void drawItemStack( + video::IVideoDriver *driver, + gui::IGUIFont *font, + const ItemStack &item, + const core::rect &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)); +} diff --git a/src/gui/drawItemStack.h b/src/gui/drawItemStack.h new file mode 100644 index 000000000..52bccf089 --- /dev/null +++ b/src/gui/drawItemStack.h @@ -0,0 +1,41 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2024 cx384 + +#pragma once + +#include +#include +#include "irrlichttypes.h" +#include "irr_v3d.h" + +struct ItemStack; +class Client; + +enum ItemRotationKind +{ + IT_ROT_SELECTED, + IT_ROT_HOVERED, + IT_ROT_DRAGGED, + IT_ROT_OTHER, + IT_ROT_NONE, // Must be last, also serves as number +}; + +void drawItemStack(video::IVideoDriver *driver, + gui::IGUIFont *font, + const ItemStack &item, + const core::rect &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, + const v3s16 &rotation_speed); diff --git a/src/gui/guiButtonItemImage.cpp b/src/gui/guiButtonItemImage.cpp index 9794b1889..bb7f55fff 100644 --- a/src/gui/guiButtonItemImage.cpp +++ b/src/gui/guiButtonItemImage.cpp @@ -5,7 +5,6 @@ #include "guiButtonItemImage.h" #include "client/client.h" -#include "client/hud.h" // drawItemStack #include "guiItemImage.h" #include "IGUIEnvironment.h" #include "itemdef.h" diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 621eba1c2..29a8e1285 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -25,7 +25,7 @@ #include "client/renderingengine.h" #include "client/joystick_controller.h" #include "log.h" -#include "client/hud.h" // drawItemStack +#include "drawItemStack.h" #include "filesys.h" #include "gettime.h" #include "gettext.h" diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 0f887ec4e..f3278a3ab 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -5,7 +5,7 @@ #include "guiHyperText.h" #include "guiScrollBar.h" #include "client/fontengine.h" -#include "client/hud.h" // drawItemStack +#include "drawItemStack.h" #include "IVideoDriver.h" #include "client/client.h" #include "client/renderingengine.h" diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp index c19d45b71..b8ed324c3 100644 --- a/src/gui/guiInventoryList.cpp +++ b/src/gui/guiInventoryList.cpp @@ -4,7 +4,7 @@ #include "guiInventoryList.h" #include "guiFormSpecMenu.h" -#include "client/hud.h" +#include "drawItemStack.h" #include "client/client.h" #include "client/renderingengine.h" #include diff --git a/src/gui/guiItemImage.cpp b/src/gui/guiItemImage.cpp index 9e9205f52..1c3c17acb 100644 --- a/src/gui/guiItemImage.cpp +++ b/src/gui/guiItemImage.cpp @@ -4,7 +4,7 @@ #include "guiItemImage.h" #include "client/client.h" -#include "client/hud.h" // drawItemStack +#include "drawItemStack.h" #include "inventory.h" #include From 287880aa271ada4b328c19146e56f658d4ecaff1 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 11 Mar 2025 19:59:03 +0100 Subject: [PATCH 046/284] Refresh win32 toolchain and libraries (#15890) --- .github/workflows/windows.yml | 4 ++-- util/buildbot/common.sh | 16 ++++++++-------- util/buildbot/download_toolchain.sh | 2 +- util/buildbot/sha256sums.txt | 26 +++++++++++++------------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d82b9785d..495d481f4 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -71,8 +71,8 @@ jobs: name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }} runs-on: windows-2019 env: - VCPKG_VERSION: 01f602195983451bc83e72f4214af2cbc495aa94 - # 2024.05.24 + VCPKG_VERSION: d5ec528843d29e3a52d745a64b469f810b2cedbf + # 2025.02.14 vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp sdl2 strategy: fail-fast: false diff --git a/util/buildbot/common.sh b/util/buildbot/common.sh index e92e08ab8..27815f349 100644 --- a/util/buildbot/common.sh +++ b/util/buildbot/common.sh @@ -3,19 +3,19 @@ CORE_BRANCH=master CORE_NAME=minetest ogg_version=1.3.5 -openal_version=1.23.1 +openal_version=1.24.2 vorbis_version=1.3.7 -curl_version=8.9.1 +curl_version=8.12.1 gettext_version=0.20.2 freetype_version=2.13.3 sqlite3_version=3.46.1 -luajit_version=20240905 +luajit_version=20250113 leveldb_version=1.23 zlib_version=1.3.1 -zstd_version=1.5.6 +zstd_version=1.5.7 libjpeg_version=3.0.1 -libpng_version=1.6.43 -sdl2_version=2.30.7 +libpng_version=1.6.47 +sdl2_version=2.32.2 download () { local url=$1 @@ -51,10 +51,10 @@ get_sources () { # sets $runtime_dlls find_runtime_dlls () { local triple=$1 - # Try to find runtime DLLs in various paths + # Try to find runtime DLLs in various paths (fun) local tmp=$(dirname "$(command -v $compiler)")/.. runtime_dlls= - for name in lib{clang_rt,c++,unwind,winpthread-}'*'.dll; do + for name in lib{c++,unwind,winpthread-}'*'.dll; do for dir in $tmp/$triple/{bin,lib}; do [ -d "$dir" ] || continue local file=$(echo $dir/$name) diff --git a/util/buildbot/download_toolchain.sh b/util/buildbot/download_toolchain.sh index d7d7afbe2..c3f3f043c 100755 --- a/util/buildbot/download_toolchain.sh +++ b/util/buildbot/download_toolchain.sh @@ -10,7 +10,7 @@ fi # * Clang + LLD + libc++ instead of GCC + binutils + stdc++ # * Mingw-w64 with UCRT enabled and winpthreads support # why are we avoiding GCC? -> Thread Local Storage (TLS) is totally broken -date=20240619 +date=20250305 name=llvm-mingw-${date}-ucrt-ubuntu-20.04-x86_64.tar.xz wget "https://github.com/mstorsjo/llvm-mingw/releases/download/$date/$name" -O "$name" sha256sum -w -c <(grep -F "$name" "$topdir/sha256sums.txt") diff --git a/util/buildbot/sha256sums.txt b/util/buildbot/sha256sums.txt index 310ab6a6e..21f30dddb 100644 --- a/util/buildbot/sha256sums.txt +++ b/util/buildbot/sha256sums.txt @@ -1,5 +1,5 @@ -627d4111ee655a68e806251974ba9d0337efac19cb07d499689c44c328a23775 curl-8.9.1-win32.zip -ed906726531388441d7f93fc0a1c9d567d476fbc8cfbae19dc5a0f7288949abe curl-8.9.1-win64.zip +b54e6252a822524a13f65bb9dcb8b8328242843f4a26e36b8273f32b78241bec curl-8.12.1-win32.zip +38501597e88a507e5e146aff2f76dfab012753d57064b98cdcaca5bac1ee9c42 curl-8.12.1-win64.zip 7a94b9e69d4872489228ad7cca6a16117a433f809d9b20fa3e44e1616a33c5d7 freetype-2.13.3-win32.zip f7d882319790f72ebc8eff00526388432bd26bff3a56c4ef5cce0a829bbbef0d freetype-2.13.3-win64.zip 41b10766de2773f0f0851fde16b363024685e0397f4bb2e5cd2a7be196960a01 gettext-0.20.2-win32.zip @@ -10,20 +10,20 @@ f54e9a577e2db47ed28f4a01e74181d2c607627c551d30f48263e01b59e84f67 libleveldb-1.2 2f039848a4e6c05a2347fe5a7fa63c430dd08d1bc88235645a863c859e14f5f8 libleveldb-1.23-win64.zip 0df94afb8efa361cceb132ecf9491720afbc45ba844a7b1c94607295829b53ca libogg-1.3.5-win32.zip 5c4acb4c99429a04b5e69650719b2eb17616bf52837d2372a0f859952eebce48 libogg-1.3.5-win64.zip -fb61536bfce414fdecb30dfbdc8b26e87969ee30b420f5fb8542f7573a1c1d12 libpng-1.6.43-win32.zip -ccd0b8ecbaa07028067a99dd4314ec7799445f80a28ddc86fa3f6bf25700177b libpng-1.6.43-win64.zip +af26fc9c5db2f448ab0e2e6f5a64c21a0ede3ff6939f88d6f3080d92c9b294fc libpng-1.6.47-win32.zip +dd92e80e505671f18a7a32151301ac429bf4f36d3a42478138b35a2a37e15cec libpng-1.6.47-win64.zip 456ece10a2be4247b27fbe88f88ddd54aae604736a6b76ba9a922b602fe40f40 libvorbis-1.3.7-win32.zip 57f4db02da00556895bb63fafa6e46b5f7dac87c25fde27af4315f56a1aa7a86 libvorbis-1.3.7-win64.zip -27d33157cc252c29ad6f777a96a0d94176fea1b534ff09b5071485def143b90e llvm-mingw-20240619-ucrt-ubuntu-20.04-x86_64.tar.xz -5380bbb0bfd4482b5774e4f7c0ff58cc0857477b88a88a178316a464d3459cf1 luajit-20240905-win32.zip -5805c75c61bf948f790e6f845adc94a4946e43ab8a78c5b5b441550f8a665d2c luajit-20240905-win64.zip -e2443451fe5c2066eb564c64b8a1762738a88b7fd749c8b5907fed45c785497b openal-soft-1.23.1-win32.zip -cb041445a118469caefbad2647470cb8571c8337bce2adc07634011ab5625417 openal-soft-1.23.1-win64.zip -af09a54f1f5d75ef6e1bf63662489ca57d44b6b522446638afe35e59b8456a3c sdl2-2.30.7-win32.zip -613abc34a84ed2c3b050314b340ba7e675879e8ed7848e6a28cd9c50262a33b0 sdl2-2.30.7-win64.zip +b2557a7089d006711247c66aa9c402165e89646eaadf01715d36157db0b66b01 llvm-mingw-20250305-ucrt-ubuntu-20.04-x86_64.tar.xz +ffec9687b581c9be380bb910249540deb2c027528cf489f0962e67116e078dee luajit-20250113-win32.zip +68d694631289416ae493253ed5d20e0ea2cd8ba1ca12153e2fd458dcd3dc0724 luajit-20250113-win64.zip +8365b9632219e8d6c624bde3001010c1a9e283f9b4c9090bd79ab00a46110d6f openal-soft-1.24.2-win32.zip +00309f225c697e9e1fe93aa1b5051b144c851c33edc70d534f7d4c401fda60d1 openal-soft-1.24.2-win64.zip +88205a0e2950e519ff0a4ef0a4e91c17a6089acbf317648b32154b7f76022547 sdl2-2.32.2-win32.zip +1a52ef059fc541c2ff3be5bd19b4cfaa0a8bb3e2549ebdb7ef48f5bdab19a6b6 sdl2-2.32.2-win64.zip 9685857ae0b418068ad4324e3711121bda97488d19235a0e68a6060162e014d7 sqlite3-3.46.1-win32.zip 7e2990619b1fd1d5ed654d1df77ea809d4332c2e914ea8bba53b2cf5acdf10ff sqlite3-3.46.1-win64.zip 8af10515d57dbfee5d2106cd66cafa2adeb4270d4c6047ccbf7e8b5d2d50681c zlib-1.3.1-win32.zip ad43f5d23052590c65633530743e5d622cc76b33c109072e6fd7b487aff56bca zlib-1.3.1-win64.zip -e1bd36f6da039ee8c1694509f379a5023c05d6c90905a2cbb424f0395167570a zstd-1.5.6-win32.zip -f65b75b04b00f6bda859a7c60667f735c664a893bf7796b38393c16cc40a1a82 zstd-1.5.6-win64.zip +07d295a4f2d727e9d2c406023e17238919b0c42b8442cfb11fe259ac5778e263 zstd-1.5.7-win32.zip +73886e5307ab236628a712abed5357649a92e895caacde8601cf51323e61fc5b zstd-1.5.7-win64.zip From afb15978d9c87011004a734d96c553646ebb0861 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 11 Mar 2025 20:00:07 +0100 Subject: [PATCH 047/284] Clean up and compress some pre-join packets (#15881) --- builtin/game/misc_s.lua | 1 + games/devtest/mods/unittests/version.lua | 2 +- src/client/client.cpp | 1 - src/network/clientpackethandler.cpp | 66 +++++++----- src/network/networkpacket.cpp | 12 +++ src/network/networkpacket.h | 10 ++ src/network/networkprotocol.cpp | 5 +- src/network/networkprotocol.h | 68 ++++++------ src/server.cpp | 127 +++++++++++++++-------- src/server.h | 2 +- src/util/serialize.cpp | 61 +++++++++++ src/util/serialize.h | 7 ++ 12 files changed, 260 insertions(+), 102 deletions(-) diff --git a/builtin/game/misc_s.lua b/builtin/game/misc_s.lua index e64134e15..9433f74bb 100644 --- a/builtin/game/misc_s.lua +++ b/builtin/game/misc_s.lua @@ -129,6 +129,7 @@ core.protocol_versions = { ["5.9.1"] = 45, ["5.10.0"] = 46, ["5.11.0"] = 47, + ["5.12.0"] = 48, } setmetatable(core.protocol_versions, {__newindex = function() diff --git a/games/devtest/mods/unittests/version.lua b/games/devtest/mods/unittests/version.lua index baf4520a4..73b4e5a11 100644 --- a/games/devtest/mods/unittests/version.lua +++ b/games/devtest/mods/unittests/version.lua @@ -35,6 +35,6 @@ unittests.register("test_protocol_version", function(player) -- The protocol version the client and server agreed on must exist in the table. local match = table.key_value_swap(core.protocol_versions)[info.protocol_version] - assert(match ~= nil) print(string.format("client proto matched: %s sent: %s", match, info.version_string)) + assert(match ~= nil) end, {player = true}) diff --git a/src/client/client.cpp b/src/client/client.cpp index 08fd2a215..031bed8d7 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -902,7 +902,6 @@ void Client::request_media(const std::vector &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); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index fe490dd8c..bb1930d96 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -606,10 +606,6 @@ void Client::handleCommand_DeathScreenLegacy(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; @@ -619,9 +615,7 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) "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; + << problem << "!" << std::endl; return; } @@ -629,16 +623,36 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) // updating content definitions sanity_check(!m_mesh_update_manager->isRunning()); - for (u16 i = 0; i < num_files; i++) { + if (m_proto_ver >= 48) { + // compressed table of media names + std::vector names; + { + std::istringstream iss(pkt->readLongString(), std::ios::binary); + std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary); + decompressZstd(iss, ss); + names = deserializeString16Array(ss); + } + + // raw hash for each media file + for (auto &name : names) { + auto sha1_raw = pkt->readRawString(20); + m_media_downloader->addFile(name, sha1_raw); + } + } else { + u16 num_files; + *pkt >> num_files; + std::string name, sha1_base64; + for (u16 i = 0; i < num_files; i++) { + *pkt >> name >> sha1_base64; - *pkt >> name >> sha1_base64; - - std::string sha1_raw = base64_decode(sha1_base64); - m_media_downloader->addFile(name, sha1_raw); + std::string sha1_raw = base64_decode(sha1_base64); + m_media_downloader->addFile(name, sha1_raw); + } } { + // Remote media servers std::string str; *pkt >> str; @@ -657,18 +671,6 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) void Client::handleCommand_Media(NetworkPacket* pkt) { - /* - u16 command - u16 total number of file bunches - u16 index of this bunch - u32 number of files in this bunch - for each file { - u16 length of name - string name - u32 length of data - data - } - */ u16 num_bunches; u16 bunch_i; u32 num_files; @@ -695,6 +697,12 @@ void Client::handleCommand_Media(NetworkPacket* pkt) *pkt >> name; data = pkt->readLongString(); + if (m_proto_ver >= 48) { + std::istringstream iss(data, std::ios::binary); + std::ostringstream oss(std::ios::binary); + decompressZstd(iss, oss); + data = oss.str(); + } bool ok = false; if (init_phase) { @@ -729,7 +737,10 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt) // Decompress node definitions std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); - decompressZlib(tmp_is, tmp_os); + if (m_proto_ver >= 48) + decompressZstd(tmp_is, tmp_os); + else + decompressZlib(tmp_is, tmp_os); // Deserialize node definitions m_nodedef->deSerialize(tmp_os, m_proto_ver); @@ -748,7 +759,10 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt) // Decompress item definitions std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); - decompressZlib(tmp_is, tmp_os); + if (m_proto_ver >= 48) + decompressZstd(tmp_is, tmp_os); + else + decompressZlib(tmp_is, tmp_os); // Deserialize node definitions m_itemdef->deSerialize(tmp_os, m_proto_ver); diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index f736d877b..ca7c53f5f 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -69,6 +69,18 @@ void NetworkPacket::putRawString(const char* src, u32 len) m_read_offset += len; } +void NetworkPacket::readRawString(char *dst, u32 len) +{ + checkReadOffset(m_read_offset, len); + + if (len == 0) + return; + + memcpy(dst, &m_data[m_read_offset], len); + m_read_offset += len; +} + + NetworkPacket& NetworkPacket::operator>>(std::string& dst) { checkReadOffset(m_read_offset, 2); diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index 32d378f47..d5e687c68 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -51,6 +51,16 @@ public: putRawString(src.data(), src.size()); } + // Reads bytes from packet into string buffer + void readRawString(char *dst, u32 len); + std::string readRawString(u32 len) + { + std::string s; + s.resize(len); + readRawString(&s[0], len); + return s; + } + NetworkPacket &operator>>(std::string &dst); NetworkPacket &operator<<(std::string_view src); diff --git a/src/network/networkprotocol.cpp b/src/network/networkprotocol.cpp index 85096930f..d1cc245d7 100644 --- a/src/network/networkprotocol.cpp +++ b/src/network/networkprotocol.cpp @@ -62,10 +62,13 @@ PROTOCOL VERSION 47 Add particle blend mode "clip" [scheduled bump for 5.11.0] + PROTOCOL VERSION 48 + Add compression to some existing packets + [scheduled bump for 5.12.0] */ // Note: Also update core.protocol_versions in builtin when bumping -const u16 LATEST_PROTOCOL_VERSION = 47; +const u16 LATEST_PROTOCOL_VERSION = 48; // See also formspec [Version History] in doc/lua_api.md const u16 FORMSPEC_API_VERSION = 8; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index a0fa6b96d..152534cbe 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -33,24 +33,28 @@ enum ToClientCommand : u16 u32 supported auth methods std::string unused (used to be username) */ + TOCLIENT_AUTH_ACCEPT = 0x03, /* Message from server to accept auth. - v3s16 player's position + v3f(0,BS/2,0) floatToInt'd + v3f unused u64 map seed f1000 recommended send interval u32 : supported auth methods for sudo mode (where the user can change their password) */ + TOCLIENT_ACCEPT_SUDO_MODE = 0x04, /* Sent to client to show it is in sudo mode now. */ + TOCLIENT_DENY_SUDO_MODE = 0x05, /* Signals client that sudo mode auth failed. */ + TOCLIENT_ACCESS_DENIED = 0x0A, /* u8 reason @@ -59,18 +63,26 @@ enum ToClientCommand : u16 */ TOCLIENT_BLOCKDATA = 0x20, + /* + v3s16 position + serialized MapBlock + */ + TOCLIENT_ADDNODE = 0x21, /* v3s16 position serialized mapnode - u8 keep_metadata // Added in protocol version 22 + u8 keep_metadata */ + TOCLIENT_REMOVENODE = 0x22, + /* + v3s16 position + */ TOCLIENT_INVENTORY = 0x27, /* - [0] u16 command - [2] serialized inventory + serialized inventory */ TOCLIENT_TIME_OF_DAY = 0x29, @@ -167,40 +179,38 @@ enum ToClientCommand : u16 TOCLIENT_MEDIA = 0x38, /* - u16 total number of texture bunches + u16 total number of bunches u16 index of this bunch u32 number of files in this bunch for each file { u16 length of name string name u32 length of data - data + data (zstd-compressed) } - u16 length of remote media server url (if applicable) - string url */ TOCLIENT_NODEDEF = 0x3a, /* - u32 length of the next item - serialized NodeDefManager + u32 length of buffer + serialized NodeDefManager (zstd-compressed) */ TOCLIENT_ANNOUNCE_MEDIA = 0x3c, /* - u32 number of files - for each texture { - u16 length of name - string name - u16 length of sha1_digest - string sha1_digest + u32 length of compressed name array + string16array names (zstd-compressed) + for each file { + char[20] sha1_digest } + u16 length of remote media server url + string url */ TOCLIENT_ITEMDEF = 0x3d, /* - u32 length of next item - serialized ItemDefManager + u32 length of buffer + serialized ItemDefManager (zstd-compressed) */ TOCLIENT_PLAY_SOUND = 0x3f, @@ -721,18 +731,16 @@ enum ToServerCommand : u16 TOSERVER_PLAYERPOS = 0x23, /* - [0] u16 command - [2] v3s32 position*100 - [2+12] v3s32 speed*100 - [2+12+12] s32 pitch*100 - [2+12+12+4] s32 yaw*100 - [2+12+12+4+4] u32 keyPressed - [2+12+12+4+4+4] u8 fov*80 - [2+12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) - [2+12+12+4+4+4+1+1] u8 camera_inverted (bool) - [2+12+12+4+4+4+1+1+1] f32 movement_speed - [2+12+12+4+4+4+1+1+1+4] f32 movement_direction - + v3s32 position*100 + v3s32 speed*100 + s32 pitch*100 + s32 yaw*100 + u32 keyPressed + u8 fov*80 + u8 ceil(wanted_range / MAP_BLOCKSIZE) + u8 camera_inverted (bool) + f32 movement_speed + f32 movement_direction */ TOSERVER_GOTBLOCKS = 0x24, diff --git a/src/server.cpp b/src/server.cpp index 16611843f..af6fed3a0 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1487,17 +1487,20 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, void Server::SendItemDef(session_t peer_id, IItemDefManager *itemdef, u16 protocol_version) { + auto *client = m_clients.getClientNoEx(peer_id, CS_Created); + assert(client); + NetworkPacket pkt(TOCLIENT_ITEMDEF, 0, peer_id); - /* - u16 command - u32 length of the next item - zlib-compressed serialized ItemDefManager - */ - std::ostringstream tmp_os(std::ios::binary); - itemdef->serialize(tmp_os, protocol_version); std::ostringstream tmp_os2(std::ios::binary); - compressZlib(tmp_os.str(), tmp_os2); + { + std::ostringstream tmp_os(std::ios::binary); + itemdef->serialize(tmp_os, protocol_version); + if (client->net_proto_version >= 48) + compressZstd(tmp_os.str(), tmp_os2); + else + compressZlib(tmp_os.str(), tmp_os2); + } pkt.putLongString(tmp_os2.str()); // Make data buffer @@ -1510,18 +1513,20 @@ void Server::SendItemDef(session_t peer_id, void Server::SendNodeDef(session_t peer_id, const NodeDefManager *nodedef, u16 protocol_version) { + auto *client = m_clients.getClientNoEx(peer_id, CS_Created); + assert(client); + NetworkPacket pkt(TOCLIENT_NODEDEF, 0, peer_id); - /* - u16 command - u32 length of the next item - zlib-compressed serialized NodeDefManager - */ - std::ostringstream tmp_os(std::ios::binary); - nodedef->serialize(tmp_os, protocol_version); std::ostringstream tmp_os2(std::ios::binary); - compressZlib(tmp_os.str(), tmp_os2); - + { + std::ostringstream tmp_os(std::ios::binary); + nodedef->serialize(tmp_os, protocol_version); + if (client->net_proto_version >= 48) + compressZstd(tmp_os.str(), tmp_os2); + else + compressZlib(tmp_os.str(), tmp_os2); + } pkt.putLongString(tmp_os2.str()); // Make data buffer @@ -2583,13 +2588,12 @@ bool Server::addMediaFile(const std::string &filename, } std::string sha1 = hashing::sha1(filedata); - std::string sha1_base64 = base64_encode(sha1); std::string sha1_hex = hex_encode(sha1); if (digest_to) *digest_to = sha1; // Put in list - m_media[filename] = MediaInfo(filepath, sha1_base64); + m_media[filename] = MediaInfo(filepath, sha1); verbosestream << "Server: " << sha1_hex << " is " << filename << std::endl; @@ -2651,20 +2655,48 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co }; // Make packet + auto *client = m_clients.getClientNoEx(peer_id, CS_Created); + assert(client); NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id); - u16 media_sent = 0; - for (const auto &i : m_media) { - if (include(i.first, i.second)) - media_sent++; - } - pkt << media_sent; + size_t media_sent = 0; + if (client->net_proto_version < 48) { + for (const auto &i : m_media) { + if (include(i.first, i.second)) + media_sent++; + } + assert(media_sent < U16_MAX); + pkt << static_cast(media_sent); + for (const auto &i : m_media) { + if (include(i.first, i.second)) + pkt << i.first << base64_encode(i.second.sha1_digest); + } + } else { + std::vector names; + for (const auto &i : m_media) { + if (include(i.first, i.second)) + names.emplace_back(i.first); + } + media_sent = names.size(); - for (const auto &i : m_media) { - if (include(i.first, i.second)) - pkt << i.first << i.second.sha1_digest; + // compressed table of media names + { + std::ostringstream oss(std::ios::binary); + auto tmp = serializeString16Array(names); + compressZstd(tmp, oss); + pkt.putLongString(oss.str()); + } + + // then the raw hash for each file + for (const auto &i : m_media) { + if (include(i.first, i.second)) { + assert(i.second.sha1_digest.size() == 20); + pkt.putRawString(i.second.sha1_digest); + } + } } + // and the remote media server(s) pkt << g_settings->get("remote_media"); Send(&pkt); @@ -2694,8 +2726,11 @@ void Server::sendRequestedMedia(session_t peer_id, auto *client = getClient(peer_id, CS_DefinitionsSent); assert(client); + const bool compress = client->net_proto_version >= 48; + infostream << "Server::sendRequestedMedia(): Sending " - << tosend.size() << " files to " << client->getName() << std::endl; + << tosend.size() << " files to " << client->getName() + << (compress ? " (compressed)" : "") << std::endl; /* Read files and prepare bunches */ @@ -2713,6 +2748,7 @@ void Server::sendRequestedMedia(session_t peer_id, // the amount of bunches quite well (at the expense of overshooting). u32 file_size_bunch_total = 0; + size_t bytes_compressed = 0, bytes_uncompressed = 0; for (const std::string &name : tosend) { auto it = m_media.find(name); @@ -2739,9 +2775,19 @@ void Server::sendRequestedMedia(session_t peer_id, if (!fs::ReadFile(m.path, data, true)) { continue; } - file_size_bunch_total += data.size(); + bytes_uncompressed += data.size(); + if (compress) { + // Zstd is very fast and can handle non-compressible data efficiently + // so we can just throw it at every file. Still we don't want to + // spend too much here, so we use the lowest compression level. + std::ostringstream oss(std::ios::binary); + compressZstd(data, oss, 1); + data = oss.str(); + } + bytes_compressed += data.size(); // Put in list + file_size_bunch_total += data.size(); file_bunches.back().emplace_back(name, m.path, std::move(data)); // Start next bunch if got enough data @@ -2756,17 +2802,6 @@ void Server::sendRequestedMedia(session_t peer_id, const u16 num_bunches = file_bunches.size(); for (u16 i = 0; i < num_bunches; i++) { auto &bunch = file_bunches[i]; - /* - u16 total number of media bunches - u16 index of this bunch - u32 number of files in this bunch - for each file { - u16 length of name - string name - u32 length of data - data - } - */ NetworkPacket pkt(TOCLIENT_MEDIA, 4 + 0, peer_id); const u32 bunch_size = bunch.size(); @@ -2784,6 +2819,14 @@ void Server::sendRequestedMedia(session_t peer_id, << " size=" << pkt.getSize() << std::endl; Send(&pkt); } + + if (compress && bytes_uncompressed != 0) { + int percent = bytes_compressed / (float)bytes_uncompressed * 100; + int diff = (int)bytes_compressed - (int)bytes_uncompressed; + infostream << "Server::sendRequestedMedia(): size after compression " + << percent << "% (" << (diff > 0 ? '+' : '-') << std::abs(diff) + << " byte)" << std::endl; + } } void Server::stepPendingDynMediaCallbacks(float dtime) @@ -4210,7 +4253,7 @@ std::unordered_map Server::getMediaList() for (auto &it : m_media) { if (it.second.no_announce) continue; - ret.emplace(base64_decode(it.second.sha1_digest), it.second.path); + ret.emplace(it.second.sha1_digest, it.second.path); } return ret; } diff --git a/src/server.h b/src/server.h index 74f192195..c9869e1dd 100644 --- a/src/server.h +++ b/src/server.h @@ -90,7 +90,7 @@ enum ClientDeletionReason { struct MediaInfo { std::string path; - std::string sha1_digest; // base64-encoded + std::string sha1_digest; // true = not announced in TOCLIENT_ANNOUNCE_MEDIA (at player join) bool no_announce; // does what it says. used by some cases of dynamic media. diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 257991008..e7a002662 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -105,6 +105,67 @@ std::string deSerializeString32(std::istream &is) return s; } +//// +//// String Array +//// + +std::string serializeString16Array(const std::vector &array) +{ + std::string ret; + const auto &at = [&] (size_t index) { + return reinterpret_cast(&ret[index]); + }; + + if (array.size() > U32_MAX) + throw SerializationError("serializeString16Array: too many strings"); + ret.resize(4 + array.size() * 2); + writeU32(at(0), array.size()); + + // Serialize lengths next to each other + size_t total = 0; + for (u32 i = 0; i < array.size(); i++) { + auto &s = array[i]; + if (s.size() > STRING_MAX_LEN) + throw SerializationError("serializeString16Array: string too long"); + writeU16(at(4 + 2*i), s.size()); + total += s.size(); + } + + // Now the contents + ret.reserve(ret.size() + total); + for (auto &s : array) + ret.append(s); + + return ret; +} + +std::vector deserializeString16Array(std::istream &is) +{ + std::vector ret; + + u32 count = readU32(is); + if (is.gcount() != 4) + throw SerializationError("deserializeString16Array: count not read"); + ret.resize(count); + + // prepare string buffers as we read the sizes + for (auto &sbuf : ret) { + u16 size = readU16(is); + if (is.gcount() != 2) + throw SerializationError("deserializeString16Array: size not read"); + sbuf.resize(size); + } + + // now extract the strings + for (auto &sbuf : ret) { + is.read(sbuf.data(), sbuf.size()); + if (is.gcount() != (std::streamsize) sbuf.size()) + throw SerializationError("deserializeString16Array: truncated"); + } + + return ret; +} + //// //// JSON-like strings //// diff --git a/src/util/serialize.h b/src/util/serialize.h index 8247eeb3e..c2cfa601d 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -469,3 +469,10 @@ std::string serializeJsonStringIfNeeded(std::string_view s); // Parses a string serialized by serializeJsonStringIfNeeded. std::string deSerializeJsonStringIfNeeded(std::istream &is); + +// Serializes an array of strings (max 2^16 chars each) +// Output is well suited for compression :) +std::string serializeString16Array(const std::vector &array); + +// Deserializes a string array +std::vector deserializeString16Array(std::istream &is); From 23d0fb2d3fa63464eba7e0646d3b753328b283d3 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 11 Mar 2025 20:00:35 +0100 Subject: [PATCH 048/284] builtin: Return 'obj' from 'core.item_drop' (#15880) This also includes a minor bugfix where 'itemstack' was cleared even if the object placement failed. --- builtin/game/item.lua | 7 +++---- doc/lua_api.md | 9 +++++++-- games/devtest/mods/unittests/entity.lua | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/builtin/game/item.lua b/builtin/game/item.lua index cc9be44af..5dd5312aa 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -360,13 +360,12 @@ end function core.item_drop(itemstack, dropper, pos) local dropper_is_player = dropper and dropper:is_player() local p = table.copy(pos) - local cnt = itemstack:get_count() if dropper_is_player then p.y = p.y + 1.2 end - local item = itemstack:take_item(cnt) - local obj = core.add_item(p, item) + local obj = core.add_item(p, ItemStack(itemstack)) if obj then + itemstack:clear() if dropper_is_player then local dir = dropper:get_look_dir() dir.x = dir.x * 2.9 @@ -375,7 +374,7 @@ function core.item_drop(itemstack, dropper, pos) obj:set_velocity(dir) obj:get_luaentity().dropped_by = dropper:get_player_name() end - return itemstack + return itemstack, obj end -- If we reach this, adding the object to the -- environment failed diff --git a/doc/lua_api.md b/doc/lua_api.md index 7f4d69021..facb20556 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6937,8 +6937,13 @@ Defaults for the `on_place` and `on_drop` item definition functions * Parameters are the same as in `on_pickup`. * Returns the leftover itemstack. * `core.item_drop(itemstack, dropper, pos)` - * Drop the item - * returns the leftover itemstack + * Converts `itemstack` to an in-world Lua entity. + * `itemstack` (`ItemStack`) is modified (cleared) on success. + * In versions < 5.12.0, `itemstack` was cleared in all cases. + * `dropper` (`ObjectRef`) is optional. + * Returned values on success: + 1. leftover itemstack + 2. `ObjectRef` of the spawned object (provided since 5.12.0) * `core.item_eat(hp_change[, replace_with_item])` * Returns `function(itemstack, user, pointed_thing)` as a function wrapper for `core.do_item_eat`. diff --git a/games/devtest/mods/unittests/entity.lua b/games/devtest/mods/unittests/entity.lua index af91a2a94..fad7d52e9 100644 --- a/games/devtest/mods/unittests/entity.lua +++ b/games/devtest/mods/unittests/entity.lua @@ -234,3 +234,24 @@ local function test_get_bone_rot(_, pos) end end unittests.register("test_get_bone_rot", test_get_bone_rot, {map=true}) + +--------- + +-- Spawn an entity from an ItemStack +local function test_item_drop(_, pos) + local itemstack_src, itemstack_ret, obj + + -- Try to place something that does not exist (placement fails) + itemstack_src = ItemStack("n_np_solution 1") + itemstack_ret, obj = core.item_drop(itemstack_src, nil, pos) + assert(obj == nil) + assert(itemstack_ret == nil) + + -- Test known item (placement successful) + itemstack_src = ItemStack("testnodes:normal 69") + itemstack_ret, obj = core.item_drop(itemstack_src, nil, pos) + assert(obj:get_hp() ~= nil) + assert(itemstack_ret and itemstack_ret:is_empty()) + assert(itemstack_ret:equals(itemstack_src)) +end +unittests.register("test_item_drop", test_item_drop, {map=true}) From 8717c7bd00a9f4c13c6277a7c6e1bd5b3c8fd529 Mon Sep 17 00:00:00 2001 From: "Miguel P.L" <99091580+MiguelPL4@users.noreply.github.com> Date: Tue, 11 Mar 2025 13:00:58 -0600 Subject: [PATCH 049/284] Fix excessive space on README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed149687c..33340053a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Translation status License -

+
Luanti is a free open-source voxel game engine with easy modding and game creation. From 077828d0d93e257b6de2b880d1965057a510b94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:52:42 +0100 Subject: [PATCH 050/284] Add `table.copy_with_metatables` (#15754) --- .luacheckrc | 2 +- builtin/common/misc_helpers.lua | 37 ++++++++++++++++------ builtin/common/tests/misc_helpers_spec.lua | 29 +++++++++++++++++ doc/lua_api.md | 5 +++ 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 82c10fcd3..8121f6f53 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -22,7 +22,7 @@ read_globals = { "PerlinNoise", "PerlinNoiseMap", string = {fields = {"split", "trim"}}, - table = {fields = {"copy", "getn", "indexof", "keyof", "insert_all"}}, + table = {fields = {"copy", "copy_with_metatables", "getn", "indexof", "keyof", "insert_all"}}, math = {fields = {"hypot", "round"}}, } diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index ce4179f54..47e0aeabc 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -457,18 +457,37 @@ do end end --------------------------------------------------------------------------------- -function table.copy(t, seen) - local n = {} - seen = seen or {} - seen[t] = n - for k, v in pairs(t) do - n[(type(k) == "table" and (seen[k] or table.copy(k, seen))) or k] = - (type(v) == "table" and (seen[v] or table.copy(v, seen))) or v + +local function table_copy(value, preserve_metatables) + local seen = {} + local function copy(val) + if type(val) ~= "table" then + return val + end + local t = val + if seen[t] then + return seen[t] + end + local res = {} + seen[t] = res + for k, v in pairs(t) do + res[copy(k)] = copy(v) + end + if preserve_metatables then + setmetatable(res, getmetatable(t)) + end + return res end - return n + return copy(value) end +function table.copy(value) + return table_copy(value, false) +end + +function table.copy_with_metatables(value) + return table_copy(value, true) +end function table.insert_all(t, other) if table.move then -- LuaJIT diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index 10e2bf277..d24fb0c8f 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -178,6 +178,35 @@ describe("table", function() assert.equal(2, table.keyof({[2] = "foo", [3] = "bar"}, "foo")) assert.equal(3, table.keyof({[1] = "foo", [3] = "bar"}, "bar")) end) + + describe("copy()", function() + it("strips metatables", function() + local v = vector.new(1, 2, 3) + local w = table.copy(v) + assert.are_not.equal(v, w) + assert.same(v, w) + assert.equal(nil, getmetatable(w)) + end) + it("preserves referential structure", function() + local t = {{}, {}} + t[1][1] = t[2] + t[2][1] = t[1] + local copy = table.copy(t) + assert.same(t, copy) + assert.equal(copy[1][1], copy[2]) + assert.equal(copy[2][1], copy[1]) + end) + end) + + describe("copy_with_metatables()", function() + it("preserves metatables", function() + local v = vector.new(1, 2, 3) + local w = table.copy_with_metatables(v) + assert.equal(getmetatable(v), getmetatable(w)) + assert(vector.check(w)) + assert.equal(v, w) -- vector overrides == + end) + end) end) describe("formspec_escape", function() diff --git a/doc/lua_api.md b/doc/lua_api.md index facb20556..ec10458a7 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -4142,6 +4142,11 @@ Helper functions * returns time with microsecond precision. May not return wall time. * `table.copy(table)`: returns a table * returns a deep copy of `table` + * strips metatables, but this may change in the future +* `table.copy_with_metatables(table)` + * since 5.12 + * `table` can also be non-table value, which will be returned as-is + * preserves metatables as they are * `table.indexof(list, val)`: returns the smallest numerical index containing the value `val` in the table `list`. Non-numerical indices are ignored. If `val` could not be found, `-1` is returned. `list` must not have From 4b85062cafd6929639f0cf727223b48be041cd03 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 12 Mar 2025 19:46:12 +0100 Subject: [PATCH 051/284] Improve robustness of GL object handling --- irr/src/COpenGLCoreCacheHandler.h | 4 +++- irr/src/COpenGLCoreRenderTarget.h | 8 +++++++- irr/src/COpenGLCoreTexture.h | 10 ++++++++++ irr/src/OpenGL/Driver.cpp | 3 +++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/irr/src/COpenGLCoreCacheHandler.h b/irr/src/COpenGLCoreCacheHandler.h index 744511629..bf588aa8a 100644 --- a/irr/src/COpenGLCoreCacheHandler.h +++ b/irr/src/COpenGLCoreCacheHandler.h @@ -100,7 +100,9 @@ class COpenGLCoreCacheHandler GL.Enable(curTextureType); #endif - GL.BindTexture(curTextureType, static_cast(texture)->getOpenGLTextureName()); + auto name = static_cast(texture)->getOpenGLTextureName(); + _IRR_DEBUG_BREAK_IF(name == 0) + GL.BindTexture(curTextureType, name); } else { texture = 0; diff --git a/irr/src/COpenGLCoreRenderTarget.h b/irr/src/COpenGLCoreRenderTarget.h index 50656ce1f..96eacc207 100644 --- a/irr/src/COpenGLCoreRenderTarget.h +++ b/irr/src/COpenGLCoreRenderTarget.h @@ -35,8 +35,14 @@ public: ColorAttachment = Driver->getFeature().ColorAttachment; MultipleRenderTarget = Driver->getFeature().MultipleRenderTarget; - if (ColorAttachment > 0) + if (ColorAttachment > 0) { + TEST_GL_ERROR(Driver); Driver->irrGlGenFramebuffers(1, &BufferID); + if (!BufferID) { + os::Printer::log("COpenGLCoreRenderTarget: framebuffer not created", ELL_ERROR); + return; + } + } AssignedTextures.set_used(static_cast(ColorAttachment)); diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 63551cc0a..1b02c9234 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -99,6 +99,11 @@ public: } GL.GenTextures(1, &TextureName); + TEST_GL_ERROR(Driver); + if (!TextureName) { + os::Printer::log("COpenGLCoreTexture: texture not created", ELL_ERROR); + return; + } const COpenGLCoreTexture *prevTexture = Driver->getCacheHandler()->getTextureCache().get(0); Driver->getCacheHandler()->getTextureCache().set(0, this); @@ -195,6 +200,11 @@ public: #endif GL.GenTextures(1, &TextureName); + TEST_GL_ERROR(Driver); + if (!TextureName) { + os::Printer::log("COpenGLCoreTexture: texture not created", ELL_ERROR); + return; + } const COpenGLCoreTexture *prevTexture = Driver->getCacheHandler()->getTextureCache().get(0); Driver->getCacheHandler()->getTextureCache().set(0, this); diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 25c9a14f6..2f6a3ebdf 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -212,6 +212,7 @@ void COpenGL3DriverBase::initQuadsIndices(u32 max_vertex_count) } QuadIndexVBO.upload(QuadsIndices.data(), QuadsIndices.size() * sizeof(u16), 0, GL_STATIC_DRAW, true); + assert(QuadIndexVBO.exists()); } void COpenGL3DriverBase::initVersion() @@ -626,6 +627,7 @@ void COpenGL3DriverBase::drawBuffers(const scene::IVertexBuffer *vb, const void *vertices = vb->getData(); if (hwvert) { assert(hwvert->IsVertex); + assert(hwvert->Vbo.exists()); GL.BindBuffer(GL_ARRAY_BUFFER, hwvert->Vbo.getName()); vertices = nullptr; } @@ -633,6 +635,7 @@ void COpenGL3DriverBase::drawBuffers(const scene::IVertexBuffer *vb, const void *indexList = ib->getData(); if (hwidx) { assert(!hwidx->IsVertex); + assert(hwidx->Vbo.exists()); GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, hwidx->Vbo.getName()); indexList = nullptr; } From c07499ccfc5622f7cdb08e69e63b61327d9242c1 Mon Sep 17 00:00:00 2001 From: Deve Date: Sun, 16 Mar 2025 17:55:39 +0100 Subject: [PATCH 052/284] Reload font manager in main thread to avoid a crash (#15900) --- src/client/fontengine.cpp | 16 ++++++++++++---- src/client/fontengine.h | 8 ++++++++ src/client/game.cpp | 2 ++ src/gui/guiEngine.cpp | 2 ++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index a8c6d4b3e..c807d5011 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -18,10 +18,9 @@ /** reference to access font engine, has to be initialized by main */ FontEngine *g_fontengine = nullptr; -/** callback to be used on change of font size setting */ -static void font_setting_changed(const std::string &name, void *userdata) +void FontEngine::fontSettingChanged(const std::string &name, void *userdata) { - static_cast(userdata)->readSettings(); + ((FontEngine *)userdata)->m_needs_reload = true; } static const char *settings[] = { @@ -49,7 +48,7 @@ FontEngine::FontEngine(gui::IGUIEnvironment* env) : readSettings(); for (auto name : settings) - g_settings->registerChangedCallback(name, font_setting_changed, this); + g_settings->registerChangedCallback(name, fontSettingChanged, this); } FontEngine::~FontEngine() @@ -162,6 +161,15 @@ void FontEngine::readSettings() refresh(); } +void FontEngine::handleReload() +{ + if (!m_needs_reload) + return; + + m_needs_reload = false; + readSettings(); +} + void FontEngine::updateSkin() { gui::IGUIFont *font = getFont(); diff --git a/src/client/fontengine.h b/src/client/fontengine.h index 70713034f..0ed81f678 100644 --- a/src/client/fontengine.h +++ b/src/client/fontengine.h @@ -121,6 +121,9 @@ public: /** update internal parameters from settings */ void readSettings(); + /** reload fonts if settings were changed */ + void handleReload(); + void setMediaFont(const std::string &name, const std::string &data); void clearMediaFonts(); @@ -142,6 +145,9 @@ private: /** refresh after fonts have been changed */ void refresh(); + /** callback to be used on change of font size setting */ + static void fontSettingChanged(const std::string &name, void *userdata); + /** pointer to irrlicht gui environment */ gui::IGUIEnvironment* m_env = nullptr; @@ -164,6 +170,8 @@ private: /** default font engine mode (fixed) */ static const FontMode m_currentMode = FM_Standard; + bool m_needs_reload = false; + DISABLE_CLASS_COPY(FontEngine); }; diff --git a/src/client/game.cpp b/src/client/game.cpp index c2513fd9b..fa9beadbd 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -970,6 +970,8 @@ void Game::run() framemarker.start(); + g_fontengine->handleReload(); + const auto current_dynamic_info = ClientDynamicInfo::getCurrent(); if (!current_dynamic_info.equal(client_display_info)) { client_display_info = current_dynamic_info; diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 9402b4770..a83a913ec 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -337,6 +337,8 @@ void GUIEngine::run() fps_control.limit(device, &dtime); framemarker.start(); + g_fontengine->handleReload(); + if (device->isWindowVisible()) { // check if we need to update the "upper left corner"-text if (text_height != g_fontengine->getTextHeight()) { From 42ac5b2f406078a8f53d1b3f4335d7de94ca1b21 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 16 Mar 2025 17:56:32 +0100 Subject: [PATCH 053/284] Mostly deal with problems caused by polygon offset (#15867) --- src/client/tile.cpp | 13 +++++++++---- src/client/tile.h | 17 ++++++++++++++--- src/nodedef.cpp | 2 ++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/client/tile.cpp b/src/client/tile.cpp index b13d39056..84281f84d 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -21,11 +21,16 @@ void TileLayer::applyMaterialOptions(video::SMaterial &material, int layer) cons /* * The second layer is for overlays, but uses the same vertex positions * as the first, which easily leads to Z-fighting. - * To fix this we can offset the polygons in the direction of the camera. + * To fix this we offset the polygons of the *first layer* away from the camera. * This only affects the depth buffer and leads to no visual gaps in geometry. + * + * However, doing so intrudes the "Z space" of the overlay of the next node + * so that leads to inconsistent Z-sorting again. :( + * HACK: For lack of a better approach we restrict this to cases where + * an overlay is actually present. */ - if (layer == 1) { - material.PolygonOffsetSlopeScale = -1; - material.PolygonOffsetDepthBias = -1; + if (need_polygon_offset) { + material.PolygonOffsetSlopeScale = 1; + material.PolygonOffsetDepthBias = 1; } } diff --git a/src/client/tile.h b/src/client/tile.h index 8336d1d85..420f0757f 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -73,7 +73,8 @@ struct TileLayer material_flags == other.material_flags && has_color == other.has_color && color == other.color && - scale == other.scale; + scale == other.scale && + need_polygon_offset == other.need_polygon_offset; } /*! @@ -92,6 +93,12 @@ struct TileLayer */ void applyMaterialOptions(video::SMaterial &material, int layer) const; + /// @return is this layer uninitalized? + bool empty() const + { + return !shader_id && !texture_id; + } + /// @return is this layer semi-transparent? bool isTransparent() const { @@ -125,6 +132,12 @@ struct TileLayer MATERIAL_FLAG_TILEABLE_HORIZONTAL| MATERIAL_FLAG_TILEABLE_VERTICAL; + u8 scale = 1; + + /// does this tile need to have a positive polygon offset set? + /// @see TileLayer::applyMaterialOptions + bool need_polygon_offset = false; + /// @note not owned by this struct std::vector *frames = nullptr; @@ -136,8 +149,6 @@ struct TileLayer //! If true, the tile has its own color. bool has_color = false; - - u8 scale = 1; }; enum class TileRotation: u8 { diff --git a/src/nodedef.cpp b/src/nodedef.cpp index ffc5503b1..2fc3264bc 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -919,6 +919,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], tdef_overlay[j], color, overlay_material, overlay_shader, tdef[j].backface_culling, tsettings); + + tiles[j].layers[0].need_polygon_offset = !tiles[j].layers[1].empty(); } MaterialType special_material = material_type; From c439d784ac106b565f496bb95d9205bf93896b8b Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Sat, 1 Mar 2025 18:27:57 +0100 Subject: [PATCH 054/284] add unit tests for map block position encoding --- src/unittest/test_mapdatabase.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/unittest/test_mapdatabase.cpp b/src/unittest/test_mapdatabase.cpp index f206285a8..c6aa7d051 100644 --- a/src/unittest/test_mapdatabase.cpp +++ b/src/unittest/test_mapdatabase.cpp @@ -72,6 +72,7 @@ public: void testLoad(); void testList(int expect); void testRemove(); + void testPositionEncoding(); private: MapDatabaseProvider *provider = nullptr; @@ -90,6 +91,8 @@ void TestMapDatabase::runTests(IGameDef *gamedef) test_data.push_back(static_cast(c)); sanity_check(!test_data.empty()); + TEST(testPositionEncoding); + rawstream << "-------- Dummy" << std::endl; // We can't re-create this object since it would lose the data @@ -193,3 +196,31 @@ void TestMapDatabase::testRemove() // FIXME: this isn't working consistently, maybe later //UASSERT(!db->deleteBlock({1, 2, 4})); } + +void TestMapDatabase::testPositionEncoding() +{ + auto db = std::make_unique(); + + // Unit vectors and extremes + UASSERTEQ(s64, db->getBlockAsInteger({0, 0, 0}), 0) + UASSERTEQ(s64, db->getBlockAsInteger({1, 0, 0}), 1) + UASSERTEQ(s64, db->getBlockAsInteger({0, 1, 0}), 0x1000) + UASSERTEQ(s64, db->getBlockAsInteger({0, 0, 1}), 0x1000000) + UASSERTEQ(s64, db->getBlockAsInteger({-1, 0, 0}), -1) + UASSERTEQ(s64, db->getBlockAsInteger({0, -1, 0}), -0x1000) + UASSERTEQ(s64, db->getBlockAsInteger({0, 0, -1}), -0x1000000) + UASSERTEQ(s64, db->getBlockAsInteger({2047, 2047, 2047}), 0x7FF7FF7FF) + UASSERTEQ(s64, db->getBlockAsInteger({-2048, -2048, -2048}), -0x800800800) + UASSERTEQ(s64, db->getBlockAsInteger({-123, 456, -789}), -0x314e3807b) + + UASSERT(db->getIntegerAsBlock(0) == v3s16(0, 0, 0)) + UASSERT(db->getIntegerAsBlock(1) == v3s16(1, 0, 0)) + UASSERT(db->getIntegerAsBlock(0x1000) == v3s16(0, 1, 0)) + UASSERT(db->getIntegerAsBlock(0x1000000) == v3s16(0, 0, 1)) + UASSERT(db->getIntegerAsBlock(-1) == v3s16(-1, 0, 0)) + UASSERT(db->getIntegerAsBlock(-0x1000) == v3s16(0, -1, 0)) + UASSERT(db->getIntegerAsBlock(-0x1000000) == v3s16(0, 0, -1)) + UASSERT(db->getIntegerAsBlock(0x7FF7FF7FF) == v3s16(2047, 2047, 2047)) + UASSERT(db->getIntegerAsBlock(-0x800800800) == v3s16(-2048, -2048, -2048)) + UASSERT(db->getIntegerAsBlock(-0x314e3807b) == v3s16(-123, 456, -789)) +} From 1f3cf59c7f94fc8e0a0ffd2ecc635699164923a3 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Tue, 25 Feb 2025 23:11:43 +0100 Subject: [PATCH 055/284] Clean up position encoding We can simply add 0x800800800 to the encoding, then use bit masking. This works because adding 0x800 maps -2048:2047 to 0x000:0xFFF. And 0x800800800 is (0x800 << 24 + 0x800 << 12 + 0x800) for x,y,z. After bitmasking, -0x800 restores the original value range. --- src/database/database.cpp | 47 +++++++++------------------------------ 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index 9182bbe95..9d3550432 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -7,48 +7,23 @@ /**************** - * Black magic! * - **************** - * The position hashing is very messed up. - * It's a lot more complicated than it looks. + * The position encoding is a bit messed up because negative + * values were not taken into account. + * But this also maps 0,0,0 to 0, which is nice, and we mostly + * need forward encoding in Luanti. */ - -static inline s16 unsigned_to_signed(u16 i, u16 max_positive) -{ - if (i < max_positive) { - return i; - } - - return i - (max_positive * 2); -} - - -// Modulo of a negative number does not work consistently in C -static inline s64 pythonmodulo(s64 i, s16 mod) -{ - if (i >= 0) { - return i % mod; - } - return mod - ((-i) % mod); -} - - s64 MapDatabase::getBlockAsInteger(const v3s16 &pos) { - return (u64) pos.Z * 0x1000000 + - (u64) pos.Y * 0x1000 + - (u64) pos.X; + return ((s64) pos.Z << 24) + ((s64) pos.Y << 12) + pos.X; } v3s16 MapDatabase::getIntegerAsBlock(s64 i) { - v3s16 pos; - pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048); - i = (i - pos.X) / 4096; - pos.Y = unsigned_to_signed(pythonmodulo(i, 4096), 2048); - i = (i - pos.Y) / 4096; - pos.Z = unsigned_to_signed(pythonmodulo(i, 4096), 2048); - return pos; + // Offset so that all negative coordinates become non-negative + i = i + 0x800800800; + // Which is now easier to decode using simple bit masks: + return { (s16)( (i & 0xFFF) - 0x800), + (s16)(((i >> 12) & 0xFFF) - 0x800), + (s16)(((i >> 24) & 0xFFF) - 0x800) }; } - From 8ac7c451e15f63667ea04d39646e803da0a0a460 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Sat, 1 Mar 2025 19:31:28 +0100 Subject: [PATCH 056/284] update documentation --- doc/world_format.md | 57 ++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/doc/world_format.md b/doc/world_format.md index a23319bdc..c519361d9 100644 --- a/doc/world_format.md +++ b/doc/world_format.md @@ -298,41 +298,44 @@ Before 5.12.0 it looked like this: CREATE TABLE `blocks` (`pos` INT NOT NULL PRIMARY KEY, `data` BLOB); ``` -## Position Hashing +## Position Encoding Applies to the pre-5.12.0 schema: -`pos` (a node position hash) is created from the three coordinates of a -`MapBlock` using this algorithm, defined here in Python: +`pos` (a node position encoding) is created from the three coordinates of a +`MapBlock` using the following simple equation: -```python -def getBlockAsInteger(p): - return int64(p[2]*16777216 + p[1]*4096 + p[0]) +```C +pos = (z << 24) + (y << 12) + x; +``` +or, equivalently, `pos = (z * 0x1000000) + (y * 0x1000) + x`. -def int64(u): - while u >= 2**63: - u -= 2**64 - while u <= -2**63: - u += 2**64 - return u +A position can be decoded using: + +```C +pos = pos + 0x800800800; +x = (pos & 0xFFF) - 0x800; +y = ((pos >> 12) & 0xFFF) - 0x800; +z = ((pos >> 24) & 0xFFF) - 0x800; ``` -It can be converted the other way by using this code: +Positions are sequential along the x axis (as easily seen from the position equation above). +It is possible to retrieve all blocks from an interval using the following SQL statement: -```python -def getIntegerAsBlock(i): - x = unsignedToSigned(i % 4096, 2048) - i = int((i - x) / 4096) - y = unsignedToSigned(i % 4096, 2048) - i = int((i - y) / 4096) - z = unsignedToSigned(i % 4096, 2048) - return x,y,z - -def unsignedToSigned(i, max_positive): - if i < max_positive: - return i - else: - return i - 2*max_positive +```sql +SELECT +`pos`, +`data`, +( (`pos` + 0x800800800) & 0xFFF) - 0x800 as x, +(((`pos` + 0x800800800) >> 12) & 0xFFF) - 0x800 as y, +(((`pos` + 0x800800800) >> 24) & 0xFFF) - 0x800 as z +FROM `blocks` WHERE +( (`pos` + 0x800800800) & 0xFFF) - 0x800 >= ? AND -- minx +( (`pos` + 0x800800800) & 0xFFF) - 0x800 <= ? AND -- maxx +(((`pos` + 0x800800800) >> 12) & 0xFFF) - 0x800 >= ? AND -- miny +(((`pos` + 0x800800800) >> 12) & 0xFFF) - 0x800 <= ? AND -- maxy +`pos` >= (? << 24) - 0x800800 AND -- minz +`pos` <= (? << 24) + 0x7FF7FF; -- maxz ``` ## Blob From efded8f0bb941132623065a71c66f40f32612add Mon Sep 17 00:00:00 2001 From: AFCMS Date: Sun, 16 Mar 2025 17:57:18 +0100 Subject: [PATCH 057/284] Bump OARS content rating to 1.1 --- misc/net.minetest.minetest.metainfo.xml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/misc/net.minetest.minetest.metainfo.xml b/misc/net.minetest.minetest.metainfo.xml index 8fc7f6197..59778bfd4 100644 --- a/misc/net.minetest.minetest.metainfo.xml +++ b/misc/net.minetest.minetest.metainfo.xml @@ -25,11 +25,30 @@ 360 - + mild mild + none + none + none + none + none + none + none + none + none + none + none + none + none intense mild + none + none + none + none + none + none From d085f0fb52206771b4d929f1fccc9c66139fbf35 Mon Sep 17 00:00:00 2001 From: Xiaochuan Ye Date: Mon, 17 Mar 2025 03:02:42 +0800 Subject: [PATCH 058/284] Document core.MAP_BLOCKSIZE constant in lua_api.md (#15911) --- doc/lua_api.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index ec10458a7..e1fb93a4d 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -1681,7 +1681,9 @@ roughly 1x1x1 meters in size. A 'mapblock' (often abbreviated to 'block') is 16x16x16 nodes and is the fundamental region of a world that is stored in the world database, sent to -clients and handled by many parts of the engine. +clients and handled by many parts of the engine. This size is defined by the constant +`core.MAP_BLOCKSIZE` (=16). + 'mapblock' is preferred terminology to 'block' to help avoid confusion with 'node', however 'block' often appears in the API. @@ -1713,7 +1715,7 @@ node position (0,0,0) to node position (15,15,15). To calculate the blockpos of the mapblock that contains the node at 'nodepos', for each axis: -* blockpos = math.floor(nodepos / 16) +* blockpos = math.floor(nodepos / core.MAP_BLOCKSIZE) #### Converting blockpos to min/max node positions @@ -1721,9 +1723,9 @@ To calculate the min/max node positions contained in the mapblock at 'blockpos', for each axis: * Minimum: - nodepos = blockpos * 16 + nodepos = blockpos * core.MAP_BLOCKSIZE * Maximum: - nodepos = blockpos * 16 + 15 + nodepos = (blockpos + 1) * core.MAP_BLOCKSIZE - 1 From e0378737b7806cdfa227b8c6614f1fbfc71764ff Mon Sep 17 00:00:00 2001 From: cx384 Date: Sun, 16 Mar 2025 20:03:31 +0100 Subject: [PATCH 059/284] Fix overrideable hand ToolCapabilities and range (#15743) --- src/client/clientobject.h | 4 ++-- src/client/content_cao.cpp | 4 ++-- src/client/content_cao.h | 4 ++-- src/client/game.cpp | 25 +++++++++++++------------ src/inventory.h | 25 +++++++++++++++++-------- src/network/serverpackethandler.cpp | 12 ++++++------ 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/client/clientobject.h b/src/client/clientobject.h index 95f926928..4002a8181 100644 --- a/src/client/clientobject.h +++ b/src/client/clientobject.h @@ -75,8 +75,8 @@ public: Client *client, ClientEnvironment *env); // If returns true, punch will not be sent to the server - virtual bool directReportPunch(v3f dir, const ItemStack *punchitem = nullptr, - float time_from_last_punch = 1000000) { return false; } + virtual bool directReportPunch(v3f dir, const ItemStack *punchitem, + const ItemStack *hand_item, float time_from_last_punch = 1000000) { return false; } protected: // Used for creating objects based on type diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index cdea7d4fd..df7404140 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1944,11 +1944,11 @@ void GenericCAO::processMessage(const std::string &data) /* \pre punchitem != NULL */ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, - float time_from_last_punch) + const ItemStack *hand_item, float time_from_last_punch) { assert(punchitem); // pre-condition const ToolCapabilities *toolcap = - &punchitem->getToolCapabilities(m_client->idef()); + &punchitem->getToolCapabilities(m_client->idef(), hand_item); PunchDamageResult result = getPunchDamage( m_armor_groups, toolcap, diff --git a/src/client/content_cao.h b/src/client/content_cao.h index fe804eeab..9762684f6 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -290,8 +290,8 @@ public: void processMessage(const std::string &data) override; - bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL, - float time_from_last_punch=1000000) override; + bool directReportPunch(v3f dir, const ItemStack *punchitem, + const ItemStack *hand_item, float time_from_last_punch=1000000) override; std::string debugInfoText() override; diff --git a/src/client/game.cpp b/src/client/game.cpp index fa9beadbd..4e7ec9cec 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -592,7 +592,7 @@ protected: void handlePointingAtNode(const PointedThing &pointed, const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, - const v3f &player_position, bool show_debug); + const ItemStack &hand_item, const v3f &player_position, bool show_debug); void handleDigging(const PointedThing &pointed, const v3s16 &nodepos, const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, @@ -2953,14 +2953,14 @@ void Game::updateCamera(f32 dtime) LocalPlayer *player = env.getLocalPlayer(); // For interaction purposes, get info about the held item - ItemStack playeritem; + ItemStack playeritem, hand; { - ItemStack selected, hand; + ItemStack selected; playeritem = player->getWieldedItem(&selected, &hand); } ToolCapabilities playeritem_toolcap = - playeritem.getToolCapabilities(itemdef_manager); + playeritem.getToolCapabilities(itemdef_manager, &hand); float full_punch_interval = playeritem_toolcap.full_punch_interval; float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval; @@ -3067,8 +3067,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) ItemStack selected_item, hand_item; const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item); - const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager); - f32 d = getToolRange(selected_item, hand_item, itemdef_manager); + const ItemDefinition &selected_def = tool_item.getDefinition(itemdef_manager); + f32 d = getToolRange(tool_item, hand_item, itemdef_manager); core::line3d shootline; @@ -3185,7 +3185,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) } else if (pointed.type == POINTEDTHING_OBJECT) { v3f player_position = player->getPosition(); bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); - handlePointingAtObject(pointed, tool_item, player_position, + handlePointingAtObject(pointed, tool_item, hand_item, player_position, m_game_ui->m_flags.show_basic_debug && basic_debug_allowed); } else if (isKeyDown(KeyType::DIG)) { // When button is held down in air, show continuous animation @@ -3599,8 +3599,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, } } -void Game::handlePointingAtObject(const PointedThing &pointed, - const ItemStack &tool_item, const v3f &player_position, bool show_debug) +void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &tool_item, + const ItemStack &hand_item, const v3f &player_position, bool show_debug) { std::wstring infotext = unescape_translate( utf8_to_wide(runData.selected_object->infoText())); @@ -3638,7 +3638,7 @@ void Game::handlePointingAtObject(const PointedThing &pointed, v3f dir = (objpos - player_position).normalize(); bool disable_send = runData.selected_object->directReportPunch( - dir, &tool_item, runData.time_from_last_punch); + dir, &tool_item, &hand_item, runData.time_from_last_punch); runData.time_from_last_punch = 0; if (!disable_send) @@ -3659,13 +3659,14 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, ClientMap &map = client->getEnv().getClientMap(); MapNode n = map.getNode(nodepos); const auto &features = nodedef_manager->get(n); + const ItemStack &tool_item = selected_item.name.empty() ? hand_item : selected_item; // NOTE: Similar piece of code exists on the server side for // cheat detection. // Get digging parameters DigParams params = getDigParams(features.groups, - &selected_item.getToolCapabilities(itemdef_manager), - selected_item.wear); + &tool_item.getToolCapabilities(itemdef_manager, &hand_item), + tool_item.wear); // If can't dig, try hand if (!params.diggable) { diff --git a/src/inventory.h b/src/inventory.h index 0da131013..a20d4bfc7 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -102,18 +102,27 @@ struct ItemStack } // Get tool digging properties, or those of the hand if not a tool + // If not hand assumes default hand "" const ToolCapabilities& getToolCapabilities( - const IItemDefManager *itemdef) const + const IItemDefManager *itemdef, const ItemStack *hand = nullptr) 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 - item_cap = itemdef->get("").tool_capabilities; + if (item_cap) { + return metadata.getToolCapabilities(*item_cap); // Check for override + } - assert(item_cap != NULL); - return metadata.getToolCapabilities(*item_cap); // Check for override + // Fall back to the hand's tool capabilities + if (hand) { + item_cap = itemdef->get(hand->name).tool_capabilities; + if (item_cap) { + return hand->metadata.getToolCapabilities(*item_cap); + } + } + + item_cap = itemdef->get("").tool_capabilities; + assert(item_cap); + return *item_cap; } const std::optional &getWearBarParams( diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 17ba8ca21..d7adfac38 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -834,8 +834,8 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) 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, hand_item, m_itemdef); + const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item); + f32 max_d = BS * getToolRange(tool_item, hand_item, m_itemdef); // Cube diagonal * 1.5 for maximal supported node extents: // sqrt(3) * 1.5 ≅ 2.6 @@ -1036,7 +1036,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) ItemStack selected_item, hand_item; ItemStack tool_item = playersao->getWieldedItem(&selected_item, &hand_item); ToolCapabilities toolcap = - tool_item.getToolCapabilities(m_itemdef); + tool_item.getToolCapabilities(m_itemdef, &hand_item); v3f dir = (pointed_object->getBasePosition() - (playersao->getBasePosition() + playersao->getEyeOffset()) ).normalize(); @@ -1093,12 +1093,12 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Get player's wielded item // See also: Game::handleDigging ItemStack selected_item, hand_item; - player->getWieldedItem(&selected_item, &hand_item); + ItemStack &tool_item = player->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.wear); + &tool_item.getToolCapabilities(m_itemdef, &hand_item), + tool_item.wear); // If can't dig, try hand if (!params.diggable) { params = getDigParams(m_nodedef->get(n).groups, From cc65c8bd70f50781314c04427d54c0d2f22cc862 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Sun, 16 Mar 2025 20:35:34 +0100 Subject: [PATCH 060/284] SDL: Use scancodes for keybindings (#14964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> Co-authored-by: sfan5 Co-authored-by: SmallJoker --- irr/include/IrrlichtDevice.h | 22 +++ irr/include/Keycodes.h | 27 ++++ irr/src/CIrrDeviceSDL.cpp | 283 +++++++++++++++++++--------------- irr/src/CIrrDeviceSDL.h | 25 +-- src/client/inputhandler.cpp | 2 +- src/client/keycode.cpp | 236 ++++++++++++++++------------ src/client/keycode.h | 83 ++++++---- src/defaultsettings.cpp | 112 +++++++------- src/gui/guiKeyChangeMenu.cpp | 5 +- src/gui/modalMenu.cpp | 13 +- src/gui/touchcontrols.cpp | 57 ++++--- src/gui/touchcontrols.h | 5 +- src/unittest/test_keycode.cpp | 8 + 13 files changed, 509 insertions(+), 369 deletions(-) diff --git a/irr/include/IrrlichtDevice.h b/irr/include/IrrlichtDevice.h index edc6ead61..6ae21a212 100644 --- a/irr/include/IrrlichtDevice.h +++ b/irr/include/IrrlichtDevice.h @@ -16,6 +16,7 @@ #include "IrrCompileConfig.h" #include "position2d.h" #include "SColor.h" // video::ECOLOR_FORMAT +#include namespace irr { @@ -342,6 +343,27 @@ public: { return video::isDriverSupported(driver); } + + //! Get the corresponding scancode for the keycode. + /** + \param key The keycode to convert. + \return The implementation-dependent scancode for the key (represented by the u32 component) or, if a scancode is not + available, the corresponding Irrlicht keycode (represented by the EKEY_CODE component). + */ + virtual std::variant getScancodeFromKey(const Keycode &key) const { + if (auto pv = std::get_if(&key)) + return *pv; + return (u32)std::get(key); + } + + //! Get the corresponding keycode for the scancode. + /** + \param scancode The implementation-dependent scancode for the key. + \return The corresponding keycode. + */ + virtual Keycode getKeyFromScancode(const u32 scancode) const { + return Keycode(KEY_UNKNOWN, (wchar_t)scancode); + } }; } // end namespace irr diff --git a/irr/include/Keycodes.h b/irr/include/Keycodes.h index cdc90d198..a6a0a5dae 100644 --- a/irr/include/Keycodes.h +++ b/irr/include/Keycodes.h @@ -3,6 +3,7 @@ // For conditions of distribution and use, see copyright notice in irrlicht.h #pragma once +#include namespace irr { @@ -182,4 +183,30 @@ enum EKEY_CODE KEY_KEY_CODES_COUNT = 0x100 // this is not a key, but the amount of keycodes there are. }; +// A Keycode is either a character produced by the key or one of Irrlicht's codes (EKEY_CODE) +class Keycode : public std::variant { + using super = std::variant; +public: + Keycode() : Keycode(KEY_KEY_CODES_COUNT, L'\0') {} + + Keycode(EKEY_CODE code, wchar_t ch) + { + emplace(code, ch); + } + + using super::emplace; + void emplace(EKEY_CODE code, wchar_t ch) + { + if (isValid(code)) + emplace(code); + else + emplace(ch); + } + + static bool isValid(EKEY_CODE code) + { + return code > 0 && code < KEY_KEY_CODES_COUNT; + } +}; + } // end namespace irr diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index 6c6b2c00f..536eda96e 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -27,6 +27,21 @@ #include "CSDLManager.h" +// Since SDL doesn't have mouse keys as keycodes we need to fall back to EKEY_CODE in some cases. +static inline bool is_fake_key(irr::EKEY_CODE key) { + switch (key) { + case irr::KEY_LBUTTON: + case irr::KEY_MBUTTON: + case irr::KEY_RBUTTON: + case irr::KEY_XBUTTON1: + case irr::KEY_XBUTTON2: + return true; + + default: + return false; + } +} + static int SDLDeviceInstances = 0; namespace irr @@ -220,6 +235,35 @@ int CIrrDeviceSDL::findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtK } } +std::variant CIrrDeviceSDL::getScancodeFromKey(const Keycode &key) const +{ + u32 keynum = 0; + if (const auto *keycode = std::get_if(&key)) { + // Fake keys (e.g. mouse buttons): use EKEY_CODE since there is no corresponding scancode. + if (is_fake_key(*keycode)) + return *keycode; + // Try to convert the EKEY_CODE to a SDL scancode. + for (const auto &entry: KeyMap) { + if (entry.second == *keycode) { + keynum = entry.first; + break; + } + } + } else { + keynum = std::get(key); + } + return (u32)SDL_GetScancodeFromKey(keynum); +} + +Keycode CIrrDeviceSDL::getKeyFromScancode(const u32 scancode) const +{ + auto keycode = SDL_GetKeyFromScancode((SDL_Scancode)scancode); + const auto &keyentry = KeyMap.find(keycode); + auto irrcode = keyentry != KeyMap.end() ? keyentry->second : KEY_UNKNOWN; + auto keychar = findCharToPassToIrrlicht(keycode, irrcode, false); + return Keycode(irrcode, keychar); +} + void CIrrDeviceSDL::resetReceiveTextInputEvents() { gui::IGUIElement *elem = GUIEnvironment->getFocus(); @@ -822,18 +866,20 @@ bool CIrrDeviceSDL::run() case SDL_KEYDOWN: case SDL_KEYUP: { - SKeyMap mp; - mp.SDLKey = SDL_event.key.keysym.sym; - s32 idx = KeyMap.binary_search(mp); + auto keysym = SDL_event.key.keysym.sym; + auto scancode = SDL_event.key.keysym.scancode; - EKEY_CODE key; - if (idx == -1) - key = (EKEY_CODE)0; - else - key = (EKEY_CODE)KeyMap[idx].Win32Key; + // Treat AC_BACK as the Escape key + if (scancode == SDL_SCANCODE_AC_BACK) { + scancode = SDL_SCANCODE_ESCAPE; + keysym = SDLK_ESCAPE; + } - if (key == (EKEY_CODE)0) - os::Printer::log("keycode not mapped", core::stringc(mp.SDLKey), ELL_DEBUG); + const auto &entry = KeyMap.find(keysym); + auto key = entry == KeyMap.end() ? KEY_UNKNOWN : entry->second; + + if (!Keycode::isValid(key)) + os::Printer::log("keycode not mapped", core::stringc(keysym), ELL_DEBUG); // Make sure to only input special characters if something is in focus, as SDL_TEXTINPUT handles normal unicode already if (SDL_IsTextInputActive() && !keyIsKnownSpecial(key) && (SDL_event.key.keysym.mod & KMOD_CTRL) == 0) @@ -844,8 +890,10 @@ bool CIrrDeviceSDL::run() irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN); irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0; irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0; - irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key, + irrevent.KeyInput.Char = findCharToPassToIrrlicht(keysym, key, (SDL_event.key.keysym.mod & KMOD_NUM) != 0); + irrevent.KeyInput.SystemKeyCode = scancode; + postEventFromUser(irrevent); } break; @@ -1310,142 +1358,135 @@ void CIrrDeviceSDL::createKeyMap() // the lookuptable, but I'll leave it like that until // I find a better version. - KeyMap.reallocate(105); - // buttons missing - // Android back button = ESC - KeyMap.push_back(SKeyMap(SDLK_AC_BACK, KEY_ESCAPE)); - - KeyMap.push_back(SKeyMap(SDLK_BACKSPACE, KEY_BACK)); - KeyMap.push_back(SKeyMap(SDLK_TAB, KEY_TAB)); - KeyMap.push_back(SKeyMap(SDLK_CLEAR, KEY_CLEAR)); - KeyMap.push_back(SKeyMap(SDLK_RETURN, KEY_RETURN)); + KeyMap.emplace(SDLK_BACKSPACE, KEY_BACK); + KeyMap.emplace(SDLK_TAB, KEY_TAB); + KeyMap.emplace(SDLK_CLEAR, KEY_CLEAR); + KeyMap.emplace(SDLK_RETURN, KEY_RETURN); // combined modifiers missing - KeyMap.push_back(SKeyMap(SDLK_PAUSE, KEY_PAUSE)); - KeyMap.push_back(SKeyMap(SDLK_CAPSLOCK, KEY_CAPITAL)); + KeyMap.emplace(SDLK_PAUSE, KEY_PAUSE); + KeyMap.emplace(SDLK_CAPSLOCK, KEY_CAPITAL); // asian letter keys missing - KeyMap.push_back(SKeyMap(SDLK_ESCAPE, KEY_ESCAPE)); + KeyMap.emplace(SDLK_ESCAPE, KEY_ESCAPE); // asian letter keys missing - KeyMap.push_back(SKeyMap(SDLK_SPACE, KEY_SPACE)); - KeyMap.push_back(SKeyMap(SDLK_PAGEUP, KEY_PRIOR)); - KeyMap.push_back(SKeyMap(SDLK_PAGEDOWN, KEY_NEXT)); - KeyMap.push_back(SKeyMap(SDLK_END, KEY_END)); - KeyMap.push_back(SKeyMap(SDLK_HOME, KEY_HOME)); - KeyMap.push_back(SKeyMap(SDLK_LEFT, KEY_LEFT)); - KeyMap.push_back(SKeyMap(SDLK_UP, KEY_UP)); - KeyMap.push_back(SKeyMap(SDLK_RIGHT, KEY_RIGHT)); - KeyMap.push_back(SKeyMap(SDLK_DOWN, KEY_DOWN)); + KeyMap.emplace(SDLK_SPACE, KEY_SPACE); + KeyMap.emplace(SDLK_PAGEUP, KEY_PRIOR); + KeyMap.emplace(SDLK_PAGEDOWN, KEY_NEXT); + KeyMap.emplace(SDLK_END, KEY_END); + KeyMap.emplace(SDLK_HOME, KEY_HOME); + KeyMap.emplace(SDLK_LEFT, KEY_LEFT); + KeyMap.emplace(SDLK_UP, KEY_UP); + KeyMap.emplace(SDLK_RIGHT, KEY_RIGHT); + KeyMap.emplace(SDLK_DOWN, KEY_DOWN); // select missing - KeyMap.push_back(SKeyMap(SDLK_PRINTSCREEN, KEY_PRINT)); + KeyMap.emplace(SDLK_PRINTSCREEN, KEY_PRINT); // execute missing - KeyMap.push_back(SKeyMap(SDLK_PRINTSCREEN, KEY_SNAPSHOT)); + KeyMap.emplace(SDLK_PRINTSCREEN, KEY_SNAPSHOT); - KeyMap.push_back(SKeyMap(SDLK_INSERT, KEY_INSERT)); - KeyMap.push_back(SKeyMap(SDLK_DELETE, KEY_DELETE)); - KeyMap.push_back(SKeyMap(SDLK_HELP, KEY_HELP)); + KeyMap.emplace(SDLK_INSERT, KEY_INSERT); + KeyMap.emplace(SDLK_DELETE, KEY_DELETE); + KeyMap.emplace(SDLK_HELP, KEY_HELP); - KeyMap.push_back(SKeyMap(SDLK_0, KEY_KEY_0)); - KeyMap.push_back(SKeyMap(SDLK_1, KEY_KEY_1)); - KeyMap.push_back(SKeyMap(SDLK_2, KEY_KEY_2)); - KeyMap.push_back(SKeyMap(SDLK_3, KEY_KEY_3)); - KeyMap.push_back(SKeyMap(SDLK_4, KEY_KEY_4)); - KeyMap.push_back(SKeyMap(SDLK_5, KEY_KEY_5)); - KeyMap.push_back(SKeyMap(SDLK_6, KEY_KEY_6)); - KeyMap.push_back(SKeyMap(SDLK_7, KEY_KEY_7)); - KeyMap.push_back(SKeyMap(SDLK_8, KEY_KEY_8)); - KeyMap.push_back(SKeyMap(SDLK_9, KEY_KEY_9)); + KeyMap.emplace(SDLK_0, KEY_KEY_0); + KeyMap.emplace(SDLK_1, KEY_KEY_1); + KeyMap.emplace(SDLK_2, KEY_KEY_2); + KeyMap.emplace(SDLK_3, KEY_KEY_3); + KeyMap.emplace(SDLK_4, KEY_KEY_4); + KeyMap.emplace(SDLK_5, KEY_KEY_5); + KeyMap.emplace(SDLK_6, KEY_KEY_6); + KeyMap.emplace(SDLK_7, KEY_KEY_7); + KeyMap.emplace(SDLK_8, KEY_KEY_8); + KeyMap.emplace(SDLK_9, KEY_KEY_9); - KeyMap.push_back(SKeyMap(SDLK_a, KEY_KEY_A)); - KeyMap.push_back(SKeyMap(SDLK_b, KEY_KEY_B)); - KeyMap.push_back(SKeyMap(SDLK_c, KEY_KEY_C)); - KeyMap.push_back(SKeyMap(SDLK_d, KEY_KEY_D)); - KeyMap.push_back(SKeyMap(SDLK_e, KEY_KEY_E)); - KeyMap.push_back(SKeyMap(SDLK_f, KEY_KEY_F)); - KeyMap.push_back(SKeyMap(SDLK_g, KEY_KEY_G)); - KeyMap.push_back(SKeyMap(SDLK_h, KEY_KEY_H)); - KeyMap.push_back(SKeyMap(SDLK_i, KEY_KEY_I)); - KeyMap.push_back(SKeyMap(SDLK_j, KEY_KEY_J)); - KeyMap.push_back(SKeyMap(SDLK_k, KEY_KEY_K)); - KeyMap.push_back(SKeyMap(SDLK_l, KEY_KEY_L)); - KeyMap.push_back(SKeyMap(SDLK_m, KEY_KEY_M)); - KeyMap.push_back(SKeyMap(SDLK_n, KEY_KEY_N)); - KeyMap.push_back(SKeyMap(SDLK_o, KEY_KEY_O)); - KeyMap.push_back(SKeyMap(SDLK_p, KEY_KEY_P)); - KeyMap.push_back(SKeyMap(SDLK_q, KEY_KEY_Q)); - KeyMap.push_back(SKeyMap(SDLK_r, KEY_KEY_R)); - KeyMap.push_back(SKeyMap(SDLK_s, KEY_KEY_S)); - KeyMap.push_back(SKeyMap(SDLK_t, KEY_KEY_T)); - KeyMap.push_back(SKeyMap(SDLK_u, KEY_KEY_U)); - KeyMap.push_back(SKeyMap(SDLK_v, KEY_KEY_V)); - KeyMap.push_back(SKeyMap(SDLK_w, KEY_KEY_W)); - KeyMap.push_back(SKeyMap(SDLK_x, KEY_KEY_X)); - KeyMap.push_back(SKeyMap(SDLK_y, KEY_KEY_Y)); - KeyMap.push_back(SKeyMap(SDLK_z, KEY_KEY_Z)); + KeyMap.emplace(SDLK_a, KEY_KEY_A); + KeyMap.emplace(SDLK_b, KEY_KEY_B); + KeyMap.emplace(SDLK_c, KEY_KEY_C); + KeyMap.emplace(SDLK_d, KEY_KEY_D); + KeyMap.emplace(SDLK_e, KEY_KEY_E); + KeyMap.emplace(SDLK_f, KEY_KEY_F); + KeyMap.emplace(SDLK_g, KEY_KEY_G); + KeyMap.emplace(SDLK_h, KEY_KEY_H); + KeyMap.emplace(SDLK_i, KEY_KEY_I); + KeyMap.emplace(SDLK_j, KEY_KEY_J); + KeyMap.emplace(SDLK_k, KEY_KEY_K); + KeyMap.emplace(SDLK_l, KEY_KEY_L); + KeyMap.emplace(SDLK_m, KEY_KEY_M); + KeyMap.emplace(SDLK_n, KEY_KEY_N); + KeyMap.emplace(SDLK_o, KEY_KEY_O); + KeyMap.emplace(SDLK_p, KEY_KEY_P); + KeyMap.emplace(SDLK_q, KEY_KEY_Q); + KeyMap.emplace(SDLK_r, KEY_KEY_R); + KeyMap.emplace(SDLK_s, KEY_KEY_S); + KeyMap.emplace(SDLK_t, KEY_KEY_T); + KeyMap.emplace(SDLK_u, KEY_KEY_U); + KeyMap.emplace(SDLK_v, KEY_KEY_V); + KeyMap.emplace(SDLK_w, KEY_KEY_W); + KeyMap.emplace(SDLK_x, KEY_KEY_X); + KeyMap.emplace(SDLK_y, KEY_KEY_Y); + KeyMap.emplace(SDLK_z, KEY_KEY_Z); - KeyMap.push_back(SKeyMap(SDLK_LGUI, KEY_LWIN)); - KeyMap.push_back(SKeyMap(SDLK_RGUI, KEY_RWIN)); + KeyMap.emplace(SDLK_LGUI, KEY_LWIN); + KeyMap.emplace(SDLK_RGUI, KEY_RWIN); // apps missing - KeyMap.push_back(SKeyMap(SDLK_POWER, KEY_SLEEP)); //?? + KeyMap.emplace(SDLK_POWER, KEY_SLEEP); //?? - KeyMap.push_back(SKeyMap(SDLK_KP_0, KEY_NUMPAD0)); - KeyMap.push_back(SKeyMap(SDLK_KP_1, KEY_NUMPAD1)); - KeyMap.push_back(SKeyMap(SDLK_KP_2, KEY_NUMPAD2)); - KeyMap.push_back(SKeyMap(SDLK_KP_3, KEY_NUMPAD3)); - KeyMap.push_back(SKeyMap(SDLK_KP_4, KEY_NUMPAD4)); - KeyMap.push_back(SKeyMap(SDLK_KP_5, KEY_NUMPAD5)); - KeyMap.push_back(SKeyMap(SDLK_KP_6, KEY_NUMPAD6)); - KeyMap.push_back(SKeyMap(SDLK_KP_7, KEY_NUMPAD7)); - KeyMap.push_back(SKeyMap(SDLK_KP_8, KEY_NUMPAD8)); - KeyMap.push_back(SKeyMap(SDLK_KP_9, KEY_NUMPAD9)); - KeyMap.push_back(SKeyMap(SDLK_KP_MULTIPLY, KEY_MULTIPLY)); - KeyMap.push_back(SKeyMap(SDLK_KP_PLUS, KEY_ADD)); - KeyMap.push_back(SKeyMap(SDLK_KP_ENTER, KEY_RETURN)); - KeyMap.push_back(SKeyMap(SDLK_KP_MINUS, KEY_SUBTRACT)); - KeyMap.push_back(SKeyMap(SDLK_KP_PERIOD, KEY_DECIMAL)); - KeyMap.push_back(SKeyMap(SDLK_KP_DIVIDE, KEY_DIVIDE)); + KeyMap.emplace(SDLK_KP_0, KEY_NUMPAD0); + KeyMap.emplace(SDLK_KP_1, KEY_NUMPAD1); + KeyMap.emplace(SDLK_KP_2, KEY_NUMPAD2); + KeyMap.emplace(SDLK_KP_3, KEY_NUMPAD3); + KeyMap.emplace(SDLK_KP_4, KEY_NUMPAD4); + KeyMap.emplace(SDLK_KP_5, KEY_NUMPAD5); + KeyMap.emplace(SDLK_KP_6, KEY_NUMPAD6); + KeyMap.emplace(SDLK_KP_7, KEY_NUMPAD7); + KeyMap.emplace(SDLK_KP_8, KEY_NUMPAD8); + KeyMap.emplace(SDLK_KP_9, KEY_NUMPAD9); + KeyMap.emplace(SDLK_KP_MULTIPLY, KEY_MULTIPLY); + KeyMap.emplace(SDLK_KP_PLUS, KEY_ADD); + KeyMap.emplace(SDLK_KP_ENTER, KEY_RETURN); + KeyMap.emplace(SDLK_KP_MINUS, KEY_SUBTRACT); + KeyMap.emplace(SDLK_KP_PERIOD, KEY_DECIMAL); + KeyMap.emplace(SDLK_KP_DIVIDE, KEY_DIVIDE); - KeyMap.push_back(SKeyMap(SDLK_F1, KEY_F1)); - KeyMap.push_back(SKeyMap(SDLK_F2, KEY_F2)); - KeyMap.push_back(SKeyMap(SDLK_F3, KEY_F3)); - KeyMap.push_back(SKeyMap(SDLK_F4, KEY_F4)); - KeyMap.push_back(SKeyMap(SDLK_F5, KEY_F5)); - KeyMap.push_back(SKeyMap(SDLK_F6, KEY_F6)); - KeyMap.push_back(SKeyMap(SDLK_F7, KEY_F7)); - KeyMap.push_back(SKeyMap(SDLK_F8, KEY_F8)); - KeyMap.push_back(SKeyMap(SDLK_F9, KEY_F9)); - KeyMap.push_back(SKeyMap(SDLK_F10, KEY_F10)); - KeyMap.push_back(SKeyMap(SDLK_F11, KEY_F11)); - KeyMap.push_back(SKeyMap(SDLK_F12, KEY_F12)); - KeyMap.push_back(SKeyMap(SDLK_F13, KEY_F13)); - KeyMap.push_back(SKeyMap(SDLK_F14, KEY_F14)); - KeyMap.push_back(SKeyMap(SDLK_F15, KEY_F15)); + KeyMap.emplace(SDLK_F1, KEY_F1); + KeyMap.emplace(SDLK_F2, KEY_F2); + KeyMap.emplace(SDLK_F3, KEY_F3); + KeyMap.emplace(SDLK_F4, KEY_F4); + KeyMap.emplace(SDLK_F5, KEY_F5); + KeyMap.emplace(SDLK_F6, KEY_F6); + KeyMap.emplace(SDLK_F7, KEY_F7); + KeyMap.emplace(SDLK_F8, KEY_F8); + KeyMap.emplace(SDLK_F9, KEY_F9); + KeyMap.emplace(SDLK_F10, KEY_F10); + KeyMap.emplace(SDLK_F11, KEY_F11); + KeyMap.emplace(SDLK_F12, KEY_F12); + KeyMap.emplace(SDLK_F13, KEY_F13); + KeyMap.emplace(SDLK_F14, KEY_F14); + KeyMap.emplace(SDLK_F15, KEY_F15); // no higher F-keys - KeyMap.push_back(SKeyMap(SDLK_NUMLOCKCLEAR, KEY_NUMLOCK)); - KeyMap.push_back(SKeyMap(SDLK_SCROLLLOCK, KEY_SCROLL)); - KeyMap.push_back(SKeyMap(SDLK_LSHIFT, KEY_LSHIFT)); - KeyMap.push_back(SKeyMap(SDLK_RSHIFT, KEY_RSHIFT)); - KeyMap.push_back(SKeyMap(SDLK_LCTRL, KEY_LCONTROL)); - KeyMap.push_back(SKeyMap(SDLK_RCTRL, KEY_RCONTROL)); - KeyMap.push_back(SKeyMap(SDLK_LALT, KEY_LMENU)); - KeyMap.push_back(SKeyMap(SDLK_RALT, KEY_RMENU)); + KeyMap.emplace(SDLK_NUMLOCKCLEAR, KEY_NUMLOCK); + KeyMap.emplace(SDLK_SCROLLLOCK, KEY_SCROLL); + KeyMap.emplace(SDLK_LSHIFT, KEY_LSHIFT); + KeyMap.emplace(SDLK_RSHIFT, KEY_RSHIFT); + KeyMap.emplace(SDLK_LCTRL, KEY_LCONTROL); + KeyMap.emplace(SDLK_RCTRL, KEY_RCONTROL); + KeyMap.emplace(SDLK_LALT, KEY_LMENU); + KeyMap.emplace(SDLK_RALT, KEY_RMENU); - KeyMap.push_back(SKeyMap(SDLK_PLUS, KEY_PLUS)); - KeyMap.push_back(SKeyMap(SDLK_COMMA, KEY_COMMA)); - KeyMap.push_back(SKeyMap(SDLK_MINUS, KEY_MINUS)); - KeyMap.push_back(SKeyMap(SDLK_PERIOD, KEY_PERIOD)); + KeyMap.emplace(SDLK_PLUS, KEY_PLUS); + KeyMap.emplace(SDLK_COMMA, KEY_COMMA); + KeyMap.emplace(SDLK_MINUS, KEY_MINUS); + KeyMap.emplace(SDLK_PERIOD, KEY_PERIOD); // some special keys missing - - KeyMap.sort(); } void CIrrDeviceSDL::CCursorControl::initCursors() diff --git a/irr/src/CIrrDeviceSDL.h b/irr/src/CIrrDeviceSDL.h index 4e7a53d9c..8a7e5f680 100644 --- a/irr/src/CIrrDeviceSDL.h +++ b/irr/src/CIrrDeviceSDL.h @@ -24,6 +24,7 @@ #include #include +#include namespace irr { @@ -286,6 +287,9 @@ private: // Return the Char that should be sent to Irrlicht for the given key (either the one passed in or 0). static int findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock); + std::variant getScancodeFromKey(const Keycode &key) const override; + Keycode getKeyFromScancode(const u32 scancode) const override; + // Check if a text box is in focus. Enable or disable SDL_TEXTINPUT events only if in focus. void resetReceiveTextInputEvents(); @@ -319,24 +323,9 @@ private: core::rect lastElemPos; - struct SKeyMap - { - SKeyMap() {} - SKeyMap(s32 x11, s32 win32) : - SDLKey(x11), Win32Key(win32) - { - } - - s32 SDLKey; - s32 Win32Key; - - bool operator<(const SKeyMap &o) const - { - return SDLKey < o.SDLKey; - } - }; - - core::array KeyMap; + // TODO: This is only used for scancode/keycode conversion with EKEY_CODE (among other things, for Luanti + // to display keys to users). Drop this along with EKEY_CODE. + std::unordered_map KeyMap; s32 CurrentTouchCount; bool IsInBackground; diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 88969f008..76019476e 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -143,7 +143,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 (keyCode && keysListenedFor[keyCode]) { // ignore key input that is invalid or irrelevant for the game. if (event.KeyInput.PressedDown) { if (!IsKeyDown(keyCode)) keyWasPressed.set(keyCode); diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp index 0a3b0db3f..18e250959 100644 --- a/src/client/keycode.cpp +++ b/src/client/keycode.cpp @@ -6,15 +6,18 @@ #include "settings.h" #include "log.h" #include "debug.h" +#include "renderingengine.h" #include "util/hex.h" #include "util/string.h" #include "util/basic_macros.h" +#include +#include struct table_key { - const char *Name; + std::string Name; // An EKEY_CODE 'symbol' name as a string irr::EKEY_CODE Key; wchar_t Char; // L'\0' means no character assigned - const char *LangName; // NULL means it doesn't have a human description + std::string LangName; // empty string means it doesn't have a human description }; #define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \ @@ -30,7 +33,7 @@ struct table_key { #define N_(text) text -static const struct table_key table[] = { +static std::vector table = { // Keys that can be reliably mapped between Char and Key DEFINEKEY3(0) DEFINEKEY3(1) @@ -126,7 +129,7 @@ static const struct table_key table[] = { DEFINEKEY1(KEY_ADD, N_("Numpad +")) DEFINEKEY1(KEY_SEPARATOR, N_("Numpad .")) DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -")) - DEFINEKEY1(KEY_DECIMAL, NULL) + DEFINEKEY1(KEY_DECIMAL, N_("Numpad .")) DEFINEKEY1(KEY_DIVIDE, N_("Numpad /")) DEFINEKEY4(1) DEFINEKEY4(2) @@ -221,122 +224,156 @@ static const struct table_key table[] = { DEFINEKEY5("_") }; +static const table_key invalid_key = {"", irr::KEY_UNKNOWN, L'\0', ""}; + #undef N_ -static const table_key &lookup_keyname(const char *name) -{ - for (const auto &table_key : table) { - if (strcmp(table_key.Name, name) == 0) - return table_key; - } - - throw UnknownKeycode(name); -} - -static const table_key &lookup_keykey(irr::EKEY_CODE key) -{ - for (const auto &table_key : table) { - if (table_key.Key == key) - return table_key; - } - - std::ostringstream os; - os << ""; - throw UnknownKeycode(os.str().c_str()); -} - static const table_key &lookup_keychar(wchar_t Char) { + if (Char == L'\0') + return invalid_key; + for (const auto &table_key : table) { if (table_key.Char == Char) return table_key; } - std::ostringstream os; - os << ""; - throw UnknownKeycode(os.str().c_str()); + // Create a new entry in the lookup table if one is not available. + auto newsym = wide_to_utf8(std::wstring_view(&Char, 1)); + table_key new_key {newsym, irr::KEY_KEY_CODES_COUNT, Char, newsym}; + return table.emplace_back(std::move(new_key)); } -KeyPress::KeyPress(const char *name) +static const table_key &lookup_keykey(irr::EKEY_CODE key) { - if (strlen(name) == 0) { - Key = irr::KEY_KEY_CODES_COUNT; - Char = L'\0'; - m_name = ""; + if (!Keycode::isValid(key)) + return invalid_key; + + for (const auto &table_key : table) { + if (table_key.Key == key) + return table_key; + } + + return invalid_key; +} + +static const table_key &lookup_keyname(std::string_view name) +{ + if (name.empty()) + return invalid_key; + + for (const auto &table_key : table) { + if (table_key.Name == name) + return table_key; + } + + auto wname = utf8_to_wide(name); + if (wname.empty()) + return invalid_key; + return lookup_keychar(wname[0]); +} + +static const table_key &lookup_scancode(const u32 scancode) +{ + auto key = RenderingEngine::get_raw_device()->getKeyFromScancode(scancode); + return std::holds_alternative(key) ? + lookup_keykey(std::get(key)) : + lookup_keychar(std::get(key)); +} + +static const table_key &lookup_scancode(const std::variant &scancode) +{ + return std::holds_alternative(scancode) ? + lookup_keykey(std::get(scancode)) : + lookup_scancode(std::get(scancode)); +} + +void KeyPress::loadFromKey(irr::EKEY_CODE keycode, wchar_t keychar) +{ + scancode = RenderingEngine::get_raw_device()->getScancodeFromKey(Keycode(keycode, keychar)); +} + +KeyPress::KeyPress(const std::string &name) +{ + if (loadFromScancode(name)) return; - } - - if (strlen(name) <= 4) { - // Lookup by resulting character - int chars_read = mbtowc(&Char, name, 1); - FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); - try { - auto &k = lookup_keychar(Char); - m_name = k.Name; - Key = k.Key; - return; - } catch (UnknownKeycode &e) {}; - } else { - // Lookup by name - m_name = name; - try { - auto &k = lookup_keyname(name); - Key = k.Key; - Char = k.Char; - return; - } catch (UnknownKeycode &e) {}; - } - - // It's not a known key, complain and try to do something - Key = irr::KEY_KEY_CODES_COUNT; - int chars_read = mbtowc(&Char, name, 1); - FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); - m_name = ""; - warningstream << "KeyPress: Unknown key '" << name - << "', falling back to first char." << std::endl; + const auto &key = lookup_keyname(name); + loadFromKey(key.Key, key.Char); } -KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character) +KeyPress::KeyPress(const irr::SEvent::SKeyInput &in) { - if (prefer_character) - Key = irr::KEY_KEY_CODES_COUNT; - else - Key = in.Key; - Char = in.Char; - - try { - if (valid_kcode(Key)) - m_name = lookup_keykey(Key).Name; + if (USE_SDL2) { + if (in.SystemKeyCode) + scancode.emplace(in.SystemKeyCode); else - m_name = lookup_keychar(Char).Name; - } catch (UnknownKeycode &e) { - m_name.clear(); - }; + scancode.emplace(in.Key); + } else { + loadFromKey(in.Key, in.Char); + } } -const char *KeyPress::sym() const +std::string KeyPress::formatScancode() const { - return m_name.c_str(); + if (USE_SDL2) { + if (auto pv = std::get_if(&scancode)) + return *pv == 0 ? "" : "SYSTEM_SCANCODE_" + std::to_string(*pv); + } + return ""; } -const char *KeyPress::name() const +std::string KeyPress::sym() const { - if (m_name.empty()) - return ""; - const char *ret; - if (valid_kcode(Key)) - ret = lookup_keykey(Key).LangName; - else - ret = lookup_keychar(Char).LangName; - return ret ? ret : ""; + std::string name = lookup_scancode(scancode).Name; + if (USE_SDL2 || name.empty()) + if (auto newname = formatScancode(); !newname.empty()) + return newname; + return name; } -const KeyPress EscapeKey("KEY_ESCAPE"); +std::string KeyPress::name() const +{ + const auto &name = lookup_scancode(scancode).LangName; + if (!name.empty()) + return name; + return formatScancode(); +} -const KeyPress LMBKey("KEY_LBUTTON"); -const KeyPress MMBKey("KEY_MBUTTON"); -const KeyPress RMBKey("KEY_RBUTTON"); +irr::EKEY_CODE KeyPress::getKeycode() const +{ + return lookup_scancode(scancode).Key; +} + +wchar_t KeyPress::getKeychar() const +{ + return lookup_scancode(scancode).Char; +} + +bool KeyPress::loadFromScancode(const std::string &name) +{ + if (USE_SDL2) { + if (!str_starts_with(name, "SYSTEM_SCANCODE_")) + return false; + char *p; + const auto code = strtoul(name.c_str()+16, &p, 10); + if (p != name.c_str() + name.size()) + return false; + scancode.emplace(code); + return true; + } else { + return false; + } +} + +std::unordered_map specialKeyCache; +const KeyPress &KeyPress::getSpecialKey(const std::string &name) +{ + auto &key = specialKeyCache[name]; + if (!key) + key = KeyPress(name); + return key; +} /* Key config @@ -345,14 +382,18 @@ const KeyPress RMBKey("KEY_RBUTTON"); // A simple cache for quicker lookup static std::unordered_map g_key_setting_cache; -const KeyPress &getKeySetting(const char *settingname) +const KeyPress &getKeySetting(const std::string &settingname) { auto n = g_key_setting_cache.find(settingname); if (n != g_key_setting_cache.end()) return n->second; + auto keysym = g_settings->get(settingname); auto &ref = g_key_setting_cache[settingname]; - ref = g_settings->get(settingname).c_str(); + ref = KeyPress(keysym); + if (!keysym.empty() && !ref) { + warningstream << "Invalid key '" << keysym << "' for '" << settingname << "'." << std::endl; + } return ref; } @@ -360,8 +401,3 @@ void clearKeyCache() { g_key_setting_cache.clear(); } - -irr::EKEY_CODE keyname_to_keycode(const char *name) -{ - return lookup_keyname(name).Key; -} diff --git a/src/client/keycode.h b/src/client/keycode.h index 4c63be7fa..a494ec930 100644 --- a/src/client/keycode.h +++ b/src/client/keycode.h @@ -4,62 +4,77 @@ #pragma once -#include "exceptions.h" #include "irrlichttypes.h" #include #include #include +#include -class UnknownKeycode : public BaseException -{ -public: - UnknownKeycode(const char *s) : - BaseException(s) {}; -}; - -/* A key press, consisting of either an Irrlicht keycode - or an actual char */ - +/* A key press, consisting of a scancode or a keycode */ class KeyPress { public: KeyPress() = default; - KeyPress(const char *name); + KeyPress(const std::string &name); - KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character = false); + KeyPress(const irr::SEvent::SKeyInput &in); - bool operator==(const KeyPress &o) const + // Get a string representation that is suitable for use in minetest.conf + std::string sym() const; + + // Get a human-readable string representation + std::string name() const; + + // Get the corresponding keycode or KEY_UNKNOWN if one is not available + irr::EKEY_CODE getKeycode() const; + + // Get the corresponding keychar or '\0' if one is not available + wchar_t getKeychar() const; + + // Get the scancode or 0 is one is not available + u32 getScancode() const { - return (Char > 0 && Char == o.Char) || (valid_kcode(Key) && Key == o.Key); + if (auto pv = std::get_if(&scancode)) + return *pv; + return 0; } - const char *sym() const; - const char *name() const; - -protected: - static bool valid_kcode(irr::EKEY_CODE k) - { - return k > 0 && k < irr::KEY_KEY_CODES_COUNT; + bool operator==(const KeyPress &o) const { + return scancode == o.scancode; + } + bool operator!=(const KeyPress &o) const { + return !(*this == o); } - irr::EKEY_CODE Key = irr::KEY_KEY_CODES_COUNT; - wchar_t Char = L'\0'; - std::string m_name = ""; + // Check whether the keypress is valid + operator bool() const + { + return std::holds_alternative(scancode) ? + Keycode::isValid(std::get(scancode)) : + std::get(scancode) != 0; + } + + static const KeyPress &getSpecialKey(const std::string &name); + +private: + bool loadFromScancode(const std::string &name); + void loadFromKey(irr::EKEY_CODE keycode, wchar_t keychar); + std::string formatScancode() const; + + std::variant scancode = irr::KEY_UNKNOWN; }; // Global defines for convenience - -extern const KeyPress EscapeKey; - -extern const KeyPress LMBKey; -extern const KeyPress MMBKey; // Middle Mouse Button -extern const KeyPress RMBKey; +// This implementation defers creation of the objects to make sure that the +// IrrlichtDevice is initialized. +#define EscapeKey KeyPress::getSpecialKey("KEY_ESCAPE") +#define LMBKey KeyPress::getSpecialKey("KEY_LBUTTON") +#define MMBKey KeyPress::getSpecialKey("KEY_MBUTTON") // Middle Mouse Button +#define RMBKey KeyPress::getSpecialKey("KEY_RBUTTON") // Key configuration getter -const KeyPress &getKeySetting(const char *settingname); +const KeyPress &getKeySetting(const std::string &settingname); // Clear fast lookup cache void clearKeyCache(); - -irr::EKEY_CODE keyname_to_keycode(const char *name); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index b6d45b073..8b0eaa677 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -82,6 +82,7 @@ void set_default_settings() // Client settings->setDefault("address", ""); + settings->setDefault("remote_port", "30000"); #if defined(__unix__) && !defined(__APPLE__) && !defined (__ANDROID__) // On Linux+X11 (not Linux+Wayland or Linux+XWayland), I've encountered a bug // where fake mouse events were generated from touch events if in relative @@ -128,65 +129,74 @@ void set_default_settings() settings->setDefault("chat_weblink_color", "#8888FF"); // Keymap - settings->setDefault("remote_port", "30000"); - settings->setDefault("keymap_forward", "KEY_KEY_W"); +#if USE_SDL2 +#define USEKEY2(name, value, _) settings->setDefault(name, value) +#else +#define USEKEY2(name, _, value) settings->setDefault(name, value) +#endif + USEKEY2("keymap_forward", "SYSTEM_SCANCODE_26", "KEY_KEY_W"); settings->setDefault("keymap_autoforward", ""); - settings->setDefault("keymap_backward", "KEY_KEY_S"); - settings->setDefault("keymap_left", "KEY_KEY_A"); - settings->setDefault("keymap_right", "KEY_KEY_D"); - settings->setDefault("keymap_jump", "KEY_SPACE"); - settings->setDefault("keymap_sneak", "KEY_LSHIFT"); + USEKEY2("keymap_backward", "SYSTEM_SCANCODE_22", "KEY_KEY_S"); + USEKEY2("keymap_left", "SYSTEM_SCANCODE_4", "KEY_KEY_A"); + USEKEY2("keymap_right", "SYSTEM_SCANCODE_7", "KEY_KEY_D"); + USEKEY2("keymap_jump", "SYSTEM_SCANCODE_44", "KEY_SPACE"); +#if !USE_SDL2 && defined(__MACH__) && defined(__APPLE__) + // Altered settings for CIrrDeviceOSX + settings->setDefault("keymap_sneak", "KEY_SHIFT"); +#else + USEKEY2("keymap_sneak", "SYSTEM_SCANCODE_225", "KEY_LSHIFT"); +#endif settings->setDefault("keymap_dig", "KEY_LBUTTON"); settings->setDefault("keymap_place", "KEY_RBUTTON"); - settings->setDefault("keymap_drop", "KEY_KEY_Q"); - settings->setDefault("keymap_zoom", "KEY_KEY_Z"); - settings->setDefault("keymap_inventory", "KEY_KEY_I"); - settings->setDefault("keymap_aux1", "KEY_KEY_E"); - settings->setDefault("keymap_chat", "KEY_KEY_T"); - settings->setDefault("keymap_cmd", "/"); - settings->setDefault("keymap_cmd_local", "."); - settings->setDefault("keymap_minimap", "KEY_KEY_V"); - settings->setDefault("keymap_console", "KEY_F10"); + USEKEY2("keymap_drop", "SYSTEM_SCANCODE_20", "KEY_KEY_Q"); + USEKEY2("keymap_zoom", "SYSTEM_SCANCODE_29", "KEY_KEY_Z"); + USEKEY2("keymap_inventory", "SYSTEM_SCANCODE_12", "KEY_KEY_I"); + USEKEY2("keymap_aux1", "SYSTEM_SCANCODE_8", "KEY_KEY_E"); + USEKEY2("keymap_chat", "SYSTEM_SCANCODE_23", "KEY_KEY_T"); + USEKEY2("keymap_cmd", "SYSTEM_SCANCODE_56", "/"); + USEKEY2("keymap_cmd_local", "SYSTEM_SCANCODE_55", "."); + USEKEY2("keymap_minimap", "SYSTEM_SCANCODE_25", "KEY_KEY_V"); + USEKEY2("keymap_console", "SYSTEM_SCANCODE_67", "KEY_F10"); // see - settings->setDefault("keymap_rangeselect", has_touch ? "KEY_KEY_R" : ""); + USEKEY2("keymap_rangeselect", has_touch ? "SYSTEM_SCANCODE_21" : "", has_touch ? "KEY_KEY_R" : ""); - settings->setDefault("keymap_freemove", "KEY_KEY_K"); + USEKEY2("keymap_freemove", "SYSTEM_SCANCODE_14", "KEY_KEY_K"); settings->setDefault("keymap_pitchmove", ""); - settings->setDefault("keymap_fastmove", "KEY_KEY_J"); - settings->setDefault("keymap_noclip", "KEY_KEY_H"); - settings->setDefault("keymap_hotbar_next", "KEY_KEY_N"); - settings->setDefault("keymap_hotbar_previous", "KEY_KEY_B"); - settings->setDefault("keymap_mute", "KEY_KEY_M"); + USEKEY2("keymap_fastmove", "SYSTEM_SCANCODE_13", "KEY_KEY_J"); + USEKEY2("keymap_noclip", "SYSTEM_SCANCODE_11", "KEY_KEY_H"); + USEKEY2("keymap_hotbar_next", "SYSTEM_SCANCODE_17", "KEY_KEY_N"); + USEKEY2("keymap_hotbar_previous", "SYSTEM_SCANCODE_5", "KEY_KEY_B"); + USEKEY2("keymap_mute", "SYSTEM_SCANCODE_16", "KEY_KEY_M"); settings->setDefault("keymap_increase_volume", ""); settings->setDefault("keymap_decrease_volume", ""); settings->setDefault("keymap_cinematic", ""); settings->setDefault("keymap_toggle_block_bounds", ""); - settings->setDefault("keymap_toggle_hud", "KEY_F1"); - settings->setDefault("keymap_toggle_chat", "KEY_F2"); - settings->setDefault("keymap_toggle_fog", "KEY_F3"); + USEKEY2("keymap_toggle_hud", "SYSTEM_SCANCODE_58", "KEY_F1"); + USEKEY2("keymap_toggle_chat", "SYSTEM_SCANCODE_59", "KEY_F2"); + USEKEY2("keymap_toggle_fog", "SYSTEM_SCANCODE_60", "KEY_F3"); #ifndef NDEBUG - settings->setDefault("keymap_toggle_update_camera", "KEY_F4"); + USEKEY2("keymap_toggle_update_camera", "SYSTEM_SCANCODE_61", "KEY_F4"); #else settings->setDefault("keymap_toggle_update_camera", ""); #endif - settings->setDefault("keymap_toggle_debug", "KEY_F5"); - settings->setDefault("keymap_toggle_profiler", "KEY_F6"); - settings->setDefault("keymap_camera_mode", "KEY_KEY_C"); - settings->setDefault("keymap_screenshot", "KEY_F12"); - settings->setDefault("keymap_fullscreen", "KEY_F11"); - settings->setDefault("keymap_increase_viewing_range_min", "+"); - settings->setDefault("keymap_decrease_viewing_range_min", "-"); - settings->setDefault("keymap_slot1", "KEY_KEY_1"); - settings->setDefault("keymap_slot2", "KEY_KEY_2"); - settings->setDefault("keymap_slot3", "KEY_KEY_3"); - settings->setDefault("keymap_slot4", "KEY_KEY_4"); - settings->setDefault("keymap_slot5", "KEY_KEY_5"); - settings->setDefault("keymap_slot6", "KEY_KEY_6"); - settings->setDefault("keymap_slot7", "KEY_KEY_7"); - settings->setDefault("keymap_slot8", "KEY_KEY_8"); - settings->setDefault("keymap_slot9", "KEY_KEY_9"); - settings->setDefault("keymap_slot10", "KEY_KEY_0"); + USEKEY2("keymap_toggle_debug", "SYSTEM_SCANCODE_62", "KEY_F5"); + USEKEY2("keymap_toggle_profiler", "SYSTEM_SCANCODE_63", "KEY_F6"); + USEKEY2("keymap_camera_mode", "SYSTEM_SCANCODE_6", "KEY_KEY_C"); + USEKEY2("keymap_screenshot", "SYSTEM_SCANCODE_69", "KEY_F12"); + USEKEY2("keymap_fullscreen", "SYSTEM_SCANCODE_68", "KEY_F11"); + USEKEY2("keymap_increase_viewing_range_min", "SYSTEM_SCANCODE_46", "+"); + USEKEY2("keymap_decrease_viewing_range_min", "SYSTEM_SCANCODE_45", "-"); + USEKEY2("keymap_slot1", "SYSTEM_SCANCODE_30", "KEY_KEY_1"); + USEKEY2("keymap_slot2", "SYSTEM_SCANCODE_31", "KEY_KEY_2"); + USEKEY2("keymap_slot3", "SYSTEM_SCANCODE_32", "KEY_KEY_3"); + USEKEY2("keymap_slot4", "SYSTEM_SCANCODE_33", "KEY_KEY_4"); + USEKEY2("keymap_slot5", "SYSTEM_SCANCODE_34", "KEY_KEY_5"); + USEKEY2("keymap_slot6", "SYSTEM_SCANCODE_35", "KEY_KEY_6"); + USEKEY2("keymap_slot7", "SYSTEM_SCANCODE_36", "KEY_KEY_7"); + USEKEY2("keymap_slot8", "SYSTEM_SCANCODE_37", "KEY_KEY_8"); + USEKEY2("keymap_slot9", "SYSTEM_SCANCODE_38", "KEY_KEY_9"); + USEKEY2("keymap_slot10", "SYSTEM_SCANCODE_39", "KEY_KEY_0"); settings->setDefault("keymap_slot11", ""); settings->setDefault("keymap_slot12", ""); settings->setDefault("keymap_slot13", ""); @@ -212,16 +222,17 @@ void set_default_settings() #ifndef NDEBUG // Default keybinds for quicktune in debug builds - settings->setDefault("keymap_quicktune_prev", "KEY_HOME"); - settings->setDefault("keymap_quicktune_next", "KEY_END"); - settings->setDefault("keymap_quicktune_dec", "KEY_NEXT"); - settings->setDefault("keymap_quicktune_inc", "KEY_PRIOR"); + USEKEY2("keymap_quicktune_prev", "SYSTEM_SCANCODE_74", "KEY_HOME"); + USEKEY2("keymap_quicktune_next", "SYSTEM_SCANCODE_77", "KEY_END"); + USEKEY2("keymap_quicktune_dec", "SYSTEM_SCANCODE_81", "KEY_NEXT"); + USEKEY2("keymap_quicktune_inc", "SYSTEM_SCANCODE_82", "KEY_PRIOR"); #else settings->setDefault("keymap_quicktune_prev", ""); settings->setDefault("keymap_quicktune_next", ""); settings->setDefault("keymap_quicktune_dec", ""); settings->setDefault("keymap_quicktune_inc", ""); #endif +#undef USEKEY2 // Visuals #ifdef NDEBUG @@ -534,11 +545,6 @@ void set_default_settings() settings->setDefault("display_density_factor", "1"); settings->setDefault("dpi_change_notifier", "0"); - // Altered settings for CIrrDeviceOSX -#if !USE_SDL2 && defined(__MACH__) && defined(__APPLE__) - settings->setDefault("keymap_sneak", "KEY_SHIFT"); -#endif - settings->setDefault("touch_layout", ""); settings->setDefault("touchscreen_sensitivity", "0.2"); settings->setDefault("touchscreen_threshold", "20"); diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 0f343bbea..9b8c8c416 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -252,8 +252,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) if (event.EventType == EET_KEY_INPUT_EVENT && active_key && event.KeyInput.PressedDown) { - bool prefer_character = shift_down; - KeyPress kp(event.KeyInput, prefer_character); + KeyPress kp(event.KeyInput); if (event.KeyInput.Key == irr::KEY_DELETE) kp = KeyPress(""); // To erase key settings @@ -269,7 +268,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) // Display Key already in use message bool key_in_use = false; - if (strcmp(kp.sym(), "") != 0) { + if (kp) { for (key_setting *ks : key_settings) { if (ks != active_key && ks->key == kp) { key_in_use = true; diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 0a130e905..d5c717d51 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -147,12 +147,13 @@ bool GUIModalMenu::remapClickOutside(const SEvent &event) if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP && current.isRelated(last)) { SEvent translated{}; - translated.EventType = EET_KEY_INPUT_EVENT; - translated.KeyInput.Key = KEY_ESCAPE; - translated.KeyInput.Control = false; - translated.KeyInput.Shift = false; - translated.KeyInput.PressedDown = true; - translated.KeyInput.Char = 0; + translated.EventType = EET_KEY_INPUT_EVENT; + translated.KeyInput.Key = KEY_ESCAPE; + translated.KeyInput.SystemKeyCode = EscapeKey.getScancode(); + translated.KeyInput.Control = false; + translated.KeyInput.Shift = false; + translated.KeyInput.PressedDown = true; + translated.KeyInput.Char = 0; OnEvent(translated); return true; } diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index 7dd837d9e..d3c072d6f 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -13,7 +13,6 @@ #include "porting.h" #include "settings.h" #include "client/guiscalingfilter.h" -#include "client/keycode.h" #include "client/renderingengine.h" #include "client/texturesource.h" #include "util/numeric.h" @@ -31,15 +30,16 @@ TouchControls *g_touchcontrols; -void TouchControls::emitKeyboardEvent(EKEY_CODE keycode, bool pressed) +void TouchControls::emitKeyboardEvent(const KeyPress &key, bool pressed) { SEvent e{}; - e.EventType = EET_KEY_INPUT_EVENT; - e.KeyInput.Key = keycode; - e.KeyInput.Control = false; - e.KeyInput.Shift = false; - e.KeyInput.Char = 0; - e.KeyInput.PressedDown = pressed; + e.EventType = EET_KEY_INPUT_EVENT; + e.KeyInput.Key = key.getKeycode(); + e.KeyInput.Control = false; + e.KeyInput.Shift = false; + e.KeyInput.Char = key.getKeychar(); + e.KeyInput.SystemKeyCode = key.getScancode(); + e.KeyInput.PressedDown = pressed; m_receiver->OnEvent(e); } @@ -54,10 +54,10 @@ void TouchControls::loadButtonTexture(IGUIImage *gui_button, const std::string & void TouchControls::buttonEmitAction(button_info &btn, bool action) { - if (btn.keycode == KEY_UNKNOWN) + if (!btn.keypress) return; - emitKeyboardEvent(btn.keycode, action); + emitKeyboardEvent(btn.keypress, action); if (action) { if (btn.toggleable == button_info::FIRST_TEXTURE) { @@ -133,12 +133,11 @@ bool TouchControls::buttonsStep(std::vector &buttons, float dtime) return has_pointers; } -static EKEY_CODE id_to_keycode(touch_gui_button_id id) +static const KeyPress &id_to_keypress(touch_gui_button_id id) { - EKEY_CODE code; // ESC isn't part of the keymap. if (id == exit_id) - return KEY_ESCAPE; + return EscapeKey; std::string key = ""; switch (id) { @@ -197,15 +196,11 @@ static EKEY_CODE id_to_keycode(touch_gui_button_id id) break; } assert(!key.empty()); - std::string resolved = g_settings->get("keymap_" + key); - try { - code = keyname_to_keycode(resolved.c_str()); - } catch (UnknownKeycode &e) { - code = KEY_UNKNOWN; - warningstream << "TouchControls: Unknown key '" << resolved - << "' for '" << key << "', hiding button." << std::endl; - } - return code; + auto &kp = getKeySetting("keymap_" + key); + if (!kp) + warningstream << "TouchControls: Unbound or invalid key for" + << key << ", hiding button." << std::endl; + return kp; } @@ -355,7 +350,7 @@ bool TouchControls::mayAddButton(touch_gui_button_id id) return false; if (id == aux1_id && m_joystick_triggers_aux1) return false; - if (id != overflow_id && id_to_keycode(id) == KEY_UNKNOWN) + if (id != overflow_id && !id_to_keypress(id)) return false; return true; } @@ -368,7 +363,7 @@ void TouchControls::addButton(std::vector &buttons, touch_gui_butto loadButtonTexture(btn_gui_button, image); button_info &btn = buttons.emplace_back(); - btn.keycode = id_to_keycode(id); + btn.keypress = id_to_keypress(id); btn.gui_button = grab_gui_element(btn_gui_button); } @@ -634,7 +629,7 @@ void TouchControls::translateEvent(const SEvent &event) void TouchControls::applyJoystickStatus() { if (m_joystick_triggers_aux1) { - auto key = id_to_keycode(aux1_id); + auto key = id_to_keypress(aux1_id); emitKeyboardEvent(key, false); if (m_joystick_status_aux1) emitKeyboardEvent(key, true); @@ -741,11 +736,11 @@ void TouchControls::releaseAll() // Release those manually too since the change initiated by // handleReleaseEvent will only be applied later by applyContextControls. if (m_dig_pressed) { - emitKeyboardEvent(id_to_keycode(dig_id), false); + emitKeyboardEvent(id_to_keypress(dig_id), false); m_dig_pressed = false; } if (m_place_pressed) { - emitKeyboardEvent(id_to_keycode(place_id), false); + emitKeyboardEvent(id_to_keypress(place_id), false); m_place_pressed = false; } } @@ -826,20 +821,20 @@ void TouchControls::applyContextControls(const TouchInteractionMode &mode) target_place_pressed |= now < m_place_pressed_until; if (target_dig_pressed && !m_dig_pressed) { - emitKeyboardEvent(id_to_keycode(dig_id), true); + emitKeyboardEvent(id_to_keypress(dig_id), true); m_dig_pressed = true; } else if (!target_dig_pressed && m_dig_pressed) { - emitKeyboardEvent(id_to_keycode(dig_id), false); + emitKeyboardEvent(id_to_keypress(dig_id), false); m_dig_pressed = false; } if (target_place_pressed && !m_place_pressed) { - emitKeyboardEvent(id_to_keycode(place_id), true); + emitKeyboardEvent(id_to_keypress(place_id), true); m_place_pressed = true; } else if (!target_place_pressed && m_place_pressed) { - emitKeyboardEvent(id_to_keycode(place_id), false); + emitKeyboardEvent(id_to_keypress(place_id), false); m_place_pressed = false; } } diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index 9241e3252..b64457ca5 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -16,6 +16,7 @@ #include "itemdef.h" #include "touchscreenlayout.h" #include "util/basic_macros.h" +#include "client/keycode.h" namespace irr { @@ -57,7 +58,7 @@ enum class TapState struct button_info { float repeat_counter; - EKEY_CODE keycode; + KeyPress keypress; std::vector pointer_ids; std::shared_ptr gui_button = nullptr; @@ -202,7 +203,7 @@ private: // for its buttons. We only want static image display, not interactivity, // from Irrlicht. - void emitKeyboardEvent(EKEY_CODE keycode, bool pressed); + void emitKeyboardEvent(const KeyPress &keycode, bool pressed); void loadButtonTexture(IGUIImage *gui_button, const std::string &path); void buttonEmitAction(button_info &btn, bool action); diff --git a/src/unittest/test_keycode.cpp b/src/unittest/test_keycode.cpp index 125098341..a805ec044 100644 --- a/src/unittest/test_keycode.cpp +++ b/src/unittest/test_keycode.cpp @@ -15,20 +15,26 @@ public: void runTests(IGameDef *gamedef); + /* TODO: Re-introduce unittests after fully switching to SDL. void testCreateFromString(); void testCreateFromSKeyInput(); void testCompare(); + */ }; static TestKeycode g_test_instance; void TestKeycode::runTests(IGameDef *gamedef) { + /* TEST(testCreateFromString); TEST(testCreateFromSKeyInput); TEST(testCompare); + */ } +#if 0 + //////////////////////////////////////////////////////////////////////////////// #define UASSERTEQ_STR(one, two) UASSERT(strcmp(one, two) == 0) @@ -112,3 +118,5 @@ void TestKeycode::testCompare() in2.Char = L';'; UASSERT(KeyPress(in) == KeyPress(in2)); } + +#endif From 5b2b2c779667f81f3305c91f3f760cad0096da2d Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 15 Mar 2025 14:31:16 +0100 Subject: [PATCH 061/284] Game: disable 'toggle_sneak_key' while flying --- builtin/settingtypes.txt | 1 + src/client/game.cpp | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 5b7de5ff2..5d3462e41 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -112,6 +112,7 @@ doubletap_jump (Double tap jump for fly) bool false always_fly_fast (Always fly fast) bool true # If enabled, the "Sneak" key will toggle when pressed. +# This functionality is ignored when fly is enabled. toggle_sneak_key (Toggle Sneak key) bool false # If enabled, the "Aux1" key will toggle when pressed. diff --git a/src/client/game.cpp b/src/client/game.cpp index 4e7ec9cec..b7d1df743 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2450,6 +2450,11 @@ void Game::updatePlayerControl(const CameraOrientation &cam) { LocalPlayer *player = client->getEnv().getLocalPlayer(); + // In free move (fly), the "toggle_sneak_key" setting would prevent precise + // up/down movements. Hence, enable the feature only during 'normal' movement. + const bool allow_sneak_toggle = m_cache_toggle_sneak_key && + !player->getPlayerSettings().free_move; + //TimeTaker tt("update player control", NULL, PRECISION_NANO); PlayerControl control( @@ -2458,8 +2463,8 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::LEFT), isKeyDown(KeyType::RIGHT), isKeyDown(KeyType::JUMP) || player->getAutojump(), - getTogglableKeyState(KeyType::AUX1, m_cache_toggle_aux1_key, player->control.aux1), - getTogglableKeyState(KeyType::SNEAK, m_cache_toggle_sneak_key, player->control.sneak), + getTogglableKeyState(KeyType::AUX1, m_cache_toggle_aux1_key, player->control.aux1), + getTogglableKeyState(KeyType::SNEAK, allow_sneak_toggle, player->control.sneak), isKeyDown(KeyType::ZOOM), isKeyDown(KeyType::DIG), isKeyDown(KeyType::PLACE), From f1364b1e0b8efe0ae30183176ae02a2f9bbc4bef Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 15 Mar 2025 15:30:35 +0100 Subject: [PATCH 062/284] GUI: Use the client's fonts for 'Open URL?' dialogues This popup is related to user safety, thus it should not use server-provided font media files. --- src/client/fontengine.cpp | 51 ++++++++++++++++++++++----------------- src/client/fontengine.h | 29 ++++++++++++++++++---- src/gui/StyleSpec.h | 2 ++ src/gui/guiOpenURL.cpp | 22 +++++++++++++---- 4 files changed, 72 insertions(+), 32 deletions(-) diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp index c807d5011..ad1ae2eb2 100644 --- a/src/client/fontengine.cpp +++ b/src/client/fontengine.cpp @@ -80,7 +80,7 @@ irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec) irr::gui::IGUIFont *FontEngine::getFont(FontSpec spec, bool may_fail) { if (spec.mode == FM_Unspecified) { - spec.mode = m_currentMode; + spec.mode = s_default_font_mode; } else if (spec.mode == _FM_Fallback) { // Fallback font doesn't support these spec.bold = false; @@ -138,7 +138,7 @@ unsigned int FontEngine::getLineHeight(const FontSpec &spec) unsigned int FontEngine::getDefaultFontSize() { - return m_default_size[m_currentMode]; + return m_default_size[s_default_font_mode]; } unsigned int FontEngine::getFontSize(FontMode mode) @@ -222,11 +222,14 @@ void FontEngine::clearMediaFonts() refresh(); } -gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) +gui::IGUIFont *FontEngine::initFont(FontSpec spec) { assert(spec.mode != FM_Unspecified); assert(spec.size != FONT_SIZE_UNSPECIFIED); + if (spec.mode == _FM_Fallback) + spec.allow_server_media = false; + std::string setting_prefix = ""; if (spec.mode == FM_Mono) setting_prefix = "mono_"; @@ -256,18 +259,6 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha", font_shadow_alpha); - std::string path_setting; - if (spec.mode == _FM_Fallback) - path_setting = "fallback_font_path"; - else - path_setting = setting_prefix + "font_path" + setting_suffix; - - std::string media_name = spec.mode == FM_Mono - ? "mono" + setting_suffix - : (setting_suffix.empty() ? "" : setting_suffix.substr(1)); - if (media_name.empty()) - media_name = "regular"; - auto createFont = [&](gui::SGUITTFace *face) -> gui::CGUITTFont* { auto *font = gui::CGUITTFont::createTTFont(m_env, face, size, true, true, font_shadow, @@ -285,15 +276,31 @@ gui::IGUIFont *FontEngine::initFont(const FontSpec &spec) return font; }; - auto it = m_media_faces.find(media_name); - if (spec.mode != _FM_Fallback && it != m_media_faces.end()) { - auto *face = it->second.get(); - if (auto *font = createFont(face)) - return font; - errorstream << "FontEngine: Cannot load media font '" << media_name << - "'. Falling back to client settings." << std::endl; + // Use the server-provided font media (if available) + if (spec.allow_server_media) { + std::string media_name = spec.mode == FM_Mono + ? "mono" + setting_suffix + : (setting_suffix.empty() ? "" : setting_suffix.substr(1)); + if (media_name.empty()) + media_name = "regular"; + + auto it = m_media_faces.find(media_name); + if (it != m_media_faces.end()) { + auto *face = it->second.get(); + if (auto *font = createFont(face)) + return font; + errorstream << "FontEngine: Cannot load media font '" << media_name << + "'. Falling back to client settings." << std::endl; + } } + // Use the local font files specified by the settings + std::string path_setting; + if (spec.mode == _FM_Fallback) + path_setting = "fallback_font_path"; + else + path_setting = setting_prefix + "font_path" + setting_suffix; + std::string fallback_settings[] = { g_settings->get(path_setting), Settings::getLayer(SL_DEFAULTS)->get(path_setting) diff --git a/src/client/fontengine.h b/src/client/fontengine.h index 0ed81f678..82949b250 100644 --- a/src/client/fontengine.h +++ b/src/client/fontengine.h @@ -23,10 +23,22 @@ namespace irr { #define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF enum FontMode : u8 { + /// Regular font (settings "font_path*", overwritable) FM_Standard = 0, + + /// Monospace font (settings "mono_font*", overwritable) FM_Mono, - _FM_Fallback, // do not use directly + + /// Use only in `FontEngine`. Fallback font to render glyphs that are not present + /// in the originally requested font (setting "fallback_font_path") + _FM_Fallback, + + /// Sum of all font modes FM_MaxMode, + + // ---------------------------- + + /// Request the defult font specified by `s_default_font_mode` FM_Unspecified }; @@ -37,15 +49,22 @@ struct FontSpec { bold(bold), italic(italic) {} + static const unsigned VARIANT_BITS = 3; + static const size_t MAX_VARIANTS = FM_MaxMode << VARIANT_BITS; + u16 getHash() const { - return (mode << 2) | (static_cast(bold) << 1) | static_cast(italic); + return (mode << VARIANT_BITS) + | (static_cast(allow_server_media) << 2) + | (static_cast(bold) << 1) + | static_cast(italic); } unsigned int size; FontMode mode; bool bold; bool italic; + bool allow_server_media = true; }; class FontEngine @@ -135,7 +154,7 @@ private: void updateCache(); /** initialize a new TTF font */ - gui::IGUIFont *initFont(const FontSpec &spec); + gui::IGUIFont *initFont(FontSpec spec); /** update current minetest skin with font changes */ void updateSkin(); @@ -155,7 +174,7 @@ private: std::recursive_mutex m_font_mutex; /** internal storage for caching fonts of different size */ - std::map m_font_cache[FM_MaxMode << 2]; + std::map m_font_cache[FontSpec::MAX_VARIANTS]; /** media-provided faces, indexed by filename (without extension) */ std::unordered_map> m_media_faces; @@ -168,7 +187,7 @@ private: bool m_default_italic = false; /** default font engine mode (fixed) */ - static const FontMode m_currentMode = FM_Standard; + static const FontMode s_default_font_mode = FM_Standard; bool m_needs_reload = false; diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 3755e105f..f78a038c2 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -316,6 +316,8 @@ public: spec.bold = true; else if (modes[i] == "italic") spec.italic = true; + else if (modes[i] == "_no_server_media") // for internal use only + spec.allow_server_media = false; } if (!size.empty()) { diff --git a/src/gui/guiOpenURL.cpp b/src/gui/guiOpenURL.cpp index 7ce79b0e5..4c54556be 100644 --- a/src/gui/guiOpenURL.cpp +++ b/src/gui/guiOpenURL.cpp @@ -87,6 +87,12 @@ void GUIOpenURLMenu::regenerateGui(v2u32 screensize) ok = false; } + std::array styles {}; + for (auto &style : styles) + style.set(StyleSpec::Property::FONT, "_no_server_media"); + + auto font_standard = styles[0].getFont(); + /* Add stuff */ @@ -99,8 +105,9 @@ void GUIOpenURLMenu::regenerateGui(v2u32 screensize) std::wstring title = ok ? wstrgettext("Open URL?") : wstrgettext("Unable to open URL"); - gui::StaticText::add(Environment, title, rect, + auto *e = gui::StaticText::add(Environment, title, rect, false, true, this, -1); + e->setOverrideFont(font_standard); } ypos += 50 * s; @@ -108,8 +115,11 @@ void GUIOpenURLMenu::regenerateGui(v2u32 screensize) { core::rect rect(0, 0, 440 * s, 60 * s); - auto font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, - ok ? FM_Mono : FM_Standard); + FontSpec fontspec(FONT_SIZE_UNSPECIFIED, FM_Mono, false, false); + fontspec.allow_server_media = false; + + auto font = ok ? g_fontengine->getFont(fontspec) : font_standard; + int scrollbar_width = Environment->getSkin()->getSize(gui::EGDS_SCROLLBAR_SIZE); int max_cols = (rect.getWidth() - scrollbar_width - 10) / font->getDimension(L"x").Width; @@ -131,14 +141,16 @@ void GUIOpenURLMenu::regenerateGui(v2u32 screensize) if (ok) { core::rect rect(0, 0, 100 * s, 40 * s); rect = rect + v2s32(size.X / 2 - 150 * s, ypos); - GUIButton::addButton(Environment, rect, m_tsrc, this, ID_open, + auto *e = GUIButton::addButton(Environment, rect, m_tsrc, this, ID_open, wstrgettext("Open").c_str()); + e->setStyles(styles); } { core::rect rect(0, 0, 100 * s, 40 * s); rect = rect + v2s32(size.X / 2 + 50 * s, ypos); - GUIButton::addButton(Environment, rect, m_tsrc, this, ID_cancel, + auto *e = GUIButton::addButton(Environment, rect, m_tsrc, this, ID_cancel, wstrgettext("Cancel").c_str()); + e->setStyles(styles); } } From 4125ce877d9f494a2a88356fc399194ac6aa0e85 Mon Sep 17 00:00:00 2001 From: JosiahWI <41302989+JosiahWI@users.noreply.github.com> Date: Wed, 19 Mar 2025 12:43:19 -0500 Subject: [PATCH 063/284] Do not discover mod directories that fail parsing (#15917) The root issue of the unit test failure is that all directories that are found in the mod search are counted as mods, even if they are detected to be invalid as such by the parser. For example, the presence of an init.lua file is required, and the parser will return false if one is not found. This return value was completely ignored. Simply counting the mod conditionally on the parsing success makes the modserver tests pass on MSVC. --- src/content/mods.cpp | 5 +++-- src/content/mods.h | 2 +- src/script/lua_api/l_mainmenu.cpp | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/content/mods.cpp b/src/content/mods.cpp index 5a9281f7d..333f1d24d 100644 --- a/src/content/mods.cpp +++ b/src/content/mods.cpp @@ -175,8 +175,9 @@ std::map getModsInPath( mod_virtual_path.append(virtual_path).append("/").append(modname); ModSpec spec(modname, mod_path, part_of_modpack, mod_virtual_path); - parseModContents(spec); - result[modname] = std::move(spec); + if (parseModContents(spec)) { + result[modname] = std::move(spec); + } } return result; } diff --git a/src/content/mods.h b/src/content/mods.h index a7e1e5041..fc98d9298 100644 --- a/src/content/mods.h +++ b/src/content/mods.h @@ -78,7 +78,7 @@ struct ModSpec * * @returns false if not a mod */ -bool parseModContents(ModSpec &mod); +[[nodiscard]] bool parseModContents(ModSpec &mod); /** * Gets a list of all mods and modpacks in path diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 43087c978..0969bb525 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -385,7 +385,8 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) if (spec.type == "mod") { ModSpec spec; spec.path = path; - parseModContents(spec); + // TODO return nothing on failure (needs callers to handle it) + static_cast(parseModContents(spec)); // Dependencies lua_newtable(L); From 2540667f04b673b764de44063d8940b40a6c2638 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 16 Mar 2025 16:46:37 +0100 Subject: [PATCH 064/284] Warn if metatable passed to itemdef registration function --- builtin/async/game.lua | 4 ++-- builtin/emerge/register.lua | 4 ++-- builtin/game/register.lua | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/builtin/async/game.lua b/builtin/async/game.lua index f7c9892c4..b6a8cc887 100644 --- a/builtin/async/game.lua +++ b/builtin/async/game.lua @@ -32,8 +32,8 @@ do all.registered_craftitems = {} all.registered_tools = {} for k, v in pairs(all.registered_items) do - -- Disable further modification - setmetatable(v, {__newindex = {}}) + -- Ignore new keys + setmetatable(v, {__newindex = function() end}) -- Reassemble the other tables if v.type == "node" then getmetatable(v).__index = all.nodedef_default diff --git a/builtin/emerge/register.lua b/builtin/emerge/register.lua index 308fe4d7e..2200a0087 100644 --- a/builtin/emerge/register.lua +++ b/builtin/emerge/register.lua @@ -9,8 +9,8 @@ do all.registered_craftitems = {} all.registered_tools = {} for k, v in pairs(all.registered_items) do - -- Disable further modification - setmetatable(v, {__newindex = {}}) + -- Ignore new keys + setmetatable(v, {__newindex = function() end}) -- Reassemble the other tables if v.type == "node" then getmetatable(v).__index = all.nodedef_default diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 3e4b9be96..e0d001824 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -139,6 +139,12 @@ function core.register_item(name, itemdef) end itemdef.name = name + local mt = getmetatable(itemdef) + if mt ~= nil and next(mt) ~= nil then + core.log("warning", "Item definition has a metatable, this is ".. + "unsupported and it will be overwritten: " .. name) + end + -- Apply defaults and add to registered_* table if itemdef.type == "node" then -- Use the nodebox as selection box if it's not set manually @@ -194,8 +200,8 @@ function core.register_item(name, itemdef) itemdef.mod_origin = core.get_current_modname() or "??" - -- Disable all further modifications - getmetatable(itemdef).__newindex = {} + -- Ignore new keys as a failsafe to prevent mistakes + getmetatable(itemdef).__newindex = function() end --core.log("Registering item: " .. itemdef.name) core.registered_items[itemdef.name] = itemdef From ca047c3e582ecd2e4fcc3445ff9742a9d60f699f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 16 Mar 2025 19:57:06 +0100 Subject: [PATCH 065/284] Warn on core.override_item() after server startup --- builtin/game/register.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index e0d001824..562fdfd50 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -412,6 +412,7 @@ core.register_item(":", { groups = {not_in_creative_inventory=1}, }) +local itemdefs_finalized = false function core.override_item(name, redefinition, del_fields) if redefinition.name ~= nil then @@ -424,10 +425,16 @@ function core.override_item(name, redefinition, del_fields) if not item then error("Attempt to override non-existent item "..name, 2) end + if itemdefs_finalized then + -- TODO: it's not clear if this needs to be allowed at all? + core.log("warning", "Overriding item " .. name .. " after server startup. " .. + "This is unsupported and can cause problems related to data inconsistency.") + end for k, v in pairs(redefinition) do rawset(item, k, v) end for _, field in ipairs(del_fields or {}) do + assert(field ~= "name" and field ~= "type") rawset(item, field, nil) end register_item_raw(item) @@ -576,6 +583,8 @@ core.registered_on_mapblocks_changed, core.register_on_mapblocks_changed = make_ core.register_on_mods_loaded(function() core.after(0, function() + itemdefs_finalized = true + setmetatable(core.registered_on_mapblocks_changed, { __newindex = function() error("on_mapblocks_changed callbacks must be registered at load time") From a9a3b05cc303878688de85980c0afee6666d9356 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 16 Mar 2025 20:22:05 +0100 Subject: [PATCH 066/284] Prevent registration of certain new content after load time --- builtin/async/game.lua | 3 ++ builtin/emerge/register.lua | 3 ++ builtin/game/register.lua | 55 +++++++++++++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/builtin/async/game.lua b/builtin/async/game.lua index b6a8cc887..6bd369fb1 100644 --- a/builtin/async/game.lua +++ b/builtin/async/game.lua @@ -59,6 +59,9 @@ end local alias_metatable = { __index = function(t, name) return rawget(t, core.registered_aliases[name]) + end, + __newindex = function() + error("table is read-only") end } setmetatable(core.registered_items, alias_metatable) diff --git a/builtin/emerge/register.lua b/builtin/emerge/register.lua index 2200a0087..a07849c7b 100644 --- a/builtin/emerge/register.lua +++ b/builtin/emerge/register.lua @@ -36,6 +36,9 @@ end local alias_metatable = { __index = function(t, name) return rawget(t, core.registered_aliases[name]) + end, + __newindex = function() + error("table is read-only") end } setmetatable(core.registered_items, alias_metatable) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 562fdfd50..dc4aa1ca1 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -61,7 +61,7 @@ local function check_modname_prefix(name) return name:sub(2) else -- Enforce that the name starts with the correct mod name. - local expected_prefix = core.get_current_modname() .. ":" + local expected_prefix = (core.get_current_modname() or "") .. ":" if name:sub(1, #expected_prefix) ~= expected_prefix then error("Name " .. name .. " does not follow naming conventions: " .. "\"" .. expected_prefix .. "\" or \":\" prefix required") @@ -95,6 +95,7 @@ function core.register_abm(spec) check_node_list(spec.nodenames, "nodenames") check_node_list(spec.neighbors, "neighbors") assert(type(spec.action) == "function", "Required field 'action' of type function") + core.registered_abms[#core.registered_abms + 1] = spec spec.mod_origin = core.get_current_modname() or "??" end @@ -581,15 +582,57 @@ core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_r core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration() core.registered_on_mapblocks_changed, core.register_on_mapblocks_changed = make_registration() +-- A bunch of registrations are read by the C++ side once on env init, so we cannot +-- allow them to change afterwards (see s_env.cpp). +-- Nodes and items do not have this problem but there are obvious consistency +-- problems if this would be allowed. + +local function freeze_table(t) + -- Freezing a Lua table is not actually possible without some very intrusive + -- metatable hackery, but we can trivially prevent new additions. + local mt = table.copy(getmetatable(t) or {}) + mt.__newindex = function() + error("modification forbidden") + end + setmetatable(t, mt) +end + +local function generic_reg_error(what) + return function(something) + local described = what + if type(something) == "table" and type(something.name) == "string" then + described = what .. " " .. something.name + elseif type(something) == "string" then + described = what .. " " .. something + end + error("Tried to register " .. described .. " after load time!") + end +end + core.register_on_mods_loaded(function() core.after(0, function() itemdefs_finalized = true - setmetatable(core.registered_on_mapblocks_changed, { - __newindex = function() - error("on_mapblocks_changed callbacks must be registered at load time") - end, - }) + -- prevent direct modification + freeze_table(core.registered_abms) + freeze_table(core.registered_lbms) + freeze_table(core.registered_items) + freeze_table(core.registered_nodes) + freeze_table(core.registered_craftitems) + freeze_table(core.registered_tools) + freeze_table(core.registered_aliases) + freeze_table(core.registered_on_mapblocks_changed) + + -- neutralize registration functions + core.register_abm = generic_reg_error("ABM") + core.register_lbm = generic_reg_error("LBM") + core.register_item = generic_reg_error("item") + core.unregister_item = function(name) + error("Refusing to unregister item " .. name .. " after load time") + end + core.register_alias = generic_reg_error("alias") + core.register_alias_force = generic_reg_error("alias") + core.register_on_mapblocks_changed = generic_reg_error("on_mapblocks_changed callback") end) end) From 1f14b7cb1bb90f63cc50ee7f92c753568d072c57 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 19 Mar 2025 22:06:34 +0100 Subject: [PATCH 067/284] Make remote media exclusively use GET for hash set (#15885) --- src/client/clientmedia.cpp | 54 ++++++++++++++------------------------ src/client/clientmedia.h | 3 ++- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp index 2ee0d747b..407b1412a 100644 --- a/src/client/clientmedia.cpp +++ b/src/client/clientmedia.cpp @@ -154,7 +154,7 @@ void ClientMediaDownloader::step(Client *client) startRemoteMediaTransfers(); // Did all remote transfers end and no new ones can be started? - // If so, request still missing files from the minetest server + // If so, request still missing files from the server // (Or report that we have all files.) if (m_httpfetch_active == 0) { if (m_uncached_received_count < m_uncached_count) { @@ -168,6 +168,15 @@ void ClientMediaDownloader::step(Client *client) } } +std::string ClientMediaDownloader::makeReferer(Client *client) +{ + std::string addr = client->getAddressName(); + if (addr.find(':') != std::string::npos) + addr = '[' + addr + ']'; + return std::string("minetest://") + addr + ':' + + std::to_string(client->getServerAddress().getPort()); +} + void ClientMediaDownloader::initialStep(Client *client) { std::wstring loading_text = wstrgettext("Media..."); @@ -227,9 +236,14 @@ void ClientMediaDownloader::initialStep(Client *client) m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit"); m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84); - // Write a list of hashes that we need. This will be POSTed - // to the server using Content-Type: application/octet-stream - std::string required_hash_set = serializeRequiredHashSet(); + // Note: we used to use a POST request that contained the set of + // hashes we needed here, but this use was discontinued in 5.12.0 as + // it's not CDN/static hosting-friendly. + // Even with a large repository of media (think 60k files), you would be + // looking at only 1.1 MB for the index file. + // If it becomes a problem we can always still introduce a v2 of the + // hash set format and truncate the hashes to 6 bytes -- at the cost of + // a false-positive rate of 2^-48 -- which is 70% less space. // minor fixme: this loop ignores m_httpfetch_active_limit @@ -251,19 +265,8 @@ void ClientMediaDownloader::initialStep(Client *client) remote->baseurl + MTHASHSET_FILE_NAME; fetch_request.caller = m_httpfetch_caller; fetch_request.request_id = m_httpfetch_next_id; // == i - fetch_request.method = HTTP_POST; - fetch_request.raw_data = required_hash_set; fetch_request.extra_headers.emplace_back( - "Content-Type: application/octet-stream"); - - // Encapsulate possible IPv6 plain address in [] - std::string addr = client->getAddressName(); - if (addr.find(':', 0) != std::string::npos) - addr = '[' + addr + ']'; - fetch_request.extra_headers.emplace_back( - std::string("Referer: minetest://") + - addr + ":" + - std::to_string(client->getServerAddress().getPort())); + "Referer: " + makeReferer(client)); httpfetch_async(fetch_request); @@ -585,25 +588,6 @@ bool IClientMediaDownloader::checkAndLoad( 1 - Initial version */ -std::string ClientMediaDownloader::serializeRequiredHashSet() -{ - std::ostringstream os(std::ios::binary); - - writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature - writeU16(os, 1); // version - - // Write list of hashes of files that have not been - // received (found in cache) yet - for (const auto &it : m_files) { - if (!it.second->received) { - FATAL_ERROR_IF(it.second->sha1.size() != 20, "Invalid SHA1 size"); - os << it.second->sha1; - } - } - - return os.str(); -} - void ClientMediaDownloader::deSerializeHashSet(const std::string &data, std::set &result) { diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h index 40801321f..c4054051b 100644 --- a/src/client/clientmedia.h +++ b/src/client/clientmedia.h @@ -119,6 +119,8 @@ protected: bool loadMedia(Client *client, const std::string &data, const std::string &name) override; + static std::string makeReferer(Client *client); + private: struct FileStatus { bool received; @@ -142,7 +144,6 @@ private: static void deSerializeHashSet(const std::string &data, std::set &result); - std::string serializeRequiredHashSet(); // Maps filename to file status std::map m_files; From ead44a27ca64f7987df03b39bd81dc77980d8d34 Mon Sep 17 00:00:00 2001 From: grorp Date: Fri, 21 Mar 2025 07:06:44 -0400 Subject: [PATCH 068/284] TouchControls: Implement an option for dig/place buttons (#15845) --- LICENSE.txt | 3 + android/icons/dig_btn.svg | 148 +++++++++++++++++++++++ android/icons/place_btn.svg | 148 +++++++++++++++++++++++ builtin/common/settings/dlg_settings.lua | 13 ++ builtin/settingtypes.txt | 50 +++++--- doc/texture_packs.md | 33 +++-- src/defaultsettings.cpp | 2 +- src/gui/touchcontrols.cpp | 59 ++++++--- src/gui/touchcontrols.h | 9 +- src/gui/touchscreeneditor.cpp | 2 +- src/gui/touchscreenlayout.cpp | 116 +++++++++++++----- src/gui/touchscreenlayout.h | 38 +++++- src/migratesettings.h | 7 ++ textures/base/pack/dig_btn.png | Bin 0 -> 2067 bytes textures/base/pack/place_btn.png | Bin 0 -> 2537 bytes 15 files changed, 540 insertions(+), 88 deletions(-) create mode 100644 android/icons/dig_btn.svg create mode 100644 android/icons/place_btn.svg create mode 100644 textures/base/pack/dig_btn.png create mode 100644 textures/base/pack/place_btn.png diff --git a/LICENSE.txt b/LICENSE.txt index 503dd62d2..772f86728 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -102,6 +102,9 @@ grorp: using the font "undefined medium" (https://undefined-medium.com/), which is licensed under the SIL Open Font License, Version 1.1 modified by DS + textures/base/pack/dig_btn.png + textures/base/pack/place_btn.png + derived by editing the text in aux1_btn.svg License of Luanti source code ------------------------------- diff --git a/android/icons/dig_btn.svg b/android/icons/dig_btn.svg new file mode 100644 index 000000000..cbfa69e6f --- /dev/null +++ b/android/icons/dig_btn.svg @@ -0,0 +1,148 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + LMB + + + diff --git a/android/icons/place_btn.svg b/android/icons/place_btn.svg new file mode 100644 index 000000000..b32dfc018 --- /dev/null +++ b/android/icons/place_btn.svg @@ -0,0 +1,148 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + RMB + + + diff --git a/builtin/common/settings/dlg_settings.lua b/builtin/common/settings/dlg_settings.lua index 570c01cd5..6f0b772af 100644 --- a/builtin/common/settings/dlg_settings.lua +++ b/builtin/common/settings/dlg_settings.lua @@ -259,6 +259,17 @@ local function load() ["true"] = fgettext_ne("Enabled"), ["false"] = fgettext_ne("Disabled"), } + + get_setting_info("touch_interaction_style").option_labels = { + ["tap"] = fgettext_ne("Tap"), + ["tap_crosshair"] = fgettext_ne("Tap with crosshair"), + ["buttons_crosshair"] = fgettext("Buttons with crosshair"), + } + + get_setting_info("touch_punch_gesture").option_labels = { + ["short_tap"] = fgettext_ne("Short tap"), + ["long_tap"] = fgettext_ne("Long tap"), + } end @@ -359,6 +370,7 @@ local function check_requirements(name, requires) local video_driver = core.get_active_driver() local touch_support = core.irrlicht_device_supports_touch() local touch_controls = core.settings:get("touch_controls") + local touch_interaction_style = core.settings:get("touch_interaction_style") local special = { android = PLATFORM == "Android", desktop = PLATFORM ~= "Android", @@ -369,6 +381,7 @@ local function check_requirements(name, requires) keyboard_mouse = not touch_support or (touch_controls == "auto" or not core.is_yes(touch_controls)), opengl = (video_driver == "opengl" or video_driver == "opengl3"), gles = video_driver:sub(1, 5) == "ogles", + touch_interaction_style_tap = touch_interaction_style ~= "buttons_crosshair", } for req_key, req_value in pairs(requires) do diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 5d3462e41..3ae55aecd 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -167,6 +167,36 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false # Requires: touch_support touch_controls (Touchscreen controls) enum auto auto,true,false +# The kind of digging/placing controls used. +# +# * Tap +# Long/short tap anywhere on the screen to interact. +# Interaction happens at finger position. +# +# * Tap with crosshair +# Long/short tap anywhere on the screen to interact. +# Interaction happens at crosshair position. +# +# * Buttons with crosshair +# Use dedicated dig/place buttons to interact. +# Interaction happens at crosshair position. +# +# Requires: touchscreen +touch_interaction_style (Interaction style) enum tap tap,tap_crosshair,buttons_crosshair + +# The gesture for punching players/entities. +# This can be overridden by games and mods. +# +# * Short tap +# Easy to use and well-known from other games that shall not be named. +# +# * Long tap +# Known from the classic Luanti mobile controls. +# Combat is more or less impossible. +# +# Requires: touchscreen, touch_interaction_style_tap +touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap + # Touchscreen sensitivity multiplier. # # Requires: touchscreen @@ -182,12 +212,6 @@ touchscreen_threshold (Movement threshold) int 20 0 100 # Requires: touchscreen touch_long_tap_delay (Threshold for long taps) int 400 100 1000 -# Use crosshair to select object instead of whole screen. -# If enabled, a crosshair will be shown and will be used for selecting object. -# -# Requires: touchscreen -touch_use_crosshair (Use crosshair for touch screen) bool false - # Fixes the position of virtual joystick. # If disabled, virtual joystick will center to first-touch's position. # @@ -200,20 +224,6 @@ fixed_virtual_joystick (Fixed virtual joystick) bool false # Requires: touchscreen virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false -# The gesture for punching players/entities. -# This can be overridden by games and mods. -# -# * short_tap -# Easy to use and well-known from other games that shall not be named. -# -# * long_tap -# Known from the classic Luanti mobile controls. -# Combat is more or less impossible. -# -# Requires: touchscreen -touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap - - [Graphics and Audio] [*Graphics] diff --git a/doc/texture_packs.md b/doc/texture_packs.md index 256915b81..d386006f2 100644 --- a/doc/texture_packs.md +++ b/doc/texture_packs.md @@ -139,21 +139,34 @@ are placeholders intended to be overwritten by the game. ### Android textures -* `drop_btn.png` -* `fast_btn.png` -* `fly_btn.png` -* `jump_btn.png` -* `noclip_btn.png` +* `dig_btn.png` +* `place_btn.png` + +* `jump_btn.png` +* `down.png` +* `zoom.png` +* `aux1_btn.png` +* `overflow_btn.png` -* `camera_btn.png` * `chat_btn.png` * `inventory_btn.png` -* `rangeview_btn.png` - -* `debug_btn.png` -* `overflow_btn.png` +* `drop_btn.png` * `exit_btn.png` +* `fly_btn.png` +* `fast_btn.png` +* `noclip_btn.png` +* `debug_btn.png` +* `camera_btn.png` +* `rangeview_btn.png` +* `minimap_btn.png` +* `chat_hide_btn.png` +* `chat_show_btn.png` + +* `joystick_off.png` +* `joystick_bg.png` +* `joystick_center.png` + Texture Overrides ----------------- diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 8b0eaa677..205fe5934 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -549,9 +549,9 @@ void set_default_settings() settings->setDefault("touchscreen_sensitivity", "0.2"); settings->setDefault("touchscreen_threshold", "20"); settings->setDefault("touch_long_tap_delay", "400"); - settings->setDefault("touch_use_crosshair", "false"); settings->setDefault("fixed_virtual_joystick", "false"); settings->setDefault("virtual_joystick_triggers_aux1", "false"); + settings->setDefault("touch_interaction_style", "tap"); settings->setDefault("touch_punch_gesture", "short_tap"); settings->setDefault("clickable_chat_weblinks", "true"); diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index d3c072d6f..4cea023ab 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -15,6 +15,7 @@ #include "client/guiscalingfilter.h" #include "client/renderingengine.h" #include "client/texturesource.h" +#include "util/enum_string.h" #include "util/numeric.h" #include "irr_gui_ptr.h" #include "IGUIImage.h" @@ -78,15 +79,18 @@ bool TouchControls::buttonsHandlePress(std::vector &buttons, size_t for (button_info &btn : buttons) { if (btn.gui_button.get() == element) { + // Allow moving the camera with the same finger that holds dig/place. + bool absorb = btn.id != dig_id && btn.id != place_id; + assert(std::find(btn.pointer_ids.begin(), btn.pointer_ids.end(), pointer_id) == btn.pointer_ids.end()); btn.pointer_ids.push_back(pointer_id); if (btn.pointer_ids.size() > 1) - return true; + return absorb; buttonEmitAction(btn, true); btn.repeat_counter = -BUTTON_REPEAT_DELAY; - return true; + return absorb; } } @@ -99,13 +103,16 @@ bool TouchControls::buttonsHandleRelease(std::vector &buttons, size for (button_info &btn : buttons) { auto it = std::find(btn.pointer_ids.begin(), btn.pointer_ids.end(), pointer_id); if (it != btn.pointer_ids.end()) { + // Don't absorb since we didn't absorb the press event either. + bool absorb = btn.id != dig_id && btn.id != place_id; + btn.pointer_ids.erase(it); if (!btn.pointer_ids.empty()) - return true; + return absorb; buttonEmitAction(btn, false); - return true; + return absorb; } } @@ -117,6 +124,8 @@ bool TouchControls::buttonsStep(std::vector &buttons, float dtime) bool has_pointers = false; for (button_info &btn : buttons) { + if (btn.id == dig_id || btn.id == place_id) + continue; // key repeats would cause glitches here if (btn.pointer_ids.empty()) continue; has_pointers = true; @@ -205,7 +214,7 @@ static const KeyPress &id_to_keypress(touch_gui_button_id id) static const char *setting_names[] = { - "touch_use_crosshair", + "touch_interaction_style", "touchscreen_threshold", "touch_long_tap_delay", "fixed_virtual_joystick", "virtual_joystick_triggers_aux1", "touch_layout", @@ -232,14 +241,20 @@ void TouchControls::settingChangedCallback(const std::string &name, void *data) void TouchControls::readSettings() { - m_use_crosshair = g_settings->getBool("touch_use_crosshair"); + const std::string &s = g_settings->get("touch_interaction_style"); + if (!string_to_enum(es_TouchInteractionStyle, m_interaction_style, s)) { + m_interaction_style = TAP; + warningstream << "Invalid touch_interaction_style value" << std::endl; + } + m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold"); m_long_tap_delay = g_settings->getU16("touch_long_tap_delay"); m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick"); m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1"); - // Note that "fixed_virtual_joystick" and "virtual_joystick_triggers_aux1" - // also affect the layout. + // Note that other settings also affect the layout: + // - ButtonLayout::loadFromSettings: "touch_interaction_style" and "virtual_joystick_triggers_aux1" + // - applyLayout: "fixed_virtual_joystick" applyLayout(ButtonLayout::loadFromSettings()); } @@ -305,8 +320,8 @@ void TouchControls::applyLayout(const ButtonLayout &layout) overflow_buttons.erase(std::remove_if( overflow_buttons.begin(), overflow_buttons.end(), [&](touch_gui_button_id id) { - // There's no sense in adding the overflow button to the overflow - // menu (also, it's impossible since it doesn't have a keycode). + // There would be no sense in adding the overflow button to the + // overflow menu. return !mayAddButton(id) || id == overflow_id; }), overflow_buttons.end()); @@ -346,13 +361,10 @@ TouchControls::~TouchControls() bool TouchControls::mayAddButton(touch_gui_button_id id) { - if (!ButtonLayout::isButtonAllowed(id)) - return false; - if (id == aux1_id && m_joystick_triggers_aux1) - return false; - if (id != overflow_id && !id_to_keypress(id)) - return false; - return true; + assert(ButtonLayout::isButtonValid(id)); + assert(ButtonLayout::isButtonAllowed(id)); + // The overflow button doesn't need a keycode to be valid. + return id == overflow_id || id_to_keypress(id); } void TouchControls::addButton(std::vector &buttons, touch_gui_button_id id, @@ -363,6 +375,7 @@ void TouchControls::addButton(std::vector &buttons, touch_gui_butto loadButtonTexture(btn_gui_button, image); button_info &btn = buttons.emplace_back(); + btn.id = id; btn.keypress = id_to_keypress(id); btn.gui_button = grab_gui_element(btn_gui_button); } @@ -429,7 +442,8 @@ void TouchControls::handleReleaseEvent(size_t pointer_id) // If m_tap_state is already set to TapState::ShortTap, we must keep // that value. Otherwise, many short taps will be ignored if you tap // very fast. - if (!m_move_has_really_moved && !m_move_prevent_short_tap && + if (m_interaction_style != BUTTONS_CROSSHAIR && + !m_move_has_really_moved && !m_move_prevent_short_tap && m_tap_state != TapState::LongTap) { m_tap_state = TapState::ShortTap; } else { @@ -655,7 +669,9 @@ void TouchControls::step(float dtime) applyJoystickStatus(); // if a new placed pointer isn't moved for some time start digging - if (m_has_move_id && !m_move_has_really_moved && m_tap_state == TapState::None) { + if (m_interaction_style != BUTTONS_CROSSHAIR && + m_has_move_id && !m_move_has_really_moved && + m_tap_state == TapState::None) { u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs()); if (delta > m_long_tap_delay) { @@ -669,7 +685,7 @@ void TouchControls::step(float dtime) // shootline when a touch event occurs. // Only updating when m_has_move_id means that the shootline will stay at // it's last in-world position when the player doesn't need it. - if (!m_use_crosshair && (m_has_move_id || m_had_move_id)) { + if (m_interaction_style == TAP && (m_has_move_id || m_had_move_id)) { m_shootline = m_device ->getSceneManager() ->getSceneCollisionManager() @@ -757,6 +773,9 @@ void TouchControls::show() void TouchControls::applyContextControls(const TouchInteractionMode &mode) { + if (m_interaction_style == BUTTONS_CROSSHAIR) + return; + // Since the pointed thing has already been determined when this function // is called, we cannot use this function to update the shootline. diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index b64457ca5..c2ed8ae7b 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -57,6 +57,7 @@ enum class TapState struct button_info { + touch_gui_button_id id; float repeat_counter; KeyPress keypress; std::vector pointer_ids; @@ -94,7 +95,7 @@ public: return res; } - bool isShootlineAvailable() { return !m_use_crosshair; } + bool isShootlineAvailable() { return m_interaction_style == TAP; } /** * Returns a line which describes what the player is pointing at. @@ -137,7 +138,7 @@ private: s32 m_button_size; // cached settings - bool m_use_crosshair; + TouchInteractionStyle m_interaction_style; double m_touchscreen_threshold; u16 m_long_tap_delay; bool m_fixed_joystick; @@ -161,7 +162,7 @@ private: * The line ends on the camera's far plane. * The coordinates do not contain the camera offset. * - * Only valid if !m_use_crosshair + * Only used for m_interaction_style == TAP */ line3d m_shootline; @@ -243,6 +244,8 @@ private: // map to store the IDs and positions of currently pressed pointers std::unordered_map m_pointer_pos; + // The following are not used if m_interaction_style == BUTTONS_CROSSHAIR + TouchInteractionMode m_last_mode = TouchInteractionMode_END; TapState m_tap_state = TapState::None; diff --git a/src/gui/touchscreeneditor.cpp b/src/gui/touchscreeneditor.cpp index 4a8468ee5..76403973f 100644 --- a/src/gui/touchscreeneditor.cpp +++ b/src/gui/touchscreeneditor.cpp @@ -371,7 +371,7 @@ bool GUITouchscreenLayout::OnEvent(const SEvent& event) } if (event.GUIEvent.Caller == m_gui_reset_btn.get()) { - m_layout = ButtonLayout::predefined; + m_layout = ButtonLayout::loadDefault(); regenerateGui(screensize); return true; } diff --git a/src/gui/touchscreenlayout.cpp b/src/gui/touchscreenlayout.cpp index e0ad5b723..bd97526ff 100644 --- a/src/gui/touchscreenlayout.cpp +++ b/src/gui/touchscreenlayout.cpp @@ -12,6 +12,15 @@ #include "IGUIFont.h" #include "IGUIStaticText.h" +#include "util/enum_string.h" + +const struct EnumString es_TouchInteractionStyle[] = +{ + {TAP, "tap"}, + {TAP_CROSSHAIR, "tap_crosshair"}, + {BUTTONS_CROSSHAIR, "buttons_crosshair"}, + {0, NULL}, +}; const char *button_names[] = { "dig", @@ -73,8 +82,8 @@ const char *button_titles[] = { }; const char *button_image_names[] = { - "", - "", + "dig_btn.png", + "place_btn.png", "jump_btn.png", "down.png", @@ -130,11 +139,21 @@ void ButtonMeta::setPos(v2s32 pos, v2u32 screensize, s32 button_size) offset.Y = (pos.Y - (position.Y * screensize.Y)) / button_size; } +bool ButtonLayout::isButtonValid(touch_gui_button_id id) +{ + return id != joystick_off_id && id != joystick_bg_id && id != joystick_center_id && + id < touch_gui_button_id_END; +} + +static const char *buttons_crosshair = enum_to_string(es_TouchInteractionStyle, BUTTONS_CROSSHAIR); + bool ButtonLayout::isButtonAllowed(touch_gui_button_id id) { - return id != dig_id && id != place_id && - id != joystick_off_id && id != joystick_bg_id && id != joystick_center_id && - id != touch_gui_button_id_END; + if (id == dig_id || id == place_id) + return g_settings->get("touch_interaction_style") == buttons_crosshair; + if (id == aux1_id) + return !g_settings->getBool("virtual_joystick_triggers_aux1"); + return true; } bool ButtonLayout::isButtonRequired(touch_gui_button_id id) @@ -149,7 +168,15 @@ s32 ButtonLayout::getButtonSize(v2u32 screensize) g_settings->getFloat("hud_scaling")); } -const ButtonLayout ButtonLayout::predefined {{ +const ButtonLayout::ButtonMap ButtonLayout::default_data { + {dig_id, { + v2f(1.0f, 1.0f), + v2f(-2.0f, -2.75f), + }}, + {place_id, { + v2f(1.0f, 1.0f), + v2f(-2.0f, -4.25f), + }}, {jump_id, { v2f(1.0f, 1.0f), v2f(-1.0f, -0.5f), @@ -170,28 +197,40 @@ const ButtonLayout ButtonLayout::predefined {{ v2f(1.0f, 1.0f), v2f(-0.75f, -5.0f), }}, -}}; +}; + +ButtonLayout ButtonLayout::postProcessLoaded(const ButtonMap &data) +{ + ButtonLayout layout; + for (const auto &[id, meta] : data) { + assert(isButtonValid(id)); + if (isButtonAllowed(id)) + layout.layout.emplace(id, meta); + else + layout.preserved_disallowed.emplace(id, meta); + } + return layout; +} + +ButtonLayout ButtonLayout::loadDefault() +{ + return postProcessLoaded(default_data); +} ButtonLayout ButtonLayout::loadFromSettings() { - bool restored = false; - ButtonLayout layout; - std::string str = g_settings->get("touch_layout"); if (!str.empty()) { std::istringstream iss(str); try { - layout.deserializeJson(iss); - restored = true; + ButtonMap data = deserializeJson(iss); + return postProcessLoaded(data); } catch (const Json::Exception &e) { warningstream << "Could not parse touchscreen layout: " << e.what() << std::endl; } } - if (!restored) - return predefined; - - return layout; + return loadDefault(); } std::unordered_map> ButtonLayout::texture_cache; @@ -234,7 +273,7 @@ std::vector ButtonLayout::getMissingButtons() std::vector missing_buttons; for (u8 i = 0; i < touch_gui_button_id_END; i++) { touch_gui_button_id btn = (touch_gui_button_id)i; - if (isButtonAllowed(btn) && layout.count(btn) == 0) + if (isButtonValid(btn) && isButtonAllowed(btn) && layout.count(btn) == 0) missing_buttons.push_back(btn); } return missing_buttons; @@ -243,16 +282,19 @@ std::vector ButtonLayout::getMissingButtons() void ButtonLayout::serializeJson(std::ostream &os) const { Json::Value root = Json::objectValue; + root["version"] = 1; root["layout"] = Json::objectValue; - for (const auto &[id, meta] : layout) { - Json::Value button = Json::objectValue; - button["position_x"] = meta.position.X; - button["position_y"] = meta.position.Y; - button["offset_x"] = meta.offset.X; - button["offset_y"] = meta.offset.Y; + for (const auto &list : {layout, preserved_disallowed}) { + for (const auto &[id, meta] : list) { + Json::Value button = Json::objectValue; + button["position_x"] = meta.position.X; + button["position_y"] = meta.position.Y; + button["offset_x"] = meta.offset.X; + button["offset_y"] = meta.offset.Y; - root["layout"][button_names[id]] = button; + root["layout"][button_names[id]] = button; + } } fastWriteJson(root, os); @@ -267,13 +309,19 @@ static touch_gui_button_id button_name_to_id(const std::string &name) return touch_gui_button_id_END; } -void ButtonLayout::deserializeJson(std::istream &is) +ButtonLayout::ButtonMap ButtonLayout::deserializeJson(std::istream &is) { - layout.clear(); + ButtonMap data; Json::Value root; is >> root; + u8 version; + if (root["version"].isUInt()) + version = root["version"].asUInt(); + else + version = 0; + if (!root["layout"].isObject()) throw Json::RuntimeError("invalid type for layout"); @@ -281,7 +329,7 @@ void ButtonLayout::deserializeJson(std::istream &is) Json::ValueIterator iter; for (iter = obj.begin(); iter != obj.end(); iter++) { touch_gui_button_id id = button_name_to_id(iter.name()); - if (!isButtonAllowed(id)) + if (!isButtonValid(id)) throw Json::RuntimeError("invalid button name"); Json::Value &value = *iter; @@ -300,8 +348,20 @@ void ButtonLayout::deserializeJson(std::istream &is) meta.offset.X = value["offset_x"].asFloat(); meta.offset.Y = value["offset_y"].asFloat(); - layout.emplace(id, meta); + data.emplace(id, meta); } + + if (version < 1) { + // Version 0 did not have dig/place buttons, so add them in. + // Otherwise, the missing buttons would cause confusion if the user + // switches to "touch_interaction_style = buttons_crosshair". + // This may result in overlapping buttons (could be fixed by resolving + // collisions in postProcessLoaded). + data.emplace(dig_id, default_data.at(dig_id)); + data.emplace(place_id, default_data.at(place_id)); + } + + return data; } void layout_button_grid(v2u32 screensize, ISimpleTextureSource *tsrc, diff --git a/src/gui/touchscreenlayout.h b/src/gui/touchscreenlayout.h index fe0d99b9d..7a181d701 100644 --- a/src/gui/touchscreenlayout.h +++ b/src/gui/touchscreenlayout.h @@ -7,6 +7,7 @@ #include "irr_ptr.h" #include "irrlichttypes_bloated.h" #include "rect.h" +#include "util/enum_string.h" #include #include @@ -20,9 +21,16 @@ namespace irr::video class ITexture; } +enum TouchInteractionStyle : u8 +{ + TAP, + TAP_CROSSHAIR, + BUTTONS_CROSSHAIR, +}; +extern const struct EnumString es_TouchInteractionStyle[]; + enum touch_gui_button_id : u8 { - // these two are no actual buttons ... yet dig_id = 0, place_id, @@ -75,15 +83,34 @@ struct ButtonMeta { }; struct ButtonLayout { + using ButtonMap = std::unordered_map; + + static bool isButtonValid(touch_gui_button_id id); static bool isButtonAllowed(touch_gui_button_id id); static bool isButtonRequired(touch_gui_button_id id); static s32 getButtonSize(v2u32 screensize); + + // Returns the default layout. + // Note: Indirectly depends on settings. + static ButtonLayout loadDefault(); + // Reads the layout from the "touch_layout" setting. Falls back to loadDefault + // if the setting is absent or invalid. + // Note: Indirectly depends on additional settings. static ButtonLayout loadFromSettings(); static video::ITexture *getTexture(touch_gui_button_id btn, ISimpleTextureSource *tsrc); static void clearTextureCache(); - std::unordered_map layout; + ButtonMap layout; + + // Contains disallowed buttons that have been loaded. + // These are only preserved to be saved again later. + // This exists to prevent data loss: If you edit the layout while some button + // is temporarily disallowed, this prevents that button's custom position + // from being lost. See isButtonAllowed. + // This may result in overlapping buttons when the buttons are allowed again + // (could be fixed by resolving collisions in postProcessLoaded). + ButtonMap preserved_disallowed; core::recti getRect(touch_gui_button_id btn, v2u32 screensize, s32 button_size, ISimpleTextureSource *tsrc); @@ -91,11 +118,12 @@ struct ButtonLayout { std::vector getMissingButtons(); void serializeJson(std::ostream &os) const; - void deserializeJson(std::istream &is); - - static const ButtonLayout predefined; private: + static const ButtonMap default_data; + static ButtonMap deserializeJson(std::istream &is); + static ButtonLayout postProcessLoaded(const ButtonMap &map); + static std::unordered_map> texture_cache; }; diff --git a/src/migratesettings.h b/src/migratesettings.h index 5f6396914..4839f9479 100644 --- a/src/migratesettings.h +++ b/src/migratesettings.h @@ -28,4 +28,11 @@ void migrate_settings() } g_settings->remove("disable_anticheat"); } + + // Convert touch_use_crosshair to touch_interaction_style + if (g_settings->existsLocal("touch_use_crosshair")) { + bool value = g_settings->getBool("touch_use_crosshair"); + g_settings->set("touch_interaction_style", value ? "tap_crosshair" : "tap"); + g_settings->remove("touch_use_crosshair"); + } } diff --git a/textures/base/pack/dig_btn.png b/textures/base/pack/dig_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..d612d1134b21cb2d313540707e08d42df9778f23 GIT binary patch literal 2067 zcmcJQ`#%%q6UczJI{?r|-FRFhUbZ+@C}9=003|| zR~N6f%lLO-Dr>vta6lIT>xbN298bm!ea!Os=Bcx;2!TsZLKN$+cpl5L{3$0kV$|KL^S9^> zBZXrB&hLk^*=%;ou&I2LJl@`RnccP8gAsl0$u9q7N6L~avJ4$TnI@EB3QQp;s=y7n zDq*$29gy~ac@eflI7MEMB-J@=Hf`IHmD4YUh!=KoO}XK}&X=Y4ZdsM#*|DS}82V!h z9oul}VckKCI$)+^AwM&^FOBa-s zl;C1xS1hUn3CcW`a9PD*Oep86_p2#iY4;6I+^sG4h8w}%of3Kdq3{XV+({QUtmOiS znhcTv1D=pzc&wzz1{oxMd|wqC9sTqzpC7@zH_+ezff5xp02MEaTZB@n6#8k^7eE2e zd3Uwu=;!S0Y-A;umQF2K0o2c)uz`*a*I)}EmB}vmpZ|mLbFe>lCE$1M*_H+Vm;A2Z zo_KfjoygODTL)4#?DhdQFl~^n8rR+O=wX^jIU^&38lpY9F?6*%5G>L%{evcG22!P? zHA_oNfgLbwlQrJk+uJ~zw;|E(%EI(C9`)M5ZYN<2dSM~5JI{X5>_EW|$XIQ{2VRGF zoknI?@GbINRmS_K1Gj6_#bPmLI4E5f>9oS?n;B1Y_p7~G`}0Khh5bNqc_e~~%4ttT zWrD?zL7kmK;h*^dQ-1rzT8HSil52aij!7?kE`0X#Kfbb7KX@|Y_f2kt#Eev|L) z{P!;*Y3?@n{YMKw>J1t^I6dDOmDyxG(@Vxg-2u}z>RXYmGe=U0d%|_iZ`QJkD#DPg zTl-I@E%c~d6t$glDXiwouXOHH%~iwZUrAb`*0_q$;FdBEb9s+*X{3B)JZq}~ z0^Ei!deTUI_rW$}uJE)ty*D2Uh_&9iyZD9>a$_l~Gxl0@z@Q=+kH;S-#iwBCqRo8b z9>eHYkO@}oZN=~$s2Dm2dR(aLg~(Hzml>&5wQ6T!JU=<<(RT0}}pf7vs z6<9sNE`q5>!G3nPxy7*#SCfR*y{M~d+6^{PELxnSIq!kiLBhe*((M_bg5*|E!i0fS zzLqw|f}?-TW=5JZ5E<^z%(;)+Wic+OC(uFB4q`tMuLcS)NcL@sJs2BpG(}>;O)u@y zn_-p0v8b1E4o3>0hVgYTguW0B1W=z+kUQY<9;C{Eg)rIvr-JdbsS7K`vzbsJ3y-JV z&ay^7T;2u$Z2`IfX z5~_J=v~EX*(j<$~SE8o;-dmlh8j)oJiXmf-a-)R)^MxS}T9n!K&KAVn@8vjljy~)e zV=GZOCm~;FTAm<;z!)$PQ^-%Cyi2lUXs(gZm7<4;XPkg%u6_nbz1Ub?_@A^tdy<9+ zErj2{al9QOpD#8YIKj?Q!)|=|BydrJdQ+eaUIR{IbBHn1*puoY5g^z<=C}UB1e`6` zWZfpG;jSy5Kmg$x&BMA28jITUzgcLuEZ;?g_p;-FmEtn5?LF?h>}|;;(E3Ig^=Gc$WpE^Nn4x~ZTQGr8`SFr z%NgsPF1ijkeizh*DLLlRF%ttrDydrYbLM_EHz0VAng*IxjovctBfvw6~1PCvJCTm5s*?>0-Bo*M+whNr_&u?#8G^;TXqFEqS-M>Nanfp%2cbMS0}ah=+`1hXH3hft@Bh!;U4kQ0NCe zMNM*tT*qAJmX2_Aa+%|nsEiG{L^TmYZb_W&U`$dc zl-p*NTP}xXV{VeEa?f-^b(o`2O&Iy&sR)@2|(>dE3L?Sz-5q-2eau zR~MYu_fP)M?UMPPJ=VcP0LV4E;_SQ={}NI!XBD4C^qaW#?kTYw6{Z!{%+QVO+Fg*r z=L3&R-@ZAhXM&|cwdlzZ{p?|EAa!>l+XfInoJ1*e`i?!O`6#HiABjLeL z;4sT->&r0PvZ;Y}l6PZaCwR7OgSufQ5>eNw%NF6z=bPRrkM5i^x(N!DrO4Vq8*)ev zxB?fbEgTeqg8xfZ_ZISAAV{vNtz8z_uLyidSY}!_d-K72EsfX*ur*+ru@-VZg zr))e%HY`f<(H^0X=ltwRqUb1+&?J3zZ&-cLwS4lexAx{(g z8zCZMz|_PfEcB^Qdv*1Z44F3CLSPm3VD`gUec&QHo@&GwZ_g-qzc^gz61OF&YiQuS zWmQYBRzUdl-EVGTV*WL7+vs9NNl8h$_wgV4Up#qOEqzrsEC4?N)U6WHtZ+_wRZD_s zRz*RyvT_Qf6F!oH0ofCCbFkWQ&8ow3tkn}d37dO)ZKtlbzTRLTn3hQkM%Jq z^|Y=Eb-(10`7Uey1TX~?4aRalaG}P3#|{_L=S<;5?X~UEMqq4a7A|Jaq}|5KRpj%0 z)p4u`T${DQ$jC^xSeR7dCJj&xCvY?2_Blb607Pn$x`ohX-ifh(LJd5=6~QGtJ<~^Q zG&BM(;X&6Qk$(RETLPC_9_jh>)vi2%gfw!VQZ>3uAhPW$b6Bq*L1WU!!cfvhNrum zUV_6o0IzXreotuJ#Sn(Ir98m@8iVO-`Bs?jpp!|I#K*`H+7%xcF+Pwk;{DSw@E!oJ z6)KdY-ad&V3R#z+LGtpvg>{(Uy8HqR5ZyYWNrWFIsn?wq1PM^Fhn&pZD~mZcQ-9fM z&j%$~<8IDf6L%D!Rr4D_*!B20w6wMLf0D|S0mS!Q=& zLo=Em?8=PWn`(qA1GyPXL180C@{h@r7WaIoJe{!B#pBS zLwWuglbn})JJjB4NIwtpE3-deOVsrunjuHU4;nXG46T-o-J zi(<=HN!zA1986JsoeLzvq(0>P{L1ou=PdL@quB#8RjM`23tw`E^e|rZgCne5n^(65 zY!!*p;flarWY5#pSG!bHRLsrJl(|J7lH=HCiSiGSLtd%NI>YX3C7xI*CcdGky;xB2 z`>CRGDvk(%`fy;LxdCi0(s+j%j3b_nn0`Qz8zbNZn@2|`GR$wMdzAj&@ z>+TvA-I@Lfbm|Z=K@t>W*dXR&u_}nb(4JlN$?#TNQqYS&prXH|W5{eC*`N%h0Q?Po z7d?^5>TY}g*2M%@oP$5%dJjGQaus==4guN8Di+_S$RJa+1fS@;1?Ke{`Sk{++^)|8 z*!7L7*N$lUT)`w&8+04M%KoHgV&a1nJO(S^nz-`N0jjOjqy3xGFiEcF#Ik?4$$n-& zY9HWYsY3)##)5?TH{=^s1C?S8_n4So$bvDp^C@eivgv~X{mgdw{Z!tEaX;CD)Pwls z&)NXpm2oAOEri8yqovv2L^TNUu-7SyZwJ|8FFv{E@FIxgMm7(>DD_qzqJEz`lmA{P zJ~yGmc(PtgcA(e@M&>YoJmk~b;svu?49?O;&8LC$av*w6trc?KEq*c##b3?i~W<}t=l*ETIc@E=N{GU_Ru8L zYQ+;^8hr^Fuf`2Lj#WkTMAP%{;%LEIj`HBlM)Mnm-@m%?18fwt@2?jdQURMbDdO96 zj>U%chQmg(&c?6n1QxkS-*r-V&0^8gjcBI^MXPP7H*+q999VyZ)UYZP z5Cux@RI@vlrpLi_c^a;shm&XQW88QMDSd^P;&`Fk;Ns^kNR6fJDW#aB8n%2F)q_5o zNKnm0b@pjlA-N^;q;fPIOm`LFGjVMrB~}`}<8micTSF5+f6{lfr9Z2JRMx({>t;s* zq^GHB%8XwV6HpMc3U;5EE~L343TlH^ z6)>8s+Zk6frN~#tVz9y)f$5!LMHo1PPTnwttA0vMGIwl-8)C$|IAt=k9q-Xn$(}J6*2DJAc^N!Nn|9dl~Pj&sf1f1)O-zgda9nuQe6O z=Fg>Q#UYIIUv^`o7iX7gTkI0!?Ht7aMwX)Q_Q^f;-{a@3EZBjDIZ&!n9v3uxCme8f LbjQ`(2i^QPON(^? literal 0 HcmV?d00001 From 4ba438a7ec10b2c9fc1ab4a7df35573a71ea2ecf Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:07:51 +0100 Subject: [PATCH 069/284] Improve KeyPress handling (#15923) * Pass KeyPress by value * TouchControls: add setting change callback for keybindings --- src/client/inputhandler.cpp | 6 +++--- src/client/inputhandler.h | 28 ++++++++++++++-------------- src/client/keycode.cpp | 4 ++-- src/client/keycode.h | 12 +++++++----- src/gui/guiKeyChangeMenu.cpp | 4 ++-- src/gui/touchcontrols.cpp | 32 ++++++++++++++++++++++---------- src/gui/touchcontrols.h | 2 +- 7 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 76019476e..a33931503 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -77,7 +77,7 @@ void KeyCache::populate() if (handler) { // First clear all keys, then re-add the ones we listen for handler->dontListenForKeys(); - for (const KeyPress &k : key) { + for (auto k : key) { handler->listenForKey(k); } handler->listenForKey(EscapeKey); @@ -111,7 +111,7 @@ bool MyEventReceiver::OnEvent(const SEvent &event) // This is separate from other keyboard handling so that it also works in menus. if (event.EventType == EET_KEY_INPUT_EVENT) { - const KeyPress keyCode(event.KeyInput); + KeyPress keyCode(event.KeyInput); if (keyCode == getKeySetting("keymap_fullscreen")) { if (event.KeyInput.PressedDown && !fullscreen_is_down) { IrrlichtDevice *device = RenderingEngine::get_raw_device(); @@ -142,7 +142,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); + KeyPress keyCode(event.KeyInput); if (keyCode && keysListenedFor[keyCode]) { // ignore key input that is invalid or irrelevant for the game. if (event.KeyInput.PressedDown) { if (!IsKeyDown(keyCode)) diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 542c41bc7..6de1a0575 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -53,7 +53,7 @@ class KeyList : private std::list typedef super::iterator iterator; typedef super::const_iterator const_iterator; - virtual const_iterator find(const KeyPress &key) const + virtual const_iterator find(KeyPress key) const { const_iterator f(begin()); const_iterator e(end()); @@ -68,7 +68,7 @@ class KeyList : private std::list return e; } - virtual iterator find(const KeyPress &key) + virtual iterator find(KeyPress key) { iterator f(begin()); iterator e(end()); @@ -86,13 +86,13 @@ class KeyList : private std::list public: void clear() { super::clear(); } - void set(const KeyPress &key) + void set(KeyPress key) { if (find(key) == end()) push_back(key); } - void unset(const KeyPress &key) + void unset(KeyPress key) { iterator p(find(key)); @@ -100,7 +100,7 @@ public: erase(p); } - void toggle(const KeyPress &key) + void toggle(KeyPress key) { iterator p(this->find(key)); @@ -112,12 +112,12 @@ public: void append(const KeyList &other) { - for (const KeyPress &key : other) { + for (auto key : other) { set(key); } } - bool operator[](const KeyPress &key) const { return find(key) != end(); } + bool operator[](KeyPress key) const { return find(key) != end(); } }; class MyEventReceiver : public IEventReceiver @@ -126,10 +126,10 @@ 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(KeyPress keyCode) const { return keyIsDown[keyCode]; } // Checks whether a key was down and resets the state - bool WasKeyDown(const KeyPress &keyCode) + bool WasKeyDown(KeyPress keyCode) { bool b = keyWasDown[keyCode]; if (b) @@ -139,13 +139,13 @@ public: // 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(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]; } + bool WasKeyReleased(KeyPress keycode) const { return keyWasReleased[keycode]; } - void listenForKey(const KeyPress &keyCode) + void listenForKey(KeyPress keyCode) { keysListenedFor.set(keyCode); } @@ -247,7 +247,7 @@ public: virtual void clearWasKeyPressed() {} virtual void clearWasKeyReleased() {} - virtual void listenForKey(const KeyPress &keyCode) {} + virtual void listenForKey(KeyPress keyCode) {} virtual void dontListenForKeys() {} virtual v2s32 getMousePos() = 0; @@ -316,7 +316,7 @@ public: m_receiver->clearWasKeyReleased(); } - virtual void listenForKey(const KeyPress &keyCode) + virtual void listenForKey(KeyPress keyCode) { m_receiver->listenForKey(keyCode); } diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp index 18e250959..71e068e30 100644 --- a/src/client/keycode.cpp +++ b/src/client/keycode.cpp @@ -367,7 +367,7 @@ bool KeyPress::loadFromScancode(const std::string &name) } std::unordered_map specialKeyCache; -const KeyPress &KeyPress::getSpecialKey(const std::string &name) +KeyPress KeyPress::getSpecialKey(const std::string &name) { auto &key = specialKeyCache[name]; if (!key) @@ -382,7 +382,7 @@ const KeyPress &KeyPress::getSpecialKey(const std::string &name) // A simple cache for quicker lookup static std::unordered_map g_key_setting_cache; -const KeyPress &getKeySetting(const std::string &settingname) +KeyPress getKeySetting(const std::string &settingname) { auto n = g_key_setting_cache.find(settingname); if (n != g_key_setting_cache.end()) diff --git a/src/client/keycode.h b/src/client/keycode.h index a494ec930..038c47ebd 100644 --- a/src/client/keycode.h +++ b/src/client/keycode.h @@ -10,7 +10,9 @@ #include #include -/* A key press, consisting of a scancode or a keycode */ +/* A key press, consisting of a scancode or a keycode. + * This fits into 64 bits, so prefer passing this by value. +*/ class KeyPress { public: @@ -40,10 +42,10 @@ public: return 0; } - bool operator==(const KeyPress &o) const { + bool operator==(KeyPress o) const { return scancode == o.scancode; } - bool operator!=(const KeyPress &o) const { + bool operator!=(KeyPress o) const { return !(*this == o); } @@ -55,7 +57,7 @@ public: std::get(scancode) != 0; } - static const KeyPress &getSpecialKey(const std::string &name); + static KeyPress getSpecialKey(const std::string &name); private: bool loadFromScancode(const std::string &name); @@ -74,7 +76,7 @@ private: #define RMBKey KeyPress::getSpecialKey("KEY_RBUTTON") // Key configuration getter -const KeyPress &getKeySetting(const std::string &settingname); +KeyPress getKeySetting(const std::string &settingname); // Clear fast lookup cache void clearKeyCache(); diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 9b8c8c416..ba94fa2f1 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -205,6 +205,8 @@ void GUIKeyChangeMenu::drawMenu() bool GUIKeyChangeMenu::acceptInput() { + clearKeyCache(); + for (key_setting *k : key_settings) { std::string default_key; Settings::getLayer(SL_DEFAULTS)->getNoEx(k->setting_name, default_key); @@ -231,8 +233,6 @@ bool GUIKeyChangeMenu::acceptInput() g_settings->setBool("autojump", ((gui::IGUICheckBox*)e)->isChecked()); } - clearKeyCache(); - g_gamecallback->signalKeyConfigChange(); return true; diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index 4cea023ab..6a3aeb108 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -31,7 +31,7 @@ TouchControls *g_touchcontrols; -void TouchControls::emitKeyboardEvent(const KeyPress &key, bool pressed) +void TouchControls::emitKeyboardEvent(KeyPress key, bool pressed) { SEvent e{}; e.EventType = EET_KEY_INPUT_EVENT; @@ -142,12 +142,8 @@ bool TouchControls::buttonsStep(std::vector &buttons, float dtime) return has_pointers; } -static const KeyPress &id_to_keypress(touch_gui_button_id id) +static std::string id_to_setting(touch_gui_button_id id) { - // ESC isn't part of the keymap. - if (id == exit_id) - return EscapeKey; - std::string key = ""; switch (id) { case dig_id: @@ -204,11 +200,22 @@ static const KeyPress &id_to_keypress(touch_gui_button_id id) default: break; } - assert(!key.empty()); - auto &kp = getKeySetting("keymap_" + key); + return key.empty() ? key : "keymap_" + key; +} + +static KeyPress id_to_keypress(touch_gui_button_id id) +{ + // ESC isn't part of the keymap. + if (id == exit_id) + return EscapeKey; + + auto setting_name = id_to_setting(id); + + assert(!setting_name.empty()); + auto kp = getKeySetting(setting_name); if (!kp) - warningstream << "TouchControls: Unbound or invalid key for" - << key << ", hiding button." << std::endl; + warningstream << "TouchControls: Unbound or invalid key for " + << setting_name << ", hiding button." << std::endl; return kp; } @@ -232,6 +239,11 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc) readSettings(); for (auto name : setting_names) g_settings->registerChangedCallback(name, settingChangedCallback, this); + + // Also update layout when keybindings change (e.g. for convertibles) + for (u8 id = 0; id < touch_gui_button_id_END; id++) + if (auto name = id_to_setting((touch_gui_button_id)id); !name.empty()) + g_settings->registerChangedCallback(name, settingChangedCallback, this); } void TouchControls::settingChangedCallback(const std::string &name, void *data) diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index c2ed8ae7b..52a3bd6b2 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -204,7 +204,7 @@ private: // for its buttons. We only want static image display, not interactivity, // from Irrlicht. - void emitKeyboardEvent(const KeyPress &keycode, bool pressed); + void emitKeyboardEvent(KeyPress keycode, bool pressed); void loadButtonTexture(IGUIImage *gui_button, const std::string &path); void buttonEmitAction(button_info &btn, bool action); From b1363fce8e60ba49fa104f1776b63c352ae7ce68 Mon Sep 17 00:00:00 2001 From: ashtrayoz <33517241+ashtrayoz@users.noreply.github.com> Date: Tue, 25 Mar 2025 07:35:20 +1100 Subject: [PATCH 070/284] Fix logic inversion for dynamic media (server-side fix) (#15870) --- src/server.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/server.cpp b/src/server.cpp index af6fed3a0..68b27513d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -3750,7 +3750,11 @@ bool Server::dynamicAddMedia(const DynamicMediaArgs &a) // Push file to existing clients if (m_env) { NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0); - pkt << raw_hash << filename << static_cast(a.ephemeral); + pkt << raw_hash << filename; + // NOTE: the meaning of a.ephemeral was accidentally inverted between proto 39 and 40, + // when dynamic_add_media v2 was added. As of 5.12.0 the server sends it correctly again. + // Compatibility code on the client-side was not added. + pkt << static_cast(!a.ephemeral); NetworkPacket legacy_pkt = pkt; From 251bf0ec317a125257358768da2e4e40b0d01539 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 26 Mar 2025 14:31:27 -0300 Subject: [PATCH 071/284] Fix possible nullptr dereference in serverpackethandler.cpp --- src/network/serverpackethandler.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index d7adfac38..d9a38e878 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -416,6 +416,8 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) ClientInterface::AutoLock lock(m_clients); RemoteClient *client = m_clients.lockedGetClientNoEx(pkt->getPeerId()); + if (!client) + return; for (u16 i = 0; i < count; i++) { v3s16 p; @@ -538,6 +540,8 @@ void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) ClientInterface::AutoLock lock(m_clients); RemoteClient *client = m_clients.lockedGetClientNoEx(pkt->getPeerId()); + if (!client) + return; for (u16 i = 0; i < count; i++) { v3s16 p; From b99f90ed80c469e14d2a2140fa8608be9f735632 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 26 Mar 2025 13:31:44 -0400 Subject: [PATCH 072/284] Mainmenu: Trim whitespace from player name, address, port --- src/script/lua_api/l_mainmenu.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 0969bb525..4f41f4075 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -30,6 +30,7 @@ #include "common/c_converter.h" #include "gui/guiOpenURL.h" #include "gettext.h" +#include "util/string.h" /******************************************************************************/ std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name) @@ -126,10 +127,13 @@ int ModApiMainMenu::l_start(lua_State *L) 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"); + // Get rid of trailing whitespace in name (may be added by autocompletion + // on Android, which would then cause SERVER_ACCESSDENIED_WRONG_CHARS_IN_NAME). + data->name = trim(getTextData(L, "playername")); + data->password = getTextData(L, "password"); + // There's no reason for these to have leading/trailing whitespace either. + data->address = trim(getTextData(L, "address")); + data->port = trim(getTextData(L, "port")); const auto val = getTextData(L, "allow_login_or_register"); if (val == "login") From 95d60083328e88ecfe001c3842342019dcc0835f Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Wed, 26 Mar 2025 18:32:23 +0100 Subject: [PATCH 073/284] IrrlichtMt: Fix orientation of IRenderTarget-textures (#15932) Textures created through a render target are flipped along the X axis. For the same reason, screenshots must be flipped back as well ('IVideoDriver::createScreenShot'). This commit implements the same flipping concept in two places: 1. Batch rendering of images (mostly used by fonts/text) 2. In-world rendering of such textures --- irr/src/OpenGL/Driver.cpp | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 2f6a3ebdf..e83a5b3ec 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -867,21 +867,25 @@ void COpenGL3DriverBase::draw2DImageBatch(const video::ITexture *texture, std::vector vtx; vtx.reserve(drawCount * 4); + // texcoords need to be flipped horizontally for RTTs + const bool isRTT = texture->isRenderTarget(); + const core::dimension2du ss = texture->getOriginalSize(); + const f32 invW = 1.f / static_cast(ss.Width); + const f32 invH = 1.f / static_cast(ss.Height); + for (u32 i = 0; i < drawCount; i++) { - core::position2d targetPos = positions[i]; - core::position2d sourcePos = sourceRects[i].UpperLeftCorner; - // This needs to be signed as it may go negative. - core::dimension2d sourceSize(sourceRects[i].getSize()); + const core::position2d targetPos = positions[i]; + const core::rect sourceRect = sourceRects[i]; // now draw it. - core::rect tcoords; - tcoords.UpperLeftCorner.X = (((f32)sourcePos.X)) / texture->getOriginalSize().Width; - tcoords.UpperLeftCorner.Y = (((f32)sourcePos.Y)) / texture->getOriginalSize().Height; - tcoords.LowerRightCorner.X = tcoords.UpperLeftCorner.X + ((f32)(sourceSize.Width) / texture->getOriginalSize().Width); - tcoords.LowerRightCorner.Y = tcoords.UpperLeftCorner.Y + ((f32)(sourceSize.Height) / texture->getOriginalSize().Height); + const core::rect tcoords( + sourceRect.UpperLeftCorner.X * invW, + (isRTT ? sourceRect.LowerRightCorner.Y : sourceRect.UpperLeftCorner.Y) * invH, + sourceRect.LowerRightCorner.X * invW, + (isRTT ? sourceRect.UpperLeftCorner.Y : sourceRect.LowerRightCorner.Y) * invH); - const core::rect poss(targetPos, sourceSize); + const core::rect poss(targetPos, sourceRect.getSize()); f32 left = (f32)poss.UpperLeftCorner.X; f32 right = (f32)poss.LowerRightCorner.X; @@ -1084,6 +1088,14 @@ ITexture *COpenGL3DriverBase::createDeviceDependentTextureCubemap(const io::path return texture; } +// Same as COpenGLDriver::TextureFlipMatrix +static const core::matrix4 s_texture_flip_matrix = { + 1, 0, 0, 0, + 0, -1, 0, 0, + 0, 1, 1, 0, + 0, 0, 0, 1 +}; + //! Sets a material. void COpenGL3DriverBase::setMaterial(const SMaterial &material) { @@ -1094,7 +1106,11 @@ void COpenGL3DriverBase::setMaterial(const SMaterial &material) auto *texture = material.getTexture(i); CacheHandler->getTextureCache().set(i, texture); if (texture) { - setTransform((E_TRANSFORMATION_STATE)(ETS_TEXTURE_0 + i), material.getTextureMatrix(i)); + setTransform((E_TRANSFORMATION_STATE)(ETS_TEXTURE_0 + i), + texture->isRenderTarget() + ? material.getTextureMatrix(i) * s_texture_flip_matrix + : material.getTextureMatrix(i) + ); } } } From 8fc7bf2af40905715550922baff98035b6af4ba6 Mon Sep 17 00:00:00 2001 From: JosiahWI <41302989+JosiahWI@users.noreply.github.com> Date: Wed, 26 Mar 2025 13:03:53 -0500 Subject: [PATCH 074/284] Fix `core.get_content_info` docs and harden implementation (#15925) --- doc/menu_lua_api.md | 2 +- src/script/lua_api/l_mainmenu.cpp | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index d1b126351..0fccf37a3 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -331,7 +331,7 @@ Package - content which is downloadable from the content db, may or may not be i ```lua { name = "technical_id", - type = "mod" or "modpack" or "game" or "txp", + type = "mod" or "modpack" or "game" or "txp" or "unknown", title = "Human readable title", description = "description", author = "author", diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 4f41f4075..f6734c788 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -30,8 +30,12 @@ #include "common/c_converter.h" #include "gui/guiOpenURL.h" #include "gettext.h" +#include "log.h" #include "util/string.h" +#include +#include + /******************************************************************************/ std::string ModApiMainMenu::getTextData(lua_State *L, const std::string &name) { @@ -355,6 +359,14 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) spec.path = path; parseContentInfo(spec); + if (spec.type == "unknown") { + // In <=5.11.0 the API call was erroneously not documented as + // being able to return type "unknown". + // TODO inspect call sites and make sure this is handled, then we can + // likely remove the warning. + warningstream << "Requested content info has type \"unknown\"" << std::endl; + } + lua_newtable(L); lua_pushstring(L, spec.name.c_str()); @@ -369,11 +381,6 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) lua_pushstring(L, spec.author.c_str()); lua_setfield(L, -2, "author"); - if (!spec.title.empty()) { - lua_pushstring(L, spec.title.c_str()); - lua_setfield(L, -2, "title"); - } - lua_pushinteger(L, spec.release); lua_setfield(L, -2, "release"); @@ -389,8 +396,12 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) if (spec.type == "mod") { ModSpec spec; spec.path = path; - // TODO return nothing on failure (needs callers to handle it) - static_cast(parseModContents(spec)); + // Since the content was already determined to be a mod, + // the parsing is guaranteed to succeed unless the init.lua + // file happens to be deleted between the content parse and + // the mod parse. + [[maybe_unused]] bool success = parseModContents(spec); + assert(success); // Dependencies lua_newtable(L); From ea1e8797a3eb949efb8a6de583a8826eaed26911 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 18 Mar 2025 20:06:39 +0100 Subject: [PATCH 075/284] Fix performance bug with applying LBMs --- src/serverenvironment.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index bab243713..9a810434f 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -270,7 +270,8 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, if (previous_c != c) { c_changed = true; lbm_list = it->second.lookup(c); - batch = &to_run[c]; + if (lbm_list) + batch = &to_run[c]; // creates entry previous_c = c; } @@ -301,6 +302,8 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, else ++it2; } + } else { + assert(!batch.p.empty()); } first = false; @@ -1061,9 +1064,6 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) dtime_s = m_game_time - stamp; dtime_s += additional_dtime; - /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: " - <setTimestampNoChangedFlag(m_game_time); - /*infostream<<"ServerEnvironment::activateBlock(): block is " - <isOrphan()) From 71cd25a798b1414f9f01fd09acf5842f2c3511e8 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 18 Mar 2025 20:26:11 +0100 Subject: [PATCH 076/284] Preserve LBM ordering when running them (broken in 811adf5d42844bbd40e98e07773db3d16e104b90) --- src/serverenvironment.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 9a810434f..b299d1f7e 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -237,6 +237,21 @@ std::string LBMManager::createIntroductionTimesString() return oss.str(); } +namespace { + struct LBMToRun { + std::unordered_set p; // node positions + std::vector l; // ordered list of LBMs + + template + void insertLBMs(const C &container) { + for (auto &it : container) { + if (!CONTAINS(l, it)) + l.push_back(it); + } + } + }; +} + void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, const u32 stamp, const float dtime_s) { @@ -245,10 +260,6 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, "attempted to query on non fully set up LBMManager"); // Collect a list of all LBMs and associated positions - struct LBMToRun { - std::unordered_set p; // node positions - std::unordered_set l; - }; std::unordered_map to_run; // Note: the iteration count of this outer loop is typically very low, so it's ok. @@ -279,7 +290,7 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, continue; batch->p.insert(pos); if (c_changed) { - batch->l.insert(lbm_list->begin(), lbm_list->end()); + batch->insertLBMs(*lbm_list); } else { // we were here before so the list must be filled assert(!batch->l.empty()); @@ -290,6 +301,11 @@ void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, // Actually run them bool first = true; for (auto &[c, batch] : to_run) { + if (tracestream) { + tracestream << "Running " << batch.l.size() << " LBMs for node " + << env->getGameDef()->ndef()->get(c).name << " (" + << batch.p.size() << "x) in block " << block->getPos() << std::endl; + } for (auto &lbm_def : batch.l) { if (!first) { // The fun part: since any LBM call can change the nodes inside of he From 3dca2cd26aae776f78859bc15ce46d54ea400cf5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 18 Mar 2025 20:36:18 +0100 Subject: [PATCH 077/284] Strip leading colon from LBM names --- src/serverenvironment.cpp | 58 ++++++++++++++++++++++----------------- src/serverenvironment.h | 3 ++ 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index b299d1f7e..883f23f4f 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -129,7 +129,11 @@ void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def) FATAL_ERROR_IF(m_query_mode, "attempted to modify LBMManager in query mode"); - if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) { + if (str_starts_with(lbm_def->name, ":")) + lbm_def->name.erase(0, 1); + + if (lbm_def->name.empty() || + !string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) { throw ModError("Error adding LBM \"" + lbm_def->name + "\": Does not follow naming conventions: " "Only characters [a-z0-9_:] are allowed."); @@ -143,30 +147,7 @@ void LBMManager::loadIntroductionTimes(const std::string ×, { m_query_mode = true; - // name -> time map. - // Storing it in a map first instead of - // handling the stuff directly in the loop - // removes all duplicate entries. - std::unordered_map introduction_times; - - /* - The introduction times string consists of name~time entries, - with each entry terminated by a semicolon. The time is decimal. - */ - - size_t idx = 0; - size_t idx_new; - while ((idx_new = times.find(';', idx)) != std::string::npos) { - 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 '~'!"); - const std::string &name = components[0]; - u32 time = from_string(components[1]); - introduction_times[name] = time; - idx = idx_new + 1; - } + auto introduction_times = parseIntroductionTimesString(times); // Put stuff from introduction_times into m_lbm_lookup for (auto &it : introduction_times) { @@ -237,6 +218,33 @@ std::string LBMManager::createIntroductionTimesString() return oss.str(); } +std::unordered_map + LBMManager::parseIntroductionTimesString(const std::string ×) +{ + std::unordered_map ret; + + size_t idx = 0; + size_t idx_new; + while ((idx_new = times.find(';', idx)) != std::string::npos) { + std::string entry = times.substr(idx, idx_new - idx); + idx = idx_new + 1; + + std::vector components = str_split(entry, '~'); + if (components.size() != 2) + throw SerializationError("Introduction times entry \"" + + entry + "\" requires exactly one '~'!"); + if (components[0].empty()) + throw SerializationError("LBM name is empty"); + std::string name = std::move(components[0]); + if (name.front() == ':') // old versions didn't strip this + name.erase(0, 1); + u32 time = from_string(components[1]); + ret[std::move(name)] = time; + } + + return ret; +} + namespace { struct LBMToRun { std::unordered_set p; // node positions diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 89d33d9bf..ea8d4c711 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -149,6 +149,9 @@ private: // The key of the map is the LBM def's first introduction time. lbm_lookup_map m_lbm_lookup; + static std::unordered_map + parseIntroductionTimesString(const std::string ×); + // Returns an iterator to the LBMs that were introduced // after the given time. This is guaranteed to return // valid values for everything From ed40ea010b80fadcc25074dbd434eb1f05622217 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 18 Mar 2025 20:57:19 +0100 Subject: [PATCH 078/284] Improve edge case handling in LBMManager --- src/serverenvironment.cpp | 57 +++++++++++++++++++++------------------ src/serverenvironment.h | 1 + 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 883f23f4f..e52bd9369 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -150,50 +150,56 @@ void LBMManager::loadIntroductionTimes(const std::string ×, auto introduction_times = parseIntroductionTimesString(times); // Put stuff from introduction_times into m_lbm_lookup - for (auto &it : introduction_times) { - const std::string &name = it.first; - u32 time = it.second; - + for (auto &[name, time] : introduction_times) { auto def_it = 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. + infostream << "LBMManager: LBM " << name << " is not registered. " + "Discarding it." << std::endl; continue; } - LoadingBlockModifierDef *lbm_def = def_it->second; + auto *lbm_def = def_it->second; if (lbm_def->run_at_every_load) { - // This seems to be an LBM entry for - // an LBM that runs at every load. - // Don't add it just yet. + continue; // These are handled below + } + if (time > now) { + warningstream << "LBMManager: LBM " << name << " was introduced in " + "the future. Pretending it's new." << std::endl; + // By skipping here it will be added as newly introduced. continue; } m_lbm_lookup[time].addLBM(lbm_def, gamedef); // Erase the entry so that we know later - // what elements didn't get put into m_lbm_lookup - m_lbm_defs.erase(name); + // which elements didn't get put into m_lbm_lookup + m_lbm_defs.erase(def_it); } // Now also add the elements from m_lbm_defs to m_lbm_lookup // that weren't added in the previous step. // They are introduced first time to this world, - // or are run at every load (introducement time hardcoded to U32_MAX). + // or are run at every load (introduction time hardcoded to U32_MAX). - LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now]; - LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX]; - - for (auto &m_lbm_def : m_lbm_defs) { - if (m_lbm_def.second->run_at_every_load) { - lbms_running_always.addLBM(m_lbm_def.second, gamedef); - } else { - lbms_we_introduce_now.addLBM(m_lbm_def.second, gamedef); - } + auto &lbms_we_introduce_now = m_lbm_lookup[now]; + auto &lbms_running_always = m_lbm_lookup[U32_MAX]; + for (auto &it : m_lbm_defs) { + if (it.second->run_at_every_load) + lbms_running_always.addLBM(it.second, gamedef); + else + lbms_we_introduce_now.addLBM(it.second, gamedef); } - // Clear the list, so that we don't delete remaining elements - // twice in the destructor + // All pointer ownership now moved to LBMContentMapping m_lbm_defs.clear(); + + // If these are empty delete them again to avoid pointless iteration. + if (lbms_we_introduce_now.empty()) + m_lbm_lookup.erase(now); + if (lbms_running_always.empty()) + m_lbm_lookup.erase(U32_MAX); + + infostream << "LBMManager: " << m_lbm_lookup.size() << + " unique times in lookup table" << std::endl; } std::string LBMManager::createIntroductionTimesString() @@ -208,8 +214,7 @@ std::string LBMManager::createIntroductionTimesString() auto &lbm_list = it.second.getList(); for (const auto &lbm_def : lbm_list) { // Don't add if the LBM runs at every load, - // then introducement time is hardcoded - // and doesn't need to be stored + // then introduction time is hardcoded and doesn't need to be stored. if (lbm_def->run_at_every_load) continue; oss << lbm_def->name << "~" << time << ";"; diff --git a/src/serverenvironment.h b/src/serverenvironment.h index ea8d4c711..8b1cc98b6 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -103,6 +103,7 @@ public: void addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef); const lbm_map::mapped_type *lookup(content_t c) const; const lbm_vector &getList() const { return lbm_list; } + bool empty() const { return lbm_list.empty(); } // This struct owns the LBM pointers. ~LBMContentMapping(); From 7b746d21f9eec2a36161486b77d7317dd9f5ce21 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 18 Mar 2025 21:50:17 +0100 Subject: [PATCH 079/284] Make sure generated blocks have their timestamp set behavior change: newly generated blocks are no longer momentarily activated. this shouldn't matter for anyone and did not consistently apply to all blocks anyway addresses issue from #15902 for new maps(!) --- doc/lua_api.md | 15 ++++++++++----- src/emerge.cpp | 8 ++------ src/serverenvironment.cpp | 13 +++++++++++-- src/serverenvironment.h | 23 ++++++++++++++++++++--- src/servermap.cpp | 20 +++++++++----------- src/servermap.h | 6 +++++- src/unittest/test_sao.cpp | 6 +++--- src/util/container.h | 7 ++++++- 8 files changed, 66 insertions(+), 32 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index e1fb93a4d..f0d9624d0 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -9479,12 +9479,17 @@ Used by `core.register_lbm`. A loading block modifier (LBM) is used to define a function that is called for specific nodes (defined by `nodenames`) when a mapblock which contains such nodes -gets activated (not loaded!). +gets activated (**not loaded!**). -Note: LBMs operate on a "snapshot" of node positions taken once before they are triggered. +*Note*: LBMs operate on a "snapshot" of node positions taken once before they are triggered. That means if an LBM callback adds a node, it won't be taken into account. -However the engine guarantees that when the callback is called that all given position(s) -contain a matching node. +However the engine guarantees that at the point in time when the callback is called +that all given positions contain a matching node. + +*Note*: For maps generated in 5.11.0 or older, many newly generated blocks did not +get a timestamp set. This means LBMs introduced between generation time and +time of first activation will never run. +Currently the only workaround is to use `run_at_every_load`. ```lua { @@ -9508,7 +9513,7 @@ contain a matching node. action = function(pos, node, dtime_s) end, -- Function triggered for each qualifying node. -- `dtime_s` is the in-game time (in seconds) elapsed since the block - -- was last active. + -- was last active (available since 5.7.0). bulk_action = function(pos_list, dtime_s) end, -- Function triggered with a list of all applicable node positions at once. diff --git a/src/emerge.cpp b/src/emerge.cpp index 71e1ed7c5..29134bccf 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -581,7 +581,8 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, Perform post-processing on blocks (invalidate lighting, queue liquid transforms, etc.) to finish block make */ - m_map->finishBlockMake(bmdata, modified_blocks); + m_map->finishBlockMake(bmdata, modified_blocks, + m_server->m_env->getGameTime()); MapBlock *block = m_map->getBlockNoCreateNoEx(pos); if (!block) { @@ -619,11 +620,6 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, m_mapgen->gennotify.clearEvents(); m_mapgen->vm = nullptr; - /* - Activate the block - */ - m_server->m_env->activateBlock(block, 0); - return block; } diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index e52bd9369..28ca0f889 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -394,10 +394,8 @@ void ActiveBlockList::update(std::vector &active_players, */ std::set newlist = m_forceloaded_list; std::set extralist; - m_abm_list = m_forceloaded_list; for (const PlayerSAO *playersao : active_players) { v3s16 pos = getNodeBlockPos(floatToInt(playersao->getBasePosition(), BS)); - 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()); @@ -417,6 +415,8 @@ void ActiveBlockList::update(std::vector &active_players, } } + m_abm_list = newlist; + /* Find out which blocks on the new list are not on the old list */ @@ -448,6 +448,7 @@ void ActiveBlockList::update(std::vector &active_players, Do some least-effort sanity checks to hopefully catch code bugs. */ assert(newlist.size() >= extralist.size()); + assert(newlist.size() >= m_abm_list.size()); assert(blocks_removed.size() <= m_list.size()); if (!blocks_added.empty()) { assert(newlist.count(*blocks_added.begin()) > 0); @@ -1076,6 +1077,14 @@ public: } }; + +void ServerEnvironment::forceActivateBlock(MapBlock *block) +{ + assert(block); + if (m_active_blocks.add(block->getPos())) + activateBlock(block); +} + void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) { // Reset usage timer immediately, otherwise a block that becomes active diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 8b1cc98b6..6d9584044 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -124,6 +124,7 @@ public: // Don't call this after loadIntroductionTimes() ran. void addLBMDef(LoadingBlockModifierDef *lbm_def); + /// @param now current game time void loadIntroductionTimes(const std::string ×, IGameDef *gamedef, u32 now); @@ -150,6 +151,7 @@ private: // The key of the map is the LBM def's first introduction time. lbm_lookup_map m_lbm_lookup; + /// @return map of LBM name -> timestamp static std::unordered_map parseIntroductionTimesString(const std::string ×); @@ -186,12 +188,24 @@ public: m_list.clear(); } + /// @return true if block was newly added + bool add(v3s16 p) { + if (m_list.insert(p).second) { + m_abm_list.insert(p); + return true; + } + return false; + } + void remove(v3s16 p) { m_list.erase(p); m_abm_list.erase(p); } + // list of all active blocks std::set m_list; + // list of blocks for ABM processing + // subset of `m_list` that does not contain view cone affected blocks std::set m_abm_list; // list of blocks that are always active, not modified by this class std::set m_forceloaded_list; @@ -312,10 +326,10 @@ public: ); /* - Activate objects and dynamically modify for the dtime determined - from timestamp and additional_dtime + Force a block to become active. It will probably be deactivated + the next time active blocks are re-calculated. */ - void activateBlock(MapBlock *block, u32 additional_dtime=0); + void forceActivateBlock(MapBlock *block); /* {Active,Loading}BlockModifiers @@ -404,6 +418,9 @@ private: const std::string &savedir, const Settings &conf); static AuthDatabase *openAuthDatabase(const std::string &name, const std::string &savedir, const Settings &conf); + + void activateBlock(MapBlock *block, u32 additional_dtime=0); + /* Internal ActiveObject interface ------------------------------------------- diff --git a/src/servermap.cpp b/src/servermap.cpp index 87e886492..ab58510d2 100644 --- a/src/servermap.cpp +++ b/src/servermap.cpp @@ -199,6 +199,7 @@ bool ServerMap::blockpos_over_mapgen_limit(v3s16 p) bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) { + assert(data); s16 csize = getMapgenParams()->chunksize; v3s16 bpmin = EmergeManager::getContainingChunk(blockpos, csize); v3s16 bpmax = bpmin + v3s16(1, 1, 1) * (csize - 1); @@ -263,8 +264,10 @@ bool ServerMap::initBlockMake(v3s16 blockpos, BlockMakeData *data) } void ServerMap::finishBlockMake(BlockMakeData *data, - std::map *changed_blocks) + std::map *changed_blocks, u32 now) { + assert(data); + assert(changed_blocks); v3s16 bpmin = data->blockpos_min; v3s16 bpmax = data->blockpos_max; @@ -283,7 +286,7 @@ void ServerMap::finishBlockMake(BlockMakeData *data, /* Copy transforming liquid information */ - while (data->transforming_liquid.size()) { + while (!data->transforming_liquid.empty()) { m_transforming_liquid.push_back(data->transforming_liquid.front()); data->transforming_liquid.pop_front(); } @@ -297,15 +300,13 @@ void ServerMap::finishBlockMake(BlockMakeData *data, */ block->expireIsAirCache(); /* - Set block as modified + Set block as modified (if it isn't already) */ block->raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_EXPIRE_IS_AIR); } - /* - Set central blocks as generated - */ + // Note: this does not apply to the extra border area 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++) { @@ -314,13 +315,10 @@ void ServerMap::finishBlockMake(BlockMakeData *data, continue; block->setGenerated(true); + // Set timestamp to ensure correct application of LBMs and other stuff + block->setTimestampNoChangedFlag(now); } - /* - Save changed parts of map - NOTE: Will be saved later. - */ - //save(MOD_STATE_WRITE_AT_UNLOAD); m_chunks_in_progress.erase(bpmin); } diff --git a/src/servermap.h b/src/servermap.h index 73b35e38f..7b33a66a9 100644 --- a/src/servermap.h +++ b/src/servermap.h @@ -61,9 +61,13 @@ public: Blocks are generated by using these and makeBlock(). */ bool blockpos_over_mapgen_limit(v3s16 p); + /// @brief copy data from map to prepare for mapgen + /// @return true if mapgen should actually happen bool initBlockMake(v3s16 blockpos, BlockMakeData *data); + /// @brief write data back to map after mapgen + /// @param now current game time void finishBlockMake(BlockMakeData *data, - std::map *changed_blocks); + std::map *changed_blocks, u32 now); /* Get a block from somewhere. diff --git a/src/unittest/test_sao.cpp b/src/unittest/test_sao.cpp index e424fb7a4..5184d529d 100644 --- a/src/unittest/test_sao.cpp +++ b/src/unittest/test_sao.cpp @@ -197,8 +197,8 @@ void TestSAO::testActivate(ServerEnvironment *env) UASSERT(block); block->m_static_objects.insert(0, s_obj); - // Activating the block will convert it to active. - env->activateBlock(block); + // this will convert it to an active object + env->forceActivateBlock(block); const u16 obj_id = assert_active_in_block(block); auto *obj = env->getActiveObject(obj_id); @@ -239,7 +239,7 @@ void TestSAO::testStaticToFalse(ServerEnvironment *env) UASSERT(block); block->m_static_objects.insert(0, s_obj); - env->activateBlock(block); + env->forceActivateBlock(block); const u16 obj_id = assert_active_in_block(block); auto *obj = env->getActiveObject(obj_id); diff --git a/src/util/container.h b/src/util/container.h index 2ab573c19..19af68497 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -53,11 +53,16 @@ public: return m_queue.front(); } - u32 size() const + size_t size() const { return m_queue.size(); } + bool empty() const + { + return m_queue.empty(); + } + private: std::set m_set; std::queue m_queue; From f63436c8d3dd82ad190928d1fb768d3524deda9f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 23 Mar 2025 20:13:24 +0100 Subject: [PATCH 080/284] Add basic unittests for LBMManager --- src/unittest/CMakeLists.txt | 1 + src/unittest/test_lbmmanager.cpp | 82 ++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 src/unittest/test_lbmmanager.cpp diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 8a635c8e8..7417819bd 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -16,6 +16,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irr_rotation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_logging.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_lbmmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_lua.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mapblock.cpp diff --git a/src/unittest/test_lbmmanager.cpp b/src/unittest/test_lbmmanager.cpp new file mode 100644 index 000000000..6f3627331 --- /dev/null +++ b/src/unittest/test_lbmmanager.cpp @@ -0,0 +1,82 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 sfan5 + +#include "test.h" + +#include + +#include "serverenvironment.h" + +class TestLBMManager : public TestBase +{ +public: + TestLBMManager() { TestManager::registerTestModule(this); } + const char *getName() { return "TestLBMManager"; } + + void runTests(IGameDef *gamedef); + + void testNew(IGameDef *gamedef); + void testExisting(IGameDef *gamedef); + void testDiscard(IGameDef *gamedef); +}; + +static TestLBMManager g_test_instance; + +void TestLBMManager::runTests(IGameDef *gamedef) +{ + TEST(testNew, gamedef); + TEST(testExisting, gamedef); + TEST(testDiscard, gamedef); +} + +namespace { + struct FakeLBM : LoadingBlockModifierDef { + FakeLBM(const std::string &name, bool every_load) { + this->name = name; + this->run_at_every_load = every_load; + trigger_contents.emplace_back("air"); + } + }; +} + +void TestLBMManager::testNew(IGameDef *gamedef) +{ + LBMManager mgr; + + mgr.addLBMDef(new FakeLBM(":foo:bar", false)); + mgr.addLBMDef(new FakeLBM("not:this", true)); + + mgr.loadIntroductionTimes("", gamedef, 1234); + + auto str = mgr.createIntroductionTimesString(); + // name of first lbm should have been stripped + // the second should not appear at all + UASSERTEQ(auto, str, "foo:bar~1234;"); +} + +void TestLBMManager::testExisting(IGameDef *gamedef) +{ + LBMManager mgr; + + mgr.addLBMDef(new FakeLBM("foo:bar", false)); + + // colon should also be stripped when loading (due to old versions) + mgr.loadIntroductionTimes(":foo:bar~22;", gamedef, 1234); + + auto str = mgr.createIntroductionTimesString(); + UASSERTEQ(auto, str, "foo:bar~22;"); +} + +void TestLBMManager::testDiscard(IGameDef *gamedef) +{ + LBMManager mgr; + + // LBMs that no longer exist are dropped + mgr.loadIntroductionTimes("some:thing~2;", gamedef, 10); + + auto str = mgr.createIntroductionTimesString(); + UASSERTEQ(auto, str, ""); +} + +// We should also test LBMManager::applyLBMs in the future. From db15bc6466bf4a1c152f387256759d0f5f13980f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 18 Mar 2025 22:11:37 +0100 Subject: [PATCH 081/284] Some more random code cleanups --- builtin/settingtypes.txt | 4 ++-- src/environment.cpp | 5 ----- src/environment.h | 14 -------------- src/mapblock.cpp | 21 ++++++++++----------- src/mapblock.h | 2 ++ src/serverenvironment.cpp | 34 ++++++++++++++++++++++++---------- src/serverenvironment.h | 12 +++++++++++- 7 files changed, 49 insertions(+), 43 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 3ae55aecd..8604ff539 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2106,14 +2106,14 @@ max_objects_per_block (Maximum objects per block) int 256 256 65535 active_block_mgmt_interval (Active block management interval) float 2.0 0.0 # Length of time between Active Block Modifier (ABM) execution cycles, stated in seconds. -abm_interval (ABM interval) float 1.0 0.0 +abm_interval (ABM interval) float 1.0 0.1 30.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, stated in seconds. -nodetimer_interval (NodeTimer interval) float 0.2 0.0 +nodetimer_interval (NodeTimer interval) float 0.2 0.1 1.0 # Max liquids processed per step. liquid_loop_max (Liquid loop max) int 100000 1 4294967295 diff --git a/src/environment.cpp b/src/environment.cpp index fe582afd4..ce8dd16e4 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -17,11 +17,6 @@ Environment::Environment(IGameDef *gamedef): m_day_count(0), m_gamedef(gamedef) { - m_cache_active_block_mgmt_interval = g_settings->getFloat("active_block_mgmt_interval"); - m_cache_abm_interval = g_settings->getFloat("abm_interval"); - m_cache_nodetimer_interval = g_settings->getFloat("nodetimer_interval"); - m_cache_abm_time_budget = g_settings->getFloat("abm_time_budget"); - m_time_of_day = g_settings->getU32("world_start_time"); m_time_of_day_f = (float)m_time_of_day / 24000.0f; } diff --git a/src/environment.h b/src/environment.h index b668e69c2..c3031a070 100644 --- a/src/environment.h +++ b/src/environment.h @@ -122,20 +122,6 @@ protected: * Above: values managed by m_time_lock */ - /* TODO: Add a callback function so these can be updated when a setting - * changes. At this point in time it doesn't matter (e.g. /set - * is documented to change server settings only) - * - * TODO: Local caching of settings is not optimal and should at some stage - * be updated to use a global settings object for getting thse values - * (as opposed to the this local caching). This can be addressed in - * a later release. - */ - float m_cache_active_block_mgmt_interval; - float m_cache_abm_interval; - float m_cache_nodetimer_interval; - float m_cache_abm_time_budget; - IGameDef *m_gamedef; private: diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 47d0a4973..6987c36a6 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -120,20 +120,19 @@ bool MapBlock::saveStaticObject(u16 id, const StaticObject &obj, u32 reason) return true; } -// This method is only for Server, don't call it on client void MapBlock::step(float dtime, const std::function &on_timer_cb) { - // Run script callbacks for elapsed node_timers + // Run callbacks for elapsed node_timers std::vector elapsed_timers = m_node_timers.step(dtime); - if (!elapsed_timers.empty()) { - MapNode n; - v3s16 p; - for (const NodeTimer &elapsed_timer : elapsed_timers) { - n = getNodeNoEx(elapsed_timer.position); - p = elapsed_timer.position + getPosRelative(); - if (on_timer_cb(p, n, elapsed_timer.elapsed)) - setNodeTimer(NodeTimer(elapsed_timer.timeout, 0, elapsed_timer.position)); - } + MapNode n; + v3s16 p; + for (const auto &it : elapsed_timers) { + n = getNodeNoEx(it.position); + p = it.position + getPosRelative(); + if (on_timer_cb(p, n, it.elapsed)) + setNodeTimer(NodeTimer(it.timeout, 0, it.position)); + if (isOrphan()) + return; } } diff --git a/src/mapblock.h b/src/mapblock.h index da5e95dff..9a13b686f 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -313,6 +313,7 @@ public: bool onObjectsActivation(); bool saveStaticObject(u16 id, const StaticObject &obj, u32 reason); + /// @note This method is only for Server, don't call it on client void step(float dtime, const std::function &on_timer_cb); //// @@ -337,6 +338,7 @@ public: return m_timestamp; } + /// @deprecated don't use in new code, unclear semantics. inline u32 getDiskTimestamp() { return m_disk_timestamp; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 28ca0f889..90127ab6f 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -45,6 +45,8 @@ static constexpr s16 ACTIVE_OBJECT_RESAVE_DISTANCE_SQ = sqr(3); +static constexpr u32 BLOCK_RESAVE_TIMESTAMP_DIFF = 60; // in units of game time + /* ABMWithState */ @@ -54,9 +56,9 @@ ABMWithState::ABMWithState(ActiveBlockModifier *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.001f, itv); // No less than 1ms + int minval = MYMAX(-0.51f*itv, -60); // Clamp to + int maxval = MYMIN(0.51f*itv, 60); // +-60 seconds timer = myrand_range(minval, maxval); } @@ -494,6 +496,11 @@ ServerEnvironment::ServerEnvironment(std::unique_ptr map, m_script(server->getScriptIface()), m_server(server) { + m_cache_active_block_mgmt_interval = g_settings->getFloat("active_block_mgmt_interval"); + m_cache_abm_interval = rangelim(g_settings->getFloat("abm_interval"), 0.1f, 30); + m_cache_nodetimer_interval = rangelim(g_settings->getFloat("nodetimer_interval"), 0.1f, 1); + m_cache_abm_time_budget = g_settings->getFloat("abm_time_budget"); + m_step_time_counter = mb->addCounter( "minetest_env_step_time", "Time spent in environment step (in microseconds)"); @@ -1083,9 +1090,10 @@ void ServerEnvironment::forceActivateBlock(MapBlock *block) assert(block); if (m_active_blocks.add(block->getPos())) activateBlock(block); + m_active_block_gauge->set(m_active_blocks.size()); } -void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) +void ServerEnvironment::activateBlock(MapBlock *block) { // Reset usage timer immediately, otherwise a block that becomes active // again at around the same time as it would normally be unloaded will @@ -1100,7 +1108,6 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) u32 stamp = block->getTimestamp(); if (m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED) dtime_s = m_game_time - stamp; - dtime_s += additional_dtime; // Remove stored static objects if clearObjects was called since block's timestamp // Note that non-generated blocks may still have stored static objects @@ -1124,7 +1131,7 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) // Run node timers block->step((float)dtime_s, [&](v3s16 p, MapNode n, f32 d) -> bool { - return !block->isOrphan() && m_script->node_on_timer(p, n, d); + return m_script->node_on_timer(p, n, d); }); } @@ -1525,7 +1532,10 @@ void ServerEnvironment::step(float dtime) 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; + // FIXME: this is not actually correct, because the block may have been + // activated just moments ago. In practice the intervnal is very small + // so this doesn't really matter. + const float dtime = m_cache_nodetimer_interval; for (const v3s16 &p: m_active_blocks.m_list) { MapBlock *block = m_map->getBlockNoCreateNoEx(p); @@ -1537,11 +1547,14 @@ void ServerEnvironment::step(float dtime) // Set current time as timestamp 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 the block timestamp has changed considerably, mark it to be + // re-saved. We do this even if there were no actual data changes + // for the sake of LBMs. + if (block->getTimestamp() > block->getDiskTimestamp() + + BLOCK_RESAVE_TIMESTAMP_DIFF) { block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD, MOD_REASON_BLOCK_EXPIRED); + } // Run node timers block->step(dtime, [&](v3s16 p, MapNode n, f32 d) -> bool { @@ -1558,6 +1571,7 @@ void ServerEnvironment::step(float dtime) std::shuffle(m_abms.begin(), m_abms.end(), MyRandGenerator()); // Initialize handling of ActiveBlockModifiers + // TODO: reinitializing this state every time is probably not efficient? ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true); int blocks_scanned = 0; diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 6d9584044..267f6af83 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -419,7 +419,7 @@ private: static AuthDatabase *openAuthDatabase(const std::string &name, const std::string &savedir, const Settings &conf); - void activateBlock(MapBlock *block, u32 additional_dtime=0); + void activateBlock(MapBlock *block); /* Internal ActiveObject interface @@ -514,6 +514,16 @@ private: // Can raise to high values like 15s with eg. map generation mods. float m_max_lag_estimate = 0.1f; + + /* + * TODO: Add a callback function so these can be updated when a setting + * changes. + */ + float m_cache_active_block_mgmt_interval; + float m_cache_abm_interval; + float m_cache_nodetimer_interval; + float m_cache_abm_time_budget; + // peer_ids in here should be unique, except that there may be many 0s std::vector m_players; From fbc525d683021d1e2cf3f7cc9d2c54419e0b0ff3 Mon Sep 17 00:00:00 2001 From: lhofhansl Date: Thu, 27 Mar 2025 18:59:38 -0700 Subject: [PATCH 082/284] Restore behavior of emergequeue_limit_total (#15947) * And make sure `emergequeue_limit_total` is >= max(`emergequeue_limit_diskonly`, `emergequeue_limit_generate`) --- src/emerge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index 29134bccf..608bdb9a9 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -108,7 +108,7 @@ EmergeManager::EmergeManager(Server *server, MetricsBackend *mb) // don't trust user input for something very important like this m_qlimit_diskonly = rangelim(m_qlimit_diskonly, 2, 1000000); m_qlimit_generate = rangelim(m_qlimit_generate, 1, 1000000); - m_qlimit_total = std::max(m_qlimit_diskonly, m_qlimit_generate); + m_qlimit_total = std::max(m_qlimit_total, std::max(m_qlimit_diskonly, m_qlimit_generate)); for (s16 i = 0; i < nthreads; i++) m_threads.push_back(new EmergeThread(server, i)); From b0bc6ce6377ed3600770b7a248553d37440940f7 Mon Sep 17 00:00:00 2001 From: grorp Date: Fri, 28 Mar 2025 07:43:59 -0400 Subject: [PATCH 083/284] TouchControls: Take FOV into account for camera movement (#15936) --- src/client/game.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index b7d1df743..4d5240d25 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2407,8 +2407,10 @@ f32 Game::getSensitivityScaleFactor() const void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) { if (g_touchcontrols) { - cam->camera_yaw += g_touchcontrols->getYawChange(); - cam->camera_pitch += g_touchcontrols->getPitchChange(); + // User setting is already applied by TouchControls. + f32 sens_scale = getSensitivityScaleFactor(); + cam->camera_yaw += g_touchcontrols->getYawChange() * sens_scale; + cam->camera_pitch += g_touchcontrols->getPitchChange() * sens_scale; } else { v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); v2s32 dist = input->getMousePos() - center; From 8f8d7c40889c7047b9be7898aa56283a9abadc1d Mon Sep 17 00:00:00 2001 From: lhofhansl Date: Fri, 28 Mar 2025 11:31:54 -0700 Subject: [PATCH 084/284] Check if a block is already in the emege queue before checking occlusion culling and trying to reemerge (#15949) --- src/server/clientiface.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index d05fedbb2..8a8a6cf1b 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -339,6 +339,15 @@ void RemoteClient::GetNextBlocks ( if (d >= d_opt && block->isAir()) continue; } + + const bool want_emerge = !block || !block->isGenerated(); + + // if the block is already in the emerge queue we don't have to check again + if (want_emerge && emerge->isBlockInQueue(p)) { + nearest_emerged_d = d; + continue; + } + /* Check occlusion cache first. */ @@ -357,7 +366,7 @@ void RemoteClient::GetNextBlocks ( /* Add inexistent block to emerge queue. */ - if (!block || !block->isGenerated()) { + if (want_emerge) { if (emerge->enqueueBlockEmerge(peer_id, p, generate)) { if (nearest_emerged_d == -1) nearest_emerged_d = d; From 915446417d3ec71ba7591399d4ff0f641e07e75c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Mar 2025 20:45:44 +0100 Subject: [PATCH 085/284] Improve warning message for registration table reuse --- builtin/game/register.lua | 66 ++++++++++++++++++++++----------------- doc/lua_api.md | 17 +++++++--- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index dc4aa1ca1..2417a1b8a 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -129,6 +129,13 @@ function core.register_entity(name, prototype) prototype.mod_origin = core.get_current_modname() or "??" end +local default_tables = { + node = core.nodedef_default, + craft = core.craftitemdef_default, + tool = core.tooldef_default, + none = core.noneitemdef_default, +} + function core.register_item(name, itemdef) -- Check name if name == nil then @@ -140,13 +147,6 @@ function core.register_item(name, itemdef) end itemdef.name = name - local mt = getmetatable(itemdef) - if mt ~= nil and next(mt) ~= nil then - core.log("warning", "Item definition has a metatable, this is ".. - "unsupported and it will be overwritten: " .. name) - end - - -- Apply defaults and add to registered_* table if itemdef.type == "node" then -- Use the nodebox as selection box if it's not set manually if itemdef.drawtype == "nodebox" and not itemdef.selection_box then @@ -162,19 +162,27 @@ function core.register_item(name, itemdef) core.log("warning", "Node 'light_source' value exceeds maximum," .. " limiting to maximum: " ..name) end - setmetatable(itemdef, {__index = core.nodedef_default}) - core.registered_nodes[itemdef.name] = itemdef - elseif itemdef.type == "craft" then - setmetatable(itemdef, {__index = core.craftitemdef_default}) - core.registered_craftitems[itemdef.name] = itemdef - elseif itemdef.type == "tool" then - setmetatable(itemdef, {__index = core.tooldef_default}) - core.registered_tools[itemdef.name] = itemdef - elseif itemdef.type == "none" then - setmetatable(itemdef, {__index = core.noneitemdef_default}) - else + end + + -- Apply defaults + local defaults = default_tables[itemdef.type] + if defaults == nil then error("Unable to register item: Type is invalid: " .. dump(itemdef)) end + local old_mt = getmetatable(itemdef) + -- TODO most of these checks should become an error after a while (maybe in 2026?) + if old_mt ~= nil and next(old_mt) ~= nil then + -- Note that even registering multiple identical items with the same table + -- is not allowed, due to the 'name' property. + if old_mt.__index == defaults then + core.log("warning", "Item definition table was reused between registrations. ".. + "This is unsupported and broken: " .. name) + else + core.log("warning", "Item definition has a metatable, this is ".. + "unsupported and it will be overwritten: " .. name) + end + end + setmetatable(itemdef, {__index = defaults}) -- Flowing liquid uses param2 if itemdef.type == "node" and itemdef.liquidtype == "flowing" then @@ -204,9 +212,17 @@ function core.register_item(name, itemdef) -- Ignore new keys as a failsafe to prevent mistakes getmetatable(itemdef).__newindex = function() end - --core.log("Registering item: " .. itemdef.name) + -- Add to registered_* tables + if itemdef.type == "node" then + core.registered_nodes[itemdef.name] = itemdef + elseif itemdef.type == "craft" then + core.registered_craftitems[itemdef.name] = itemdef + elseif itemdef.type == "tool" then + core.registered_tools[itemdef.name] = itemdef + end core.registered_items[itemdef.name] = itemdef core.registered_aliases[itemdef.name] = nil + register_item_raw(itemdef) end @@ -217,17 +233,11 @@ function core.unregister_item(name) return end -- Erase from registered_* table - local type = core.registered_items[name].type - if type == "node" then - core.registered_nodes[name] = nil - elseif type == "craft" then - core.registered_craftitems[name] = nil - elseif type == "tool" then - core.registered_tools[name] = nil - end + core.registered_nodes[name] = nil + core.registered_craftitems[name] = nil + core.registered_tools[name] = nil core.registered_items[name] = nil - unregister_item_raw(name) end diff --git a/doc/lua_api.md b/doc/lua_api.md index f0d9624d0..765a1d31b 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5895,16 +5895,23 @@ Call these functions only at load time! ### Environment -* `core.register_node(name, node definition)` -* `core.register_craftitem(name, item definition)` -* `core.register_tool(name, item definition)` +* `core.register_node(name, nodedef)` + * register a node with its definition + * Note: you must pass a clean table that hasn't already been used for + another registration to this function, as it will be modified. +* `core.register_craftitem(name, itemdef)` + * register an item with its definition + * Note: (as above) +* `core.register_tool(name, tooldef)` + * register a tool item with its definition + * Note: (as above) * `core.override_item(name, redefinition, del_fields)` * `redefinition` is a table of fields `[name] = new_value`, overwriting fields of or adding fields to the existing definition. * `del_fields` is a list of field names to be set to `nil` ("deleted from") the original definition. * Overrides fields of an item registered with register_node/tool/craftitem. - * Note: Item must already be defined, (opt)depend on the mod defining it. + * Note: Item must already be defined. * Example: `core.override_item("default:mese", {light_source=core.LIGHT_MAX}, {"sounds"})`: Overwrites the `light_source` field, @@ -5912,7 +5919,7 @@ Call these functions only at load time! * `core.unregister_item(name)` * Unregisters the item from the engine, and deletes the entry with key `name` from `core.registered_items` and from the associated item table - according to its nature: `core.registered_nodes`, etc. + according to its nature (e.g. `core.registered_nodes`) * `core.register_entity(name, entity definition)` * `core.register_abm(abm definition)` * `core.register_lbm(lbm definition)` From a94c9a73ba2d87bec616fd6c256b5036216d943f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 24 Mar 2025 21:10:55 +0100 Subject: [PATCH 086/284] Move all registration logic into core.register_item for consistency --- builtin/game/register.lua | 190 ++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 88 deletions(-) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 2417a1b8a..0a0dff486 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -129,6 +129,86 @@ function core.register_entity(name, prototype) prototype.mod_origin = core.get_current_modname() or "??" end +local function preprocess_node(nodedef) + -- Use the nodebox as selection box if it's not set manually + if nodedef.drawtype == "nodebox" and not nodedef.selection_box then + nodedef.selection_box = nodedef.node_box + elseif nodedef.drawtype == "fencelike" and not nodedef.selection_box then + nodedef.selection_box = { + type = "fixed", + fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}, + } + end + + if nodedef.light_source and nodedef.light_source > core.LIGHT_MAX then + nodedef.light_source = core.LIGHT_MAX + core.log("warning", "Node 'light_source' value exceeds maximum," .. + " limiting it: " .. nodedef.name) + end + + -- Flowing liquid uses param2 + if nodedef.liquidtype == "flowing" then + nodedef.paramtype2 = "flowingliquid" + end +end + +local function preprocess_craft(itemdef) + -- BEGIN Legacy stuff + if itemdef.inventory_image == nil and itemdef.image ~= nil then + itemdef.inventory_image = itemdef.image + end + -- END Legacy stuff +end + +local function preprocess_tool(tooldef) + tooldef.stack_max = 1 + + -- BEGIN Legacy stuff + if tooldef.inventory_image == nil and tooldef.image ~= nil then + tooldef.inventory_image = tooldef.image + end + + if tooldef.tool_capabilities == nil and + (tooldef.full_punch_interval ~= nil or + tooldef.basetime ~= nil or + tooldef.dt_weight ~= nil or + tooldef.dt_crackiness ~= nil or + tooldef.dt_crumbliness ~= nil or + tooldef.dt_cuttability ~= nil or + tooldef.basedurability ~= nil or + tooldef.dd_weight ~= nil or + tooldef.dd_crackiness ~= nil or + tooldef.dd_crumbliness ~= nil or + tooldef.dd_cuttability ~= nil) then + tooldef.tool_capabilities = { + full_punch_interval = tooldef.full_punch_interval, + basetime = tooldef.basetime, + dt_weight = tooldef.dt_weight, + dt_crackiness = tooldef.dt_crackiness, + dt_crumbliness = tooldef.dt_crumbliness, + dt_cuttability = tooldef.dt_cuttability, + basedurability = tooldef.basedurability, + dd_weight = tooldef.dd_weight, + dd_crackiness = tooldef.dd_crackiness, + dd_crumbliness = tooldef.dd_crumbliness, + dd_cuttability = tooldef.dd_cuttability, + } + end + -- END Legacy stuff + + -- Automatically set punch_attack_uses as a convenience feature + local toolcaps = tooldef.tool_capabilities + if toolcaps and toolcaps.punch_attack_uses == nil then + for _, cap in pairs(toolcaps.groupcaps or {}) do + local level = (cap.maxlevel or 0) - 1 + if (cap.uses or 0) ~= 0 and level >= 0 then + toolcaps.punch_attack_uses = cap.uses * (3 ^ level) + break + end + end + end +end + local default_tables = { node = core.nodedef_default, craft = core.craftitemdef_default, @@ -136,6 +216,12 @@ local default_tables = { none = core.noneitemdef_default, } +local preprocess_fns = { + node = preprocess_node, + craft = preprocess_craft, + tool = preprocess_tool, +} + function core.register_item(name, itemdef) -- Check name if name == nil then @@ -145,23 +231,13 @@ function core.register_item(name, itemdef) if forbidden_item_names[name] then error("Unable to register item: Name is forbidden: " .. name) end + itemdef.name = name - if itemdef.type == "node" then - -- Use the nodebox as selection box if it's not set manually - if itemdef.drawtype == "nodebox" and not itemdef.selection_box then - itemdef.selection_box = itemdef.node_box - elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then - itemdef.selection_box = { - type = "fixed", - fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}, - } - end - if itemdef.light_source and itemdef.light_source > core.LIGHT_MAX then - itemdef.light_source = core.LIGHT_MAX - core.log("warning", "Node 'light_source' value exceeds maximum," .. - " limiting to maximum: " ..name) - end + -- Compatibility stuff depending on type + local fn = preprocess_fns[itemdef.type] + if fn then + fn(itemdef) end -- Apply defaults @@ -184,11 +260,6 @@ function core.register_item(name, itemdef) end setmetatable(itemdef, {__index = defaults}) - -- Flowing liquid uses param2 - if itemdef.type == "node" and itemdef.liquidtype == "flowing" then - itemdef.paramtype2 = "flowingliquid" - end - -- BEGIN Legacy stuff if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then core.register_craft({ @@ -226,6 +297,17 @@ function core.register_item(name, itemdef) register_item_raw(itemdef) end +local function make_register_item_wrapper(the_type) + return function(name, itemdef) + itemdef.type = the_type + return core.register_item(name, itemdef) + end +end + +core.register_node = make_register_item_wrapper("node") +core.register_craftitem = make_register_item_wrapper("craft") +core.register_tool = make_register_item_wrapper("tool") + function core.unregister_item(name) if not core.registered_items[name] then core.log("warning", "Not unregistering item " ..name.. @@ -241,74 +323,6 @@ function core.unregister_item(name) unregister_item_raw(name) end -function core.register_node(name, nodedef) - nodedef.type = "node" - core.register_item(name, nodedef) -end - -function core.register_craftitem(name, craftitemdef) - craftitemdef.type = "craft" - - -- BEGIN Legacy stuff - if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then - craftitemdef.inventory_image = craftitemdef.image - end - -- END Legacy stuff - - core.register_item(name, craftitemdef) -end - -function core.register_tool(name, tooldef) - tooldef.type = "tool" - tooldef.stack_max = 1 - - -- BEGIN Legacy stuff - if tooldef.inventory_image == nil and tooldef.image ~= nil then - tooldef.inventory_image = tooldef.image - end - if tooldef.tool_capabilities == nil and - (tooldef.full_punch_interval ~= nil or - tooldef.basetime ~= nil or - tooldef.dt_weight ~= nil or - tooldef.dt_crackiness ~= nil or - tooldef.dt_crumbliness ~= nil or - tooldef.dt_cuttability ~= nil or - tooldef.basedurability ~= nil or - tooldef.dd_weight ~= nil or - tooldef.dd_crackiness ~= nil or - tooldef.dd_crumbliness ~= nil or - tooldef.dd_cuttability ~= nil) then - tooldef.tool_capabilities = { - full_punch_interval = tooldef.full_punch_interval, - basetime = tooldef.basetime, - dt_weight = tooldef.dt_weight, - dt_crackiness = tooldef.dt_crackiness, - dt_crumbliness = tooldef.dt_crumbliness, - dt_cuttability = tooldef.dt_cuttability, - basedurability = tooldef.basedurability, - dd_weight = tooldef.dd_weight, - dd_crackiness = tooldef.dd_crackiness, - dd_crumbliness = tooldef.dd_crumbliness, - dd_cuttability = tooldef.dd_cuttability, - } - end - -- END Legacy stuff - - -- This isn't just legacy, but more of a convenience feature - local toolcaps = tooldef.tool_capabilities - if toolcaps and toolcaps.punch_attack_uses == nil then - for _, cap in pairs(toolcaps.groupcaps or {}) do - local level = (cap.maxlevel or 0) - 1 - if (cap.uses or 0) ~= 0 and level >= 0 then - toolcaps.punch_attack_uses = cap.uses * (3 ^ level) - break - end - end - end - - core.register_item(name, tooldef) -end - function core.register_alias(name, convert_to) if forbidden_item_names[name] then error("Unable to register alias: Name is forbidden: " .. name) From af1ffce084ff9a37e46649ab693e9f924159d606 Mon Sep 17 00:00:00 2001 From: cx384 Date: Sat, 29 Mar 2025 15:09:15 +0100 Subject: [PATCH 087/284] Improve hand override documentation --- doc/lua_api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 765a1d31b..ab8efd411 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -3768,7 +3768,8 @@ Player Inventory lists * `craftresult`: list containing the crafted output * `hand`: list containing an override for the empty hand * Is not created automatically, use `InvRef:set_size` - * Is only used to enhance the empty hand's tool capabilities + * Players use the first item in this list as their hand + * It behaves as if the default hand `""` has been overridden for this specific player Custom lists can be added and deleted with `InvRef:set_size(name, size)` like any other inventory. From 882f132062498b6019e8177fca1d53df875a2e72 Mon Sep 17 00:00:00 2001 From: cx384 Date: Sun, 30 Mar 2025 17:26:28 +0200 Subject: [PATCH 088/284] Document special items --- doc/lua_api.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/lua_api.md b/doc/lua_api.md index ab8efd411..d58ed5fc2 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2026,6 +2026,17 @@ that acts as tool in a gameplay sense as a craftitem, and vice-versa. Craftitems can be used for items that neither need to be a node nor a tool. +Special Items +------------- +The following items are predefined and have special properties. + +* `"unknown"`: An item that represents every item which has not been registered +* `"air"`: The node which appears everywhere where no other node is +* `"ignore"`: Mapblocks which have not been yet generated consist of this node +* `""`: The player's hand, which is in use whenever the player wields no item + * Its rage and tool capabilities are also used as an fallback for the wield item + * It can be overridden to change those properties + Amount and wear --------------- From 309c0a0cb6420d13495a003d07b49d126eda80bf Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 30 Mar 2025 18:15:38 +0200 Subject: [PATCH 089/284] Formspec: fix clamped scroll offset of scroll_containers larger than 1000px --- src/gui/guiFormSpecMenu.cpp | 7 +++++-- src/gui/guiFormSpecMenu.h | 2 +- src/gui/guiScrollBar.cpp | 4 ++-- src/gui/guiScrollBar.h | 4 ++-- src/gui/guiScrollContainer.cpp | 2 -- src/gui/guiScrollContainer.h | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 29a8e1285..59d6dc5a7 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -695,7 +695,9 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen e->setMax(max); e->setMin(min); - e->setPos(stoi(value)); + // Preserve for min/max values defined by `scroll_container[]`. + spec.aux_f32 = stoi(value); // scroll position + e->setPos(spec.aux_f32); e->setSmallStep(data->scrollbar_options.small_step); e->setLargeStep(data->scrollbar_options.large_step); @@ -2198,7 +2200,6 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string & spec_btn.ftype = f_Button; rect += data->basepos-padding; - spec_btn.rect = rect; m_fields.push_back(spec_btn); } @@ -3235,6 +3236,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) for (const std::pair &b : m_scrollbars) { if (c.first == b.first.fname) { c.second->setScrollBar(b.second); + b.second->setPos(b.first.aux_f32); // scroll position + c.second->updateScrolling(); break; } } diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 5c3fcd80d..04b65a967 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -129,9 +129,9 @@ class GUIFormSpecMenu : public GUIModalMenu bool is_exit; // Draw priority for formspec version < 3 int priority; - core::rect rect; gui::ECURSOR_ICON fcursor_icon; std::string sound; + f32 aux_f32 = 0; }; struct TooltipSpec diff --git a/src/gui/guiScrollBar.cpp b/src/gui/guiScrollBar.cpp index 01a7af8c9..387f45297 100644 --- a/src/gui/guiScrollBar.cpp +++ b/src/gui/guiScrollBar.cpp @@ -249,7 +249,7 @@ void GUIScrollBar::updatePos() setPosRaw(scroll_pos); } -void GUIScrollBar::setPosRaw(const s32 &pos) +void GUIScrollBar::setPosRaw(const s32 pos) { s32 thumb_area = 0; s32 thumb_min = 0; @@ -275,7 +275,7 @@ void GUIScrollBar::setPosRaw(const s32 &pos) border_size; } -void GUIScrollBar::setPos(const s32 &pos) +void GUIScrollBar::setPos(const s32 pos) { setPosRaw(pos); target_pos = std::nullopt; diff --git a/src/gui/guiScrollBar.h b/src/gui/guiScrollBar.h index c485fbd83..7f6621940 100644 --- a/src/gui/guiScrollBar.h +++ b/src/gui/guiScrollBar.h @@ -54,7 +54,7 @@ public: void setLargeStep(const s32 &step); //! Sets a position immediately, aborting any ongoing interpolation. // setPos does not send EGET_SCROLL_BAR_CHANGED events for you. - void setPos(const s32 &pos); + void setPos(const s32 pos); //! The same as setPos, but it takes care of sending EGET_SCROLL_BAR_CHANGED events. void setPosAndSend(const s32 &pos); //! Sets a target position for interpolation. @@ -94,7 +94,7 @@ private: ISimpleTextureSource *m_tsrc; - void setPosRaw(const s32 &pos); + void setPosRaw(const s32 pos); void updatePos(); std::optional target_pos; u32 last_time_ms = 0; diff --git a/src/gui/guiScrollContainer.cpp b/src/gui/guiScrollContainer.cpp index ff94b2769..0ef3fb612 100644 --- a/src/gui/guiScrollContainer.cpp +++ b/src/gui/guiScrollContainer.cpp @@ -92,8 +92,6 @@ void GUIScrollContainer::setScrollBar(GUIScrollBar *scrollbar) m_scrollbar->setPageSize((total_content_px * scrollbar_px) / visible_content_px); } - - updateScrolling(); } void GUIScrollContainer::updateScrolling() diff --git a/src/gui/guiScrollContainer.h b/src/gui/guiScrollContainer.h index ee09a16e5..d1632f765 100644 --- a/src/gui/guiScrollContainer.h +++ b/src/gui/guiScrollContainer.h @@ -29,6 +29,7 @@ public: } void setScrollBar(GUIScrollBar *scrollbar); + void updateScrolling(); private: enum OrientationEnum @@ -43,5 +44,4 @@ private: f32 m_scrollfactor; //< scrollbar pos * scrollfactor = scroll offset in pixels std::optional m_content_padding_px; //< in pixels - void updateScrolling(); }; From 4cd22733499b3ad19424e28ba6399a36ef592927 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:16:20 +0200 Subject: [PATCH 090/284] Refactor input handler (#15933) --- src/client/game.cpp | 2 +- src/client/game_formspec.cpp | 2 +- src/client/inputhandler.cpp | 191 ++++++++++++++++---------------- src/client/inputhandler.h | 209 +++++++++-------------------------- src/client/keycode.h | 18 ++- 5 files changed, 168 insertions(+), 254 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 4d5240d25..87e0d4e11 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -885,7 +885,7 @@ bool Game::startup(bool *kill, this->chat_backend = chat_backend; simple_singleplayer_mode = start_data.isSinglePlayer(); - input->keycache.populate(); + input->reloadKeybindings(); driver = device->getVideoDriver(); smgr = m_rendering_engine->get_scene_manager(); diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp index f573a1543..4bd1a42bd 100644 --- a/src/client/game_formspec.cpp +++ b/src/client/game_formspec.cpp @@ -554,7 +554,7 @@ bool GameFormSpec::handleCallbacks() } if (g_gamecallback->keyconfig_changed) { - m_input->keycache.populate(); // update the cache with new settings + m_input->reloadKeybindings(); // update the cache with new settings g_gamecallback->keyconfig_changed = false; } diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index a33931503..492f2fd6c 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -12,75 +12,98 @@ #include "log_internal.h" #include "client/renderingengine.h" -void KeyCache::populate_nonchanging() +void MyEventReceiver::reloadKeybindings() { - key[KeyType::ESC] = EscapeKey; -} + keybindings[KeyType::FORWARD] = getKeySetting("keymap_forward"); + keybindings[KeyType::BACKWARD] = getKeySetting("keymap_backward"); + keybindings[KeyType::LEFT] = getKeySetting("keymap_left"); + keybindings[KeyType::RIGHT] = getKeySetting("keymap_right"); + keybindings[KeyType::JUMP] = getKeySetting("keymap_jump"); + keybindings[KeyType::AUX1] = getKeySetting("keymap_aux1"); + keybindings[KeyType::SNEAK] = getKeySetting("keymap_sneak"); + keybindings[KeyType::DIG] = getKeySetting("keymap_dig"); + keybindings[KeyType::PLACE] = getKeySetting("keymap_place"); -void KeyCache::populate() -{ - key[KeyType::FORWARD] = getKeySetting("keymap_forward"); - key[KeyType::BACKWARD] = getKeySetting("keymap_backward"); - key[KeyType::LEFT] = getKeySetting("keymap_left"); - key[KeyType::RIGHT] = getKeySetting("keymap_right"); - key[KeyType::JUMP] = getKeySetting("keymap_jump"); - key[KeyType::AUX1] = getKeySetting("keymap_aux1"); - key[KeyType::SNEAK] = getKeySetting("keymap_sneak"); - key[KeyType::DIG] = getKeySetting("keymap_dig"); - key[KeyType::PLACE] = getKeySetting("keymap_place"); + keybindings[KeyType::ESC] = EscapeKey; - key[KeyType::AUTOFORWARD] = getKeySetting("keymap_autoforward"); + keybindings[KeyType::AUTOFORWARD] = getKeySetting("keymap_autoforward"); - key[KeyType::DROP] = getKeySetting("keymap_drop"); - key[KeyType::INVENTORY] = getKeySetting("keymap_inventory"); - key[KeyType::CHAT] = getKeySetting("keymap_chat"); - key[KeyType::CMD] = getKeySetting("keymap_cmd"); - key[KeyType::CMD_LOCAL] = getKeySetting("keymap_cmd_local"); - key[KeyType::CONSOLE] = getKeySetting("keymap_console"); - key[KeyType::MINIMAP] = getKeySetting("keymap_minimap"); - key[KeyType::FREEMOVE] = getKeySetting("keymap_freemove"); - key[KeyType::PITCHMOVE] = getKeySetting("keymap_pitchmove"); - key[KeyType::FASTMOVE] = getKeySetting("keymap_fastmove"); - key[KeyType::NOCLIP] = getKeySetting("keymap_noclip"); - key[KeyType::HOTBAR_PREV] = getKeySetting("keymap_hotbar_previous"); - key[KeyType::HOTBAR_NEXT] = getKeySetting("keymap_hotbar_next"); - key[KeyType::MUTE] = getKeySetting("keymap_mute"); - key[KeyType::INC_VOLUME] = getKeySetting("keymap_increase_volume"); - key[KeyType::DEC_VOLUME] = getKeySetting("keymap_decrease_volume"); - key[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic"); - key[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot"); - key[KeyType::TOGGLE_BLOCK_BOUNDS] = getKeySetting("keymap_toggle_block_bounds"); - key[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud"); - key[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat"); - key[KeyType::TOGGLE_FOG] = getKeySetting("keymap_toggle_fog"); - key[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera"); - key[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug"); - key[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler"); - key[KeyType::CAMERA_MODE] = getKeySetting("keymap_camera_mode"); - key[KeyType::INCREASE_VIEWING_RANGE] = + keybindings[KeyType::DROP] = getKeySetting("keymap_drop"); + keybindings[KeyType::INVENTORY] = getKeySetting("keymap_inventory"); + keybindings[KeyType::CHAT] = getKeySetting("keymap_chat"); + keybindings[KeyType::CMD] = getKeySetting("keymap_cmd"); + keybindings[KeyType::CMD_LOCAL] = getKeySetting("keymap_cmd_local"); + keybindings[KeyType::CONSOLE] = getKeySetting("keymap_console"); + keybindings[KeyType::MINIMAP] = getKeySetting("keymap_minimap"); + keybindings[KeyType::FREEMOVE] = getKeySetting("keymap_freemove"); + keybindings[KeyType::PITCHMOVE] = getKeySetting("keymap_pitchmove"); + keybindings[KeyType::FASTMOVE] = getKeySetting("keymap_fastmove"); + keybindings[KeyType::NOCLIP] = getKeySetting("keymap_noclip"); + keybindings[KeyType::HOTBAR_PREV] = getKeySetting("keymap_hotbar_previous"); + keybindings[KeyType::HOTBAR_NEXT] = getKeySetting("keymap_hotbar_next"); + keybindings[KeyType::MUTE] = getKeySetting("keymap_mute"); + keybindings[KeyType::INC_VOLUME] = getKeySetting("keymap_increase_volume"); + keybindings[KeyType::DEC_VOLUME] = getKeySetting("keymap_decrease_volume"); + keybindings[KeyType::CINEMATIC] = getKeySetting("keymap_cinematic"); + keybindings[KeyType::SCREENSHOT] = getKeySetting("keymap_screenshot"); + keybindings[KeyType::TOGGLE_BLOCK_BOUNDS] = getKeySetting("keymap_toggle_block_bounds"); + keybindings[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud"); + keybindings[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat"); + keybindings[KeyType::TOGGLE_FOG] = getKeySetting("keymap_toggle_fog"); + keybindings[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera"); + keybindings[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug"); + keybindings[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler"); + keybindings[KeyType::CAMERA_MODE] = getKeySetting("keymap_camera_mode"); + keybindings[KeyType::INCREASE_VIEWING_RANGE] = getKeySetting("keymap_increase_viewing_range_min"); - key[KeyType::DECREASE_VIEWING_RANGE] = + keybindings[KeyType::DECREASE_VIEWING_RANGE] = getKeySetting("keymap_decrease_viewing_range_min"); - key[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect"); - key[KeyType::ZOOM] = getKeySetting("keymap_zoom"); + keybindings[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect"); + keybindings[KeyType::ZOOM] = getKeySetting("keymap_zoom"); - key[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); - key[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); - key[KeyType::QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc"); - key[KeyType::QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec"); + keybindings[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); + keybindings[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); + keybindings[KeyType::QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc"); + keybindings[KeyType::QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec"); for (int i = 0; i < HUD_HOTBAR_ITEMCOUNT_MAX; i++) { std::string slot_key_name = "keymap_slot" + std::to_string(i + 1); - key[KeyType::SLOT_1 + i] = getKeySetting(slot_key_name.c_str()); + keybindings[KeyType::SLOT_1 + i] = getKeySetting(slot_key_name.c_str()); } - if (handler) { - // First clear all keys, then re-add the ones we listen for - handler->dontListenForKeys(); - for (auto k : key) { - handler->listenForKey(k); - } - handler->listenForKey(EscapeKey); + // First clear all keys, then re-add the ones we listen for + keysListenedFor.clear(); + for (int i = 0; i < KeyType::INTERNAL_ENUM_COUNT; i++) { + listenForKey(keybindings[i], static_cast(i)); + } +} + +bool MyEventReceiver::setKeyDown(KeyPress keyCode, bool is_down) +{ + if (keysListenedFor.find(keyCode) == keysListenedFor.end()) // ignore irrelevant key input + return false; + auto action = keysListenedFor[keyCode]; + if (is_down) { + physicalKeyDown.insert(keyCode); + setKeyDown(action, true); + } else { + physicalKeyDown.erase(keyCode); + setKeyDown(action, false); + } + return true; +} + +void MyEventReceiver::setKeyDown(GameKeyType action, bool is_down) +{ + if (is_down) { + if (!IsKeyDown(action)) + keyWasPressed.set(action); + keyIsDown.set(action); + keyWasDown.set(action); + } else { + if (IsKeyDown(action)) + keyWasReleased.set(action); + keyIsDown.reset(action); } } @@ -143,23 +166,8 @@ bool MyEventReceiver::OnEvent(const SEvent &event) // Remember whether each key is down or up if (event.EventType == irr::EET_KEY_INPUT_EVENT) { KeyPress keyCode(event.KeyInput); - if (keyCode && keysListenedFor[keyCode]) { // ignore key input that is invalid or irrelevant for the game. - if (event.KeyInput.PressedDown) { - if (!IsKeyDown(keyCode)) - keyWasPressed.set(keyCode); - - keyIsDown.set(keyCode); - keyWasDown.set(keyCode); - } else { - if (IsKeyDown(keyCode)) - keyWasReleased.set(keyCode); - - keyIsDown.unset(keyCode); - } - + if (setKeyDown(keyCode, event.KeyInput.PressedDown)) return true; - } - } else if (g_touchcontrols && event.EventType == irr::EET_TOUCH_INPUT_EVENT) { // In case of touchcontrols, we have to handle different events g_touchcontrols->translateEvent(event); @@ -171,31 +179,22 @@ bool MyEventReceiver::OnEvent(const SEvent &event) // Handle mouse events switch (event.MouseInput.Event) { case EMIE_LMOUSE_PRESSED_DOWN: - keyIsDown.set(LMBKey); - keyWasDown.set(LMBKey); - keyWasPressed.set(LMBKey); + setKeyDown(LMBKey, true); break; case EMIE_MMOUSE_PRESSED_DOWN: - keyIsDown.set(MMBKey); - keyWasDown.set(MMBKey); - keyWasPressed.set(MMBKey); + setKeyDown(MMBKey, true); break; case EMIE_RMOUSE_PRESSED_DOWN: - keyIsDown.set(RMBKey); - keyWasDown.set(RMBKey); - keyWasPressed.set(RMBKey); + setKeyDown(RMBKey, true); break; case EMIE_LMOUSE_LEFT_UP: - keyIsDown.unset(LMBKey); - keyWasReleased.set(LMBKey); + setKeyDown(LMBKey, false); break; case EMIE_MMOUSE_LEFT_UP: - keyIsDown.unset(MMBKey); - keyWasReleased.set(MMBKey); + setKeyDown(MMBKey, false); break; case EMIE_RMOUSE_LEFT_UP: - keyIsDown.unset(RMBKey); - keyWasReleased.set(RMBKey); + setKeyDown(RMBKey, false); break; case EMIE_MOUSE_WHEEL: mouse_wheel += event.MouseInput.Wheel; @@ -257,7 +256,7 @@ s32 RandomInputHandler::Rand(s32 min, s32 max) } struct RandomInputHandlerSimData { - std::string key; + GameKeyType key; float counter; int time_max; }; @@ -265,19 +264,19 @@ struct RandomInputHandlerSimData { void RandomInputHandler::step(float dtime) { static RandomInputHandlerSimData rnd_data[] = { - { "keymap_jump", 0.0f, 40 }, - { "keymap_aux1", 0.0f, 40 }, - { "keymap_forward", 0.0f, 40 }, - { "keymap_left", 0.0f, 40 }, - { "keymap_dig", 0.0f, 30 }, - { "keymap_place", 0.0f, 15 } + { KeyType::JUMP, 0.0f, 40 }, + { KeyType::AUX1, 0.0f, 40 }, + { KeyType::FORWARD, 0.0f, 40 }, + { KeyType::LEFT, 0.0f, 40 }, + { KeyType::DIG, 0.0f, 30 }, + { KeyType::PLACE, 0.0f, 15 } }; for (auto &i : rnd_data) { i.counter -= dtime; if (i.counter < 0.0) { i.counter = 0.1 * Rand(1, i.time_max); - keydown.toggle(getKeySetting(i.key.c_str())); + keydown.flip(i.key); } } { diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 6de1a0575..fec52a74d 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -7,7 +7,10 @@ #include "irrlichttypes.h" #include "irr_v2d.h" #include "joystick_controller.h" +#include #include +#include +#include #include "keycode.h" class InputHandler; @@ -17,142 +20,32 @@ enum class PointerType { Touch, }; -/**************************************************************************** - Fast key cache for main game loop - ****************************************************************************/ - -/* This is faster than using getKeySetting with the tradeoff that functions - * using it must make sure that it's initialised before using it and there is - * no error handling (for example bounds checking). This is really intended for - * use only in the main running loop of the client (the_game()) where the faster - * (up to 10x faster) key lookup is an asset. Other parts of the codebase - * (e.g. formspecs) should continue using getKeySetting(). - */ -struct KeyCache -{ - - KeyCache() - { - handler = NULL; - populate(); - populate_nonchanging(); - } - - void populate(); - - // Keys that are not settings dependent - void populate_nonchanging(); - - KeyPress key[KeyType::INTERNAL_ENUM_COUNT]; - InputHandler *handler; -}; - -class KeyList : private std::list -{ - typedef std::list super; - typedef super::iterator iterator; - typedef super::const_iterator const_iterator; - - virtual const_iterator find(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(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(KeyPress key) - { - if (find(key) == end()) - push_back(key); - } - - void unset(KeyPress key) - { - iterator p(find(key)); - - if (p != end()) - erase(p); - } - - void toggle(KeyPress key) - { - iterator p(this->find(key)); - - if (p != end()) - erase(p); - else - push_back(key); - } - - void append(const KeyList &other) - { - for (auto key : other) { - set(key); - } - } - - bool operator[](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(KeyPress keyCode) const { return keyIsDown[keyCode]; } + bool IsKeyDown(GameKeyType key) const { return keyIsDown[key]; } // Checks whether a key was down and resets the state - bool WasKeyDown(KeyPress keyCode) + bool WasKeyDown(GameKeyType key) { - bool b = keyWasDown[keyCode]; + bool b = keyWasDown[key]; if (b) - keyWasDown.unset(keyCode); + keyWasDown.reset(key); return b; } // Checks whether a key was just pressed. State will be cleared // in the subsequent iteration of Game::processPlayerInteraction - bool WasKeyPressed(KeyPress keycode) const { return keyWasPressed[keycode]; } + bool WasKeyPressed(GameKeyType key) const { return keyWasPressed[key]; } // Checks whether a key was just released. State will be cleared // in the subsequent iteration of Game::processPlayerInteraction - bool WasKeyReleased(KeyPress keycode) const { return keyWasReleased[keycode]; } + bool WasKeyReleased(GameKeyType key) const { return keyWasReleased[key]; } - void listenForKey(KeyPress keyCode) - { - keysListenedFor.set(keyCode); - } - void dontListenForKeys() - { - keysListenedFor.clear(); - } + void reloadKeybindings(); s32 getMouseWheel() { @@ -163,28 +56,30 @@ public: void clearInput() { - keyIsDown.clear(); - keyWasDown.clear(); - keyWasPressed.clear(); - keyWasReleased.clear(); + physicalKeyDown.clear(); + keyIsDown.reset(); + keyWasDown.reset(); + keyWasPressed.reset(); + keyWasReleased.reset(); mouse_wheel = 0; } void releaseAllKeys() { - keyWasReleased.append(keyIsDown); - keyIsDown.clear(); + physicalKeyDown.clear(); + keyWasReleased |= keyIsDown; + keyIsDown.reset(); } void clearWasKeyPressed() { - keyWasPressed.clear(); + keyWasPressed.reset(); } void clearWasKeyReleased() { - keyWasReleased.clear(); + keyWasReleased.reset(); } JoystickController *joystick = nullptr; @@ -192,26 +87,41 @@ public: PointerType getLastPointerType() { return last_pointer_type; } private: + void listenForKey(KeyPress keyCode, GameKeyType action) + { + if (keyCode) + keysListenedFor[keyCode] = action; + } + + bool setKeyDown(KeyPress keyCode, bool is_down); + void setKeyDown(GameKeyType action, bool is_down); + + /* This is faster than using getKeySetting with the tradeoff that functions + * using it must make sure that it's initialised before using it and there is + * no error handling (for example bounds checking). This is useful here as the + * faster (up to 10x faster) key lookup is an asset. + */ + std::array keybindings; + s32 mouse_wheel = 0; + // The current state of physical keys. + std::set physicalKeyDown; + // The current state of keys - KeyList keyIsDown; + std::bitset keyIsDown; // Like keyIsDown but only reset when that key is read - KeyList keyWasDown; + std::bitset keyWasDown; // Whether a key has just been pressed - KeyList keyWasPressed; + std::bitset keyWasPressed; // Whether a key has just been released - KeyList keyWasReleased; + std::bitset keyWasReleased; // List of keys we listen for - // TODO perhaps the type of this is not really - // performant as KeyList is designed for few but - // often changing keys, and keysListenedFor is expected - // to change seldomly but contain lots of keys. - KeyList keysListenedFor; + std::unordered_map keysListenedFor; // Intentionally not reset by clearInput/releaseAllKeys. bool fullscreen_is_down = false; @@ -222,12 +132,6 @@ private: class InputHandler { public: - InputHandler() - { - keycache.handler = this; - keycache.populate(); - } - virtual ~InputHandler() = default; virtual bool isRandom() const @@ -247,8 +151,7 @@ public: virtual void clearWasKeyPressed() {} virtual void clearWasKeyReleased() {} - virtual void listenForKey(KeyPress keyCode) {} - virtual void dontListenForKeys() {} + virtual void reloadKeybindings() {} virtual v2s32 getMousePos() = 0; virtual void setMousePos(s32 x, s32 y) = 0; @@ -261,7 +164,6 @@ public: virtual void releaseAllKeys() {} JoystickController joystick; - KeyCache keycache; }; /* @@ -274,6 +176,7 @@ public: RealInputHandler(MyEventReceiver *receiver) : m_receiver(receiver) { m_receiver->joystick = &joystick; + m_receiver->reloadKeybindings(); } virtual ~RealInputHandler() @@ -283,19 +186,19 @@ public: virtual bool isKeyDown(GameKeyType k) { - return m_receiver->IsKeyDown(keycache.key[k]) || joystick.isKeyDown(k); + return m_receiver->IsKeyDown(k) || joystick.isKeyDown(k); } virtual bool wasKeyDown(GameKeyType k) { - return m_receiver->WasKeyDown(keycache.key[k]) || joystick.wasKeyDown(k); + return m_receiver->WasKeyDown(k) || joystick.wasKeyDown(k); } virtual bool wasKeyPressed(GameKeyType k) { - return m_receiver->WasKeyPressed(keycache.key[k]) || joystick.wasKeyPressed(k); + return m_receiver->WasKeyPressed(k) || joystick.wasKeyPressed(k); } virtual bool wasKeyReleased(GameKeyType k) { - return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); + return m_receiver->WasKeyReleased(k) || joystick.wasKeyReleased(k); } virtual float getJoystickSpeed(); @@ -316,13 +219,9 @@ public: m_receiver->clearWasKeyReleased(); } - virtual void listenForKey(KeyPress keyCode) + virtual void reloadKeybindings() { - m_receiver->listenForKey(keyCode); - } - virtual void dontListenForKeys() - { - m_receiver->dontListenForKeys(); + m_receiver->reloadKeybindings(); } virtual v2s32 getMousePos(); @@ -360,7 +259,7 @@ public: return true; } - virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; } + virtual bool isKeyDown(GameKeyType k) { return keydown[k]; } virtual bool wasKeyDown(GameKeyType k) { return false; } virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } @@ -377,7 +276,7 @@ public: s32 Rand(s32 min, s32 max); private: - KeyList keydown; + std::bitset keydown; v2s32 mousepos; v2s32 mousespeed; float joystickSpeed; diff --git a/src/client/keycode.h b/src/client/keycode.h index 038c47ebd..503529b52 100644 --- a/src/client/keycode.h +++ b/src/client/keycode.h @@ -49,6 +49,11 @@ public: return !(*this == o); } + // Used for e.g. std::set + bool operator<(KeyPress o) const { + return scancode < o.scancode; + } + // Check whether the keypress is valid operator bool() const { @@ -60,11 +65,22 @@ public: static KeyPress getSpecialKey(const std::string &name); private: + using value_type = std::variant; bool loadFromScancode(const std::string &name); void loadFromKey(irr::EKEY_CODE keycode, wchar_t keychar); std::string formatScancode() const; - std::variant scancode = irr::KEY_UNKNOWN; + value_type scancode = irr::KEY_UNKNOWN; + + friend std::hash; +}; + +template <> +struct std::hash +{ + size_t operator()(KeyPress kp) const noexcept { + return std::hash{}(kp.scancode); + } }; // Global defines for convenience From 41d43e8d95a4dd1b983b877b51b04e5ed7983198 Mon Sep 17 00:00:00 2001 From: cx384 Date: Sun, 30 Mar 2025 18:16:34 +0200 Subject: [PATCH 091/284] Document server texture pack in texture_packs.md (#15951) --- doc/texture_packs.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/texture_packs.md b/doc/texture_packs.md index d386006f2..bde5eecfd 100644 --- a/doc/texture_packs.md +++ b/doc/texture_packs.md @@ -22,6 +22,11 @@ This is a directory containing the entire contents of a single texture pack. It can be chosen more or less freely and will also become the name of the texture pack. The name must not be “base”. +### The "server" texture pack +If a texture pack named `server` exists, the textures in it will replace textures +sent to clients. +It's independent of the client's texture pack, which will take precedence as usual. + ### `texture_pack.conf` A key-value config file with the following keys: From 4bc366b984aece91fa95a0315586eaa1700994f7 Mon Sep 17 00:00:00 2001 From: wrrrzr <161970349+wrrrzr@users.noreply.github.com> Date: Sun, 30 Mar 2025 19:16:45 +0300 Subject: [PATCH 092/284] Refactor createShadowRenderer --- src/client/shadows/dynamicshadowsrender.cpp | 26 ++++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 835f995f1..a7d21d647 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -686,21 +686,19 @@ std::string ShadowRenderer::readShaderFile(const std::string &path) ShadowRenderer *createShadowRenderer(IrrlichtDevice *device, Client *client) { + if (!g_settings->getBool("enable_dynamic_shadows")) + return nullptr; + // disable if unsupported - if (g_settings->getBool("enable_dynamic_shadows")) { - // See also checks in builtin/mainmenu/settings/dlg_settings.lua - const video::E_DRIVER_TYPE type = device->getVideoDriver()->getDriverType(); - if (type != video::EDT_OPENGL && type != video::EDT_OPENGL3) { - warningstream << "Shadows: disabled dynamic shadows due to being unsupported" << std::endl; - g_settings->setBool("enable_dynamic_shadows", false); - } + // See also checks in builtin/mainmenu/settings/dlg_settings.lua + const video::E_DRIVER_TYPE type = device->getVideoDriver()->getDriverType(); + if (type != video::EDT_OPENGL && type != video::EDT_OPENGL3) { + warningstream << "Shadows: disabled dynamic shadows due to being unsupported" << std::endl; + g_settings->setBool("enable_dynamic_shadows", false); + return nullptr; } - if (g_settings->getBool("enable_dynamic_shadows")) { - ShadowRenderer *shadow_renderer = new ShadowRenderer(device, client); - shadow_renderer->initialize(); - return shadow_renderer; - } - - return nullptr; + ShadowRenderer *shadow_renderer = new ShadowRenderer(device, client); + shadow_renderer->initialize(); + return shadow_renderer; } From d7edf0b229874e8f9f2f5ca3e03df5fe73a53a85 Mon Sep 17 00:00:00 2001 From: wrrrzr <161970349+wrrrzr@users.noreply.github.com> Date: Sun, 30 Mar 2025 19:17:10 +0300 Subject: [PATCH 093/284] Set StartupWMClass in desktop file see #15942 --- misc/net.minetest.minetest.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/net.minetest.minetest.desktop b/misc/net.minetest.minetest.desktop index 571eb58a9..325bd59d6 100644 --- a/misc/net.minetest.minetest.desktop +++ b/misc/net.minetest.minetest.desktop @@ -11,4 +11,5 @@ PrefersNonDefaultGPU=true Type=Application Categories=Game;Simulation; StartupNotify=false +StartupWMClass=Luanti Keywords=sandbox;world;mining;crafting;blocks;nodes;multiplayer;roleplaying;minetest; From 89e3bc8d562c23e1e527826b27c5f1e2bf094077 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Mar 2025 18:28:25 +0100 Subject: [PATCH 094/284] Improve std::hash implementation --- irr/include/SMaterial.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/irr/include/SMaterial.h b/irr/include/SMaterial.h index d48328a31..d5b9341f4 100644 --- a/irr/include/SMaterial.h +++ b/irr/include/SMaterial.h @@ -483,9 +483,15 @@ struct std::hash /// @brief std::hash specialization for video::SMaterial std::size_t operator()(const irr::video::SMaterial &m) const noexcept { - // basic implementation that hashes the two things most likely to differ - auto h1 = std::hash{}(m.getTexture(0)); - auto h2 = std::hash{}(m.MaterialType); - return (h1 << 1) ^ h2; + std::size_t ret = 0; + for (auto h : { // the three members most likely to differ + std::hash{}(m.getTexture(0)), + std::hash{}(m.MaterialType), + std::hash{}(m.ColorParam.color) + }) { + ret += h; + ret ^= (ret << 6) + (ret >> 2); // distribute bits + } + return ret; } }; From e73eed247e3eca8730b985f81dceb2b7b1f4a207 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Mar 2025 19:08:31 +0100 Subject: [PATCH 095/284] Apply some refactoring/cleanup to mainly util functions --- src/client/client.cpp | 5 --- src/client/client.h | 7 +++- src/filesys.cpp | 54 +++++++++++++------------- src/filesys.h | 11 +++--- src/porting.cpp | 2 +- src/unittest/test_utilities.cpp | 10 +++++ src/util/container.h | 9 ++--- src/util/hashing.cpp | 2 + src/util/hex.h | 23 +++++------ src/util/numeric.cpp | 16 ++------ src/util/numeric.h | 69 +++++++++++++++++++++------------ src/util/pointer.h | 56 +++++++++++++++----------- src/util/serialize.h | 6 +-- src/util/sha1.cpp | 25 ++++-------- src/util/sha1.h | 9 ++--- src/util/srp.h | 2 + src/util/string.cpp | 28 ++++++------- src/util/string.h | 4 +- src/util/thread.h | 12 +++++- 19 files changed, 190 insertions(+), 160 deletions(-) diff --git a/src/client/client.cpp b/src/client/client.cpp index 031bed8d7..214420de5 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1642,11 +1642,6 @@ void Client::inventoryAction(InventoryAction *a) delete a; } -float Client::getAnimationTime() -{ - return m_animation_time; -} - int Client::getCrackLevel() { return m_crack_level; diff --git a/src/client/client.h b/src/client/client.h index 237dd93b6..e4cbac1f5 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -277,7 +277,10 @@ public: return m_env.getPlayerNames(); } - float getAnimationTime(); + float getAnimationTime() const + { + return m_animation_time; + } int getCrackLevel(); v3s16 getCrackPos(); @@ -294,7 +297,7 @@ public: bool getChatMessage(std::wstring &message); void typeChatMessage(const std::wstring& message); - u64 getMapSeed(){ return m_map_seed; } + u64 getMapSeed() const { return m_map_seed; } void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); // Including blocks at appropriate edges diff --git a/src/filesys.cpp b/src/filesys.cpp index be1751c0b..62352403f 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -136,20 +136,23 @@ bool IsDir(const std::string &path) (attr & FILE_ATTRIBUTE_DIRECTORY)); } +bool IsFile(const std::string &path) +{ + DWORD attr = GetFileAttributes(path.c_str()); + return (attr != INVALID_FILE_ATTRIBUTES && + !(attr & FILE_ATTRIBUTE_DIRECTORY)); +} + bool IsExecutable(const std::string &path) { DWORD type; return GetBinaryType(path.c_str(), &type) != 0; } -bool IsDirDelimiter(char c) -{ - return c == '/' || c == '\\'; -} - bool RecursiveDelete(const std::string &path) { infostream << "Recursively deleting \"" << path << "\"" << std::endl; + assert(IsPathAbsolute(path)); if (!IsDir(path)) { infostream << "RecursiveDelete: Deleting file " << path << std::endl; if (!DeleteFile(path.c_str())) { @@ -181,19 +184,9 @@ bool RecursiveDelete(const std::string &path) bool DeleteSingleFileOrEmptyDirectory(const std::string &path) { - DWORD attr = GetFileAttributes(path.c_str()); - bool is_directory = (attr != INVALID_FILE_ATTRIBUTES && - (attr & FILE_ATTRIBUTE_DIRECTORY)); - if(!is_directory) - { - bool did = DeleteFile(path.c_str()); - return did; - } - else - { - bool did = RemoveDirectory(path.c_str()); - return did; - } + if (!IsDir(path)) + return DeleteFile(path.c_str()); + return RemoveDirectory(path.c_str()); } std::string TempPath() @@ -336,8 +329,7 @@ bool CreateDir(const std::string &path) bool PathExists(const std::string &path) { - struct stat st{}; - return (stat(path.c_str(),&st) == 0); + return access(path.c_str(), F_OK) == 0; } bool IsPathAbsolute(const std::string &path) @@ -348,21 +340,29 @@ bool IsPathAbsolute(const std::string &path) bool IsDir(const std::string &path) { struct stat statbuf{}; - if(stat(path.c_str(), &statbuf)) + if (stat(path.c_str(), &statbuf)) return false; // Actually error; but certainly not a directory return ((statbuf.st_mode & S_IFDIR) == S_IFDIR); } +bool IsFile(const std::string &path) +{ + struct stat statbuf{}; + if (stat(path.c_str(), &statbuf)) + return false; +#ifdef S_IFSOCK + // sockets cannot be opened in any way, so they are not files. + if ((statbuf.st_mode & S_IFSOCK) == S_IFSOCK) + return false; +#endif + return ((statbuf.st_mode & S_IFDIR) != S_IFDIR); +} + bool IsExecutable(const std::string &path) { return access(path.c_str(), X_OK) == 0; } -bool IsDirDelimiter(char c) -{ - return c == '/'; -} - bool RecursiveDelete(const std::string &path) { /* @@ -877,7 +877,7 @@ const char *GetFilenameFromPath(const char *path) { const char *filename = strrchr(path, DIR_DELIM_CHAR); // Consistent with IsDirDelimiter this function handles '/' too - if (DIR_DELIM_CHAR != '/') { + if constexpr (DIR_DELIM_CHAR != '/') { const char *tmp = strrchr(path, '/'); if (tmp && tmp > filename) filename = tmp; diff --git a/src/filesys.h b/src/filesys.h index 0e974d822..a2f7b749c 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -49,15 +49,14 @@ bool IsDir(const std::string &path); bool IsExecutable(const std::string &path); -inline bool IsFile(const std::string &path) +bool IsFile(const std::string &path); + +inline bool IsDirDelimiter(char c) { - return PathExists(path) && !IsDir(path); + return c == '/' || c == DIR_DELIM_CHAR; } -bool IsDirDelimiter(char c); - -// Only pass full paths to this one. True on success. -// NOTE: The WIN32 version returns always true. +// Only pass full paths to this one. returns true on success. bool RecursiveDelete(const std::string &path); bool DeleteSingleFileOrEmptyDirectory(const std::string &path); diff --git a/src/porting.cpp b/src/porting.cpp index faef75b7c..11ff63a88 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -270,7 +270,7 @@ const std::string &get_sysinfo() } -bool getCurrentWorkingDir(char *buf, size_t len) +[[maybe_unused]] static bool getCurrentWorkingDir(char *buf, size_t len) { #ifdef _WIN32 DWORD ret = GetCurrentDirectory(len, buf); diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index ac1f29b30..1a810c06d 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -47,6 +47,7 @@ public: void testIsBlockInSight(); void testColorizeURL(); void testSanitizeUntrusted(); + void testReadSeed(); }; static TestUtilities g_test_instance; @@ -82,6 +83,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testIsBlockInSight); TEST(testColorizeURL); TEST(testSanitizeUntrusted); + TEST(testReadSeed); } //////////////////////////////////////////////////////////////////////////////// @@ -753,3 +755,11 @@ void TestUtilities::testSanitizeUntrusted() UASSERTEQ(auto, sanitize_untrusted("\x1b(", keep), "("); } } + +void TestUtilities::testReadSeed() +{ + UASSERTEQ(int, read_seed("123"), 123); + UASSERTEQ(int, read_seed("0x123"), 0x123); + // hashing should produce some non-zero number + UASSERT(read_seed("hello") != 0); +} diff --git a/src/util/container.h b/src/util/container.h index 19af68497..50a57fc08 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -362,11 +362,10 @@ public: // This conditional block was converted from a ternary to ensure no // temporary values are created in evaluating the return expression, // which could cause a dangling reference. - if (it != m_values.end()) { + if (it != m_values.end()) return it->second; - } else { + else return null_value; - } } void put(const K &key, const V &value) { @@ -430,7 +429,7 @@ public: return !!take(key); } - // Warning: not constant-time! + /// @warning not constant-time! size_t size() const { if (m_iterating) { // This is by no means impossible to determine, it's just annoying @@ -446,7 +445,7 @@ public: return n; } - // Warning: not constant-time! + /// @warning not constant-time! bool empty() const { if (m_iterating) return false; // maybe diff --git a/src/util/hashing.cpp b/src/util/hashing.cpp index 452cd6818..c1fed34f2 100644 --- a/src/util/hashing.cpp +++ b/src/util/hashing.cpp @@ -4,6 +4,8 @@ #include "hashing.h" +#define IN_HASHING_CPP + #include "debug.h" #include "config.h" #if USE_OPENSSL diff --git a/src/util/hex.h b/src/util/hex.h index 0d3ea6d0c..c528fe4bd 100644 --- a/src/util/hex.h +++ b/src/util/hex.h @@ -9,27 +9,22 @@ static const char hex_chars[] = "0123456789abcdef"; -static inline std::string hex_encode(const char *data, unsigned int data_size) +static inline std::string hex_encode(std::string_view data) { std::string ret; - ret.reserve(data_size * 2); - - char buf2[3]; - buf2[2] = '\0'; - - for (unsigned int i = 0; i < data_size; i++) { - unsigned char c = (unsigned char)data[i]; - buf2[0] = hex_chars[(c & 0xf0) >> 4]; - buf2[1] = hex_chars[c & 0x0f]; - ret.append(buf2); + ret.reserve(data.size() * 2); + for (unsigned char c : data) { + ret.push_back(hex_chars[(c & 0xf0) >> 4]); + ret.push_back(hex_chars[c & 0x0f]); } - return ret; } -static inline std::string hex_encode(std::string_view data) +static inline std::string hex_encode(const char *data, size_t data_size) { - return hex_encode(data.data(), data.size()); + if (!data_size) + return ""; + return hex_encode(std::string_view(data, data_size)); } static inline bool hex_digit_decode(char hexdigit, unsigned char &value) diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 7dc3d4dea..ddc658cb3 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -6,7 +6,7 @@ #include "log.h" #include "constants.h" // BS, MAP_BLOCKSIZE -#include "noise.h" // PseudoRandom, PcgRandom +#include "noise.h" // PcgRandom #include #include @@ -47,10 +47,7 @@ float myrand_range(float min, float max) } -/* - 64-bit unaligned version of MurmurHash -*/ -u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed) +u64 murmur_hash_64_ua(const void *key, size_t len, unsigned int seed) { const u64 m = 0xc6a4a7935bd1e995ULL; const int r = 47; @@ -90,13 +87,7 @@ u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed) return h; } -/* - blockpos_b: position of block in block coordinates - camera_pos: position of camera in nodes - camera_dir: an unit vector pointing to camera direction - range: viewing range - distance_ptr: return location for distance from the camera -*/ + bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, f32 camera_fov, f32 range, f32 *distance_ptr) { @@ -149,6 +140,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, return true; } + inline float adjustDist(float dist, float zoom_fov) { // 1.775 ~= 72 * PI / 180 * 1.4, the default FOV on the client. diff --git a/src/util/numeric.h b/src/util/numeric.h index 5c0d2cebf..60d86064f 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -115,7 +115,8 @@ inline bool isInArea(v3s16 p, v3s16 d) ); } -inline void sortBoxVerticies(v3s16 &p1, v3s16 &p2) +template +inline void sortBoxVerticies(core::vector3d &p1, core::vector3d &p2) { if (p1.X > p2.X) std::swap(p1.X, p2.X); @@ -125,14 +126,18 @@ inline void sortBoxVerticies(v3s16 &p1, v3s16 &p2) std::swap(p1.Z, p2.Z); } -inline v3s16 componentwise_min(const v3s16 &a, const v3s16 &b) +template +inline constexpr core::vector3d componentwise_min(const core::vector3d &a, + const core::vector3d &b) { - return v3s16(std::min(a.X, b.X), std::min(a.Y, b.Y), std::min(a.Z, b.Z)); + return {std::min(a.X, b.X), std::min(a.Y, b.Y), std::min(a.Z, b.Z)}; } -inline v3s16 componentwise_max(const v3s16 &a, const v3s16 &b) +template +inline constexpr core::vector3d componentwise_max(const core::vector3d &a, + const core::vector3d &b) { - return v3s16(std::max(a.X, b.X), std::max(a.Y, b.Y), std::max(a.Z, b.Z)); + return {std::max(a.X, b.X), std::max(a.Y, b.Y), std::max(a.Z, b.Z)}; } /// @brief Describes a grid with given step, oirginating at (0,0,0) @@ -277,8 +282,22 @@ inline u32 calc_parity(u32 v) return (0x6996 >> v) & 1; } -u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed); +/** + * Calculate MurmurHash64A hash for an arbitrary block of data. + * @param key data to hash (does not need to be aligned) + * @param len length in bytes + * @param seed initial seed value + * @return hash value + */ +u64 murmur_hash_64_ua(const void *key, size_t len, unsigned int seed); +/** + * @param blockpos_b position of block in block coordinates + * @param camera_pos position of camera in nodes + * @param camera_dir an unit vector pointing to camera direction + * @param range viewing range + * @param distance_ptr return location for distance from the camera + */ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, f32 camera_fov, f32 range, f32 *distance_ptr=NULL); @@ -399,13 +418,6 @@ inline void paging(u32 length, u32 page, u32 pagecount, u32 &minindex, u32 &maxi } } -inline float cycle_shift(float value, float by = 0, float max = 1) -{ - if (value + by < 0) return value + by + max; - if (value + by > max) return value + by - max; - return value + by; -} - constexpr inline bool is_power_of_two(u32 n) { return n != 0 && (n & (n - 1)) == 0; @@ -469,31 +481,40 @@ inline v3f getPitchYawRoll(const core::matrix4 &m) } // Muliply the RGB value of a color linearly, and clamp to black/white -inline irr::video::SColor multiplyColorValue(const irr::video::SColor &color, float mod) +inline video::SColor multiplyColorValue(const video::SColor &color, float mod) { - return irr::video::SColor(color.getAlpha(), + return video::SColor(color.getAlpha(), core::clamp(color.getRed() * mod, 0, 255), core::clamp(color.getGreen() * mod, 0, 255), core::clamp(color.getBlue() * mod, 0, 255)); } -template inline T numericAbsolute(T v) { return v < 0 ? T(-v) : v; } -template inline T numericSign(T v) { return T(v < 0 ? -1 : (v == 0 ? 0 : 1)); } - -inline v3f vecAbsolute(v3f v) +template constexpr inline T numericAbsolute(T v) { - return v3f( + return v < 0 ? T(-v) : v; +} + +template constexpr inline T numericSign(T v) +{ + return T(v < 0 ? -1 : (v == 0 ? 0 : 1)); +} + +template +inline constexpr core::vector3d vecAbsolute(const core::vector3d &v) +{ + return { numericAbsolute(v.X), numericAbsolute(v.Y), numericAbsolute(v.Z) - ); + }; } -inline v3f vecSign(v3f v) +template +inline constexpr core::vector3d vecSign(const core::vector3d &v) { - return v3f( + return { numericSign(v.X), numericSign(v.Y), numericSign(v.Z) - ); + }; } diff --git a/src/util/pointer.h b/src/util/pointer.h index 957277a40..fe90a3866 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -11,7 +11,8 @@ #include -template class ConstSharedPtr { +template +class ConstSharedPtr { public: ConstSharedPtr(T *ptr) : ptr(ptr) {} ConstSharedPtr(const std::shared_ptr &ptr) : ptr(ptr) {} @@ -33,7 +34,7 @@ public: m_size = 0; data = nullptr; } - Buffer(unsigned int size) + Buffer(size_t size) { m_size = size; if (size != 0) { @@ -59,7 +60,7 @@ public: } } // Copies whole buffer - Buffer(const T *t, unsigned int size) + Buffer(const T *t, size_t size) { m_size = size; if (size != 0) { @@ -77,9 +78,8 @@ public: Buffer& operator=(Buffer &&buffer) { - if (this == &buffer) { + if (this == &buffer) return *this; - } drop(); m_size = buffer.m_size; if (m_size != 0) { @@ -104,7 +104,7 @@ public: } } - T & operator[](unsigned int i) const + T & operator[](size_t i) const { return data[i]; } @@ -113,7 +113,7 @@ public: return data; } - unsigned int getSize() const + size_t getSize() const { return m_size; } @@ -132,7 +132,7 @@ private: delete[] data; } T *data; - unsigned int m_size; + size_t m_size; }; /************************************************ @@ -149,11 +149,12 @@ public: SharedBuffer() { m_size = 0; - data = NULL; - refcount = new unsigned int; + data = nullptr; + refcount = new u32; (*refcount) = 1; } - SharedBuffer(unsigned int size) + + SharedBuffer(size_t size) { m_size = size; if (m_size != 0) { @@ -162,10 +163,11 @@ public: data = nullptr; } - refcount = new unsigned int; + refcount = new u32; memset(data, 0, sizeof(T) * m_size); (*refcount) = 1; } + SharedBuffer(const SharedBuffer &buffer) { m_size = buffer.m_size; @@ -173,12 +175,11 @@ public: refcount = buffer.refcount; (*refcount)++; } - SharedBuffer & operator=(const SharedBuffer & buffer) - { - if (this == &buffer) { - return *this; - } + SharedBuffer & operator=(const SharedBuffer &buffer) + { + if (this == &buffer) + return *this; drop(); m_size = buffer.m_size; data = buffer.data; @@ -186,8 +187,9 @@ public: (*refcount)++; return *this; } + //! Copies whole buffer - SharedBuffer(const T *t, unsigned int size) + SharedBuffer(const T *t, size_t size) { m_size = size; if (m_size != 0) { @@ -196,34 +198,41 @@ public: } else { data = nullptr; } - refcount = new unsigned int; + refcount = new u32; (*refcount) = 1; } + //! Copies whole buffer SharedBuffer(const Buffer &buffer) : SharedBuffer(*buffer, buffer.getSize()) { } + ~SharedBuffer() { drop(); } - T & operator[](unsigned int i) const + + T & operator[](size_t i) const { assert(i < m_size); return data[i]; } + T * operator*() const { return data; } - unsigned int getSize() const + + size_t getSize() const { return m_size; } + operator Buffer() const { return Buffer(data, m_size); } + private: void drop() { @@ -234,9 +243,10 @@ private: delete refcount; } } + T *data; - unsigned int m_size; - unsigned int *refcount; + size_t m_size; + u32 *refcount; }; // This class is not thread-safe! diff --git a/src/util/serialize.h b/src/util/serialize.h index c2cfa601d..b12d551ac 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -48,8 +48,8 @@ // not represent the full range, but rather the largest safe range, of values on // all supported architectures. Note: This definition makes assumptions on // platform float-to-int conversion behavior. -#define F1000_MIN ((float)(s32)((float)(-0x7FFFFFFF - 1) / FIXEDPOINT_FACTOR)) -#define F1000_MAX ((float)(s32)((float)(0x7FFFFFFF) / FIXEDPOINT_FACTOR)) +static constexpr float F1000_MIN = (s32)((float)(S32_MIN) / FIXEDPOINT_FACTOR); +static constexpr float F1000_MAX = (s32)((float)(S32_MAX) / FIXEDPOINT_FACTOR); #define STRING_MAX_LEN 0xFFFF #define WIDE_STRING_MAX_LEN 0xFFFF @@ -159,7 +159,7 @@ inline void writeU64(u8 *data, u64 i) inline u8 readU8(const u8 *data) { - return ((u8)data[0] << 0); + return data[0]; } inline s8 readS8(const u8 *data) diff --git a/src/util/sha1.cpp b/src/util/sha1.cpp index 5d8b92598..850c7201d 100644 --- a/src/util/sha1.cpp +++ b/src/util/sha1.cpp @@ -29,37 +29,29 @@ SOFTWARE. #include #include +#define IN_SHA1_CPP + #include "sha1.h" -// print out memory in hexadecimal -void SHA1::hexPrinter( unsigned char* c, int l ) -{ - assert( c ); - assert( l > 0 ); - while( l > 0 ) - { - printf( " %02x", *c ); - l--; - c++; - } -} +namespace { // circular left bit rotation. MSB wraps around to LSB -Uint32 SHA1::lrot( Uint32 x, int bits ) +inline Uint32 lrot( Uint32 x, int bits ) { return (x<>(32 - bits)); -}; +} // Save a 32-bit unsigned integer to memory, in big-endian order -void SHA1::storeBigEndianUint32( unsigned char* byte, Uint32 num ) +inline void storeBigEndianUint32( unsigned char* byte, Uint32 num ) { - assert( byte ); byte[0] = (unsigned char)(num>>24); byte[1] = (unsigned char)(num>>16); byte[2] = (unsigned char)(num>>8); byte[3] = (unsigned char)num; } +} + // Constructor ******************************************************* SHA1::SHA1() @@ -81,7 +73,6 @@ SHA1::~SHA1() void SHA1::process() { assert( unprocessedBytes == 64 ); - //printf( "process: " ); hexPrinter( bytes, 64 ); printf( "\n" ); int t; Uint32 a, b, c, d, e, K, f, W[80]; // starting values diff --git a/src/util/sha1.h b/src/util/sha1.h index 81eeeca97..39c72f2da 100644 --- a/src/util/sha1.h +++ b/src/util/sha1.h @@ -26,6 +26,10 @@ SOFTWARE. #pragma once +#if !defined(IN_HASHING_CPP) && !defined(IN_SHA1_CPP) +#error do not include directly +#endif + #include #include #include @@ -59,9 +63,4 @@ public: getDigest(reinterpret_cast(ret.data())); return ret; } - - // utility methods - static Uint32 lrot(Uint32 x, int bits); - static void storeBigEndianUint32(unsigned char *byte, Uint32 num); - static void hexPrinter(unsigned char *c, int l); }; diff --git a/src/util/srp.h b/src/util/srp.h index fc4d2dc89..7cbfe3a19 100644 --- a/src/util/srp.h +++ b/src/util/srp.h @@ -55,6 +55,8 @@ #pragma once +#include + struct SRPVerifier; struct SRPUser; diff --git a/src/util/string.cpp b/src/util/string.cpp index c08421d55..e06990356 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -189,35 +189,37 @@ std::string urlencode(std::string_view str) // Encodes reserved URI characters by a percent sign // followed by two hex digits. See RFC 3986, section 2.3. static const char url_hex_chars[] = "0123456789ABCDEF"; - std::ostringstream oss(std::ios::binary); + std::string ret; + ret.reserve(str.size()); for (unsigned char c : str) { if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') { - oss << c; + ret.push_back(c); } else { - oss << "%" - << url_hex_chars[(c & 0xf0) >> 4] - << url_hex_chars[c & 0x0f]; + ret.push_back('%'); + ret.push_back(url_hex_chars[(c & 0xf0) >> 4]); + ret.push_back(url_hex_chars[c & 0x0f]); } } - return oss.str(); + return ret; } std::string urldecode(std::string_view str) { // Inverse of urlencode - std::ostringstream oss(std::ios::binary); + std::string ret; + ret.reserve(str.size()); for (u32 i = 0; i < str.size(); i++) { unsigned char highvalue, lowvalue; - if (str[i] == '%' && + if (str[i] == '%' && i+2 < str.size() && hex_digit_decode(str[i+1], highvalue) && hex_digit_decode(str[i+2], lowvalue)) { - oss << (char) ((highvalue << 4) | lowvalue); + ret.push_back(static_cast((highvalue << 4) | lowvalue)); i += 2; } else { - oss << str[i]; + ret.push_back(str[i]); } } - return oss.str(); + return ret; } u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask) @@ -318,7 +320,7 @@ char *mystrtok_r(char *s, const char *sep, char **lasts) noexcept u64 read_seed(const char *str) { - char *endptr; + char *endptr = nullptr; u64 num; if (str[0] == '0' && str[1] == 'x') @@ -327,7 +329,7 @@ u64 read_seed(const char *str) num = strtoull(str, &endptr, 10); if (*endptr) - num = murmur_hash_64_ua(str, (int)strlen(str), 0x1337); + num = murmur_hash_64_ua(str, strlen(str), 0x1337); return num; } diff --git a/src/util/string.h b/src/util/string.h index 8b0848f8e..d57a7baa9 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -86,7 +86,9 @@ std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask); size_t mystrlcpy(char *dst, const char *src, size_t size) noexcept; char *mystrtok_r(char *s, const char *sep, char **lasts) noexcept; +/// @brief turn string into a map seed. either directly if it's a number or by hashing it. u64 read_seed(const char *str); + bool parseColorString(const std::string &value, video::SColor &color, bool quiet, unsigned char default_alpha = 0xff); std::string encodeHexColorString(video::SColor color); @@ -330,7 +332,7 @@ inline bool my_isspace(const wchar_t c) * @return A view of \p str with leading and trailing whitespace removed. */ template -inline std::basic_string_view trim(const std::basic_string_view &str) +inline std::basic_string_view trim(std::basic_string_view str) { size_t front = 0; size_t back = str.size(); diff --git a/src/util/thread.h b/src/util/thread.h index 8b28bf72e..857a341be 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -18,6 +18,9 @@ public: MutexedVariable(const T &value): m_value(value) {} + MutexedVariable(T &&value): + m_value(std::move(value)) + {} T get() { @@ -31,9 +34,14 @@ public: m_value = value; } - // You pretty surely want to grab the lock when accessing this - T m_value; + void set(T &&value) + { + MutexAutoLock lock(m_mutex); + m_value = std::move(value); + } + private: + T m_value; std::mutex m_mutex; }; From b146673c3df091fc2ff87580be736ab4c81b5078 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Mar 2025 19:59:55 +0100 Subject: [PATCH 096/284] Remove old mystrtok_r for MinGW --- src/porting.h | 5 ----- src/util/string.cpp | 26 -------------------------- src/util/string.h | 1 - 3 files changed, 32 deletions(-) diff --git a/src/porting.h b/src/porting.h index 4963916f0..7c652663a 100644 --- a/src/porting.h +++ b/src/porting.h @@ -55,11 +55,6 @@ #define strncasecmp(x, y, n) strnicmp(x, y, n) #endif -#ifdef __MINGW32__ - // was broken in 2013, unclear if still needed - #define strtok_r(x, y, z) mystrtok_r(x, y, z) -#endif - #if !HAVE_STRLCPY #define strlcpy(d, s, n) mystrlcpy(d, s, n) #endif diff --git a/src/util/string.cpp b/src/util/string.cpp index e06990356..03f9d5cf1 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -292,32 +292,6 @@ size_t mystrlcpy(char *dst, const char *src, size_t size) noexcept return srclen; } -char *mystrtok_r(char *s, const char *sep, char **lasts) noexcept -{ - char *t; - - if (!s) - s = *lasts; - - while (*s && strchr(sep, *s)) - s++; - - if (!*s) - return nullptr; - - t = s; - while (*t) { - if (strchr(sep, *t)) { - *t++ = '\0'; - break; - } - t++; - } - - *lasts = t; - return s; -} - u64 read_seed(const char *str) { char *endptr = nullptr; diff --git a/src/util/string.h b/src/util/string.h index d57a7baa9..eaa13a264 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -84,7 +84,6 @@ u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask); std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask); size_t mystrlcpy(char *dst, const char *src, size_t size) noexcept; -char *mystrtok_r(char *s, const char *sep, char **lasts) noexcept; /// @brief turn string into a map seed. either directly if it's a number or by hashing it. u64 read_seed(const char *str); From dea95c7339948e6cc35c95dc1ade8d70f796d946 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Mar 2025 19:47:40 +0100 Subject: [PATCH 097/284] Reduce transitive includes by moving a class --- src/client/particles.h | 1 + src/irrlichttypes.h | 5 ---- src/mapgen/mg_biome.h | 1 + src/mapsector.h | 1 + src/network/mtp/impl.h | 2 +- src/network/mtp/internal.h | 41 ++++++++++++++++++++---------- src/network/networkpacket.h | 2 ++ src/network/networkprotocol.h | 3 +-- src/nodedef.h | 1 + src/unittest/test_address.cpp | 1 + src/unittest/test_eventmanager.cpp | 3 ++- src/util/pointer.h | 19 ++------------ 12 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/client/particles.h b/src/client/particles.h index 72294a552..fe7d68475 100644 --- a/src/client/particles.h +++ b/src/client/particles.h @@ -11,6 +11,7 @@ #include "SMeshBuffer.h" #include +#include #include #include #include "../particles.h" diff --git a/src/irrlichttypes.h b/src/irrlichttypes.h index 0994bbd94..0474786a1 100644 --- a/src/irrlichttypes.h +++ b/src/irrlichttypes.h @@ -4,12 +4,7 @@ #pragma once -/* - * IrrlichtMt already includes stdint.h in irrTypes.h. This works everywhere - * we need it to (including recent MSVC), so should be fine here too. - */ #include - #include using namespace irr; diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h index 8adce5db6..37e09136e 100644 --- a/src/mapgen/mg_biome.h +++ b/src/mapgen/mg_biome.h @@ -8,6 +8,7 @@ #include "objdef.h" #include "nodedef.h" #include "noise.h" +#include "debug.h" // FATAL_ERROR_IF class Server; class Settings; diff --git a/src/mapsector.h b/src/mapsector.h index bc5a2a731..d977bc63c 100644 --- a/src/mapsector.h +++ b/src/mapsector.h @@ -8,6 +8,7 @@ #include "irr_v2d.h" #include "mapblock.h" #include +#include #include #include diff --git a/src/network/mtp/impl.h b/src/network/mtp/impl.h index a88012b22..3f81fe27b 100644 --- a/src/network/mtp/impl.h +++ b/src/network/mtp/impl.h @@ -14,6 +14,7 @@ #include "network/networkprotocol.h" #include #include +#include #include namespace con @@ -24,7 +25,6 @@ class ConnectionSendThread; class Peer; -// FIXME: Peer refcounting should generally be replaced by std::shared_ptr class PeerHelper { public: diff --git a/src/network/mtp/internal.h b/src/network/mtp/internal.h index e3ad39bde..dc3ad55d7 100644 --- a/src/network/mtp/internal.h +++ b/src/network/mtp/internal.h @@ -33,7 +33,7 @@ channel: /* Packet types: -CONTROL: This is a packet used by the protocol. +PACKET_TYPE_CONTROL: This is a packet used by the protocol. - When this is processed, nothing is handed to the user. Header (2 byte): [0] u8 type @@ -48,25 +48,18 @@ controltype and data description: packet to get a reply CONTROLTYPE_DISCO */ -enum ControlType : u8 { - CONTROLTYPE_ACK = 0, - CONTROLTYPE_SET_PEER_ID = 1, - CONTROLTYPE_PING = 2, - CONTROLTYPE_DISCO = 3, -}; /* -ORIGINAL: This is a plain packet with no control and no error +PACKET_TYPE_ORIGINAL: This is a plain packet with no control and no error checking at all. - When this is processed, it is directly handed to the user. Header (1 byte): [0] u8 type */ -//#define TYPE_ORIGINAL 1 #define ORIGINAL_HEADER_SIZE 1 /* -SPLIT: These are sequences of packets forming one bigger piece of +PACKET_TYPE_SPLIT: These are sequences of packets forming one bigger piece of data. - When processed and all the packet_nums 0...packet_count-1 are present (this should be buffered), the resulting data shall be @@ -80,10 +73,9 @@ data. [3] u16 chunk_count [5] u16 chunk_num */ -//#define TYPE_SPLIT 2 /* -RELIABLE: Delivery of all RELIABLE packets shall be forced by ACKs, +PACKET_TYPE_RELIABLE: Delivery of all RELIABLE packets shall be forced by ACKs, and they shall be delivered in the same order as sent. This is done with a buffer in the receiving and transmitting end. - When this is processed, the contents of each packet is recursively @@ -93,15 +85,29 @@ with a buffer in the receiving and transmitting end. [1] u16 seqnum */ -//#define TYPE_RELIABLE 3 #define RELIABLE_HEADER_SIZE 3 #define SEQNUM_INITIAL 65500 #define SEQNUM_MAX 65535 +/****/ + +template +class ConstSharedPtr { +public: + ConstSharedPtr(T *ptr) : ptr(ptr) {} + ConstSharedPtr(const std::shared_ptr &ptr) : ptr(ptr) {} + + const T* get() const noexcept { return ptr.get(); } + const T& operator*() const noexcept { return *ptr.get(); } + const T* operator->() const noexcept { return ptr.get(); } + +private: + std::shared_ptr ptr; +}; + namespace con { - enum PacketType : u8 { PACKET_TYPE_CONTROL = 0, PACKET_TYPE_ORIGINAL = 1, @@ -110,6 +116,13 @@ enum PacketType : u8 { PACKET_TYPE_MAX }; +enum ControlType : u8 { + CONTROLTYPE_ACK = 0, + CONTROLTYPE_SET_PEER_ID = 1, + CONTROLTYPE_PING = 2, + CONTROLTYPE_DISCO = 3, +}; + inline bool seqnum_higher(u16 totest, u16 base) { if (totest > base) diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index d5e687c68..107d07cfd 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -8,6 +8,8 @@ #include "irrlichttypes_bloated.h" #include "networkprotocol.h" #include +#include +#include #include class NetworkPacket diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 152534cbe..5ce3f4221 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -4,8 +4,7 @@ #pragma once -#include "irrTypes.h" -using namespace irr; +#include "irrlichttypes.h" extern const u16 LATEST_PROTOCOL_VERSION; diff --git a/src/nodedef.h b/src/nodedef.h index 24bb0c460..71a61896b 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -7,6 +7,7 @@ #include "irrlichttypes_bloated.h" #include #include +#include // shared_ptr #include #include "mapnode.h" #include "nameidmapping.h" diff --git a/src/unittest/test_address.cpp b/src/unittest/test_address.cpp index 7ae7f85cf..364e82fad 100644 --- a/src/unittest/test_address.cpp +++ b/src/unittest/test_address.cpp @@ -4,6 +4,7 @@ #include "test.h" +#include #include "log.h" #include "settings.h" #include "network/socket.h" diff --git a/src/unittest/test_eventmanager.cpp b/src/unittest/test_eventmanager.cpp index 0b1d3bbd1..76019f730 100644 --- a/src/unittest/test_eventmanager.cpp +++ b/src/unittest/test_eventmanager.cpp @@ -3,6 +3,7 @@ // Copyright (C) 2018 nerzhul, Loic BLOT #include +#include #include "test.h" #include "client/event_manager.h" @@ -94,4 +95,4 @@ void TestEventManager::testRealEventAfterDereg() // Push the new event & ensure we target the default value ev.put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); UASSERT(emt->getTestValue() == 0); -} \ No newline at end of file +} diff --git a/src/util/pointer.h b/src/util/pointer.h index fe90a3866..e260b4a5c 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -5,26 +5,11 @@ #pragma once #include "irrlichttypes.h" -#include "debug.h" // For assert() +#include "util/basic_macros.h" +#include #include -#include // std::shared_ptr #include - -template -class ConstSharedPtr { -public: - ConstSharedPtr(T *ptr) : ptr(ptr) {} - ConstSharedPtr(const std::shared_ptr &ptr) : ptr(ptr) {} - - const T* get() const noexcept { return ptr.get(); } - const T& operator*() const noexcept { return *ptr.get(); } - const T* operator->() const noexcept { return ptr.get(); } - -private: - std::shared_ptr ptr; -}; - template class Buffer { From 2602d03b3479f24ba0cb4701ba0948eea58eebce Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Mar 2025 21:25:20 +0100 Subject: [PATCH 098/284] Split ABM/LBM from serverenvironment.cpp to own file --- src/server/CMakeLists.txt | 3 +- src/server/blockmodifier.cpp | 542 ++++++++++++++++++++++++++++++ src/server/blockmodifier.h | 176 ++++++++++ src/serverenvironment.cpp | 547 ------------------------------- src/serverenvironment.h | 134 +------- src/unittest/test_lbmmanager.cpp | 2 +- 6 files changed, 722 insertions(+), 682 deletions(-) create mode 100644 src/server/blockmodifier.cpp create mode 100644 src/server/blockmodifier.h diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 3588451c0..98318e93d 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -1,13 +1,14 @@ set(common_server_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/activeobjectmgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ban.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/blockmodifier.cpp ${CMAKE_CURRENT_SOURCE_DIR}/clientiface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/luaentity_sao.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp ${CMAKE_CURRENT_SOURCE_DIR}/player_sao.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rollback.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serveractiveobject.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serverinventorymgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serverlist.cpp ${CMAKE_CURRENT_SOURCE_DIR}/unit_sao.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rollback.cpp PARENT_SCOPE) diff --git a/src/server/blockmodifier.cpp b/src/server/blockmodifier.cpp new file mode 100644 index 000000000..1983d5def --- /dev/null +++ b/src/server/blockmodifier.cpp @@ -0,0 +1,542 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2010-2017 celeron55, Perttu Ahola + +#include +#include "blockmodifier.h" +#include "serverenvironment.h" +#include "server.h" +#include "mapblock.h" +#include "nodedef.h" +#include "gamedef.h" + +/* + ABMs +*/ + +ABMWithState::ABMWithState(ActiveBlockModifier *abm_): + abm(abm_) +{ + // Initialize timer to random value to spread processing + float itv = abm->getTriggerInterval(); + itv = MYMAX(0.001f, itv); // No less than 1ms + int minval = MYMAX(-0.51f * itv, -60); // Clamp to + int maxval = MYMIN( 0.51f * itv, 60); // +-60 seconds + timer = myrand_range(minval, maxval); +} + +struct ActiveABM +{ + ActiveBlockModifier *abm; + std::vector required_neighbors; + std::vector without_neighbors; + int chance; + s16 min_y, max_y; +}; + +#define CONTENT_TYPE_CACHE_MAX 64 + +ABMHandler::ABMHandler(std::vector &abms, + float dtime_s, ServerEnvironment *env, + bool use_timers): + m_env(env) +{ + if (dtime_s < 0.001f) + return; + const NodeDefManager *ndef = env->getGameDef()->ndef(); + for (ABMWithState &abmws : abms) { + ActiveBlockModifier *abm = abmws.abm; + float trigger_interval = abm->getTriggerInterval(); + if (trigger_interval < 0.001f) + trigger_interval = 0.001f; + float actual_interval = dtime_s; + if (use_timers) { + abmws.timer += dtime_s; + if (abmws.timer < trigger_interval) + continue; + abmws.timer -= trigger_interval; + actual_interval = trigger_interval; + } + float chance = abm->getTriggerChance(); + if (chance == 0) + chance = 1; + + ActiveABM aabm; + aabm.abm = abm; + if (abm->getSimpleCatchUp()) { + float intervals = actual_interval / trigger_interval; + if (intervals == 0) + continue; + aabm.chance = chance / intervals; + if (aabm.chance == 0) + aabm.chance = 1; + } else { + aabm.chance = chance; + } + // y limits + aabm.min_y = abm->getMinY(); + aabm.max_y = abm->getMaxY(); + + // Trigger neighbors + for (const auto &s : abm->getRequiredNeighbors()) + ndef->getIds(s, aabm.required_neighbors); + SORT_AND_UNIQUE(aabm.required_neighbors); + + for (const auto &s : abm->getWithoutNeighbors()) + ndef->getIds(s, aabm.without_neighbors); + SORT_AND_UNIQUE(aabm.without_neighbors); + + // Trigger contents + std::vector ids; + for (const auto &s : abm->getTriggerContents()) + ndef->getIds(s, ids); + SORT_AND_UNIQUE(ids); + for (content_t c : ids) { + if (c >= m_aabms.size()) + m_aabms.resize(c + 256, nullptr); + if (!m_aabms[c]) + m_aabms[c] = new std::vector; + m_aabms[c]->push_back(aabm); + } + } +} + +ABMHandler::~ABMHandler() +{ + for (auto &aabms : m_aabms) + delete aabms; +} + +u32 ABMHandler::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++) + { + MapBlock *block2 = map->getBlockNoCreateNoEx( + block->getPos() + v3s16(x,y,z)); + if (!block2) { + wider_unknown_count++; + continue; + } + wider += block2->m_static_objects.size(); + } + // Extrapolate + u32 active_object_count = block->m_static_objects.getActiveSize(); + u32 wider_known_count = 3 * 3 * 3 - wider_unknown_count; + wider += wider_unknown_count * wider / wider_known_count; + return active_object_count; +} + +void ABMHandler::apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached) +{ + if (m_aabms.empty()) + return; + + // Check the content type cache first + // to see whether there are any ABMs + // to be run at all for this block. + if (!block->contents.empty()) { + assert(!block->do_not_cache_contents); // invariant + blocks_cached++; + bool run_abms = false; + for (content_t c : block->contents) { + if (c < m_aabms.size() && m_aabms[c]) { + run_abms = true; + break; + } + } + if (!run_abms) + return; + } + blocks_scanned++; + + ServerMap *map = &m_env->getServerMap(); + + u32 active_object_count_wider; + u32 active_object_count = countObjects(block, map, active_object_count_wider); + m_env->m_added_objects = 0; + + bool want_contents_cached = block->contents.empty() && !block->do_not_cache_contents; + + v3s16 p0; + for(p0.Z=0; p0.ZgetNodeNoCheck(p0); + content_t c = n.getContent(); + + // Cache content types as we go + if (want_contents_cached && !CONTAINS(block->contents, c)) { + if (block->contents.size() >= CONTENT_TYPE_CACHE_MAX) { + // Too many different nodes... don't try to cache + want_contents_cached = false; + block->do_not_cache_contents = true; + decltype(block->contents) empty; + std::swap(block->contents, empty); + } else { + block->contents.push_back(c); + } + } + + if (c >= m_aabms.size() || !m_aabms[c]) + continue; + + v3s16 p = p0 + block->getPosRelative(); + for (ActiveABM &aabm : *m_aabms[c]) { + if (p.Y < aabm.min_y || p.Y > aabm.max_y) + continue; + + if (myrand() % aabm.chance != 0) + continue; + + // Check neighbors + const bool check_required_neighbors = !aabm.required_neighbors.empty(); + const bool check_without_neighbors = !aabm.without_neighbors.empty(); + if (check_required_neighbors || check_without_neighbors) { + v3s16 p1; + bool have_required = false; + 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->getNodeNoCheck(p1); + c = n.getContent(); + } else { + // otherwise consult the map + MapNode n = map->getNode(p1 + block->getPosRelative()); + c = n.getContent(); + } + if (check_required_neighbors && !have_required) { + if (CONTAINS(aabm.required_neighbors, c)) { + if (!check_without_neighbors) + goto neighbor_found; + have_required = true; + } + } + if (check_without_neighbors) { + if (CONTAINS(aabm.without_neighbors, c)) + goto neighbor_invalid; + } + } + if (have_required || !check_required_neighbors) + goto neighbor_found; + // No required neighbor found +neighbor_invalid: + 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 (block->isOrphan()) + return; + + // 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; + } + + // Update and check node after possible modification + n = block->getNodeNoCheck(p0); + if (n.getContent() != c) + break; + } + } +} + +/* + LBMs +*/ + +LBMContentMapping::~LBMContentMapping() +{ + map.clear(); + for (auto &it : lbm_list) + delete it; +} + +void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef) +{ + // Add the lbm_def to the LBMContentMapping. + // Unknown names get added to the global NameIdMapping. + const NodeDefManager *nodedef = gamedef->ndef(); + + FATAL_ERROR_IF(CONTAINS(lbm_list, lbm_def), "Same LBM registered twice"); + lbm_list.push_back(lbm_def); + + std::vector c_ids; + + for (const auto &node : lbm_def->trigger_contents) { + bool found = nodedef->getIds(node, c_ids); + if (!found) { + content_t c_id = gamedef->allocateUnknownNodeId(node); + if (c_id == CONTENT_IGNORE) { + // Seems it can't be allocated. + warningstream << "Could not internalize node name \"" << node + << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl; + continue; + } + c_ids.push_back(c_id); + } + } + + SORT_AND_UNIQUE(c_ids); + + for (content_t c_id : c_ids) + map[c_id].push_back(lbm_def); +} + +const LBMContentMapping::lbm_vector * +LBMContentMapping::lookup(content_t c) const +{ + lbm_map::const_iterator it = map.find(c); + if (it == map.end()) + return nullptr; + return &(it->second); +} + +LBMManager::~LBMManager() +{ + for (auto &m_lbm_def : m_lbm_defs) + delete m_lbm_def.second; + + m_lbm_lookup.clear(); +} + +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"); + + if (str_starts_with(lbm_def->name, ":")) + lbm_def->name.erase(0, 1); + + if (lbm_def->name.empty() || + !string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) { + throw ModError("Error adding LBM \"" + lbm_def->name + + "\": 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) +{ + m_query_mode = true; + + auto introduction_times = parseIntroductionTimesString(times); + + // Put stuff from introduction_times into m_lbm_lookup + for (auto &[name, time] : introduction_times) { + auto def_it = m_lbm_defs.find(name); + if (def_it == m_lbm_defs.end()) { + infostream << "LBMManager: LBM " << name << " is not registered. " + "Discarding it." << std::endl; + continue; + } + auto *lbm_def = def_it->second; + if (lbm_def->run_at_every_load) { + continue; // These are handled below + } + if (time > now) { + warningstream << "LBMManager: LBM " << name << " was introduced in " + "the future. Pretending it's new." << std::endl; + // By skipping here it will be added as newly introduced. + continue; + } + + m_lbm_lookup[time].addLBM(lbm_def, gamedef); + + // Erase the entry so that we know later + // which elements didn't get put into m_lbm_lookup + m_lbm_defs.erase(def_it); + } + + // Now also add the elements from m_lbm_defs to m_lbm_lookup + // that weren't added in the previous step. + // They are introduced first time to this world, + // or are run at every load (introduction time hardcoded to U32_MAX). + + auto &lbms_we_introduce_now = m_lbm_lookup[now]; + auto &lbms_running_always = m_lbm_lookup[U32_MAX]; + for (auto &it : m_lbm_defs) { + if (it.second->run_at_every_load) + lbms_running_always.addLBM(it.second, gamedef); + else + lbms_we_introduce_now.addLBM(it.second, gamedef); + } + + // All pointer ownership now moved to LBMContentMapping + m_lbm_defs.clear(); + + // If these are empty delete them again to avoid pointless iteration. + if (lbms_we_introduce_now.empty()) + m_lbm_lookup.erase(now); + if (lbms_running_always.empty()) + m_lbm_lookup.erase(U32_MAX); + + infostream << "LBMManager: " << m_lbm_lookup.size() << + " unique times in lookup table" << std::endl; +} + +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"); + + std::ostringstream oss; + for (const auto &it : m_lbm_lookup) { + u32 time = it.first; + auto &lbm_list = it.second.getList(); + for (const auto &lbm_def : lbm_list) { + // Don't add if the LBM runs at every load, + // then introduction time is hardcoded and doesn't need to be stored. + if (lbm_def->run_at_every_load) + continue; + oss << lbm_def->name << "~" << time << ";"; + } + } + return oss.str(); +} + +std::unordered_map + LBMManager::parseIntroductionTimesString(const std::string ×) +{ + std::unordered_map ret; + + size_t idx = 0; + size_t idx_new; + while ((idx_new = times.find(';', idx)) != std::string::npos) { + std::string entry = times.substr(idx, idx_new - idx); + idx = idx_new + 1; + + std::vector components = str_split(entry, '~'); + if (components.size() != 2) + throw SerializationError("Introduction times entry \"" + + entry + "\" requires exactly one '~'!"); + if (components[0].empty()) + throw SerializationError("LBM name is empty"); + std::string name = std::move(components[0]); + if (name.front() == ':') // old versions didn't strip this + name.erase(0, 1); + u32 time = from_string(components[1]); + ret[std::move(name)] = time; + } + + return ret; +} + +namespace { + struct LBMToRun { + std::unordered_set p; // node positions + std::vector l; // ordered list of LBMs + + template + void insertLBMs(const C &container) { + for (auto &it : container) { + if (!CONTAINS(l, it)) + l.push_back(it); + } + } + }; +} + +void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, + const u32 stamp, const float dtime_s) +{ + // Precondition, we need m_lbm_lookup to be initialized + FATAL_ERROR_IF(!m_query_mode, + "attempted to query on non fully set up LBMManager"); + + // Collect a list of all LBMs and associated positions + std::unordered_map to_run; + + // Note: the iteration count of this outer loop is typically very low, so it's ok. + for (auto it = getLBMsIntroducedAfter(stamp); it != m_lbm_lookup.end(); ++it) { + v3s16 pos; + content_t c; + + // Cache previous lookups since it has a high performance penalty. + content_t previous_c = CONTENT_IGNORE; + const LBMContentMapping::lbm_vector *lbm_list = nullptr; + LBMToRun *batch = nullptr; + + for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) + for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) + for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) { + c = block->getNodeNoCheck(pos).getContent(); + + bool c_changed = false; + if (previous_c != c) { + c_changed = true; + lbm_list = it->second.lookup(c); + if (lbm_list) + batch = &to_run[c]; // creates entry + previous_c = c; + } + + if (!lbm_list) + continue; + batch->p.insert(pos); + if (c_changed) { + batch->insertLBMs(*lbm_list); + } else { + // we were here before so the list must be filled + assert(!batch->l.empty()); + } + } + } + + // Actually run them + bool first = true; + for (auto &[c, batch] : to_run) { + if (tracestream) { + tracestream << "Running " << batch.l.size() << " LBMs for node " + << env->getGameDef()->ndef()->get(c).name << " (" + << batch.p.size() << "x) in block " << block->getPos() << std::endl; + } + for (auto &lbm_def : batch.l) { + if (!first) { + // The fun part: since any LBM call can change the nodes inside of he + // block, we have to recheck the positions to see if the wanted node + // is still there. + // Note that we don't rescan the whole block, we don't want to include new changes. + for (auto it2 = batch.p.begin(); it2 != batch.p.end(); ) { + if (block->getNodeNoCheck(*it2).getContent() != c) + it2 = batch.p.erase(it2); + else + ++it2; + } + } else { + assert(!batch.p.empty()); + } + first = false; + + if (batch.p.empty()) + break; + lbm_def->trigger(env, block, batch.p, dtime_s); + if (block->isOrphan()) + return; + } + } +} diff --git a/src/server/blockmodifier.h b/src/server/blockmodifier.h new file mode 100644 index 000000000..602147ae0 --- /dev/null +++ b/src/server/blockmodifier.h @@ -0,0 +1,176 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2010-2017 celeron55, Perttu Ahola + +#pragma once + +#include +#include +#include +#include + +#include "irr_v3d.h" +#include "mapnode.h" + +class ServerEnvironment; +class ServerMap; +class MapBlock; +class IGameDef; + +/* + ABMs +*/ + +class ActiveBlockModifier +{ +public: + ActiveBlockModifier() = default; + virtual ~ActiveBlockModifier() = default; + + // Set of contents to trigger on + virtual const std::vector &getTriggerContents() const = 0; + // Set of required neighbors (trigger doesn't happen if none are found) + // Empty = do not check neighbors + virtual const std::vector &getRequiredNeighbors() const = 0; + // Set of without neighbors (trigger doesn't happen if any are found) + // Empty = do not check neighbors + virtual const std::vector &getWithoutNeighbors() const = 0; + // Trigger interval in seconds + virtual float getTriggerInterval() = 0; + // Random chance of (1 / return value), 0 is disallowed + virtual u32 getTriggerChance() = 0; + // Whether to modify chance to simulate time lost by an unnattended block + virtual bool getSimpleCatchUp() = 0; + // get min Y for apply abm + virtual s16 getMinY() = 0; + // get max Y for apply abm + virtual s16 getMaxY() = 0; + // 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){}; +}; + +struct ABMWithState +{ + ActiveBlockModifier *abm; + float timer = 0.0f; + + ABMWithState(ActiveBlockModifier *abm_); +}; + +struct ActiveABM; // hidden + +class ABMHandler +{ + ServerEnvironment *m_env; + // vector index = content_t + std::vector*> m_aabms; + +public: + ABMHandler(std::vector &abms, + float dtime_s, ServerEnvironment *env, + bool use_timers); + ~ABMHandler(); + + // Find out how many objects the given block and its neighbors contain. + // Returns the number of objects in the block, and also in 'wider' the + // number of objects in the block and all its neighbors. The latter + // may be an estimate if any neighbors are unloaded. + static u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider); + + void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached); +}; + +/* + LBMs +*/ + +#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:" + +struct LoadingBlockModifierDef +{ + // Set of contents to trigger on + std::vector trigger_contents; + std::string name; + bool run_at_every_load = false; + + virtual ~LoadingBlockModifierDef() = default; + + /// @brief Called to invoke LBM + /// @param env environment + /// @param block the block in question + /// @param positions set of node positions (block-relative!) + /// @param dtime_s game time since last deactivation + virtual void trigger(ServerEnvironment *env, MapBlock *block, + const std::unordered_set &positions, float dtime_s) {}; +}; + +class LBMContentMapping +{ +public: + typedef std::vector lbm_vector; + typedef std::unordered_map lbm_map; + + LBMContentMapping() = default; + void addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef); + const lbm_map::mapped_type *lookup(content_t c) const; + const lbm_vector &getList() const { return lbm_list; } + bool empty() const { return lbm_list.empty(); } + + // This struct owns the LBM pointers. + ~LBMContentMapping(); + DISABLE_CLASS_COPY(LBMContentMapping); + ALLOW_CLASS_MOVE(LBMContentMapping); + +private: + lbm_vector lbm_list; + lbm_map map; +}; + +class LBMManager +{ +public: + LBMManager() = default; + ~LBMManager(); + + // Don't call this after loadIntroductionTimes() ran. + void addLBMDef(LoadingBlockModifierDef *lbm_def); + + /// @param now current game time + void loadIntroductionTimes(const std::string ×, + IGameDef *gamedef, u32 now); + + // Don't call this before loadIntroductionTimes() ran. + std::string createIntroductionTimesString(); + + // Don't call this before loadIntroductionTimes() ran. + void applyLBMs(ServerEnvironment *env, MapBlock *block, + u32 stamp, float dtime_s); + + // Warning: do not make this std::unordered_map, order is relevant here + typedef std::map lbm_lookup_map; + +private: + // Once we set this to true, we can only query, + // not modify + bool m_query_mode = false; + + // For m_query_mode == false: + // The key of the map is the LBM def's name. + std::unordered_map m_lbm_defs; + + // For m_query_mode == true: + // The key of the map is the LBM def's first introduction time. + lbm_lookup_map m_lbm_lookup; + + /// @return map of LBM name -> timestamp + static std::unordered_map + parseIntroductionTimesString(const std::string ×); + + // Returns an iterator to the LBMs that were introduced + // 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); } +}; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 90127ab6f..697b7b073 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -38,8 +38,6 @@ #include "server/luaentity_sao.h" #include "server/player_sao.h" -#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:" - // A number that is much smaller than the timeout for particle spawners should/could ever be #define PARTICLE_SPAWNER_NO_EXPIRY -1024.f @@ -47,305 +45,6 @@ static constexpr s16 ACTIVE_OBJECT_RESAVE_DISTANCE_SQ = sqr(3); static constexpr u32 BLOCK_RESAVE_TIMESTAMP_DIFF = 60; // in units of game time -/* - ABMWithState -*/ - -ABMWithState::ABMWithState(ActiveBlockModifier *abm_): - abm(abm_) -{ - // Initialize timer to random value to spread processing - float itv = abm->getTriggerInterval(); - itv = MYMAX(0.001f, itv); // No less than 1ms - int minval = MYMAX(-0.51f*itv, -60); // Clamp to - int maxval = MYMIN(0.51f*itv, 60); // +-60 seconds - timer = myrand_range(minval, maxval); -} - -/* - LBMManager -*/ - -LBMContentMapping::~LBMContentMapping() -{ - map.clear(); - for (auto &it : lbm_list) - delete it; -} - -void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef) -{ - // Add the lbm_def to the LBMContentMapping. - // Unknown names get added to the global NameIdMapping. - const NodeDefManager *nodedef = gamedef->ndef(); - - FATAL_ERROR_IF(CONTAINS(lbm_list, lbm_def), "Same LBM registered twice"); - lbm_list.push_back(lbm_def); - - std::vector c_ids; - - for (const auto &node : lbm_def->trigger_contents) { - bool found = nodedef->getIds(node, c_ids); - if (!found) { - content_t c_id = gamedef->allocateUnknownNodeId(node); - if (c_id == CONTENT_IGNORE) { - // Seems it can't be allocated. - warningstream << "Could not internalize node name \"" << node - << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl; - continue; - } - c_ids.push_back(c_id); - } - } - - SORT_AND_UNIQUE(c_ids); - - for (content_t c_id : c_ids) - map[c_id].push_back(lbm_def); -} - -const LBMContentMapping::lbm_vector * -LBMContentMapping::lookup(content_t c) const -{ - lbm_map::const_iterator it = map.find(c); - if (it == map.end()) - return NULL; - // This first dereferences the iterator, returning - // a std::vector - // reference, then we convert it to a pointer. - return &(it->second); -} - -LBMManager::~LBMManager() -{ - for (auto &m_lbm_def : m_lbm_defs) { - delete m_lbm_def.second; - } - - m_lbm_lookup.clear(); -} - -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"); - - if (str_starts_with(lbm_def->name, ":")) - lbm_def->name.erase(0, 1); - - if (lbm_def->name.empty() || - !string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) { - throw ModError("Error adding LBM \"" + lbm_def->name + - "\": 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) -{ - m_query_mode = true; - - auto introduction_times = parseIntroductionTimesString(times); - - // Put stuff from introduction_times into m_lbm_lookup - for (auto &[name, time] : introduction_times) { - auto def_it = m_lbm_defs.find(name); - if (def_it == m_lbm_defs.end()) { - infostream << "LBMManager: LBM " << name << " is not registered. " - "Discarding it." << std::endl; - continue; - } - auto *lbm_def = def_it->second; - if (lbm_def->run_at_every_load) { - continue; // These are handled below - } - if (time > now) { - warningstream << "LBMManager: LBM " << name << " was introduced in " - "the future. Pretending it's new." << std::endl; - // By skipping here it will be added as newly introduced. - continue; - } - - m_lbm_lookup[time].addLBM(lbm_def, gamedef); - - // Erase the entry so that we know later - // which elements didn't get put into m_lbm_lookup - m_lbm_defs.erase(def_it); - } - - // Now also add the elements from m_lbm_defs to m_lbm_lookup - // that weren't added in the previous step. - // They are introduced first time to this world, - // or are run at every load (introduction time hardcoded to U32_MAX). - - auto &lbms_we_introduce_now = m_lbm_lookup[now]; - auto &lbms_running_always = m_lbm_lookup[U32_MAX]; - for (auto &it : m_lbm_defs) { - if (it.second->run_at_every_load) - lbms_running_always.addLBM(it.second, gamedef); - else - lbms_we_introduce_now.addLBM(it.second, gamedef); - } - - // All pointer ownership now moved to LBMContentMapping - m_lbm_defs.clear(); - - // If these are empty delete them again to avoid pointless iteration. - if (lbms_we_introduce_now.empty()) - m_lbm_lookup.erase(now); - if (lbms_running_always.empty()) - m_lbm_lookup.erase(U32_MAX); - - infostream << "LBMManager: " << m_lbm_lookup.size() << - " unique times in lookup table" << std::endl; -} - -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"); - - std::ostringstream oss; - for (const auto &it : m_lbm_lookup) { - u32 time = it.first; - auto &lbm_list = it.second.getList(); - for (const auto &lbm_def : lbm_list) { - // Don't add if the LBM runs at every load, - // then introduction time is hardcoded and doesn't need to be stored. - if (lbm_def->run_at_every_load) - continue; - oss << lbm_def->name << "~" << time << ";"; - } - } - return oss.str(); -} - -std::unordered_map - LBMManager::parseIntroductionTimesString(const std::string ×) -{ - std::unordered_map ret; - - size_t idx = 0; - size_t idx_new; - while ((idx_new = times.find(';', idx)) != std::string::npos) { - std::string entry = times.substr(idx, idx_new - idx); - idx = idx_new + 1; - - std::vector components = str_split(entry, '~'); - if (components.size() != 2) - throw SerializationError("Introduction times entry \"" - + entry + "\" requires exactly one '~'!"); - if (components[0].empty()) - throw SerializationError("LBM name is empty"); - std::string name = std::move(components[0]); - if (name.front() == ':') // old versions didn't strip this - name.erase(0, 1); - u32 time = from_string(components[1]); - ret[std::move(name)] = time; - } - - return ret; -} - -namespace { - struct LBMToRun { - std::unordered_set p; // node positions - std::vector l; // ordered list of LBMs - - template - void insertLBMs(const C &container) { - for (auto &it : container) { - if (!CONTAINS(l, it)) - l.push_back(it); - } - } - }; -} - -void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, - const u32 stamp, const float dtime_s) -{ - // Precondition, we need m_lbm_lookup to be initialized - FATAL_ERROR_IF(!m_query_mode, - "attempted to query on non fully set up LBMManager"); - - // Collect a list of all LBMs and associated positions - std::unordered_map to_run; - - // Note: the iteration count of this outer loop is typically very low, so it's ok. - for (auto it = getLBMsIntroducedAfter(stamp); it != m_lbm_lookup.end(); ++it) { - v3s16 pos; - content_t c; - - // Cache previous lookups since it has a high performance penalty. - content_t previous_c = CONTENT_IGNORE; - const LBMContentMapping::lbm_vector *lbm_list = nullptr; - LBMToRun *batch = nullptr; - - for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) - for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) - for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) { - c = block->getNodeNoCheck(pos).getContent(); - - bool c_changed = false; - if (previous_c != c) { - c_changed = true; - lbm_list = it->second.lookup(c); - if (lbm_list) - batch = &to_run[c]; // creates entry - previous_c = c; - } - - if (!lbm_list) - continue; - batch->p.insert(pos); - if (c_changed) { - batch->insertLBMs(*lbm_list); - } else { - // we were here before so the list must be filled - assert(!batch->l.empty()); - } - } - } - - // Actually run them - bool first = true; - for (auto &[c, batch] : to_run) { - if (tracestream) { - tracestream << "Running " << batch.l.size() << " LBMs for node " - << env->getGameDef()->ndef()->get(c).name << " (" - << batch.p.size() << "x) in block " << block->getPos() << std::endl; - } - for (auto &lbm_def : batch.l) { - if (!first) { - // The fun part: since any LBM call can change the nodes inside of he - // block, we have to recheck the positions to see if the wanted node - // is still there. - // Note that we don't rescan the whole block, we don't want to include new changes. - for (auto it2 = batch.p.begin(); it2 != batch.p.end(); ) { - if (block->getNodeNoCheck(*it2).getContent() != c) - it2 = batch.p.erase(it2); - else - ++it2; - } - } else { - assert(!batch.p.empty()); - } - first = false; - - if (batch.p.empty()) - break; - lbm_def->trigger(env, block, batch.p, dtime_s); - if (block->isOrphan()) - return; - } - } -} /* ActiveBlockList @@ -839,252 +538,6 @@ void ServerEnvironment::loadDefaultMeta() m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time); } -struct ActiveABM -{ - ActiveBlockModifier *abm; - std::vector required_neighbors; - std::vector without_neighbors; - int chance; - s16 min_y, max_y; -}; - -#define CONTENT_TYPE_CACHE_MAX 64 - -class ABMHandler -{ -private: - ServerEnvironment *m_env; - std::vector *> m_aabms; -public: - ABMHandler(std::vector &abms, - float dtime_s, ServerEnvironment *env, - bool use_timers): - m_env(env) - { - if (dtime_s < 0.001f) - return; - const NodeDefManager *ndef = env->getGameDef()->ndef(); - for (ABMWithState &abmws : abms) { - ActiveBlockModifier *abm = abmws.abm; - float trigger_interval = abm->getTriggerInterval(); - if (trigger_interval < 0.001f) - trigger_interval = 0.001f; - float actual_interval = dtime_s; - if (use_timers) { - abmws.timer += dtime_s; - if(abmws.timer < trigger_interval) - continue; - abmws.timer -= trigger_interval; - actual_interval = trigger_interval; - } - float chance = abm->getTriggerChance(); - if (chance == 0) - chance = 1; - - ActiveABM aabm; - aabm.abm = abm; - if (abm->getSimpleCatchUp()) { - float intervals = actual_interval / trigger_interval; - if (intervals == 0) - continue; - aabm.chance = chance / intervals; - if (aabm.chance == 0) - aabm.chance = 1; - } else { - aabm.chance = chance; - } - // y limits - aabm.min_y = abm->getMinY(); - aabm.max_y = abm->getMaxY(); - - // Trigger neighbors - for (const auto &s : abm->getRequiredNeighbors()) - ndef->getIds(s, aabm.required_neighbors); - SORT_AND_UNIQUE(aabm.required_neighbors); - - for (const auto &s : abm->getWithoutNeighbors()) - ndef->getIds(s, aabm.without_neighbors); - SORT_AND_UNIQUE(aabm.without_neighbors); - - // Trigger contents - std::vector ids; - for (const auto &s : abm->getTriggerContents()) - ndef->getIds(s, ids); - SORT_AND_UNIQUE(ids); - for (content_t c : ids) { - if (c >= m_aabms.size()) - m_aabms.resize(c + 256, nullptr); - if (!m_aabms[c]) - m_aabms[c] = new std::vector; - m_aabms[c]->push_back(aabm); - } - } - } - - ~ABMHandler() - { - for (auto &aabms : m_aabms) - delete aabms; - } - - // Find out how many objects the given block and its neighbors contain. - // Returns the number of objects in the block, and also in 'wider' the - // number of objects in the block and all its neighbors. The latter - // may an estimate if any neighbors are unloaded. - 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++) - { - MapBlock *block2 = map->getBlockNoCreateNoEx( - block->getPos() + v3s16(x,y,z)); - if(block2==NULL){ - wider_unknown_count++; - continue; - } - wider += block2->m_static_objects.size(); - } - // Extrapolate - u32 active_object_count = block->m_static_objects.getActiveSize(); - 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) - { - if (m_aabms.empty()) - return; - - // Check the content type cache first - // to see whether there are any ABMs - // to be run at all for this block. - if (!block->contents.empty()) { - assert(!block->do_not_cache_contents); // invariant - blocks_cached++; - bool run_abms = false; - for (content_t c : block->contents) { - if (c < m_aabms.size() && m_aabms[c]) { - run_abms = true; - break; - } - } - if (!run_abms) - return; - } - blocks_scanned++; - - ServerMap *map = &m_env->getServerMap(); - - u32 active_object_count_wider; - u32 active_object_count = this->countObjects(block, map, active_object_count_wider); - m_env->m_added_objects = 0; - - bool want_contents_cached = block->contents.empty() && !block->do_not_cache_contents; - - v3s16 p0; - for(p0.Z=0; p0.ZgetNodeNoCheck(p0); - content_t c = n.getContent(); - - // Cache content types as we go - if (want_contents_cached && !CONTAINS(block->contents, c)) { - if (block->contents.size() >= CONTENT_TYPE_CACHE_MAX) { - // Too many different nodes... don't try to cache - want_contents_cached = false; - block->do_not_cache_contents = true; - block->contents.clear(); - block->contents.shrink_to_fit(); - } else { - block->contents.push_back(c); - } - } - - if (c >= m_aabms.size() || !m_aabms[c]) - continue; - - v3s16 p = p0 + block->getPosRelative(); - for (ActiveABM &aabm : *m_aabms[c]) { - if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y)) - continue; - - if (myrand() % aabm.chance != 0) - continue; - - // Check neighbors - const bool check_required_neighbors = !aabm.required_neighbors.empty(); - const bool check_without_neighbors = !aabm.without_neighbors.empty(); - if (check_required_neighbors || check_without_neighbors) { - v3s16 p1; - bool have_required = false; - 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->getNodeNoCheck(p1); - c = n.getContent(); - } else { - // otherwise consult the map - MapNode n = map->getNode(p1 + block->getPosRelative()); - c = n.getContent(); - } - if (check_required_neighbors && !have_required) { - if (CONTAINS(aabm.required_neighbors, c)) { - if (!check_without_neighbors) - goto neighbor_found; - have_required = true; - } - } - if (check_without_neighbors) { - if (CONTAINS(aabm.without_neighbors, c)) - goto neighbor_invalid; - } - } - if (have_required || !check_required_neighbors) - goto neighbor_found; - // No required neighbor found - neighbor_invalid: - 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 (block->isOrphan()) - return; - - // 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; - } - - // Update and check node after possible modification - n = block->getNodeNoCheck(p0); - if (n.getContent() != c) - break; - } - } - } -}; - - void ServerEnvironment::forceActivateBlock(MapBlock *block) { assert(block); diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 267f6af83..c7396987a 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -12,6 +12,7 @@ #include "servermap.h" #include "settings.h" #include "server/activeobjectmgr.h" +#include "server/blockmodifier.h" #include "util/numeric.h" #include "util/metricsbackend.h" @@ -22,7 +23,6 @@ class PlayerDatabase; class AuthDatabase; class PlayerSAO; class ServerEnvironment; -class ActiveBlockModifier; struct StaticObject; class ServerActiveObject; class Server; @@ -30,138 +30,6 @@ class ServerScripting; enum AccessDeniedCode : u8; typedef u16 session_t; -/* - {Active, Loading} block modifier interface. - - These are fed into ServerEnvironment at initialization time; - ServerEnvironment handles deleting them. -*/ - -class ActiveBlockModifier -{ -public: - ActiveBlockModifier() = default; - virtual ~ActiveBlockModifier() = default; - - // Set of contents to trigger on - virtual const std::vector &getTriggerContents() const = 0; - // Set of required neighbors (trigger doesn't happen if none are found) - // Empty = do not check neighbors - virtual const std::vector &getRequiredNeighbors() const = 0; - // Set of without neighbors (trigger doesn't happen if any are found) - // Empty = do not check neighbors - virtual const std::vector &getWithoutNeighbors() const = 0; - // Trigger interval in seconds - virtual float getTriggerInterval() = 0; - // Random chance of (1 / return value), 0 is disallowed - virtual u32 getTriggerChance() = 0; - // Whether to modify chance to simulate time lost by an unnattended block - virtual bool getSimpleCatchUp() = 0; - // get min Y for apply abm - virtual s16 getMinY() = 0; - // get max Y for apply abm - virtual s16 getMaxY() = 0; - // 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){}; -}; - -struct ABMWithState -{ - ActiveBlockModifier *abm; - float timer = 0.0f; - - ABMWithState(ActiveBlockModifier *abm_); -}; - -struct LoadingBlockModifierDef -{ - // Set of contents to trigger on - std::vector trigger_contents; - std::string name; - bool run_at_every_load = false; - - virtual ~LoadingBlockModifierDef() = default; - - /// @brief Called to invoke LBM - /// @param env environment - /// @param block the block in question - /// @param positions set of node positions (block-relative!) - /// @param dtime_s game time since last deactivation - virtual void trigger(ServerEnvironment *env, MapBlock *block, - const std::unordered_set &positions, float dtime_s) {}; -}; - -class LBMContentMapping -{ -public: - typedef std::vector lbm_vector; - typedef std::unordered_map lbm_map; - - LBMContentMapping() = default; - void addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef); - const lbm_map::mapped_type *lookup(content_t c) const; - const lbm_vector &getList() const { return lbm_list; } - bool empty() const { return lbm_list.empty(); } - - // This struct owns the LBM pointers. - ~LBMContentMapping(); - DISABLE_CLASS_COPY(LBMContentMapping); - ALLOW_CLASS_MOVE(LBMContentMapping); - -private: - lbm_vector lbm_list; - lbm_map map; -}; - -class LBMManager -{ -public: - LBMManager() = default; - ~LBMManager(); - - // Don't call this after loadIntroductionTimes() ran. - void addLBMDef(LoadingBlockModifierDef *lbm_def); - - /// @param now current game time - void loadIntroductionTimes(const std::string ×, - IGameDef *gamedef, u32 now); - - // Don't call this before loadIntroductionTimes() ran. - std::string createIntroductionTimesString(); - - // Don't call this before loadIntroductionTimes() ran. - void applyLBMs(ServerEnvironment *env, MapBlock *block, - u32 stamp, float dtime_s); - - // Warning: do not make this std::unordered_map, order is relevant here - typedef std::map lbm_lookup_map; - -private: - // Once we set this to true, we can only query, - // not modify - bool m_query_mode = false; - - // For m_query_mode == false: - // The key of the map is the LBM def's name. - std::unordered_map m_lbm_defs; - - // For m_query_mode == true: - // The key of the map is the LBM def's first introduction time. - lbm_lookup_map m_lbm_lookup; - - /// @return map of LBM name -> timestamp - static std::unordered_map - parseIntroductionTimesString(const std::string ×); - - // Returns an iterator to the LBMs that were introduced - // 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); } -}; - /* List of active blocks, used by ServerEnvironment */ diff --git a/src/unittest/test_lbmmanager.cpp b/src/unittest/test_lbmmanager.cpp index 6f3627331..d4490bd01 100644 --- a/src/unittest/test_lbmmanager.cpp +++ b/src/unittest/test_lbmmanager.cpp @@ -6,7 +6,7 @@ #include -#include "serverenvironment.h" +#include "server/blockmodifier.h" class TestLBMManager : public TestBase { From e6acc4e7ed9b967a4dea02bcad72f38e2fb056c5 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Mar 2025 21:33:35 +0100 Subject: [PATCH 099/284] Delete TestCAO --- src/client/content_cao.cpp | 133 ------------------------------------- 1 file changed, 133 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index df7404140..30429f80e 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -231,139 +231,6 @@ static scene::SMesh *generateNodeMesh(Client *client, MapNode n, return mesh.release(); } -/* - TestCAO -*/ - -class TestCAO : public ClientActiveObject -{ -public: - TestCAO(Client *client, ClientEnvironment *env); - virtual ~TestCAO() = default; - - ActiveObjectType getType() const - { - return ACTIVEOBJECT_TYPE_TEST; - } - - static std::unique_ptr create(Client *client, ClientEnvironment *env); - - void addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr); - void removeFromScene(bool permanent); - void updateLight(u32 day_night_ratio); - void updateNodePos(); - - void step(float dtime, ClientEnvironment *env); - - void processMessage(const std::string &data); - - bool getCollisionBox(aabb3f *toset) const { return false; } -private: - scene::IMeshSceneNode *m_node; - v3f m_position; -}; - -// Prototype -static TestCAO proto_TestCAO(nullptr, nullptr); - -TestCAO::TestCAO(Client *client, ClientEnvironment *env): - ClientActiveObject(0, client, env), - m_node(NULL), - m_position(v3f(0,10*BS,0)) -{ - if (!client) - ClientActiveObject::registerType(getType(), create); -} - -std::unique_ptr TestCAO::create(Client *client, ClientEnvironment *env) -{ - return std::make_unique(client, env); -} - -void TestCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) -{ - if(m_node != NULL) - return; - - //video::IVideoDriver* driver = smgr->getVideoDriver(); - - scene::SMesh *mesh = new scene::SMesh(); - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); - video::S3DVertex vertices[4] = - { - video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1), - video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1), - video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0), - }; - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().BackfaceCulling = false; - buf->getMaterial().TextureLayers[0].Texture = tsrc->getTextureForMesh("rat.png"); - buf->getMaterial().TextureLayers[0].MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST; - buf->getMaterial().TextureLayers[0].MagFilter = video::ETMAGF_NEAREST; - buf->getMaterial().FogEnable = true; - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - m_node = smgr->addMeshSceneNode(mesh, NULL); - mesh->drop(); - updateNodePos(); -} - -void TestCAO::removeFromScene(bool permanent) -{ - if (!m_node) - return; - - m_node->remove(); - m_node = NULL; -} - -void TestCAO::updateLight(u32 day_night_ratio) -{ -} - -void TestCAO::updateNodePos() -{ - if (!m_node) - return; - - m_node->setPosition(m_position); - //m_node->setRotation(v3f(0, 45, 0)); -} - -void TestCAO::step(float dtime, ClientEnvironment *env) -{ - if(m_node) - { - v3f rot = m_node->getRotation(); - //infostream<<"dtime="<>cmd; - if(cmd == 0) - { - v3f newpos; - is>>newpos.X; - is>>newpos.Y; - is>>newpos.Z; - m_position = newpos; - updateNodePos(); - } -} - /* GenericCAO */ From ae0f955a0e7c797254028077122122c4d3b72c91 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 26 Mar 2025 21:56:09 +0100 Subject: [PATCH 100/284] Add nodiscard attribute to helper functions where it makes sense --- src/filesys.h | 25 ++++++++++++++++--------- src/porting.h | 11 +++++++---- src/util/base64.h | 6 +++--- src/util/colorize.h | 2 +- src/util/enum_string.h | 2 +- src/util/hex.h | 2 ++ src/util/ieee_float.h | 4 ++-- src/util/numeric.h | 24 +++++++++++++++++++----- src/util/serialize.h | 4 ++-- src/util/string.h | 20 ++++++++++++++++++-- 10 files changed, 71 insertions(+), 29 deletions(-) diff --git a/src/filesys.h b/src/filesys.h index a2f7b749c..3f2b81d54 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -36,22 +36,23 @@ struct DirListNode bool dir; }; +[[nodiscard]] std::vector GetDirListing(const std::string &path); // Returns true if already exists bool CreateDir(const std::string &path); -bool PathExists(const std::string &path); +[[nodiscard]] bool PathExists(const std::string &path); -bool IsPathAbsolute(const std::string &path); +[[nodiscard]] bool IsPathAbsolute(const std::string &path); -bool IsDir(const std::string &path); +[[nodiscard]] bool IsDir(const std::string &path); -bool IsExecutable(const std::string &path); +[[nodiscard]] bool IsExecutable(const std::string &path); -bool IsFile(const std::string &path); +[[nodiscard]] bool IsFile(const std::string &path); -inline bool IsDirDelimiter(char c) +[[nodiscard]] inline bool IsDirDelimiter(char c) { return c == '/' || c == DIR_DELIM_CHAR; } @@ -64,20 +65,21 @@ bool DeleteSingleFileOrEmptyDirectory(const std::string &path); /// Returns path to temp directory. /// You probably don't want to use this directly, see `CreateTempFile` or `CreateTempDir`. /// @return path or "" on error -std::string TempPath(); +[[nodiscard]] std::string TempPath(); /// Returns path to securely-created temporary file (will already exist when this function returns). /// @return path or "" on error -std::string CreateTempFile(); +[[nodiscard]] std::string CreateTempFile(); /// Returns path to securely-created temporary directory (will already exist when this function returns). /// @return path or "" on error -std::string CreateTempDir(); +[[nodiscard]] std::string CreateTempDir(); /* Returns a list of subdirectories, including the path itself, but excluding hidden directories (whose names start with . or _) */ void GetRecursiveDirs(std::vector &dirs, const std::string &dir); +[[nodiscard]] std::vector GetRecursiveDirs(const std::string &dir); /* Multiplatform */ @@ -128,16 +130,19 @@ std::string RemoveRelativePathComponents(std::string path); // Returns the absolute path for the passed path, with "." and ".." path // components and symlinks removed. Returns "" on error. +[[nodiscard]] std::string AbsolutePath(const std::string &path); // This is a combination of RemoveRelativePathComponents() and AbsolutePath() // It will resolve symlinks for the leading path components that exist and // still remove "." and ".." in the rest of the path. // Returns "" on error. +[[nodiscard]] std::string AbsolutePathPartial(const std::string &path); // Returns the filename from a path or the entire path if no directory // delimiter is found. +[[nodiscard]] const char *GetFilenameFromPath(const char *path); // Replace the content of a file on disk in a way that is safe from @@ -180,6 +185,7 @@ bool OpenStream(std::filebuf &stream, const char *filename, * @param mode additional mode bits (e.g. std::ios::app) * @return file stream, will be !good in case of error */ +[[nodiscard]] inline std::ofstream open_ofstream(const char *name, bool log, std::ios::openmode mode = std::ios::openmode()) { @@ -202,6 +208,7 @@ inline std::ofstream open_ofstream(const char *name, bool log, * @param mode additional mode bits (e.g. std::ios::ate) * @return file stream, will be !good in case of error */ +[[nodiscard]] inline std::ifstream open_ifstream(const char *name, bool log, std::ios::openmode mode = std::ios::openmode()) { diff --git a/src/porting.h b/src/porting.h index 7c652663a..f7d623d33 100644 --- a/src/porting.h +++ b/src/porting.h @@ -77,7 +77,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(); +[[nodiscard]] bool *signal_handler_killstatus(); /* Path of static data directory. @@ -105,11 +105,13 @@ extern std::string path_cache; /* Gets the path of our executable. */ +[[nodiscard]] bool getCurrentExecPath(char *buf, size_t len); /* Concatenate subpath to path_share. */ +[[nodiscard]] std::string getDataPath(const char *subpath); /* @@ -280,7 +282,8 @@ inline const char *getPlatformName() ; } -bool secure_rand_fill_buf(void *buf, size_t len); +// Securely fills buffer with bytes from system's random source +[[nodiscard]] bool secure_rand_fill_buf(void *buf, size_t len); // Call once near beginning of main function. void osSpecificInit(); @@ -308,10 +311,10 @@ static inline void TriggerMemoryTrim() { (void)0; } #ifdef _WIN32 // Quotes an argument for use in a CreateProcess() commandline (not cmd.exe!!) -std::string QuoteArgv(const std::string &arg); +[[nodiscard]] std::string QuoteArgv(const std::string &arg); // Convert an error code (e.g. from GetLastError()) into a string. -std::string ConvertError(DWORD error_code); +[[nodiscard]] std::string ConvertError(DWORD error_code); #endif // snprintf wrapper diff --git a/src/util/base64.h b/src/util/base64.h index 3846d3a25..9cdf5ebc5 100644 --- a/src/util/base64.h +++ b/src/util/base64.h @@ -31,6 +31,6 @@ René Nyffenegger rene.nyffenegger@adp-gmbh.ch #include #include -bool base64_is_valid(std::string_view s); -std::string base64_encode(std::string_view s); -std::string base64_decode(std::string_view s); +[[nodiscard]] bool base64_is_valid(std::string_view s); +[[nodiscard]] std::string base64_encode(std::string_view s); +[[nodiscard]] std::string base64_decode(std::string_view s); diff --git a/src/util/colorize.h b/src/util/colorize.h index cb7ae7c30..924bf7a78 100644 --- a/src/util/colorize.h +++ b/src/util/colorize.h @@ -37,6 +37,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Throws an exception if the url is invalid. */ -std::string colorize_url(const std::string &url); +[[nodiscard]] std::string colorize_url(const std::string &url); #endif diff --git a/src/util/enum_string.h b/src/util/enum_string.h index 5c084c718..08de0b7e2 100644 --- a/src/util/enum_string.h +++ b/src/util/enum_string.h @@ -24,4 +24,4 @@ bool string_to_enum(const EnumString *spec, T &result, std::string_view str) return ret; } -const char *enum_to_string(const EnumString *spec, int num); +[[nodiscard]] const char *enum_to_string(const EnumString *spec, int num); diff --git a/src/util/hex.h b/src/util/hex.h index c528fe4bd..7a5d761a8 100644 --- a/src/util/hex.h +++ b/src/util/hex.h @@ -9,6 +9,7 @@ static const char hex_chars[] = "0123456789abcdef"; +[[nodiscard]] static inline std::string hex_encode(std::string_view data) { std::string ret; @@ -20,6 +21,7 @@ static inline std::string hex_encode(std::string_view data) return ret; } +[[nodiscard]] static inline std::string hex_encode(const char *data, size_t data_size) { if (!data_size) diff --git a/src/util/ieee_float.h b/src/util/ieee_float.h index f3f6de442..316d0589e 100644 --- a/src/util/ieee_float.h +++ b/src/util/ieee_float.h @@ -13,7 +13,7 @@ enum FloatType FLOATTYPE_SYSTEM }; -f32 u32Tof32Slow(u32 i); -u32 f32Tou32Slow(f32 f); +[[nodiscard]] f32 u32Tof32Slow(u32 i); +[[nodiscard]] u32 f32Tou32Slow(f32 f); FloatType getFloatSerializationType(); diff --git a/src/util/numeric.h b/src/util/numeric.h index 60d86064f..cacb01621 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -17,6 +17,7 @@ // Like std::clamp but allows mismatched types template +[[nodiscard]] inline constexpr T rangelim(const T &d, const T2 &min, const T3 &max) { if (d < (T)min) @@ -88,7 +89,6 @@ inline void getContainerPosWithOffset(const v3s16 &p, s16 d, v3s16 &container, v getContainerPosWithOffset(p.Z, d, container.Z, offset.Z); } - inline bool isInArea(v3s16 p, s16 d) { return ( @@ -193,6 +193,7 @@ struct MeshGrid { * \note This is also used in cases where degrees wrapped to the range [0, 360] * is innapropriate (e.g. pitch needs negative values) */ +[[nodiscard]] inline float modulo360f(float f) { return fmodf(f, 360.0f); @@ -201,6 +202,7 @@ inline float modulo360f(float f) /** Returns \p f wrapped to the range [0, 360] */ +[[nodiscard]] inline float wrapDegrees_0_360(float f) { float value = modulo360f(f); @@ -210,6 +212,7 @@ inline float wrapDegrees_0_360(float f) /** Returns \p v3f wrapped to the range [0, 360] */ +[[nodiscard]] inline v3f wrapDegrees_0_360_v3f(v3f v) { v3f value_v3f; @@ -227,6 +230,7 @@ inline v3f wrapDegrees_0_360_v3f(v3f v) /** Returns \p f wrapped to the range [-180, 180] */ +[[nodiscard]] inline float wrapDegrees_180(float f) { float value = modulo360f(f + 180); @@ -289,6 +293,7 @@ inline u32 calc_parity(u32 v) * @param seed initial seed value * @return hash value */ +[[nodiscard]] u64 murmur_hash_64_ua(const void *key, size_t len, unsigned int seed); /** @@ -299,7 +304,7 @@ u64 murmur_hash_64_ua(const void *key, size_t len, unsigned int seed); * @param distance_ptr return location for distance from the camera */ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, - f32 camera_fov, f32 range, f32 *distance_ptr=NULL); + f32 camera_fov, f32 range, f32 *distance_ptr=nullptr); s16 adjustDist(s16 dist, float zoom_fov); @@ -307,12 +312,14 @@ s16 adjustDist(s16 dist, float zoom_fov); Returns nearest 32-bit integer for given floating point number. and in VC++ don't provide round(). */ +[[nodiscard]] inline s32 myround(f32 f) { return (s32)(f < 0.f ? (f - 0.5f) : (f + 0.5f)); } template +[[nodiscard]] inline constexpr T sqr(T f) { return f * f; @@ -321,6 +328,7 @@ inline constexpr T sqr(T f) /* Returns integer position of node in given floating point position */ +[[nodiscard]] inline v3s16 floatToInt(v3f p, f32 d) { return v3s16( @@ -332,6 +340,7 @@ inline v3s16 floatToInt(v3f p, f32 d) /* Returns integer position of node in given double precision position */ +[[nodiscard]] inline v3s16 doubleToInt(v3d p, double d) { return v3s16( @@ -343,12 +352,14 @@ inline v3s16 doubleToInt(v3d p, double d) /* Returns floating point position of node in given integer position */ +[[nodiscard]] inline v3f intToFloat(v3s16 p, f32 d) { return v3f::from(p) * d; } -// Random helper. Usually d=BS +// Returns box of a node as in-world box. Usually d=BS +[[nodiscard]] inline aabb3f getNodeBox(v3s16 p, float d) { return aabb3f( @@ -368,6 +379,7 @@ public: @param wanted_interval interval wanted @return true if action should be done */ + [[nodiscard]] bool step(float dtime, float wanted_interval) { m_accumulator += dtime; @@ -489,12 +501,14 @@ inline video::SColor multiplyColorValue(const video::SColor &color, float mod) core::clamp(color.getBlue() * mod, 0, 255)); } -template constexpr inline T numericAbsolute(T v) +template +constexpr inline T numericAbsolute(T v) { return v < 0 ? T(-v) : v; } -template constexpr inline T numericSign(T v) +template +constexpr inline T numericSign(T v) { return T(v < 0 ? -1 : (v == 0 ? 0 : 1)); } diff --git a/src/util/serialize.h b/src/util/serialize.h index b12d551ac..7da5f44d6 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -435,12 +435,12 @@ MAKE_STREAM_WRITE_FXN(video::SColor, ARGB8, 4); //// More serialization stuff //// -inline float clampToF1000(float v) +[[nodiscard]] inline float clampToF1000(float v) { return core::clamp(v, F1000_MIN, F1000_MAX); } -inline v3f clampToF1000(v3f v) +[[nodiscard]] inline v3f clampToF1000(v3f v) { return {clampToF1000(v.X), clampToF1000(v.Y), clampToF1000(v.Z)}; } diff --git a/src/util/string.h b/src/util/string.h index eaa13a264..3dcbfe85f 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -72,8 +72,8 @@ struct FlagDesc { // Try to avoid converting between wide and UTF-8 unless you need to // input/output stuff via Irrlicht -std::wstring utf8_to_wide(std::string_view input); -std::string wide_to_utf8(std::wstring_view input); +[[nodiscard]] std::wstring utf8_to_wide(std::string_view input); +[[nodiscard]] std::string wide_to_utf8(std::wstring_view input); void wide_add_codepoint(std::wstring &result, char32_t codepoint); @@ -287,6 +287,7 @@ MAKE_VARIANT(str_ends_with, std::basic_string_view, const T*) * @return An std::vector > of the component parts */ template +[[nodiscard]] inline std::vector > str_split( const std::basic_string &str, T delimiter) @@ -306,6 +307,7 @@ inline std::vector > str_split( * @param str * @return A copy of \p str converted to all lowercase characters. */ +[[nodiscard]] inline std::string lowercase(std::string_view str) { std::string s2; @@ -331,6 +333,7 @@ inline bool my_isspace(const wchar_t c) * @return A view of \p str with leading and trailing whitespace removed. */ template +[[nodiscard]] inline std::basic_string_view trim(std::basic_string_view str) { size_t front = 0; @@ -354,6 +357,7 @@ inline std::basic_string_view trim(std::basic_string_view str) * @return A copy of \p str with leading and trailing whitespace removed. */ template +[[nodiscard]] inline std::basic_string trim(std::basic_string &&str) { std::basic_string ret(trim(std::basic_string_view(str))); @@ -361,6 +365,7 @@ inline std::basic_string trim(std::basic_string &&str) } template +[[nodiscard]] inline std::basic_string_view trim(const std::basic_string &str) { return trim(std::basic_string_view(str)); @@ -368,6 +373,7 @@ inline std::basic_string_view trim(const std::basic_string &str) // The above declaration causes ambiguity with char pointers so we have to fix that: template +[[nodiscard]] inline std::basic_string_view trim(const T *str) { return trim(std::basic_string_view(str)); @@ -556,6 +562,7 @@ std::string wrap_rows(std::string_view from, unsigned row_len, bool has_color_co * Removes backslashes from an escaped string (FormSpec strings) */ template +[[nodiscard]] inline std::basic_string unescape_string(const std::basic_string &s) { std::basic_string res; @@ -580,6 +587,7 @@ inline std::basic_string unescape_string(const std::basic_string &s) * @return \p s, with escape sequences removed. */ template +[[nodiscard]] std::basic_string unescape_enriched(const std::basic_string &s) { std::basic_string output; @@ -610,6 +618,7 @@ std::basic_string unescape_enriched(const std::basic_string &s) } template +[[nodiscard]] std::vector > split(const std::basic_string &s, T delim) { std::vector > tokens; @@ -641,10 +650,13 @@ std::vector > split(const std::basic_string &s, T delim) return tokens; } +[[nodiscard]] std::wstring translate_string(std::wstring_view s, Translations *translations); +[[nodiscard]] std::wstring translate_string(std::wstring_view s); +[[nodiscard]] inline std::wstring unescape_translate(std::wstring_view s) { return unescape_enriched(translate_string(s)); @@ -730,6 +742,7 @@ inline const std::string duration_to_string(int sec) * * @return A std::string */ +[[nodiscard]] inline std::string str_join(const std::vector &list, std::string_view delimiter) { @@ -748,6 +761,7 @@ inline std::string str_join(const std::vector &list, /** * Create a UTF8 std::string from an irr::core::stringw. */ +[[nodiscard]] inline std::string stringw_to_utf8(const irr::core::stringw &input) { std::wstring_view sv(input.c_str(), input.size()); @@ -757,6 +771,7 @@ inline std::string stringw_to_utf8(const irr::core::stringw &input) /** * Create an irr::core:stringw from a UTF8 std::string. */ +[[nodiscard]] inline irr::core::stringw utf8_to_stringw(std::string_view input) { std::wstring str = utf8_to_wide(input); @@ -770,6 +785,7 @@ inline irr::core::stringw utf8_to_stringw(std::string_view input) * and add a prefix to them * 2. Remove 'unsafe' characters from the name by replacing them with '_' */ +[[nodiscard]] std::string sanitizeDirName(std::string_view str, std::string_view optional_prefix); /** From 785c042f1fc9b576a1bb460df8f42c0d2ad57888 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 29 Mar 2025 11:55:40 +0100 Subject: [PATCH 101/284] Drop gzip support from CZipReader This allowed reading concatenated gzip-compressed files as if they were an archive. Aside from being generally uncommon we literally don't need this. --- irr/include/IFileArchive.h | 13 ---- irr/src/CZipReader.cpp | 133 +++---------------------------------- irr/src/CZipReader.h | 44 +----------- 3 files changed, 10 insertions(+), 180 deletions(-) diff --git a/irr/include/IFileArchive.h b/irr/include/IFileArchive.h index fb77b48c4..5f505ae91 100644 --- a/irr/include/IFileArchive.h +++ b/irr/include/IFileArchive.h @@ -26,12 +26,6 @@ enum E_FILE_ARCHIVE_TYPE //! A PKZIP archive EFAT_ZIP = MAKE_IRR_ID('Z', 'I', 'P', 0), - //! A gzip archive - EFAT_GZIP = MAKE_IRR_ID('g', 'z', 'i', 'p'), - - //! An Android asset file archive - EFAT_ANDROID_ASSET = MAKE_IRR_ID('A', 'S', 'S', 'E'), - //! The type of this archive is unknown EFAT_UNKNOWN = MAKE_IRR_ID('u', 'n', 'k', 'n') }; @@ -73,13 +67,6 @@ public: but checks if file exists will fail. */ virtual void addDirectoryToFileList(const io::path &filename) {} - - //! An optionally used password string - /** This variable is publicly accessible from the interface in order to - avoid single access patterns to this place, and hence allow some more - obscurity. - */ - core::stringc Password; }; //! Class which is able to create an archive from a file. diff --git a/irr/src/CZipReader.cpp b/irr/src/CZipReader.cpp index dc5c0a4f0..7ec6a5ff3 100644 --- a/irr/src/CZipReader.cpp +++ b/irr/src/CZipReader.cpp @@ -29,14 +29,13 @@ CArchiveLoaderZIP::CArchiveLoaderZIP(io::IFileSystem *fs) : //! returns true if the file maybe is able to be loaded by this class bool CArchiveLoaderZIP::isALoadableFileFormat(const io::path &filename) const { - return core::hasFileExtension(filename, "zip", "pk3") || - core::hasFileExtension(filename, "gz", "tgz"); + return core::hasFileExtension(filename, "zip", "pk3"); } //! Check to see if the loader can create archives of this type. bool CArchiveLoaderZIP::isALoadableFileFormat(E_FILE_ARCHIVE_TYPE fileType) const { - return (fileType == EFAT_ZIP || fileType == EFAT_GZIP); + return fileType == EFAT_ZIP; } //! Creates an archive from the filename @@ -63,18 +62,7 @@ IFileArchive *CArchiveLoaderZIP::createArchive(io::IReadFile *file, bool ignoreC if (file) { file->seek(0); - u16 sig; - file->read(&sig, 2); - -#ifdef __BIG_ENDIAN__ - sig = os::Byteswap::byteswap(sig); -#endif - - file->seek(0); - - bool isGZip = (sig == 0x8b1f); - - archive = new CZipReader(FileSystem, file, ignoreCase, ignorePaths, isGZip); + archive = new CZipReader(FileSystem, file, ignoreCase, ignorePaths); } return archive; } @@ -92,27 +80,21 @@ bool CArchiveLoaderZIP::isALoadableFileFormat(io::IReadFile *file) const header.Sig = os::Byteswap::byteswap(header.Sig); #endif - return header.Sig == 0x04034b50 || // ZIP - (header.Sig & 0xffff) == 0x8b1f; // gzip + return header.Sig == 0x04034b50; // ZIP } // ----------------------------------------------------------------------------- // zip archive // ----------------------------------------------------------------------------- -CZipReader::CZipReader(IFileSystem *fs, IReadFile *file, bool ignoreCase, bool ignorePaths, bool isGZip) : - CFileList((file ? file->getFileName() : io::path("")), ignoreCase, ignorePaths), FileSystem(fs), File(file), IsGZip(isGZip) +CZipReader::CZipReader(IFileSystem *fs, IReadFile *file, bool ignoreCase, bool ignorePaths) : + CFileList((file ? file->getFileName() : io::path("")), ignoreCase, ignorePaths), FileSystem(fs), File(file) { if (File) { File->grab(); // load file entries - if (IsGZip) - while (scanGZipHeader()) { - } - else - while (scanZipHeader()) { - } + while (scanZipHeader()) {} sort(); } @@ -127,7 +109,7 @@ CZipReader::~CZipReader() //! get the archive type E_FILE_ARCHIVE_TYPE CZipReader::getType() const { - return IsGZip ? EFAT_GZIP : EFAT_ZIP; + return EFAT_ZIP; } const IFileList *CZipReader::getFileList() const @@ -135,105 +117,6 @@ const IFileList *CZipReader::getFileList() const return this; } -//! scans for a local header, returns false if there is no more local file header. -//! The gzip file format seems to think that there can be multiple files in a gzip file -//! but none -bool CZipReader::scanGZipHeader() -{ - SZipFileEntry entry; - entry.Offset = 0; - memset(&entry.header, 0, sizeof(SZIPFileHeader)); - - // read header - SGZIPMemberHeader header; - if (File->read(&header, sizeof(SGZIPMemberHeader)) == sizeof(SGZIPMemberHeader)) { - -#ifdef __BIG_ENDIAN__ - header.sig = os::Byteswap::byteswap(header.sig); - header.time = os::Byteswap::byteswap(header.time); -#endif - - // check header value - if (header.sig != 0x8b1f) - return false; - - // now get the file info - if (header.flags & EGZF_EXTRA_FIELDS) { - // read lenth of extra data - u16 dataLen; - - File->read(&dataLen, 2); - -#ifdef __BIG_ENDIAN__ - dataLen = os::Byteswap::byteswap(dataLen); -#endif - - // skip it - File->seek(dataLen, true); - } - - io::path ZipFileName = ""; - - if (header.flags & EGZF_FILE_NAME) { - c8 c; - File->read(&c, 1); - while (c) { - ZipFileName.append(c); - File->read(&c, 1); - } - } else { - // no file name? - ZipFileName = core::deletePathFromFilename(Path); - - // rename tgz to tar or remove gz extension - if (core::hasFileExtension(ZipFileName, "tgz")) { - ZipFileName[ZipFileName.size() - 2] = 'a'; - ZipFileName[ZipFileName.size() - 1] = 'r'; - } else if (core::hasFileExtension(ZipFileName, "gz")) { - ZipFileName[ZipFileName.size() - 3] = 0; - ZipFileName.validate(); - } - } - - if (header.flags & EGZF_COMMENT) { - c8 c = 'a'; - while (c) - File->read(&c, 1); - } - - if (header.flags & EGZF_CRC16) - File->seek(2, true); - - // we are now at the start of the data blocks - entry.Offset = File->getPos(); - - entry.header.FilenameLength = ZipFileName.size(); - - entry.header.CompressionMethod = header.compressionMethod; - entry.header.DataDescriptor.CompressedSize = (File->getSize() - 8) - File->getPos(); - - // seek to file end - File->seek(entry.header.DataDescriptor.CompressedSize, true); - - // read CRC - File->read(&entry.header.DataDescriptor.CRC32, 4); - // read uncompressed size - File->read(&entry.header.DataDescriptor.UncompressedSize, 4); - -#ifdef __BIG_ENDIAN__ - entry.header.DataDescriptor.CRC32 = os::Byteswap::byteswap(entry.header.DataDescriptor.CRC32); - entry.header.DataDescriptor.UncompressedSize = os::Byteswap::byteswap(entry.header.DataDescriptor.UncompressedSize); -#endif - - // now we've filled all the fields, this is just a standard deflate block - addItem(ZipFileName, entry.Offset, entry.header.DataDescriptor.UncompressedSize, false, 0); - FileInfo.push_back(entry); - } - - // there's only one block of data in a gzip file - return false; -} - //! scans for a local header, returns false if there is no more local file header. bool CZipReader::scanZipHeader(bool ignoreGPBits) { diff --git a/irr/src/CZipReader.h b/irr/src/CZipReader.h index b520c2030..654f7a80a 100644 --- a/irr/src/CZipReader.h +++ b/irr/src/CZipReader.h @@ -14,11 +14,9 @@ namespace irr { namespace io { -// set if the file is encrypted -const s16 ZIP_FILE_ENCRYPTED = 0x0001; // the fields crc-32, compressed size and uncompressed size are set to // zero in the local header -const s16 ZIP_INFO_IN_DATA_DESCRIPTOR = 0x0008; +static constexpr s16 ZIP_INFO_IN_DATA_DESCRIPTOR = 0x0008; // byte-align structures #include "irrpack.h" @@ -84,39 +82,6 @@ struct SZIPFileCentralDirEnd // zipfile comment (variable size) } PACK_STRUCT; -struct SZipFileExtraHeader -{ - s16 ID; - s16 Size; -} PACK_STRUCT; - -struct SZipFileAESExtraData -{ - s16 Version; - u8 Vendor[2]; - u8 EncryptionStrength; - s16 CompressionMode; -} PACK_STRUCT; - -enum E_GZIP_FLAGS -{ - EGZF_TEXT_DAT = 1, - EGZF_CRC16 = 2, - EGZF_EXTRA_FIELDS = 4, - EGZF_FILE_NAME = 8, - EGZF_COMMENT = 16 -}; - -struct SGZIPMemberHeader -{ - u16 sig; // 0x8b1f - u8 compressionMethod; // 8 = deflate - u8 flags; - u32 time; - u8 extraFlags; // slow compress = 2, fast compress = 4 - u8 operatingSystem; -} PACK_STRUCT; - // Default alignment #include "irrunpack.h" @@ -173,7 +138,7 @@ class CZipReader : public virtual IFileArchive, virtual CFileList { public: //! constructor - CZipReader(IFileSystem *fs, IReadFile *file, bool ignoreCase, bool ignorePaths, bool isGZip = false); + CZipReader(IFileSystem *fs, IReadFile *file, bool ignoreCase, bool ignorePaths); //! destructor virtual ~CZipReader(); @@ -200,9 +165,6 @@ protected: directory. */ bool scanZipHeader(bool ignoreGPBits = false); - //! the same but for gzip files - bool scanGZipHeader(); - bool scanCentralDirectoryHeader(); io::IFileSystem *FileSystem; @@ -210,8 +172,6 @@ protected: // holds extended info about files std::vector FileInfo; - - bool IsGZip; }; } // end namespace io From 1281173e50eb8f808b6aead6e092bef689213a75 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 30 Mar 2025 16:42:26 +0200 Subject: [PATCH 102/284] Use secure randomness to seed internal RNG --- src/httpfetch.cpp | 16 +++++----------- src/main.cpp | 16 +++++++++++----- src/util/numeric.cpp | 2 +- src/util/numeric.h | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index ccb36b3f6..13daaefb6 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -16,14 +16,13 @@ #include "porting.h" #include "util/container.h" #include "util/thread.h" +#include "util/numeric.h" #include "version.h" #include "settings.h" -#include "noise.h" static std::mutex g_httpfetch_mutex; static std::unordered_map> g_httpfetch_results; -static PcgRandom g_callerid_randomness; static std::string default_user_agent() { @@ -78,18 +77,18 @@ u64 httpfetch_caller_alloc_secure() // Generate random caller IDs and make sure they're not // already used or reserved. // Give up after 100 tries to prevent infinite loop - size_t tries = 100; + int tries = 100; u64 caller; do { - caller = (((u64) g_callerid_randomness.next()) << 32) | - g_callerid_randomness.next(); + // Global RNG is seeded securely, so we can use it. + myrand_bytes(&caller, sizeof(caller)); if (--tries < 1) { FATAL_ERROR("httpfetch_caller_alloc_secure: ran out of caller IDs"); return HTTPFETCH_DISCARD; } - } while (caller >= HTTPFETCH_CID_START && + } while (caller < HTTPFETCH_CID_START || g_httpfetch_results.find(caller) != g_httpfetch_results.end()); verbosestream << "httpfetch_caller_alloc_secure: allocating " @@ -702,11 +701,6 @@ void httpfetch_init(int parallel_limit) FATAL_ERROR_IF(res != CURLE_OK, "cURL init failed"); g_httpfetch_thread = std::make_unique(parallel_limit); - - // Initialize g_callerid_randomness for httpfetch_caller_alloc_secure - u64 randbuf[2]; - porting::secure_rand_fill_buf(randbuf, sizeof(u64) * 2); - g_callerid_randomness = PcgRandom(randbuf[0], randbuf[1]); } void httpfetch_cleanup() diff --git a/src/main.cpp b/src/main.cpp index d3e698f33..d62ee1f21 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -700,12 +700,18 @@ static bool init_common(const Settings &cmd_args, int argc, char *argv[]) init_log_streams(cmd_args); // Initialize random seed - { - u32 seed = static_cast(time(nullptr)) << 16; - seed |= porting::getTimeUs() & 0xffff; - srand(seed); - mysrand(seed); + u64 seed; + if (!porting::secure_rand_fill_buf(&seed, sizeof(seed))) { + verbosestream << "Secure randomness not available to seed global RNG." << std::endl; + std::ostringstream oss; + // some stuff that's hard to predict: + oss << time(nullptr) << porting::getTimeUs() << argc << g_settings_path; + print_version(oss); + std::string data = oss.str(); + seed = murmur_hash_64_ua(data.c_str(), data.size(), 0xc0ffee); } + srand(seed); + mysrand(seed); // Initialize HTTP fetcher httpfetch_init(g_settings->getS32("curl_parallel_limit")); diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index ddc658cb3..59dd0a204 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -20,7 +20,7 @@ u32 myrand() return g_pcgrand.next(); } -void mysrand(unsigned int seed) +void mysrand(u64 seed) { g_pcgrand.seed(seed); } diff --git a/src/util/numeric.h b/src/util/numeric.h index cacb01621..b79ef2aef 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -244,7 +244,7 @@ inline float wrapDegrees_180(float f) */ #define MYRAND_RANGE 0xffffffff u32 myrand(); -void mysrand(unsigned int seed); +void mysrand(u64 seed); void myrand_bytes(void *out, size_t len); int myrand_range(int min, int max); float myrand_range(float min, float max); From 67240686599f8106640f231cbad73000287e8a60 Mon Sep 17 00:00:00 2001 From: lhofhansl Date: Mon, 31 Mar 2025 21:31:10 -0700 Subject: [PATCH 103/284] Slight fix to #15949 to handle emerge queue full (#15960) Partially restore the existing logic, and try to enqueue a block as before, if the queue is full it will be handled correctly. --- src/server/clientiface.cpp | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/server/clientiface.cpp b/src/server/clientiface.cpp index 8a8a6cf1b..96db69bbe 100644 --- a/src/server/clientiface.cpp +++ b/src/server/clientiface.cpp @@ -343,24 +343,21 @@ void RemoteClient::GetNextBlocks ( const bool want_emerge = !block || !block->isGenerated(); // if the block is already in the emerge queue we don't have to check again - if (want_emerge && emerge->isBlockInQueue(p)) { - nearest_emerged_d = d; - continue; - } + if (!want_emerge || !emerge->isBlockInQueue(p)) { + /* + Check occlusion cache first. + */ + if (m_blocks_occ.find(p) != m_blocks_occ.end()) + continue; - /* - Check occlusion cache first. - */ - if (m_blocks_occ.find(p) != m_blocks_occ.end()) - continue; - - /* - Note that we do this even before the block is loaded as this does not depend on its contents. - */ - if (m_occ_cull && - env->getMap().isBlockOccluded(p * MAP_BLOCKSIZE, cam_pos_nodes, d >= d_cull_opt)) { - m_blocks_occ.insert(p); - continue; + /* + Note that we do this even before the block is loaded as this does not depend on its contents. + */ + if (m_occ_cull && + env->getMap().isBlockOccluded(p * MAP_BLOCKSIZE, cam_pos_nodes, d >= d_cull_opt)) { + m_blocks_occ.insert(p); + continue; + } } /* From c30c94dfaad46bede44397c26b4e5d1fb2b024c2 Mon Sep 17 00:00:00 2001 From: grorp Date: Tue, 1 Apr 2025 07:55:47 -0400 Subject: [PATCH 104/284] Add server/client annotations to settingtypes.txt and make use of them (#15756) --- builtin/common/settings/components.lua | 4 +- builtin/common/settings/dlg_settings.lua | 37 +++-- builtin/common/settings/settingtypes.lua | 93 +++++++++++-- builtin/common/settings/shadows_component.lua | 1 + builtin/settingtypes.txt | 128 ++++++++++-------- src/client/client.cpp | 11 +- src/client/client.h | 16 ++- src/client/game.cpp | 18 +-- src/gameparams.h | 5 + src/gui/guiMainMenu.h | 4 + src/script/lua_api/l_pause_menu.cpp | 9 ++ src/script/lua_api/l_pause_menu.h | 1 + 12 files changed, 231 insertions(+), 96 deletions(-) diff --git a/builtin/common/settings/components.lua b/builtin/common/settings/components.lua index aec2b8898..de7a63fee 100644 --- a/builtin/common/settings/components.lua +++ b/builtin/common/settings/components.lua @@ -447,8 +447,8 @@ if INIT == "pause_menu" then -- require porting "FSTK" (at least the dialog API) from the mainmenu formspec -- API to the in-game formspec API. -- There's no reason you'd want to adjust mapgen noise parameter settings - -- in-game (they only apply to new worlds), so there's no reason to implement - -- this. + -- in-game (they only apply to new worlds, hidden as [world_creation]), + -- so there's no reason to implement this. local empty = function() return { get_formspec = function() return "", 0 end } end diff --git a/builtin/common/settings/dlg_settings.lua b/builtin/common/settings/dlg_settings.lua index 6f0b772af..b43d9ebdc 100644 --- a/builtin/common/settings/dlg_settings.lua +++ b/builtin/common/settings/dlg_settings.lua @@ -111,6 +111,7 @@ local function load() requires = { keyboard_mouse = true, }, + context = "client", get_formspec = function(self, avail_w) local btn_w = math.min(avail_w, 3) return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8 @@ -127,6 +128,7 @@ local function load() requires = { touchscreen = true, }, + context = "client", get_formspec = function(self, avail_w) local btn_w = math.min(avail_w, 6) return ("button[0,0;%f,0.8;btn_touch_layout;%s]"):format(btn_w, fgettext("Touchscreen layout")), 0.8 @@ -173,18 +175,24 @@ local function load() table.insert(content, idx, shadows_component) idx = table.indexof(content, "enable_auto_exposure") + 1 + local setting_info = get_setting_info("enable_auto_exposure") local note = component_funcs.note(fgettext_ne("(The game will need to enable automatic exposure as well)")) - note.requires = get_setting_info("enable_auto_exposure").requires + note.requires = setting_info.requires + note.context = setting_info.context table.insert(content, idx, note) idx = table.indexof(content, "enable_bloom") + 1 + setting_info = get_setting_info("enable_bloom") note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)")) - note.requires = get_setting_info("enable_bloom").requires + note.requires = setting_info.requires + note.context = setting_info.context table.insert(content, idx, note) idx = table.indexof(content, "enable_volumetric_lighting") + 1 + setting_info = get_setting_info("enable_volumetric_lighting") note = component_funcs.note(fgettext_ne("(The game will need to enable volumetric lighting as well)")) - note.requires = get_setting_info("enable_volumetric_lighting").requires + note.requires = setting_info.requires + note.context = setting_info.context table.insert(content, idx, note) end @@ -362,7 +370,18 @@ local function update_filtered_pages(query) end -local function check_requirements(name, requires) +local shown_contexts = { + common = true, + client = true, + server = INIT ~= "pause_menu" or core.is_internal_server(), + world_creation = INIT ~= "pause_menu", +} + +local function check_requirements(name, requires, context) + if context and not shown_contexts[context] then + return false + end + if requires == nil then return true end @@ -423,11 +442,11 @@ function page_has_contents(page, actual_content) elseif type(item) == "string" then local setting = get_setting_info(item) assert(setting, "Unknown setting: " .. item) - if check_requirements(setting.name, setting.requires) then + if check_requirements(setting.name, setting.requires, setting.context) then return true end elseif item.get_formspec then - if check_requirements(item.id, item.requires) then + if check_requirements(item.id, item.requires, item.context) then return true end else @@ -449,20 +468,22 @@ local function build_page_components(page) elseif item.heading then last_heading = item else - local name, requires + local name, requires, context if type(item) == "string" then local setting = get_setting_info(item) assert(setting, "Unknown setting: " .. item) name = setting.name requires = setting.requires + context = setting.context elseif item.get_formspec then name = item.id requires = item.requires + context = item.context else error("Unknown content in page: " .. dump(item)) end - if check_requirements(name, requires) then + if check_requirements(name, requires, context) then if last_heading then content[#content + 1] = last_heading last_heading = nil diff --git a/builtin/common/settings/settingtypes.lua b/builtin/common/settings/settingtypes.lua index a4dd28483..39a50e1f4 100644 --- a/builtin/common/settings/settingtypes.lua +++ b/builtin/common/settings/settingtypes.lua @@ -40,12 +40,24 @@ local CHAR_CLASSES = { FLAGS = "[%w_%-%.,]", } +local valid_contexts = {common = true, client = true, server = true, world_creation = true} + +local function check_context_annotation(context, force_context) + if force_context then + return "Context annotations are not allowed, context is always " .. force_context + end + if not valid_contexts[context] then + return "Unknown context" + end + return nil +end + local function flags_to_table(flags) return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split end -- returns error message, or nil -local function parse_setting_line(settings, line, read_all, base_level, allow_secure) +local function parse_setting_line(settings, line, read_all, base_level, allow_secure, force_context) -- strip carriage returns (CR, /r) line = line:gsub("\r", "") @@ -69,9 +81,32 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se -- category local stars, category = line:match("^%[([%*]*)([^%]]+)%]$") + local category_context + if not category then + stars, category, category_context = line:match("^%[([%*]*)([^%]]+)%] %[([^%]]+)%]$") + end if category then local category_level = stars:len() + base_level + if settings.current_context_level and + category_level <= settings.current_context_level then + -- The start of this category marks the end of the context annotation's scope. + settings.current_context_level = nil + settings.current_context = nil + end + + if category_context then + local err = check_context_annotation(category_context, force_context) + if err then + return err + end + if settings.current_context_level then + return "Category context annotations cannot be nested" + end + settings.current_context_level = category_level + settings.current_context = category_context + end + if settings.current_hide_level then if settings.current_hide_level < category_level then -- Skip this category, it's inside a hidden category. @@ -102,7 +137,8 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se end -- settings - local first_part, name, readable_name, setting_type = line:match("^" + local function make_pattern(include_context) + return "^" -- this first capture group matches the whole first part, -- so we can later strip it from the rest of the line .. "(" @@ -110,9 +146,19 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se .. CHAR_CLASSES.SPACE .. "*" .. "%(([^%)]*)%)" -- readable name .. CHAR_CLASSES.SPACE .. "*" + .. (include_context and ( + "%[([^%]]+)%]" -- context annotation + .. CHAR_CLASSES.SPACE .. "*" + ) or "") .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type .. CHAR_CLASSES.SPACE .. "*" - .. ")") + .. ")" + end + local first_part, name, readable_name, setting_type = line:match(make_pattern(false)) + local setting_context + if not first_part then + first_part, name, readable_name, setting_context, setting_type = line:match(make_pattern(true)) + end if not first_part then return "Invalid line" @@ -122,6 +168,26 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se return "Tried to add \"secure.\" setting" end + if setting_context then + local err = check_context_annotation(setting_context, force_context) + if err then + return err + end + end + + local context + if force_context then + context = force_context + else + if setting_context then + context = setting_context + elseif settings.current_context_level then + context = settings.current_context + else + return "Missing context annotation" + end + end + local requires = {} local last_line = #current_comment > 0 and current_comment[#current_comment]:trim() if last_line and last_line:lower():sub(1, 9) == "requires:" then @@ -170,6 +236,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se min = min, max = max, requires = requires, + context = context, comment = comment, }) return @@ -193,6 +260,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se type = setting_type, default = default, requires = requires, + context = context, comment = comment, }) return @@ -245,6 +313,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se }, values = values, requires = requires, + context = context, comment = comment, noise_params = true, flags = flags_to_table("defaults,eased,absvalue") @@ -263,6 +332,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se type = "bool", default = remaining_line, requires = requires, + context = context, comment = comment, }) return @@ -290,6 +360,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se min = min, max = max, requires = requires, + context = context, comment = comment, }) return @@ -313,6 +384,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se default = default, values = values:split(",", true), requires = requires, + context = context, comment = comment, }) return @@ -331,6 +403,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se type = setting_type, default = default, requires = requires, + context = context, comment = comment, }) return @@ -361,6 +434,7 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se default = default, possible = flags_to_table(possible), requires = requires, + context = context, comment = comment, }) return @@ -369,14 +443,14 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se return "Invalid setting type \"" .. setting_type .. "\"" end -local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure) +local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure, force_context) -- store this helper variable in the table so it's easier to pass to parse_setting_line() result.current_comment = {} result.current_hide_level = nil local line = file:read("*line") while line do - local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure) + local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure, force_context) if error_msg then core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"") end @@ -411,7 +485,8 @@ function settingtypes.parse_config_file(read_all, parse_mods) -- TODO: Support game/mod settings in the pause menu too -- Note that this will need to work different from how it's done in the -- mainmenu: - -- * Only if in singleplayer / on local server, not on remote servers + -- * ~~Only if in singleplayer / on local server, not on remote servers~~ + -- (done now: context annotations) -- * Only show settings for the active game and mods -- (add API function to get them, can return nil if on a remote server) -- (names are probably not enough, will need paths for uniqueness) @@ -441,7 +516,7 @@ function settingtypes.parse_config_file(read_all, parse_mods) type = "category", }) - parse_single_file(file, path, read_all, settings, 2, false) + parse_single_file(file, path, read_all, settings, 2, false, "server") file:close() end @@ -474,7 +549,7 @@ function settingtypes.parse_config_file(read_all, parse_mods) type = "category", }) - parse_single_file(file, path, read_all, settings, 2, false) + parse_single_file(file, path, read_all, settings, 2, false, "server") file:close() end @@ -505,7 +580,7 @@ function settingtypes.parse_config_file(read_all, parse_mods) type = "category", }) - parse_single_file(file, path, read_all, settings, 2, false) + parse_single_file(file, path, read_all, settings, 2, false, "client") file:close() end diff --git a/builtin/common/settings/shadows_component.lua b/builtin/common/settings/shadows_component.lua index 2d68f9d3d..405517058 100644 --- a/builtin/common/settings/shadows_component.lua +++ b/builtin/common/settings/shadows_component.lua @@ -84,6 +84,7 @@ return { requires = { opengl = true, }, + context = "client", get_formspec = function(self, avail_w) local labels = table.copy(shadow_levels_labels) local idx = detect_mapping_idx() diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 8604ff539..825a85b97 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2,9 +2,27 @@ # # General format: # name (Readable name) type type_args +# name (Readable name) [context] type type_args # # Note that the parts are separated by exactly one space # +# `context` (optional) is used to document where the setting is read. It can be: +# - common: Read by both client and server. +# - client: Read by the client. +# (Includes settings read by the mainmenu.) +# - server: Read by the server. +# - world_creation: Read at world creation, thus only applied to new worlds. +# (Worlds are commonly created in the mainmenu (part of the client), but +# world creation is conceptually a server-side thing...) +# If not specified, the value is inherited from the context value of the containing +# category instead. +# For the builtin/settingtypes.txt file, every setting needs to have a context defined, +# either via a category containing it or via the setting itself. In game/mod-provided +# settingtypes.txt files, context annotations are invalid. +# Note: For context annotations, it's irrelevant whether changes to a setting +# after startup/game-join will be read. A separate mechanism for declaring that +# is needed. +# # `type` can be: # - int # - string @@ -77,6 +95,8 @@ # Sections are marked by a single line in the format: [Section Name] # Sub-section are marked by adding * in front of the section name: [*Sub-section] # Sub-sub-sections have two * etc. +# A context (see above) can be specified optionally: [Section Name] [context] +# Context annotations on categories cannot be nested. # There shouldn't be too many settings per category. # # The top-level categories "Advanced", "Client and Server" and "Mapgen" are @@ -84,7 +104,7 @@ # They contain settings not intended for the "average user". -[Controls] +[Controls] [client] [*General] @@ -224,7 +244,7 @@ fixed_virtual_joystick (Fixed virtual joystick) bool false # Requires: touchscreen virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false -[Graphics and Audio] +[Graphics and Audio] [client] [*Graphics] @@ -762,13 +782,13 @@ contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3 1 [Client and Server] -[*Client] +[*Client] [client] # Save the map received by the client on disk. enable_local_map_saving (Saving map received from server) bool false # URL to the server list displayed in the Multiplayer Tab. -serverlist_url (Serverlist URL) string https://servers.luanti.org +serverlist_url (Serverlist URL) [common] string https://servers.luanti.org # If enabled, server account registration is separate from login in the UI. # If disabled, connecting to a server will automatically register a new account. @@ -778,7 +798,7 @@ enable_split_login_register (Enable split login/register) bool true # If this is empty the engine will never check for updates. update_information_url (Update information URL) string https://www.luanti.org/release_info.json -[*Server] +[*Server] [server] # Name of the player. # When running a server, a client connecting with this name is admin. @@ -806,7 +826,7 @@ server_announce (Announce server) bool false server_announce_send_players (Send player names to the server list) bool true # Announce to this serverlist. -serverlist_url (Serverlist URL) string https://servers.luanti.org +serverlist_url (Serverlist URL) [common] string https://servers.luanti.org # Message of the day displayed to players connecting. motd (Message of the day) string @@ -852,7 +872,7 @@ remote_media (Remote media) string # Requires: enable_ipv6 ipv6_server (IPv6 server) bool true -[*Server Security] +[*Server Security] [server] # New users need to input this password. default_password (Default password) string @@ -912,7 +932,7 @@ chat_message_limit_per_10sec (Chat message count limit) float 8.0 1.0 # Kick players who sent more than X messages per 10 seconds. chat_message_limit_trigger_kick (Chat message kick threshold) int 50 1 65535 -[*Server Gameplay] +[*Server Gameplay] [server] # Controls length of day/night cycle. # Examples: @@ -920,7 +940,7 @@ chat_message_limit_trigger_kick (Chat message kick threshold) int 50 1 65535 time_speed (Time speed) int 72 0 # Time of day when a new world is started, in millihours (0-23999). -world_start_time (World start time) int 6125 0 23999 +world_start_time (World start time) [world_creation] int 6125 0 23999 # Time in seconds for item entity (dropped items) to live. # Setting it to -1 disables the feature. @@ -975,7 +995,7 @@ movement_liquid_sink (Liquid sinking) float 10.0 movement_gravity (Gravity) float 9.81 -[Mapgen] +[Mapgen] [world_creation] # A chosen map seed for a new map, leave empty for random. # Will be overridden when creating a new world in the main menu. @@ -991,7 +1011,7 @@ mg_name (Mapgen name) enum v7 v7,valleys,carpathian,v5,flat,fractal,singlenode,v water_level (Water level) int 1 -31000 31000 # From how far blocks are generated for clients, stated in mapblocks (16 nodes). -max_block_generate_distance (Max block generate distance) int 10 1 32767 +max_block_generate_distance (Max block generate distance) [server] int 10 1 32767 # Limit of map generation, in nodes, in all 6 directions from (0, 0, 0). # Only mapchunks completely within the mapgen limit are generated. @@ -1704,12 +1724,12 @@ mgvalleys_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), # Enable Lua modding support on client. # This support is experimental and API can change. -enable_client_modding (Client modding) bool false +enable_client_modding (Client modding) [client] bool false # Replaces the default main menu with a custom one. -main_menu_script (Main menu script) string +main_menu_script (Main menu script) [client] string -[**Mod Security] +[**Mod Security] [server] # Prevent mods from doing insecure things like running shell commands. secure.enable_security (Enable mod security) bool true @@ -1733,33 +1753,33 @@ secure.http_mods (HTTP mods) string # - info # - verbose # - trace -debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose,trace +debug_log_level (Debug log level) [common] enum action ,none,error,warning,action,info,verbose,trace # If the file size of debug.txt exceeds the number of megabytes specified in # this setting when it is opened, the file is moved to debug.txt.1, # deleting an older debug.txt.1 if it exists. # debug.txt is only moved if this setting is positive. -debug_log_size_max (Debug log file size threshold) int 50 1 +debug_log_size_max (Debug log file size threshold) [common] int 50 1 # Minimal level of logging to be written to chat. -chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose,trace +chat_log_level (Chat log level) [client] enum error ,none,error,warning,action,info,verbose,trace # Handling for deprecated Lua API calls: # - 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 log none,log,error +deprecated_lua_api_handling (Deprecated Lua API handling) [common] enum log none,log,error # Enable random user input (only used for testing). -random_input (Random input) bool false +random_input (Random input) [client] bool false # Enable random mod loading (mainly used for testing). -random_mod_load_order (Random mod load order) bool false +random_mod_load_order (Random mod load order) [server] bool false # Enable mod channels support. -enable_mod_channels (Mod channels) bool false +enable_mod_channels (Mod channels) [server] bool false -[**Mod Profiler] +[**Mod Profiler] [server] # Load the game profiler to collect game profiling data. # Provides a /profiler command to access the compiled profile. @@ -1799,7 +1819,7 @@ instrument.builtin (Builtin) bool false # * Instrument the sampler being used to update the statistics. instrument.profiler (Profiler) bool false -[**Engine Profiler] +[**Engine Profiler] [common] # Print the engine's profiling data in regular intervals (in seconds). # 0 = disable. Useful for developers. @@ -1808,7 +1828,7 @@ profiler_print_interval (Engine profiling data print interval) int 0 0 [*Advanced] -[**Graphics] +[**Graphics] [client] # Enables debug and error-checking in the OpenGL driver. opengl_debug (OpenGL debug) bool false @@ -1912,12 +1932,12 @@ shadow_update_frames (Map shadows update frames) int 16 1 32 # Requires: enable_post_processing, enable_bloom enable_bloom_debug (Enable Bloom Debug) bool false -[**Sound] +[**Sound] [client] # Comma-separated list of AL and ALC extensions that should not be used. # Useful for testing. See al_extensions.[h,cpp] for details. sound_extensions_blacklist (Sound Extensions Blacklist) string -[**Font] +[**Font] [client] font_bold (Font bold by default) bool false @@ -1967,7 +1987,7 @@ mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/ # This font will be used for certain languages or if the default font is unavailable. fallback_font_path (Fallback font path) filepath fonts/DroidSansFallbackFull.ttf -[**Lighting] +[**Lighting] [client] # Gradient of light curve at minimum light level. # Controls the contrast of the lowest light levels. @@ -1995,47 +2015,47 @@ lighting_boost_spread (Light curve boost spread) float 0.2 0.0 0.4 # Enable IPv6 support (for both client and server). # Required for IPv6 connections to work at all. -enable_ipv6 (IPv6) bool true +enable_ipv6 (IPv6) [common] bool true # Prometheus listener address. # If Luanti is compiled with ENABLE_PROMETHEUS option enabled, # enable metrics listener for Prometheus on that address. # Metrics can be fetched on http://127.0.0.1:30000/metrics -prometheus_listener_address (Prometheus listener address) string 127.0.0.1:30000 +prometheus_listener_address (Prometheus listener address) [server] string 127.0.0.1:30000 -# Maximum size of the outgoing chat queue. +# Maximum size of the client's outgoing chat queue. # 0 to disable queueing and -1 to make the queue size unlimited. -max_out_chat_queue_size (Maximum size of the outgoing chat queue) int 20 -1 32767 +max_out_chat_queue_size (Maximum size of the client's outgoing chat queue) [client] int 20 -1 32767 # Timeout for client to remove unused map data from memory, in seconds. -client_unload_unused_data_timeout (Mapblock unload timeout) float 600.0 0.0 +client_unload_unused_data_timeout (Mapblock unload timeout) [client] float 600.0 0.0 # Maximum number of mapblocks for client to be kept in memory. # Note that there is an internal dynamic minimum number of blocks that # won't be deleted, depending on the current view range. # Set to -1 for no limit. -client_mapblock_limit (Mapblock limit) int 7500 -1 2147483647 +client_mapblock_limit (Mapblock limit) [client] int 7500 -1 2147483647 # 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 1 4294967295 +max_simultaneous_block_sends_per_client (Maximum simultaneous block sends per client) [server] int 40 1 4294967295 # 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. -full_block_send_enable_min_time_from_building (Delay in sending blocks after building) float 2.0 0.0 +full_block_send_enable_min_time_from_building (Delay in sending blocks after building) [server] float 2.0 0.0 # Maximum number of packets sent per send step in the low-level networking code. # You generally don't need to change this, however busy servers may benefit from a higher number. -max_packets_per_iteration (Max. packets per iteration) int 1024 1 65535 +max_packets_per_iteration (Max. packets per iteration) [common] int 1024 1 65535 # Compression level to use when sending mapblocks to the client. # -1 - use default compression level # 0 - least compression, fastest # 9 - best compression, slowest -map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9 +map_compression_level_net (Map Compression Level for Network Transfer) [server] int -1 -1 9 -[**Server] +[**Server] [server] # Format of player chat messages. The following strings are valid placeholders: # @name, @message, @timestamp (optional) @@ -2055,7 +2075,7 @@ kick_msg_crash (Crash message) string This server has experienced an internal er # Set this to true if your server is set up to restart automatically. ask_reconnect_on_crash (Ask to reconnect after crash) bool false -[**Server/Env Performance] +[**Server/Env Performance] [server] # Length of a server tick (the interval at which everything is generally updated), # stated in seconds. @@ -2148,7 +2168,7 @@ server_side_occlusion_culling (Server-side occlusion culling) bool true # Stated in MapBlocks (16 nodes). block_cull_optimize_distance (Block cull optimize distance) int 25 2 2047 -[**Mapgen] +[**Mapgen] [server] # Size of mapchunks generated by mapgen, stated in mapblocks (16 nodes). # WARNING: There is no benefit, and there are several dangers, in @@ -2156,7 +2176,7 @@ block_cull_optimize_distance (Block cull optimize distance) int 25 2 2047 # Reducing this value increases cave and dungeon density. # Altering this value is for special usage, leaving it unchanged is # recommended. -chunksize (Chunk size) int 5 1 10 +chunksize (Chunk size) [world_creation] int 5 1 10 # Dump the mapgen debug information. enable_mapgen_debug_info (Mapgen debug) bool false @@ -2184,7 +2204,7 @@ emergequeue_limit_generate (Per-player limit of queued blocks to generate) int 1 # 'on_generated'. For many users the optimum setting may be '1'. num_emerge_threads (Number of emerge threads) int 1 0 32767 -[**cURL] +[**cURL] [common] # Maximum time an interactive request (e.g. server list fetch) may take, stated in milliseconds. curl_timeout (cURL interactive timeout) int 20000 1000 2147483647 @@ -2202,48 +2222,48 @@ curl_file_download_timeout (cURL file download timeout) int 300000 5000 21474836 [**Miscellaneous] # Clickable weblinks (middle-click or Ctrl+left-click) enabled in chat console output. -clickable_chat_weblinks (Chat weblinks) bool true +clickable_chat_weblinks (Chat weblinks) [client] bool true # If enabled, invalid world data won't cause the server to shut down. # Only enable this if you know what you are doing. -ignore_world_load_errors (Ignore world errors) bool false +ignore_world_load_errors (Ignore world errors) [server] bool false # Adjust the detected display density, used for scaling UI elements. -display_density_factor (Display Density Scaling Factor) float 1 0.5 5.0 +display_density_factor (Display Density Scaling Factor) [client] float 1 0.5 5.0 # Windows systems only: Start Luanti with the command line window in the background. # Contains the same information as the file debug.txt (default name). -enable_console (Enable console window) bool false +enable_console (Enable console window) [common] bool false # Number of extra blocks that can be loaded by /clearobjects at once. # This is a trade-off between SQLite transaction overhead and # memory consumption (4096=100MB, as a rule of thumb). -max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) int 4096 0 4294967295 +max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) [server] int 4096 0 4294967295 # World directory (everything in the world is stored here). # Not needed if starting from the main menu. -map-dir (Map directory) path +map-dir (Map directory) [server] path # See https://www.sqlite.org/pragma.html#pragma_synchronous -sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2 +sqlite_synchronous (Synchronous SQLite) [server] enum 2 0,1,2 # Compression level to use when saving mapblocks to disk. # -1 - use default compression level # 0 - least compression, fastest # 9 - best compression, slowest -map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9 +map_compression_level_disk (Map Compression Level for Disk Storage) [server] int -1 -1 9 # Enable usage of remote media server (if provided by server). # Remote servers offer a significantly faster way to download media (e.g. textures) # when connecting to the server. -enable_remote_media_server (Connect to external media server) bool true +enable_remote_media_server (Connect to external media server) [client] bool true # File in client/serverlist/ that contains your favorite servers displayed in the # Multiplayer Tab. -serverlist_file (Serverlist file) string favoriteservers.json +serverlist_file (Serverlist file) [client] string favoriteservers.json -[*Gamepads] +[*Gamepads] [client] # Enable joysticks. Requires a restart to take effect enable_joysticks (Enable joysticks) bool false @@ -2266,7 +2286,7 @@ joystick_deadzone (Joystick dead zone) int 2048 0 65535 joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170.0 0.001 -[*Hide: Temporary Settings] +[*Hide: Temporary Settings] [common] # Path to texture directory. All textures are first searched from here. texture_path (Texture path) path diff --git a/src/client/client.cpp b/src/client/client.cpp index 214420de5..77853b535 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -367,8 +367,7 @@ Client::~Client() g_fontengine->clearMediaFonts(); } -void Client::connect(const Address &address, const std::string &address_name, - bool is_local_server) +void Client::connect(const Address &address, const std::string &address_name) { if (m_con) { // can't do this if the connection has entered auth phase @@ -389,7 +388,7 @@ void Client::connect(const Address &address, const std::string &address_name, m_con->Connect(address); - initLocalMapSaving(address, m_address_name, is_local_server); + initLocalMapSaving(address, m_address_name); } void Client::step(float dtime) @@ -917,11 +916,9 @@ void Client::request_media(const std::vector &file_requests) << pkt.getSize() << ")" << std::endl; } -void Client::initLocalMapSaving(const Address &address, - const std::string &hostname, - bool is_local_server) +void Client::initLocalMapSaving(const Address &address, const std::string &hostname) { - if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { + if (!g_settings->getBool("enable_local_map_saving") || m_internal_server) { return; } if (m_localdb) { diff --git a/src/client/client.h b/src/client/client.h index e4cbac1f5..d9cb7c6af 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -140,8 +140,7 @@ public: bool isShutdown(); - void connect(const Address &address, const std::string &address_name, - bool is_local_server); + void connect(const Address &address, const std::string &address_name); /* Stuff that references the environment is valid only as @@ -338,8 +337,17 @@ public: u16 getProtoVersion() const { return m_proto_ver; } + // Whether the server is in "simple singleplayer mode". + // This implies "m_internal_server = true". bool m_simple_singleplayer_mode; + // Whether the server is hosted by the same Luanti instance and singletons + // like g_settings are shared between client and server. + // + // This is intentionally *not* true if we're just connecting to a localhost + // server hosted by a different Luanti instance. + bool m_internal_server; + float mediaReceiveProgress(); void drawLoadScreen(const std::wstring &text, float dtime, int percent); @@ -441,9 +449,7 @@ private: void peerAdded(con::IPeer *peer) override; void deletingPeer(con::IPeer *peer, bool timeout) override; - void initLocalMapSaving(const Address &address, - const std::string &hostname, - bool is_local_server); + void initLocalMapSaving(const Address &address, const std::string &hostname); void ReceiveAll(); diff --git a/src/client/game.cpp b/src/client/game.cpp index 87e0d4e11..5fdb35e7e 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -508,7 +508,7 @@ protected: bool init(const std::string &map_dir, const std::string &address, u16 port, const SubgameSpec &gamespec); bool initSound(); - bool createSingleplayerServer(const std::string &map_dir, + bool createServer(const std::string &map_dir, const SubgameSpec &gamespec, u16 port); void copyServerClientCache(); @@ -908,7 +908,6 @@ 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)) return false; @@ -1138,7 +1137,7 @@ bool Game::init( // Create a server if not connecting to an existing one if (address.empty()) { - if (!createSingleplayerServer(map_dir, gamespec, port)) + if (!createServer(map_dir, gamespec, port)) return false; } @@ -1173,7 +1172,7 @@ bool Game::initSound() return true; } -bool Game::createSingleplayerServer(const std::string &map_dir, +bool Game::createServer(const std::string &map_dir, const SubgameSpec &gamespec, u16 port) { showOverlayMessage(N_("Creating server..."), 0, 5); @@ -1389,7 +1388,6 @@ bool Game::connectToServer(const GameStartData &start_data, { *connect_ok = false; // Let's not be overly optimistic *connection_aborted = false; - bool local_server_mode = false; const auto &address_name = start_data.address; showOverlayMessage(N_("Resolving address..."), 0, 15); @@ -1409,7 +1407,6 @@ bool Game::connectToServer(const GameStartData &start_data, } else { connect_address.setAddress(127, 0, 0, 1); } - local_server_mode = true; } } catch (ResolveError &e) { *error_message = fmtgettext("Couldn't resolve address: %s", e.what()); @@ -1455,13 +1452,13 @@ bool Game::connectToServer(const GameStartData &start_data, client->migrateModStorage(); client->m_simple_singleplayer_mode = simple_singleplayer_mode; + client->m_internal_server = !!server; /* Wait for server to accept connection */ - client->connect(connect_address, address_name, - simple_singleplayer_mode || local_server_mode); + client->connect(connect_address, address_name); try { input->clear(); @@ -1508,12 +1505,11 @@ bool Game::connectToServer(const GameStartData &start_data, } wait_time += dtime; - if (local_server_mode) { + if (server) { // never time out } else if (wait_time > GAME_FALLBACK_TIMEOUT && !did_fallback) { if (!client->hasServerReplied() && fallback_address.isValid()) { - client->connect(fallback_address, address_name, - simple_singleplayer_mode || local_server_mode); + client->connect(fallback_address, address_name); } did_fallback = true; } else if (wait_time > GAME_CONNECTION_TIMEOUT) { diff --git a/src/gameparams.h b/src/gameparams.h index 7c20cccf3..0dc7a3713 100644 --- a/src/gameparams.h +++ b/src/gameparams.h @@ -25,6 +25,7 @@ enum class ELoginRegister { }; // Information processed by main menu +// TODO: unify with MainMenuData struct GameStartData : GameParams { GameStartData() = default; @@ -33,7 +34,11 @@ struct GameStartData : GameParams std::string name; std::string password; + // If empty, we're hosting a server. + // This may or may not be in "simple singleplayer mode". std::string address; + // If true, we're hosting a server and are *not* in "simple singleplayer + // mode". bool local_server; ELoginRegister allow_login_or_register = ELoginRegister::Any; diff --git a/src/gui/guiMainMenu.h b/src/gui/guiMainMenu.h index 5a635c596..e5a5d2717 100644 --- a/src/gui/guiMainMenu.h +++ b/src/gui/guiMainMenu.h @@ -16,10 +16,13 @@ struct MainMenuDataForScript { std::string errormessage = ""; }; +// TODO: unify with GameStartData struct MainMenuData { // Client options std::string servername; std::string serverdescription; + // If empty, we're hosting a server. + // This may or may not be in "simple singleplayer mode". std::string address; std::string port; std::string name; @@ -29,6 +32,7 @@ struct MainMenuData { // Server options int selected_world = 0; + // If true, we're hosting a server and *are* in "simple singleplayer mode". bool simple_singleplayer_mode = false; // Data to be passed to the script diff --git a/src/script/lua_api/l_pause_menu.cpp b/src/script/lua_api/l_pause_menu.cpp index 4fc17766d..c2a9a81e5 100644 --- a/src/script/lua_api/l_pause_menu.cpp +++ b/src/script/lua_api/l_pause_menu.cpp @@ -5,6 +5,7 @@ #include "l_pause_menu.h" #include "gui/mainmenumanager.h" #include "lua_api/l_internal.h" +#include "client/client.h" int ModApiPauseMenu::l_show_keys_menu(lua_State *L) @@ -21,8 +22,16 @@ int ModApiPauseMenu::l_show_touchscreen_layout(lua_State *L) } +int ModApiPauseMenu::l_is_internal_server(lua_State *L) +{ + lua_pushboolean(L, getClient(L)->m_internal_server); + return 1; +} + + void ModApiPauseMenu::Initialize(lua_State *L, int top) { API_FCT(show_keys_menu); API_FCT(show_touchscreen_layout); + API_FCT(is_internal_server); } diff --git a/src/script/lua_api/l_pause_menu.h b/src/script/lua_api/l_pause_menu.h index 507c1c4b7..2d7eb62d7 100644 --- a/src/script/lua_api/l_pause_menu.h +++ b/src/script/lua_api/l_pause_menu.h @@ -11,6 +11,7 @@ class ModApiPauseMenu: public ModApiBase private: static int l_show_keys_menu(lua_State *L); static int l_show_touchscreen_layout(lua_State *L); + static int l_is_internal_server(lua_State *L); public: static void Initialize(lua_State *L, int top); From 2569b50252d8306a5e680c7577bf037e9640f44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 1 Apr 2025 19:12:00 +0200 Subject: [PATCH 105/284] Deprecate some legacy item registration logic (#15950) --- builtin/game/register.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 0a0dff486..dc9dcfb0e 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -155,6 +155,8 @@ end local function preprocess_craft(itemdef) -- BEGIN Legacy stuff if itemdef.inventory_image == nil and itemdef.image ~= nil then + core.log("deprecated", "The `image` field in craftitem definitions " .. + "is deprecated. Use `inventory_image` instead.") itemdef.inventory_image = itemdef.image end -- END Legacy stuff @@ -165,6 +167,8 @@ local function preprocess_tool(tooldef) -- BEGIN Legacy stuff if tooldef.inventory_image == nil and tooldef.image ~= nil then + core.log("deprecated", "The `image` field in tool definitions " .. + "is deprecated. Use `inventory_image` instead.") tooldef.inventory_image = tooldef.image end @@ -180,6 +184,8 @@ local function preprocess_tool(tooldef) tooldef.dd_crackiness ~= nil or tooldef.dd_crumbliness ~= nil or tooldef.dd_cuttability ~= nil) then + core.log("deprecated", "Specifying tool capabilities directly in the tool " .. + "definition is deprecated. Use the `tool_capabilities` field instead.") tooldef.tool_capabilities = { full_punch_interval = tooldef.full_punch_interval, basetime = tooldef.basetime, @@ -262,6 +268,8 @@ function core.register_item(name, itemdef) -- BEGIN Legacy stuff if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then + core.log("deprecated", "The `cookresult_itemstring` item definition " .. + "field is deprecated. Use `core.register_craft` instead.") core.register_craft({ type="cooking", output=itemdef.cookresult_itemstring, @@ -270,6 +278,8 @@ function core.register_item(name, itemdef) }) end if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then + core.log("deprecated", "The `furnace_burntime` item definition " .. + "field is deprecated. Use `core.register_craft` instead.") core.register_craft({ type="fuel", recipe=itemdef.name, @@ -331,7 +341,6 @@ function core.register_alias(name, convert_to) core.log("warning", "Not registering alias, item with same name" .. " is already defined: " .. name .. " -> " .. convert_to) else - --core.log("Registering alias: " .. name .. " -> " .. convert_to) core.registered_aliases[name] = convert_to register_alias_raw(name, convert_to) end @@ -346,7 +355,6 @@ function core.register_alias_force(name, convert_to) core.log("info", "Removed item " ..name.. " while attempting to force add an alias") end - --core.log("Registering alias: " .. name .. " -> " .. convert_to) core.registered_aliases[name] = convert_to register_alias_raw(name, convert_to) end From 0179021acc8bc03eec5634f5e42c2ea9927e0fa7 Mon Sep 17 00:00:00 2001 From: Jisk <37682565+Jiskster@users.noreply.github.com> Date: Tue, 1 Apr 2025 12:12:22 -0500 Subject: [PATCH 106/284] lua_api.md: MAX_WORKING_VOLUME is now 150 million --- doc/lua_api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index d58ed5fc2..cc0c551a1 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6475,12 +6475,12 @@ Environment access first value: Table with all node positions second value: Table with the count of each node with the node name as index - * Area volume is limited to 4,096,000 nodes + * Area volume is limited to 150,000,000 nodes * `core.find_nodes_in_area_under_air(pos1, pos2, nodenames)`: returns a list of positions. * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` * Return value: Table with all node positions with a node air above - * Area volume is limited to 4,096,000 nodes + * Area volume is limited to 150,000,000 nodes * `core.get_perlin(noiseparams)` * Return world-specific perlin noise. * The actual seed used is the noiseparams seed plus the world seed. From 47c75b3294fc01250488cf7304dd786b8e120e4e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 1 Apr 2025 19:12:37 +0200 Subject: [PATCH 107/284] ImageSource: restrict max dimensions to protect from integer overflows (#15965) --- src/client/imagesource.cpp | 21 +++++++++++++-------- src/client/imagesource.h | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/client/imagesource.cpp b/src/client/imagesource.cpp index e2538c372..3e55dd386 100644 --- a/src/client/imagesource.cpp +++ b/src/client/imagesource.cpp @@ -949,9 +949,10 @@ static void imageTransform(u32 transform, video::IImage *src, video::IImage *dst #define CHECK_DIM(w, h) \ do { \ - if ((w) <= 0 || (h) <= 0 || (w) >= 0xffff || (h) >= 0xffff) { \ - COMPLAIN_INVALID("width or height"); \ - } \ + if ((w) <= 0 || (w) > MAX_IMAGE_DIMENSION) \ + COMPLAIN_INVALID("width"); \ + if ((h) <= 0 || (h) > MAX_IMAGE_DIMENSION) \ + COMPLAIN_INVALID("height"); \ } while(0) bool ImageSource::generateImagePart(std::string_view part_of_name, @@ -1350,6 +1351,8 @@ bool ImageSource::generateImagePart(std::string_view part_of_name, v2u32 frame_size = baseimg->getDimension(); frame_size.Y /= frame_count; + if (frame_size.Y == 0) + frame_size.Y = 1; video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, frame_size); @@ -1498,11 +1501,13 @@ bool ImageSource::generateImagePart(std::string_view part_of_name, u32 w = scale * dim.Width; u32 h = scale * dim.Height; const core::dimension2d newdim(w, h); - video::IImage *newimg = driver->createImage( - baseimg->getColorFormat(), newdim); - baseimg->copyToScaling(newimg); - baseimg->drop(); - baseimg = newimg; + if (w <= MAX_IMAGE_DIMENSION && h <= MAX_IMAGE_DIMENSION) { + video::IImage *newimg = driver->createImage( + baseimg->getColorFormat(), newdim); + baseimg->copyToScaling(newimg); + baseimg->drop(); + baseimg = newimg; + } } } } diff --git a/src/client/imagesource.h b/src/client/imagesource.h index 310dbb7e8..b5e3d8d3a 100644 --- a/src/client/imagesource.h +++ b/src/client/imagesource.h @@ -45,6 +45,12 @@ struct ImageSource { // Insert a source image into the cache without touching the filesystem. void insertSourceImage(const std::string &name, video::IImage *img, bool prefer_local); + // This was picked so that the image buffer size fits in an s32 (assuming 32bpp). + // The exact value is 23170 but this provides some leeway. + // In theory something like 33333x123 could be allowed, but there is no strong + // need or argument. Irrlicht also has the same limit. + static constexpr int MAX_IMAGE_DIMENSION = 23000; + private: // Generate image based on a string like "stone.png" or "[crack:1:0". From 7dbd3a0744e5ac4f72e812b0ac8ba2459a053520 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 2 Apr 2025 10:05:23 -0400 Subject: [PATCH 108/284] lua_api.md: More info in LBM run_at_every_load documentation (#15956) --- doc/lua_api.md | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index cc0c551a1..8b6ff856f 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -9498,17 +9498,29 @@ Used by `core.register_lbm`. A loading block modifier (LBM) is used to define a function that is called for specific nodes (defined by `nodenames`) when a mapblock which contains such nodes -gets activated (**not loaded!**). +gets **activated** (**not loaded!**). *Note*: LBMs operate on a "snapshot" of node positions taken once before they are triggered. That means if an LBM callback adds a node, it won't be taken into account. However the engine guarantees that at the point in time when the callback is called that all given positions contain a matching node. -*Note*: For maps generated in 5.11.0 or older, many newly generated blocks did not -get a timestamp set. This means LBMs introduced between generation time and -time of first activation will never run. -Currently the only workaround is to use `run_at_every_load`. +For `run_at_every_load = false` to work, both mapblocks and LBMs have timestamps +associated with them: + +* Each mapblock has a "last active" timestamp. It is also updated when the + mapblock is generated. +* For each LBM, an introduction timestamp is stored in the world data, identified + by the LBM's `name` field. If an LBM disappears, the corresponding timestamp + is cleared. + +When a mapblock is activated, only LBMs whose introduction timestamp is newer +than the mapblock's timestamp are run. + +*Note*: For maps generated in 5.11.0 or older, many newly generated mapblocks +did not get a timestamp set. This means LBMs introduced between generation time +and time of first activation will never run. +Currently the only workaround is to use `run_at_every_load = true`. ```lua { @@ -9525,13 +9537,16 @@ Currently the only workaround is to use `run_at_every_load`. -- will work as well. run_at_every_load = false, - -- Whether to run the LBM's action every time a block gets activated, - -- and not only the first time the block gets activated after the LBM - -- was introduced. + -- If `false`: The LBM only runs on mapblocks the first time they are + -- activated after the LBM was introduced. + -- It never runs on mapblocks generated after the LBM's introduction. + -- See above for details. + -- + -- If `true`: The LBM runs every time a mapblock is activated. action = function(pos, node, dtime_s) end, -- Function triggered for each qualifying node. - -- `dtime_s` is the in-game time (in seconds) elapsed since the block + -- `dtime_s` is the in-game time (in seconds) elapsed since the mapblock -- was last active (available since 5.7.0). bulk_action = function(pos_list, dtime_s) end, From 66dedf1e216f203c2c6bb673b689a77051d15c71 Mon Sep 17 00:00:00 2001 From: grorp Date: Thu, 3 Apr 2025 13:46:06 -0400 Subject: [PATCH 109/284] lua_api.md: Mapblock-related and misc improvements (#15972) Co-authored-by: sfan5 Co-authored-by: DS --- doc/lua_api.md | 57 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 8b6ff856f..12b412dbe 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -1596,7 +1596,8 @@ There are a bunch of different looking node types. Node boxes ---------- -Node selection boxes are defined using "node boxes". +Node selection boxes and collision boxes, and the appearance of the `nodebox` +drawtype, are defined using "node boxes". A nodebox is defined as any of: @@ -1681,8 +1682,8 @@ roughly 1x1x1 meters in size. A 'mapblock' (often abbreviated to 'block') is 16x16x16 nodes and is the fundamental region of a world that is stored in the world database, sent to -clients and handled by many parts of the engine. This size is defined by the constant -`core.MAP_BLOCKSIZE` (=16). +clients and handled by many parts of the engine. This size is available as the +constant `core.MAP_BLOCKSIZE` (=16). 'mapblock' is preferred terminology to 'block' to help avoid confusion with 'node', however 'block' often appears in the API. @@ -1692,6 +1693,38 @@ A 'mapchunk' (sometimes abbreviated to 'chunk') is usually 5x5x5 mapblocks the map generator. The size in mapblocks has been chosen to optimize map generation. +### Mapblock status + +A mapblock being "loaded" means that is in memory. These are the mapblocks that +API functions like `core.get_node` or `core.set_node` can operate on. To reach +this state, the mapblock must first go through the process of being "emerged". +This means that it is loaded from disk, and/or, if it isn't yet generated, +generated by the map generator. + +Mapblocks are loaded in a broad area around each player. They become "unloaded" +again if no player is close enough. The engine commonly represents the contents +of unloaded mapblocks as `"ignore"` nodes. + +A mapblock being "active" means that it is not only in memory, but also affected +by world simulation: + +* Entities are active + * They are in memory as `ServerActiveObject`, exposed to Lua as `ObjectRef` + * They exist in Lua as luaentity tables +* ABMs are executed +* Node timers are executed + +Also, when a mapblock is "activated", LBMs are executed. Mapblocks are active +in a smaller area around each player, and are "deactivated" again if no player +is close enough. + +Related API functions: + +* `core.compare_block_status` +* `core.forceload_block` +* `core.load_area` +* `core.emerge_area` + Coordinates ----------- @@ -2032,10 +2065,14 @@ The following items are predefined and have special properties. * `"unknown"`: An item that represents every item which has not been registered * `"air"`: The node which appears everywhere where no other node is -* `"ignore"`: Mapblocks which have not been yet generated consist of this node -* `""`: The player's hand, which is in use whenever the player wields no item - * Its rage and tool capabilities are also used as an fallback for the wield item - * It can be overridden to change those properties +* `"ignore"`: Mapblocks that are not loaded are represented using this node. + * Also used for nodes that have not yet been set by the map generator. + * This is also what appears outside of the map boundary. +* `""`: The player's hand, which is in use whenever the player wields no item. + * Its range and tool capabilities are also used as a fallback for the wielded item. + * It can be overridden to change those properties: + * globally using `core.override_item` + * per-player using the special `"hand"` inventory list Amount and wear --------------- @@ -7648,6 +7685,8 @@ Misc. * `core.forceload_block(pos[, transient[, limit]])` * forceloads the position `pos`. + * this means that the mapblock containing `pos` will always be kept in the + `"active"` state, regardless of nearby players or server settings. * returns `true` if area could be forceloaded * If `transient` is `false` or absent, the forceload will be persistent (saved between server runs). If `true`, the forceload will be transient @@ -9442,6 +9481,10 @@ ABM (ActiveBlockModifier) definition Used by `core.register_abm`. +An active block modifier (ABM) is used to define a function that is continously +and randomly called for specific nodes (defined by `nodenames` and other conditions) +in active mapblocks. + ```lua { label = "Lava cooling", From 884f411387ec33fd80b524aea38cbfa008002c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Fri, 4 Apr 2025 18:46:03 +0200 Subject: [PATCH 110/284] Set `CMAKE_POLICY_VERSION_MINIMUM=3.5` to make MSVC CI work again (#15978) Co-authored-by: Josiah VanderZee --- .github/workflows/windows.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 495d481f4..e9698c93b 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -106,10 +106,12 @@ jobs: vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }} - name: CMake + # Note: See #15976 for why CMAKE_POLICY_VERSION_MINIMUM=3.5 is set. run: | cmake ${{matrix.config.generator}} ` -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" ` -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ` -DENABLE_POSTGRESQL=OFF ` -DENABLE_LUAJIT=TRUE ` -DREQUIRE_LUAJIT=TRUE ` From 1db5a2f9507e19604de3ea335537cef3c0da388e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20=C3=85str=C3=B6m?= Date: Fri, 4 Apr 2025 18:46:27 +0200 Subject: [PATCH 111/284] Add delay between punching and digging node (#15931) --- src/client/game.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/game.cpp b/src/client/game.cpp index 5fdb35e7e..79889e775 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3633,6 +3633,7 @@ void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack & if (do_punch) { infostream << "Punched object" << std::endl; runData.punching = true; + runData.nodig_delay_timer = std::max(0.15f, m_repeat_dig_time); } if (do_punch_damage) { From a6d4cd7c15078f600766a70e3a8fbc69f5b29be4 Mon Sep 17 00:00:00 2001 From: cx384 Date: Fri, 4 Apr 2025 18:47:11 +0200 Subject: [PATCH 112/284] Draw node animation for items (#15930) --- src/client/camera.cpp | 6 ++-- src/client/content_cao.cpp | 3 +- src/client/mapblock_mesh.cpp | 35 +++++------------- src/client/mapblock_mesh.h | 4 --- src/client/tile.cpp | 13 +++++++ src/client/tile.h | 22 ++++++++++++ src/client/wieldmesh.cpp | 70 +++++++++++++++++++++--------------- src/client/wieldmesh.h | 26 +++++++++----- src/gui/drawItemStack.cpp | 12 +++++-- 9 files changed, 117 insertions(+), 74 deletions(-) diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 3f8b4d51f..dfa2649ed 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -134,7 +134,8 @@ void Camera::step(f32 dtime) if (m_wield_change_timer >= 0 && was_under_zero) { m_wieldnode->setItem(m_wield_item_next, m_client); - m_wieldnode->setNodeLightColor(m_player_light_color); + m_wieldnode->setLightColorAndAnimation(m_player_light_color, + m_client->getAnimationTime()); } if (m_view_bobbing_state != 0) @@ -537,7 +538,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) m_wieldnode->setRotation(wield_rotation); m_player_light_color = player->light_color; - m_wieldnode->setNodeLightColor(m_player_light_color); + m_wieldnode->setLightColorAndAnimation(m_player_light_color, + m_client->getAnimationTime()); // Set render distance updateViewingRange(); diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 30429f80e..624ed4b5b 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -809,7 +809,8 @@ void GenericCAO::setNodeLight(const video::SColor &light_color) { if (m_prop.visual == OBJECTVISUAL_WIELDITEM || m_prop.visual == OBJECTVISUAL_ITEM) { if (m_wield_meshnode) - m_wield_meshnode->setNodeLightColor(light_color); + m_wield_meshnode->setLightColorAndAnimation(light_color, + m_client->getAnimationTime()); return; } diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 35ac65de4..a53a5c073 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -678,9 +678,7 @@ MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data): // - Texture animation if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { // Add to MapBlockMesh in order to animate these tiles - auto &info = m_animation_info[{layer, i}]; - info.tile = p.layer; - info.frame = 0; + m_animation_info.emplace(std::make_pair(layer, i), AnimationInfo(p.layer)); // Replace tile texture with the first animation frame p.layer.texture = (*p.layer.frames)[0].texture; } @@ -763,6 +761,12 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, // Cracks if (crack != m_last_crack) { for (auto &crack_material : m_crack_materials) { + + // TODO crack on animated tiles does not work + auto anim_it = m_animation_info.find(crack_material.first); + if (anim_it != m_animation_info.end()) + continue; + scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]-> getMeshBuffer(crack_material.first.second); @@ -772,16 +776,6 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, video::ITexture *new_texture = m_tsrc->getTextureForMesh(s, &new_texture_id); buf->getMaterial().setTexture(0, new_texture); - - // If the current material is also animated, update animation info - auto anim_it = m_animation_info.find(crack_material.first); - if (anim_it != m_animation_info.end()) { - TileLayer &tile = anim_it->second.tile; - tile.texture = new_texture; - tile.texture_id = new_texture_id; - // force animation update - anim_it->second.frame = -1; - } } m_last_crack = crack; @@ -789,20 +783,9 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, // Texture animation for (auto &it : m_animation_info) { - const TileLayer &tile = it.second.tile; - // Figure out current frame - int frameno = (int)(time * 1000 / tile.animation_frame_length_ms) % - tile.animation_frame_count; - // If frame doesn't change, skip - if (frameno == it.second.frame) - continue; - - it.second.frame = frameno; - scene::IMeshBuffer *buf = m_mesh[it.first.first]->getMeshBuffer(it.first.second); - - const FrameSpec &frame = (*tile.frames)[frameno]; - buf->getMaterial().setTexture(0, frame.texture); + video::SMaterial &material = buf->getMaterial(); + it.second.updateTexture(material, time); } return true; diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 5e69b9329..6d8ddc36c 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -246,10 +246,6 @@ public: } private: - struct AnimationInfo { - int frame; // last animation frame - TileLayer tile; - }; irr_ptr m_mesh[MAX_TILE_LAYERS]; std::vector m_minimap_mapblocks; diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 84281f84d..894e97339 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -3,6 +3,19 @@ // Copyright (C) 2010-2013 celeron55, Perttu Ahola #include "tile.h" +#include + +void AnimationInfo::updateTexture(video::SMaterial &material, float animation_time) +{ + // Figure out current frame + u16 frame = (u16)(animation_time * 1000 / m_frame_length_ms) % m_frame_count; + // Only adjust if frame changed + if (frame != m_frame) { + m_frame = frame; + assert(m_frame < m_frames->size()); + material.setTexture(0, (*m_frames)[m_frame].texture); + } +}; void TileLayer::applyMaterialOptions(video::SMaterial &material, int layer) const { diff --git a/src/client/tile.h b/src/client/tile.h index 420f0757f..ffbe78bac 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -151,6 +151,28 @@ struct TileLayer bool has_color = false; }; +// Stores information for drawing an animated tile +struct AnimationInfo { + + AnimationInfo() = default; + + AnimationInfo(const TileLayer &tile) : + m_frame_length_ms(tile.animation_frame_length_ms), + m_frame_count(tile.animation_frame_count), + m_frames(tile.frames) + {}; + + void updateTexture(video::SMaterial &material, float animation_time); + +private: + u16 m_frame = 0; // last animation frame + u16 m_frame_length_ms = 0; + u16 m_frame_count = 1; + + /// @note not owned by this struct + std::vector *m_frames = nullptr; +}; + enum class TileRotation: u8 { None, R90, diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 9e8d72cf2..dbba0de2a 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -30,6 +30,14 @@ #define MIN_EXTRUSION_MESH_RESOLUTION 16 #define MAX_EXTRUSION_MESH_RESOLUTION 512 +ItemMeshBufferInfo::ItemMeshBufferInfo(const TileLayer &layer) : + override_color(layer.color), + override_color_set(layer.has_color), + animation_info((layer.material_flags & MATERIAL_FLAG_ANIMATION) ? + std::make_unique(layer) : + nullptr) +{} + static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) { const f32 r = 0.5; @@ -285,7 +293,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, } static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, - std::vector *colors, const ContentFeatures &f) + std::vector *buffer_info, const ContentFeatures &f) { n.setParam1(0xff); if (n.getParam2()) { @@ -309,7 +317,7 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, MapblockMeshGenerator(&mmd, &collector).generate(); } - colors->clear(); + buffer_info->clear(); scene::SMesh *mesh = new scene::SMesh(); for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { auto &prebuffers = collector.prebuffers[layer]; @@ -329,7 +337,7 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, p.layer.applyMaterialOptions(buf->Material, layer); mesh->addMeshBuffer(buf.get()); - colors->emplace_back(p.layer.has_color, p.layer.color); + buffer_info->emplace_back(p.layer); } } mesh->recalculateBoundingBox(); @@ -352,7 +360,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che m_material_type = shdrsrc->getShaderInfo(shader_id).material; // Color-related - m_colors.clear(); + m_buffer_info.clear(); m_base_color = idef->getItemstackColor(item, client); const std::string wield_image = item.getWieldImage(idef); @@ -361,11 +369,10 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // If wield_image needs to be checked and is defined, it overrides everything else if (!wield_image.empty() && check_wield_image) { - setExtruded(wield_image, wield_overlay, wield_scale, tsrc, - 1); - m_colors.emplace_back(); + setExtruded(wield_image, wield_overlay, wield_scale, tsrc, 1); + m_buffer_info.emplace_back(); // overlay is white, if present - m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + m_buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF)); // initialize the color setColor(video::SColor(0xFFFFFFFF)); return; @@ -394,8 +401,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che wscale, tsrc, l0.animation_frame_count); // Add color - m_colors.emplace_back(l0.has_color, l0.color); - m_colors.emplace_back(l1.has_color, l1.color); + m_buffer_info.emplace_back(l0.has_color, l0.color); + m_buffer_info.emplace_back(l1.has_color, l1.color); break; } case NDT_PLANTLIKE_ROOTED: { @@ -404,7 +411,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che setExtruded(tsrc->getTextureName(l0.texture_id), "", wield_scale, tsrc, l0.animation_frame_count); - m_colors.emplace_back(l0.has_color, l0.color); + m_buffer_info.emplace_back(l0.has_color, l0.color); break; } default: { @@ -413,7 +420,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che if (def.place_param2) n.setParam2(*def.place_param2); - mesh = createGenericNodeMesh(client, n, &m_colors, f); + mesh = createGenericNodeMesh(client, n, &m_buffer_info, f); changeToMesh(mesh); mesh->drop(); m_meshnode->setScale( @@ -447,9 +454,9 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che setExtruded("no_texture.png", "", def.wield_scale, tsrc, 1); } - m_colors.emplace_back(); + m_buffer_info.emplace_back(); // overlay is white, if present - m_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + m_buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF)); // initialize the color setColor(video::SColor(0xFFFFFFFF)); @@ -471,33 +478,38 @@ void WieldMeshSceneNode::setColor(video::SColor c) u8 blue = c.getBlue(); const u32 mc = mesh->getMeshBufferCount(); - if (mc > m_colors.size()) - m_colors.resize(mc); + if (mc > m_buffer_info.size()) + m_buffer_info.resize(mc); for (u32 j = 0; j < mc; j++) { video::SColor bc(m_base_color); - m_colors[j].applyOverride(bc); + m_buffer_info[j].applyOverride(bc); video::SColor buffercolor(255, bc.getRed() * red / 255, bc.getGreen() * green / 255, bc.getBlue() * blue / 255); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - if (m_colors[j].needColorize(buffercolor)) { + if (m_buffer_info[j].needColorize(buffercolor)) { buf->setDirty(scene::EBT_VERTEX); setMeshBufferColor(buf, buffercolor); } } } -void WieldMeshSceneNode::setNodeLightColor(video::SColor color) +void WieldMeshSceneNode::setLightColorAndAnimation(video::SColor color, float animation_time) { if (!m_meshnode) return; - { - for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) { - video::SMaterial &material = m_meshnode->getMaterial(i); - material.ColorParam = color; + for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) { + // Color + video::SMaterial &material = m_meshnode->getMaterial(i); + material.ColorParam = color; + + // Animation + const ItemMeshBufferInfo &buf_info = m_buffer_info[i]; + if (buf_info.animation_info) { + buf_info.animation_info->updateTexture(material, animation_time); } } } @@ -544,9 +556,9 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) const std::string inventory_overlay = item.getInventoryOverlay(idef); if (!inventory_image.empty()) { mesh = getExtrudedMesh(tsrc, inventory_image, inventory_overlay); - result->buffer_colors.emplace_back(); + result->buffer_info.emplace_back(); // overlay is white, if present - result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF)); + result->buffer_info.emplace_back(true, video::SColor(0xFFFFFFFF)); // Items with inventory images do not need shading result->needs_shading = false; } else if (def.type == ITEM_NODE && f.drawtype == NDT_AIRLIKE) { @@ -562,8 +574,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) tsrc->getTextureName(l0.texture_id), tsrc->getTextureName(l1.texture_id)); // Add color - result->buffer_colors.emplace_back(l0.has_color, l0.color); - result->buffer_colors.emplace_back(l1.has_color, l1.color); + result->buffer_info.emplace_back(l0.has_color, l0.color); + result->buffer_info.emplace_back(l1.has_color, l1.color); break; } case NDT_PLANTLIKE_ROOTED: { @@ -571,7 +583,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) const TileLayer &l0 = f.special_tiles[0].layers[0]; mesh = getExtrudedMesh(tsrc, tsrc->getTextureName(l0.texture_id), ""); - result->buffer_colors.emplace_back(l0.has_color, l0.color); + result->buffer_info.emplace_back(l0.has_color, l0.color); break; } default: { @@ -580,7 +592,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) if (def.place_param2) n.setParam2(*def.place_param2); - mesh = createGenericNodeMesh(client, n, &result->buffer_colors, f); + mesh = createGenericNodeMesh(client, n, &result->buffer_info, f); scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); break; } diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index 0b65dfbd9..1dced09e1 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include "tile.h" namespace irr::scene { @@ -28,9 +30,10 @@ struct ContentFeatures; class ShadowRenderer; /* - * Holds color information of an item mesh's buffer. + * Holds information of an item mesh's buffer. + * Used for coloring and animation. */ -class ItemPartColor +class ItemMeshBufferInfo { /* * Optional color that overrides the global base color. @@ -47,12 +50,14 @@ class ItemPartColor public: - ItemPartColor() = default; + ItemMeshBufferInfo() = default; - ItemPartColor(bool override, video::SColor color) : + ItemMeshBufferInfo(bool override, video::SColor color) : override_color(color), override_color_set(override) {} + ItemMeshBufferInfo(const TileLayer &layer); + void applyOverride(video::SColor &dest) const { if (override_color_set) dest = override_color; @@ -65,15 +70,18 @@ public: last_colorized = target; return true; } + + // Null for no animated parts + std::unique_ptr animation_info; }; struct ItemMesh { scene::IMesh *mesh = nullptr; /* - * Stores the color of each mesh buffer. + * Stores draw information of each mesh buffer. */ - std::vector buffer_colors; + std::vector buffer_info; /* * If false, all faces of the item should have the same brightness. * Disables shading based on normal vectors. @@ -101,7 +109,7 @@ public: // Must only be used if the constructor was called with lighting = false void setColor(video::SColor color); - void setNodeLightColor(video::SColor color); + void setLightColorAndAnimation(video::SColor color, float animation_time); scene::IMesh *getMesh() { return m_meshnode->getMesh(); } @@ -120,10 +128,10 @@ private: bool m_bilinear_filter; bool m_trilinear_filter; /*! - * Stores the colors of the mesh's mesh buffers. + * Stores the colors and animation data of the mesh's mesh buffers. * This does not include lighting. */ - std::vector m_colors; + std::vector m_buffer_info; /*! * The base color of this mesh. This is the default * for all mesh buffers. diff --git a/src/gui/drawItemStack.cpp b/src/gui/drawItemStack.cpp index 728f5fd11..1afe93395 100644 --- a/src/gui/drawItemStack.cpp +++ b/src/gui/drawItemStack.cpp @@ -118,13 +118,13 @@ void drawItemStack( client->idef()->getItemstackColor(item, client); const u32 mc = mesh->getMeshBufferCount(); - if (mc > imesh->buffer_colors.size()) - imesh->buffer_colors.resize(mc); + if (mc > imesh->buffer_info.size()) + imesh->buffer_info.resize(mc); for (u32 j = 0; j < mc; ++j) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); video::SColor c = basecolor; - auto &p = imesh->buffer_colors[j]; + auto &p = imesh->buffer_info[j]; p.applyOverride(c); // TODO: could be moved to a shader @@ -137,6 +137,12 @@ void drawItemStack( } video::SMaterial &material = buf->getMaterial(); + + // Texture animation + if (p.animation_info) { + p.animation_info->updateTexture(material, client->getAnimationTime()); + } + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; driver->setMaterial(material); driver->drawMeshBuffer(buf); From 52b974184daab188da1624bf918d820ff3b7fcb8 Mon Sep 17 00:00:00 2001 From: cx384 Date: Fri, 4 Apr 2025 18:58:14 +0200 Subject: [PATCH 113/284] Move client code out of ItemDefManager (#15967) --- src/client/CMakeLists.txt | 1 + src/client/client.cpp | 5 ++ src/client/client.h | 5 ++ src/client/game.cpp | 5 ++ src/client/item_visuals_manager.cpp | 102 ++++++++++++++++++++++ src/client/item_visuals_manager.h | 68 +++++++++++++++ src/client/wieldmesh.cpp | 4 +- src/gui/drawItemStack.cpp | 11 +-- src/itemdef.cpp | 126 ---------------------------- src/itemdef.h | 31 ------- 10 files changed, 195 insertions(+), 163 deletions(-) create mode 100644 src/client/item_visuals_manager.cpp create mode 100644 src/client/item_visuals_manager.h diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index c17148a35..b11e6ee6f 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -56,6 +56,7 @@ set(client_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp ${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inputhandler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/item_visuals_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/joystick_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/keycode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/localplayer.cpp diff --git a/src/client/client.cpp b/src/client/client.cpp index 77853b535..cf19d7e58 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -53,6 +53,7 @@ #include "translation.h" #include "content/mod_configuration.h" #include "mapnode.h" +#include "item_visuals_manager.h" extern gui::IGUIEnvironment* guienv; @@ -95,6 +96,7 @@ Client::Client( ISoundManager *sound, MtEventManager *event, RenderingEngine *rendering_engine, + ItemVisualsManager *item_visuals_manager, ELoginRegister allow_login_or_register ): m_tsrc(tsrc), @@ -104,6 +106,7 @@ Client::Client( m_sound(sound), m_event(event), m_rendering_engine(rendering_engine), + m_item_visuals_manager(item_visuals_manager), m_mesh_update_manager(std::make_unique(this)), m_env( make_irr(this, rendering_engine, control, 666), @@ -346,6 +349,8 @@ Client::~Client() // cleanup 3d model meshes on client shutdown m_rendering_engine->cleanupMeshCache(); + m_item_visuals_manager->clear(); + guiScalingCacheClear(); delete m_minimap; diff --git a/src/client/client.h b/src/client/client.h index d9cb7c6af..12625f24e 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -55,6 +55,7 @@ struct MeshMakeData; struct MinimapMapblock; struct PlayerControl; struct PointedThing; +struct ItemVisualsManager; namespace con { class IConnection; @@ -118,6 +119,7 @@ public: ISoundManager *sound, MtEventManager *event, RenderingEngine *rendering_engine, + ItemVisualsManager *item_visuals, ELoginRegister allow_login_or_register ); @@ -383,6 +385,8 @@ public: const std::string* getModFile(std::string filename); ModStorageDatabase *getModStorageDatabase() override { return m_mod_storage_database; } + ItemVisualsManager *getItemVisualsManager() { return m_item_visuals_manager; } + // Migrates away old files-based mod storage if necessary void migrateModStorage(); @@ -480,6 +484,7 @@ private: ISoundManager *m_sound; MtEventManager *m_event; RenderingEngine *m_rendering_engine; + ItemVisualsManager *m_item_visuals_manager; std::unique_ptr m_mesh_update_manager; diff --git a/src/client/game.cpp b/src/client/game.cpp index 79889e775..ee6cd0af4 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -60,6 +60,7 @@ #include "clientdynamicinfo.h" #include #include "util/tracy_wrapper.h" +#include "item_visuals_manager.h" #if USE_SOUND #include "client/sound/sound_openal.h" @@ -692,6 +693,7 @@ private: // When created, these will be filled with data received from the server IWritableItemDefManager *itemdef_manager = nullptr; NodeDefManager *nodedef_manager = nullptr; + std::unique_ptr m_item_visuals_manager; std::unique_ptr sound_manager; SoundMaker *soundmaker = nullptr; @@ -1125,6 +1127,8 @@ bool Game::init( itemdef_manager = createItemDefManager(); nodedef_manager = createNodeDefManager(); + m_item_visuals_manager = std::make_unique(); + eventmgr = new EventManager(); quicktune = new QuicktuneShortcutter(); @@ -1443,6 +1447,7 @@ bool Game::connectToServer(const GameStartData &start_data, *draw_control, texture_src, shader_src, itemdef_manager, nodedef_manager, sound_manager.get(), eventmgr, m_rendering_engine, + m_item_visuals_manager.get(), start_data.allow_login_or_register); } catch (const BaseException &e) { *error_message = fmtgettext("Error creating client: %s", e.what()); diff --git a/src/client/item_visuals_manager.cpp b/src/client/item_visuals_manager.cpp new file mode 100644 index 000000000..7503d496b --- /dev/null +++ b/src/client/item_visuals_manager.cpp @@ -0,0 +1,102 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 cx384 + +#include "item_visuals_manager.h" + +#include "mesh.h" +#include "client.h" +#include "texturesource.h" +#include "itemdef.h" +#include "inventory.h" + +ItemVisualsManager::ItemVisuals::~ItemVisuals() { + if (wield_mesh.mesh) + wield_mesh.mesh->drop(); +} + +ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const ItemStack &item, + Client *client) const +{ + // This is not thread-safe + sanity_check(std::this_thread::get_id() == m_main_thread); + + IItemDefManager *idef = client->idef(); + + const ItemDefinition &def = item.getDefinition(idef); + std::string inventory_image = item.getInventoryImage(idef); + std::string inventory_overlay = item.getInventoryOverlay(idef); + std::string cache_key = def.name; + if (!inventory_image.empty()) + cache_key += "/" + inventory_image; + if (!inventory_overlay.empty()) + cache_key += ":" + inventory_overlay; + + // Skip if already in cache + auto it = m_cached_item_visuals.find(cache_key); + if (it != m_cached_item_visuals.end()) + return it->second.get(); + + infostream << "Lazily creating item texture and mesh for \"" + << cache_key << "\"" << std::endl; + + ITextureSource *tsrc = client->getTextureSource(); + + // Create new ItemVisuals + auto cc = std::make_unique(); + + cc->inventory_texture = NULL; + if (!inventory_image.empty()) + cc->inventory_texture = tsrc->getTexture(inventory_image); + getItemMesh(client, item, &(cc->wield_mesh)); + + cc->palette = tsrc->getPalette(def.palette_image); + + // Put in cache + ItemVisuals *ptr = cc.get(); + m_cached_item_visuals[cache_key] = std::move(cc); + return ptr; +} + +video::ITexture* ItemVisualsManager::getInventoryTexture(const ItemStack &item, + Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + if (!iv) + return nullptr; + return iv->inventory_texture; +} + +ItemMesh* ItemVisualsManager::getWieldMesh(const ItemStack &item, Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + if (!iv) + return nullptr; + return &(iv->wield_mesh); +} + +Palette* ItemVisualsManager::getPalette(const ItemStack &item, Client *client) const +{ + ItemVisuals *iv = createItemVisuals(item, client); + if (!iv) + return nullptr; + return iv->palette; +} + +video::SColor ItemVisualsManager::getItemstackColor(const ItemStack &stack, + Client *client) const +{ + // Look for direct color definition + const std::string &colorstring = stack.metadata.getString("color", 0); + video::SColor directcolor; + if (!colorstring.empty() && parseColorString(colorstring, directcolor, true)) + return directcolor; + // See if there is a palette + Palette *palette = getPalette(stack, client); + const std::string &index = stack.metadata.getString("palette_index", 0); + if (palette && !index.empty()) + return (*palette)[mystoi(index, 0, 255)]; + // Fallback color + return client->idef()->get(stack.name).color; +} + diff --git a/src/client/item_visuals_manager.h b/src/client/item_visuals_manager.h new file mode 100644 index 000000000..bacc4f2b7 --- /dev/null +++ b/src/client/item_visuals_manager.h @@ -0,0 +1,68 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2025 cx384 + +#pragma once + +#include +#include +#include +#include "wieldmesh.h" // ItemMesh +#include "util/basic_macros.h" + +class Client; +class ItemStack; +typedef std::vector Palette; // copied from src/client/texturesource.h +namespace irr::video { class ITexture; } + +// Caches data needed to draw an itemstack + +struct ItemVisualsManager +{ + ItemVisualsManager() + { + m_main_thread = std::this_thread::get_id(); + } + + void clear() { + m_cached_item_visuals.clear(); + } + + // Get item inventory texture + video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const; + + // Get item wield mesh + // Once said to return nullptr if there is an inventory image, but this is wrong + ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const; + + // Get item palette + Palette* getPalette(const ItemStack &item, Client *client) const; + + // Returns the base color of an item stack: the color of all + // tiles that do not define their own color. + video::SColor getItemstackColor(const ItemStack &stack, Client *client) const; + +private: + struct ItemVisuals + { + video::ITexture *inventory_texture; + ItemMesh wield_mesh; + Palette *palette; + + ItemVisuals(): + inventory_texture(nullptr), + palette(nullptr) + {} + + ~ItemVisuals(); + + DISABLE_CLASS_COPY(ItemVisuals); + }; + + // The id of the thread that is allowed to use irrlicht directly + std::thread::id m_main_thread; + // Cached textures and meshes + mutable std::unordered_map> m_cached_item_visuals; + + ItemVisuals* createItemVisuals(const ItemStack &item, Client *client) const; +}; diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index dbba0de2a..11116a5c3 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -23,6 +23,7 @@ #include #include #include +#include "item_visuals_manager.h" #define WIELD_SCALE_FACTOR 30.0f #define WIELD_SCALE_FACTOR_EXTRUDED 40.0f @@ -348,6 +349,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che { ITextureSource *tsrc = client->getTextureSource(); IItemDefManager *idef = client->getItemDefManager(); + ItemVisualsManager *item_visuals = client->getItemVisualsManager(); IShaderSource *shdrsrc = client->getShaderSource(); const NodeDefManager *ndef = client->getNodeDefManager(); const ItemDefinition &def = item.getDefinition(idef); @@ -361,7 +363,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che // Color-related m_buffer_info.clear(); - m_base_color = idef->getItemstackColor(item, client); + m_base_color = item_visuals->getItemstackColor(item, client); const std::string wield_image = item.getWieldImage(idef); const std::string wield_overlay = item.getWieldOverlay(idef); diff --git a/src/gui/drawItemStack.cpp b/src/gui/drawItemStack.cpp index 1afe93395..111109fd4 100644 --- a/src/gui/drawItemStack.cpp +++ b/src/gui/drawItemStack.cpp @@ -13,6 +13,7 @@ #include "client/wieldmesh.h" #include "client/texturesource.h" #include "client/guiscalingfilter.h" +#include "client/item_visuals_manager.h" struct MeshTimeInfo { u64 time; @@ -43,6 +44,7 @@ void drawItemStack( auto *idef = client->idef(); const ItemDefinition &def = item.getDefinition(idef); + ItemVisualsManager* item_visuals = client->getItemVisualsManager(); bool draw_overlay = false; @@ -58,7 +60,7 @@ void drawItemStack( // Render as mesh if animated or no inventory image if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) { - imesh = idef->getWieldMesh(item, client); + imesh = item_visuals->getWieldMesh(item, client); has_mesh = imesh && imesh->mesh; } if (has_mesh) { @@ -114,8 +116,7 @@ void drawItemStack( driver->setTransform(video::ETS_WORLD, matrix); driver->setViewPort(viewrect); - video::SColor basecolor = - client->idef()->getItemstackColor(item, client); + video::SColor basecolor = item_visuals->getItemstackColor(item, client); const u32 mc = mesh->getMeshBufferCount(); if (mc > imesh->buffer_info.size()) @@ -154,10 +155,10 @@ void drawItemStack( draw_overlay = def.type == ITEM_NODE && inventory_image.empty(); } else { // Otherwise just draw as 2D - video::ITexture *texture = client->idef()->getInventoryTexture(item, client); + video::ITexture *texture = item_visuals->getInventoryTexture(item, client); video::SColor color; if (texture) { - color = client->idef()->getItemstackColor(item, client); + color = item_visuals->getItemstackColor(item, client); } else { color = video::SColor(255, 255, 255, 255); ITextureSource *tsrc = client->getTextureSource(); diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 23f948f8c..17f46dedc 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -7,14 +7,6 @@ #include "nodedef.h" #include "tool.h" -#include "inventory.h" -#if CHECK_CLIENT_BUILD() -#include "client/mapblock_mesh.h" -#include "client/mesh.h" -#include "client/wieldmesh.h" -#include "client/client.h" -#include "client/texturesource.h" -#endif #include "log.h" #include "settings.h" #include "util/serialize.h" @@ -360,34 +352,10 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) class CItemDefManager: public IWritableItemDefManager { -#if CHECK_CLIENT_BUILD() - struct ClientCached - { - video::ITexture *inventory_texture; - ItemMesh wield_mesh; - Palette *palette; - - ClientCached(): - inventory_texture(NULL), - palette(NULL) - {} - - ~ClientCached() { - if (wield_mesh.mesh) - wield_mesh.mesh->drop(); - } - - DISABLE_CLASS_COPY(ClientCached); - }; -#endif public: CItemDefManager() { - -#if CHECK_CLIENT_BUILD() - m_main_thread = std::this_thread::get_id(); -#endif clear(); } @@ -435,94 +403,6 @@ public: return m_item_definitions.find(name) != m_item_definitions.cend(); } -#if CHECK_CLIENT_BUILD() -protected: - ClientCached* createClientCachedDirect(const ItemStack &item, Client *client) const - { - // This is not thread-safe - sanity_check(std::this_thread::get_id() == m_main_thread); - - const ItemDefinition &def = item.getDefinition(this); - std::string inventory_image = item.getInventoryImage(this); - std::string inventory_overlay = item.getInventoryOverlay(this); - std::string cache_key = def.name; - if (!inventory_image.empty()) - cache_key += "/" + inventory_image; - if (!inventory_overlay.empty()) - cache_key += ":" + inventory_overlay; - - // Skip if already in cache - auto it = m_clientcached.find(cache_key); - if (it != m_clientcached.end()) - return it->second.get(); - - infostream << "Lazily creating item texture and mesh for \"" - << cache_key << "\"" << std::endl; - - ITextureSource *tsrc = client->getTextureSource(); - - // Create new ClientCached - auto cc = std::make_unique(); - - cc->inventory_texture = NULL; - if (!inventory_image.empty()) - cc->inventory_texture = tsrc->getTexture(inventory_image); - getItemMesh(client, item, &(cc->wield_mesh)); - - cc->palette = tsrc->getPalette(def.palette_image); - - // Put in cache - ClientCached *ptr = cc.get(); - m_clientcached[cache_key] = std::move(cc); - return ptr; - } - -public: - // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const ItemStack &item, - Client *client) const - { - ClientCached *cc = createClientCachedDirect(item, client); - if (!cc) - return nullptr; - return cc->inventory_texture; - } - - // Get item wield mesh - virtual ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const - { - ClientCached *cc = createClientCachedDirect(item, client); - if (!cc) - return nullptr; - return &(cc->wield_mesh); - } - - // Get item palette - virtual Palette* getPalette(const ItemStack &item, Client *client) const - { - ClientCached *cc = createClientCachedDirect(item, client); - if (!cc) - return nullptr; - return cc->palette; - } - - 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)) - return directcolor; - // See if there is a palette - Palette *palette = getPalette(stack, client); - const std::string &index = stack.metadata.getString("palette_index", 0); - if (palette && !index.empty()) - return (*palette)[mystoi(index, 0, 255)]; - // Fallback color - return get(stack.name).color; - } -#endif void applyTextureOverrides(const std::vector &overrides) { infostream << "ItemDefManager::applyTextureOverrides(): Applying " @@ -666,12 +546,6 @@ private: std::map m_item_definitions; // Aliases StringMap m_aliases; -#if CHECK_CLIENT_BUILD() - // The id of the thread that is allowed to use irrlicht directly - std::thread::id m_main_thread; - // Cached textures and meshes - mutable std::unordered_map> m_clientcached; -#endif }; IWritableItemDefManager* createItemDefManager() diff --git a/src/itemdef.h b/src/itemdef.h index fdad86a69..3feee79d1 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -17,14 +17,7 @@ #include "util/pointabilities.h" #include "util/pointedthing.h" -class IGameDef; -class Client; struct ToolCapabilities; -struct ItemMesh; -struct ItemStack; -typedef std::vector Palette; // copied from src/client/texturesource.h -namespace irr::video { class ITexture; } -using namespace irr; /* Base item definition @@ -142,30 +135,6 @@ public: virtual bool isKnown(const std::string &name) const=0; virtual void serialize(std::ostream &os, u16 protocol_version)=0; - - /* Client-specific methods */ - // TODO: should be moved elsewhere in the future - - // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const - { return nullptr; } - - /** - * Get wield mesh - * @returns nullptr if there is an inventory image - */ - virtual ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const - { return nullptr; } - - // Get item palette - virtual Palette* getPalette(const ItemStack &item, Client *client) const - { return nullptr; } - - // 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 - { return video::SColor(0); } }; class IWritableItemDefManager : public IItemDefManager From 6a71095655813d922f7041a97996db8f0b0b3c85 Mon Sep 17 00:00:00 2001 From: lhofhansl Date: Sat, 5 Apr 2025 08:01:39 -1000 Subject: [PATCH 114/284] Break liquid reflow scan early for all-air blocks (#15975) Avoid scanning the a newly loaded block if it is all air and no liquid is flowing from above. --- src/reflowscan.cpp | 71 +++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/reflowscan.cpp b/src/reflowscan.cpp index 091dfa323..2a4f67c78 100644 --- a/src/reflowscan.cpp +++ b/src/reflowscan.cpp @@ -121,41 +121,48 @@ void ReflowScan::scanColumn(int x, int z) bool was_checked = false; bool was_pushed = false; - // Scan through the whole block - for (s16 y = MAP_BLOCKSIZE - 1; y >= 0; y--) { - MapNode node = block->getNodeNoCheck(dx, y, dz); - const ContentFeatures &f = m_ndef->get(node); - bool is_ignore = node.getContent() == CONTENT_IGNORE; - bool is_liquid = f.isLiquid(); + // if there is no liquid above and the current block is air + // we can skip scanning the block + if (!was_liquid && block->isAir()) { + // continue after the block with air + was_ignore = false; + } else { + // Scan through the whole block + for (s16 y = MAP_BLOCKSIZE - 1; y >= 0; y--) { + MapNode node = block->getNodeNoCheck(dx, y, dz); + 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 - was_checked = false; - was_pushed = false; - } else if (is_liquid) { - // This is the topmost node in the column - 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)); - is_pushed = true; - } - // Remember waschecked and waspushed to avoid repeated - // checks/pushes in case the column consists of only this node - was_checked = true; - 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)))) { - // 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)); + if (is_ignore || was_ignore || is_liquid == was_liquid) { + // Neither topmost node of liquid column nor topmost node below column + was_checked = false; + was_pushed = false; + } else if (is_liquid) { + // This is the topmost node in the column + 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)); + is_pushed = true; + } + // Remember waschecked and waspushed to avoid repeated + // checks/pushes in case the column consists of only this node + was_checked = true; + 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)))) { + // 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)); + } } + + was_liquid = is_liquid; + was_ignore = is_ignore; } - - was_liquid = is_liquid; - was_ignore = is_ignore; } // Check the node below the current block From bed36139dbd47d8a2121d0449132b7864bc91d12 Mon Sep 17 00:00:00 2001 From: cx384 Date: Mon, 7 Apr 2025 01:38:32 +0200 Subject: [PATCH 115/284] Fix struct forward declaration --- src/client/item_visuals_manager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/item_visuals_manager.h b/src/client/item_visuals_manager.h index bacc4f2b7..8684ef477 100644 --- a/src/client/item_visuals_manager.h +++ b/src/client/item_visuals_manager.h @@ -11,7 +11,7 @@ #include "util/basic_macros.h" class Client; -class ItemStack; +struct ItemStack; typedef std::vector Palette; // copied from src/client/texturesource.h namespace irr::video { class ITexture; } From a3648b0b163a0c8d92c5cf4581f72b1f0c04eee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 8 Apr 2025 08:44:53 +0200 Subject: [PATCH 116/284] Add spatial index for objects (#14631) --- irr/include/vector3d.h | 6 + src/activeobjectmgr.h | 2 +- src/benchmark/benchmark_activeobjectmgr.cpp | 4 + src/collision.cpp | 16 +- src/server/activeobjectmgr.cpp | 53 +- src/server/activeobjectmgr.h | 10 +- src/server/luaentity_sao.cpp | 28 +- src/server/player_sao.cpp | 23 +- src/server/player_sao.h | 2 +- src/server/serveractiveobject.cpp | 12 + src/server/serveractiveobject.h | 6 +- src/serverenvironment.cpp | 9 +- src/serverenvironment.h | 5 + src/unittest/CMakeLists.txt | 1 + src/unittest/test_k_d_tree.cpp | 138 ++++++ src/unittest/test_serveractiveobjectmgr.cpp | 268 +++++++--- src/util/k_d_tree.h | 515 ++++++++++++++++++++ 17 files changed, 982 insertions(+), 116 deletions(-) create mode 100644 src/unittest/test_k_d_tree.cpp create mode 100644 src/util/k_d_tree.h diff --git a/irr/include/vector3d.h b/irr/include/vector3d.h index fd788e734..562efb2d6 100644 --- a/irr/include/vector3d.h +++ b/irr/include/vector3d.h @@ -7,6 +7,7 @@ #include "irrMath.h" #include +#include namespace irr { @@ -32,6 +33,9 @@ public: //! Constructor with the same value for all elements explicit constexpr vector3d(T n) : X(n), Y(n), Z(n) {} + //! Array - vector conversion + constexpr vector3d(const std::array &arr) : + X(arr[0]), Y(arr[1]), Z(arr[2]) {} template constexpr static vector3d from(const vector3d &other) @@ -187,6 +191,8 @@ public: return *this; } + std::array toArray() const { return {X, Y, Z}; } + //! Get length of the vector. T getLength() const { return core::squareroot(X * X + Y * Y + Z * Z); } diff --git a/src/activeobjectmgr.h b/src/activeobjectmgr.h index a9b007018..952d812ef 100644 --- a/src/activeobjectmgr.h +++ b/src/activeobjectmgr.h @@ -39,7 +39,7 @@ public: for (auto &it : m_active_objects.iter()) { if (!it.second) continue; - m_active_objects.remove(it.first); + removeObject(it.first); } } while (!m_active_objects.empty()); } diff --git a/src/benchmark/benchmark_activeobjectmgr.cpp b/src/benchmark/benchmark_activeobjectmgr.cpp index d9036c632..23a712af6 100644 --- a/src/benchmark/benchmark_activeobjectmgr.cpp +++ b/src/benchmark/benchmark_activeobjectmgr.cpp @@ -105,7 +105,11 @@ void benchGetObjectsInArea(Catch::Benchmark::Chronometer &meter) TEST_CASE("ActiveObjectMgr") { BENCH_INSIDE_RADIUS(200) BENCH_INSIDE_RADIUS(1450) + BENCH_INSIDE_RADIUS(10000) BENCH_IN_AREA(200) BENCH_IN_AREA(1450) + BENCH_IN_AREA(10000) } + +// TODO benchmark active object manager update costs diff --git a/src/collision.cpp b/src/collision.cpp index 8f9cc788c..53d97553b 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -4,10 +4,12 @@ #include "collision.h" #include +#include "irr_aabb3d.h" #include "mapblock.h" #include "map.h" #include "nodedef.h" #include "gamedef.h" +#include "util/numeric.h" #if CHECK_CLIENT_BUILD() #include "client/clientenvironment.h" #include "client/localplayer.h" @@ -311,13 +313,14 @@ static void add_object_boxes(Environment *env, } }; - // Calculate distance by speed, add own extent and 1.5m of tolerance - const f32 distance = speed_f.getLength() * dtime + - box_0.getExtent().getLength() + 1.5f * BS; + constexpr f32 tolerance = 1.5f * BS; #if CHECK_CLIENT_BUILD() ClientEnvironment *c_env = dynamic_cast(env); if (c_env) { + // Calculate distance by speed, add own extent and tolerance + const f32 distance = speed_f.getLength() * dtime + + box_0.getExtent().getLength() + tolerance; std::vector clientobjects; c_env->getActiveObjects(pos_f, distance, clientobjects); @@ -356,9 +359,14 @@ static void add_object_boxes(Environment *env, return false; }; + // Calculate distance by speed, add own extent and tolerance + const v3f movement = speed_f * dtime; + const v3f min = pos_f + box_0.MinEdge - v3f(tolerance) + componentwise_min(movement, v3f()); + const v3f max = pos_f + box_0.MaxEdge + v3f(tolerance) + componentwise_max(movement, v3f()); + // nothing is put into this vector std::vector s_objects; - s_env->getObjectsInsideRadius(s_objects, pos_f, distance, include_obj_cb); + s_env->getObjectsInArea(s_objects, aabb3f(min, max), include_obj_cb); } } } diff --git a/src/server/activeobjectmgr.cpp b/src/server/activeobjectmgr.cpp index 155cf50fb..452017786 100644 --- a/src/server/activeobjectmgr.cpp +++ b/src/server/activeobjectmgr.cpp @@ -26,7 +26,7 @@ void ActiveObjectMgr::clearIf(const std::function obj) return false; } - if (objectpos_over_limit(obj->getBasePosition())) { - v3f p = obj->getBasePosition(); + const v3f pos = obj->getBasePosition(); + if (objectpos_over_limit(pos)) { warningstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): " - << "object position (" << p.X << "," << p.Y << "," << p.Z + << "object position (" << pos.X << "," << pos.Y << "," << pos.Z << ") outside maximum range" << std::endl; return false; } auto obj_id = obj->getId(); m_active_objects.put(obj_id, std::move(obj)); + m_spatial_index.insert(pos.toArray(), obj_id); auto new_size = m_active_objects.size(); verbosestream << "Server::ActiveObjectMgr::addActiveObjectRaw(): " @@ -100,6 +101,8 @@ void ActiveObjectMgr::removeObject(u16 id) if (!ok) { infostream << "Server::ActiveObjectMgr::removeObject(): " << "id=" << id << " not found" << std::endl; + } else { + m_spatial_index.remove(id); } } @@ -113,43 +116,47 @@ void ActiveObjectMgr::invalidateActiveObjectObserverCaches() } } -void ActiveObjectMgr::getObjectsInsideRadius(const v3f &pos, float radius, +void ActiveObjectMgr::updateObjectPos(u16 id, v3f pos) +{ + // HACK defensively only update if we already know the object, + // otherwise we're still waiting to be inserted into the index + // (or have already been removed). + if (m_active_objects.get(id)) + m_spatial_index.update(pos.toArray(), id); +} + +void ActiveObjectMgr::getObjectsInsideRadius(v3f pos, float radius, std::vector &result, std::function include_obj_cb) { - float r2 = radius * radius; - for (auto &activeObject : m_active_objects.iter()) { - ServerActiveObject *obj = activeObject.second.get(); - if (!obj) - continue; - const v3f &objectpos = obj->getBasePosition(); - if (objectpos.getDistanceFromSQ(pos) > r2) - continue; + float r_squared = radius * radius; + m_spatial_index.rangeQuery((pos - v3f(radius)).toArray(), (pos + v3f(radius)).toArray(), [&](auto objPos, u16 id) { + if (v3f(objPos).getDistanceFromSQ(pos) > r_squared) + return; + auto obj = m_active_objects.get(id).get(); + if (!obj) + return; if (!include_obj_cb || include_obj_cb(obj)) result.push_back(obj); - } + }); } void ActiveObjectMgr::getObjectsInArea(const aabb3f &box, std::vector &result, std::function include_obj_cb) { - for (auto &activeObject : m_active_objects.iter()) { - ServerActiveObject *obj = activeObject.second.get(); + m_spatial_index.rangeQuery(box.MinEdge.toArray(), box.MaxEdge.toArray(), [&](auto _, u16 id) { + auto obj = m_active_objects.get(id).get(); if (!obj) - continue; - const v3f &objectpos = obj->getBasePosition(); - if (!box.isPointInside(objectpos)) - continue; - + return; if (!include_obj_cb || include_obj_cb(obj)) result.push_back(obj); - } + }); } void ActiveObjectMgr::getAddedActiveObjectsAroundPos( - const v3f &player_pos, const std::string &player_name, + v3f player_pos, const std::string &player_name, f32 radius, f32 player_radius, const std::set ¤t_objects, std::vector &added_objects) diff --git a/src/server/activeobjectmgr.h b/src/server/activeobjectmgr.h index 854a75b18..9c65ad514 100644 --- a/src/server/activeobjectmgr.h +++ b/src/server/activeobjectmgr.h @@ -8,6 +8,7 @@ #include #include "../activeobjectmgr.h" #include "serveractiveobject.h" +#include "util/k_d_tree.h" namespace server { @@ -25,16 +26,21 @@ public: void invalidateActiveObjectObserverCaches(); - void getObjectsInsideRadius(const v3f &pos, float radius, + void updateObjectPos(u16 id, v3f pos); + + void getObjectsInsideRadius(v3f pos, float radius, std::vector &result, std::function include_obj_cb); void getObjectsInArea(const aabb3f &box, std::vector &result, std::function include_obj_cb); void getAddedActiveObjectsAroundPos( - const v3f &player_pos, const std::string &player_name, + v3f player_pos, const std::string &player_name, f32 radius, f32 player_radius, const std::set ¤t_objects, std::vector &added_objects); + +private: + k_d_tree::DynamicKdTrees<3, f32, u16> m_spatial_index; }; } // namespace server diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 5de0167d6..0ad3daba6 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -147,7 +147,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) // 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 (auto *parent = getParent()) { - m_base_position = parent->getBasePosition(); + setBasePosition(parent->getBasePosition()); m_velocity = v3f(0,0,0); m_acceleration = v3f(0,0,0); } else { @@ -155,7 +155,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) aabb3f box = m_prop.collisionbox; box.MinEdge *= BS; box.MaxEdge *= BS; - v3f p_pos = m_base_position; + v3f p_pos = getBasePosition(); v3f p_velocity = m_velocity; v3f p_acceleration = m_acceleration; moveresult = collisionMoveSimple(m_env, m_env->getGameDef(), @@ -165,11 +165,11 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) moveresult_p = &moveresult; // Apply results - m_base_position = p_pos; + setBasePosition(p_pos); m_velocity = p_velocity; m_acceleration = p_acceleration; } else { - m_base_position += (m_velocity + m_acceleration * 0.5f * dtime) * dtime; + addPos((m_velocity + m_acceleration * 0.5f * dtime) * dtime); m_velocity += dtime * m_acceleration; } @@ -212,7 +212,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) } else if(m_last_sent_position_timer > 0.2){ minchange = 0.05*BS; } - float move_d = m_base_position.getDistanceFrom(m_last_sent_position); + float move_d = getBasePosition().getDistanceFrom(m_last_sent_position); move_d += m_last_sent_move_precision; float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity); if (move_d > minchange || vel_d > minchange || @@ -236,7 +236,7 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) os << serializeString16(m_init_name); // name writeU8(os, 0); // is_player writeU16(os, getId()); //id - writeV3F32(os, m_base_position); + writeV3F32(os, getBasePosition()); writeV3F32(os, m_rotation); writeU16(os, m_hp); @@ -365,7 +365,7 @@ void LuaEntitySAO::setPos(const v3f &pos) { if(isAttached()) return; - m_base_position = pos; + setBasePosition(pos); sendPosition(false, true); } @@ -373,7 +373,7 @@ void LuaEntitySAO::moveTo(v3f pos, bool continuous) { if(isAttached()) return; - m_base_position = pos; + setBasePosition(pos); if(!continuous) sendPosition(true, true); } @@ -387,7 +387,7 @@ std::string LuaEntitySAO::getDescription() { std::ostringstream oss; oss << "LuaEntitySAO \"" << m_init_name << "\" "; - auto pos = floatToInt(m_base_position, BS); + auto pos = floatToInt(getBasePosition(), BS); oss << "at " << pos; return oss.str(); } @@ -503,10 +503,10 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) // Send attachment updates instantly to the client prior updating position sendOutdatedData(); - m_last_sent_move_precision = m_base_position.getDistanceFrom( + m_last_sent_move_precision = getBasePosition().getDistanceFrom( m_last_sent_position); m_last_sent_position_timer = 0; - m_last_sent_position = m_base_position; + m_last_sent_position = getBasePosition(); m_last_sent_velocity = m_velocity; //m_last_sent_acceleration = m_acceleration; m_last_sent_rotation = m_rotation; @@ -514,7 +514,7 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) float update_interval = m_env->getSendRecommendedInterval(); std::string str = generateUpdatePositionCommand( - m_base_position, + getBasePosition(), m_velocity, m_acceleration, m_rotation, @@ -534,8 +534,8 @@ bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const toset->MinEdge = m_prop.collisionbox.MinEdge * BS; toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; - toset->MinEdge += m_base_position; - toset->MaxEdge += m_base_position; + toset->MinEdge += getBasePosition(); + toset->MaxEdge += getBasePosition(); return true; } diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 11fc15597..068b2b29f 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -70,11 +70,10 @@ std::string PlayerSAO::getDescription() void PlayerSAO::addedToEnvironment(u32 dtime_s) { ServerActiveObject::addedToEnvironment(dtime_s); - ServerActiveObject::setBasePosition(m_base_position); m_player->setPlayerSAO(this); m_player->setPeerId(m_peer_id_initial); m_peer_id_initial = PEER_ID_INEXISTENT; // don't try to use it again. - m_last_good_position = m_base_position; + m_last_good_position = getBasePosition(); } // Called before removing from environment @@ -100,7 +99,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) os << serializeString16(m_player->getName()); // name writeU8(os, 1); // is_player writeS16(os, getId()); // id - writeV3F32(os, m_base_position); + writeV3F32(os, getBasePosition()); writeV3F32(os, m_rotation); writeU16(os, getHP()); @@ -184,7 +183,7 @@ 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 + + v3s16 p = floatToInt(getBasePosition() + 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); @@ -196,7 +195,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) } // Top damage point - v3s16 ptop = floatToInt(m_base_position + + v3s16 ptop = floatToInt(getBasePosition() + 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); @@ -273,7 +272,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (isAttached()) pos = m_last_good_position; else - pos = m_base_position; + pos = getBasePosition(); std::string str = generateUpdatePositionCommand( pos, @@ -332,7 +331,7 @@ std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const void PlayerSAO::setBasePosition(v3f position) { - if (m_player && position != m_base_position) + if (m_player && position != getBasePosition()) m_player->setDirty(true); // This needs to be ran for attachments too @@ -636,7 +635,7 @@ bool PlayerSAO::checkMovementCheat() if (m_is_singleplayer || isAttached() || !(anticheat_flags & AC_MOVEMENT)) { - m_last_good_position = m_base_position; + m_last_good_position = getBasePosition(); return false; } @@ -701,7 +700,7 @@ bool PlayerSAO::checkMovementCheat() if (player_max_jump < 0.0001f) player_max_jump = 0.0001f; - v3f diff = (m_base_position - m_last_good_position); + v3f diff = (getBasePosition() - m_last_good_position); float d_vert = diff.Y; diff.Y = 0; float d_horiz = diff.getLength(); @@ -722,7 +721,7 @@ bool PlayerSAO::checkMovementCheat() required_time /= anticheat_movement_tolerance; if (m_move_pool.grab(required_time)) { - m_last_good_position = m_base_position; + m_last_good_position = getBasePosition(); } else { const float LAG_POOL_MIN = 5.0; float lag_pool_max = m_env->getMaxLagEstimate() * 2.0; @@ -744,8 +743,8 @@ bool PlayerSAO::getCollisionBox(aabb3f *toset) const toset->MinEdge = m_prop.collisionbox.MinEdge * BS; toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; - toset->MinEdge += m_base_position; - toset->MaxEdge += m_base_position; + toset->MinEdge += getBasePosition(); + toset->MaxEdge += getBasePosition(); return true; } diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 0ce26f7cc..a19177a7e 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -170,7 +170,7 @@ public: void finalize(RemotePlayer *player, const std::set &privs); - v3f getEyePosition() const { return m_base_position + getEyeOffset(); } + v3f getEyePosition() const { return getBasePosition() + getEyeOffset(); } v3f getEyeOffset() const; float getZoomFOV() const; diff --git a/src/server/serveractiveobject.cpp b/src/server/serveractiveobject.cpp index 913c402ed..fa0c76a70 100644 --- a/src/server/serveractiveobject.cpp +++ b/src/server/serveractiveobject.cpp @@ -6,6 +6,7 @@ #include "inventory.h" #include "inventorymanager.h" #include "constants.h" // BS +#include "serverenvironment.h" ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos): ActiveObject(0), @@ -14,6 +15,17 @@ ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos): { } +void ServerActiveObject::setBasePosition(v3f pos) +{ + bool changed = m_base_position != pos; + m_base_position = pos; + if (changed && getEnv()) { + // getEnv() should never be null if the object is in an environment. + // It may however be null e.g. in tests or database migrations. + getEnv()->updateObjectPos(getId(), pos); + } +} + float ServerActiveObject::getMinimumSavedMovement() { return 2.0*BS; diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h index 8b60bc5f8..da3dc17bd 100644 --- a/src/server/serveractiveobject.h +++ b/src/server/serveractiveobject.h @@ -63,7 +63,7 @@ public: Some simple getters/setters */ v3f getBasePosition() const { return m_base_position; } - void setBasePosition(v3f pos){ m_base_position = pos; } + void setBasePosition(v3f pos); ServerEnvironment* getEnv(){ return m_env; } /* @@ -245,7 +245,6 @@ protected: virtual void onMarkedForRemoval() {} ServerEnvironment *m_env; - v3f m_base_position; std::unordered_set m_attached_particle_spawners; /* @@ -273,4 +272,7 @@ protected: Queue of messages to be sent to the client */ std::queue m_messages_out; + +private: + v3f m_base_position; // setBasePosition updates index and MUST be called }; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 697b7b073..55306ee59 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -6,6 +6,7 @@ #include #include #include "serverenvironment.h" +#include "irr_aabb3d.h" #include "settings.h" #include "log.h" #include "mapblock.h" @@ -1399,10 +1400,14 @@ void ServerEnvironment::getSelectedActiveObjects( return false; }; + aabb3f search_area(shootline_on_map.start, shootline_on_map.end); + search_area.repair(); + search_area.MinEdge -= 5 * BS; + search_area.MaxEdge += 5 * BS; + // Use "logic in callback" pattern to avoid useless vector filling std::vector tmp; - getObjectsInsideRadius(tmp, shootline_on_map.getMiddle(), - 0.5 * shootline_on_map.getLength() + 5 * BS, process); + getObjectsInArea(tmp, search_area, process); } /* diff --git a/src/serverenvironment.h b/src/serverenvironment.h index c7396987a..04153e944 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -220,6 +220,11 @@ public: // Find the daylight value at pos with a Depth First Search u8 findSunlight(v3s16 pos) const; + void updateObjectPos(u16 id, v3f pos) + { + return m_ao_manager.updateObjectPos(id, pos); + } + // 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) diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 7417819bd..9ac275d7f 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -10,6 +10,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_craft.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_datastructures.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_k_d_tree.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_filesys.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp diff --git a/src/unittest/test_k_d_tree.cpp b/src/unittest/test_k_d_tree.cpp new file mode 100644 index 000000000..9dbe0b545 --- /dev/null +++ b/src/unittest/test_k_d_tree.cpp @@ -0,0 +1,138 @@ +// Copyright (C) 2024 Lars Müller +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "catch.h" +#include "irrTypes.h" +#include "noise.h" +#include "util/k_d_tree.h" + +#include +#include + +template +class ObjectVector +{ +public: + using Point = std::array; + + void insert(const Point &p, Id id) + { + entries.push_back(Entry{p, id}); + } + + void remove(Id id) + { + const auto it = std::find_if(entries.begin(), entries.end(), [&](const auto &e) { + return e.id == id; + }); + assert(it != entries.end()); + entries.erase(it); + } + + void update(const Point &p, Id id) + { + remove(id); + insert(p, id); + } + + template + void rangeQuery(const Point &min, const Point &max, const F &cb) + { + for (const auto &e : entries) { + for (uint8_t d = 0; d < Dim; ++d) + if (e.point[d] < min[d] || e.point[d] > max[d]) + goto next; + cb(e.point, e.id); // TODO check + next: {} + } + } + +private: + struct Entry { + Point point; + Id id; + }; + std::vector entries; +}; + +TEST_CASE("k-d-tree") { + +SECTION("single update") { + k_d_tree::DynamicKdTrees<3, u16, u16> kds; + for (u16 i = 1; i <= 5; ++i) + kds.insert({i, i, i}, i); + for (u16 i = 1; i <= 5; ++i) { + u16 j = i - 1; + kds.update({j, j, j}, i); + } +} + +SECTION("random operations") { + PseudoRandom pr(Catch::getSeed()); + + ObjectVector<3, f32, u16> objvec; + k_d_tree::DynamicKdTrees<3, f32, u16> kds; + + const auto randPos = [&]() { + std::array point; + for (uint8_t d = 0; d < 3; ++d) + point[d] = pr.range(-1000, 1000); + return point; + }; + + const auto testRandomQuery = [&]() { + std::array min, max; + for (uint8_t d = 0; d < 3; ++d) { + min[d] = pr.range(-1500, 1500); + max[d] = min[d] + pr.range(1, 2500); + } + std::unordered_set expected_ids; + objvec.rangeQuery(min, max, [&](auto _, u16 id) { + CHECK(expected_ids.count(id) == 0); + expected_ids.insert(id); + }); + kds.rangeQuery(min, max, [&](auto point, u16 id) { + CHECK(expected_ids.count(id) == 1); + expected_ids.erase(id); + }); + CHECK(expected_ids.empty()); + }; + + for (u16 id = 1; id < 1000; ++id) { + const auto point = randPos(); + objvec.insert(point, id); + kds.insert(point, id); + testRandomQuery(); + } + + const auto testRandomQueries = [&]() { + for (int i = 0; i < 1000; ++i) { + testRandomQuery(); + } + }; + + testRandomQueries(); + + for (u16 id = 1; id < 800; ++id) { + objvec.remove(id); + kds.remove(id); + } + + testRandomQueries(); + + for (u16 id = 800; id < 1000; ++id) { + const auto point = randPos(); + objvec.update(point, id); + kds.update(point, id); + } + + testRandomQueries(); + + for (u16 id = 800; id < 1000; ++id) { + objvec.remove(id); + kds.remove(id); + testRandomQuery(); + } +} + +} diff --git a/src/unittest/test_serveractiveobjectmgr.cpp b/src/unittest/test_serveractiveobjectmgr.cpp index 0d370b5c5..861f5e06f 100644 --- a/src/unittest/test_serveractiveobjectmgr.cpp +++ b/src/unittest/test_serveractiveobjectmgr.cpp @@ -2,52 +2,162 @@ // SPDX-License-Identifier: LGPL-2.1-or-later // Copyright (C) 2018 nerzhul, Loic Blot -#include "test.h" +#include "activeobjectmgr.h" +#include "catch.h" +#include "irrTypes.h" +#include "irr_aabb3d.h" #include "mock_serveractiveobject.h" #include -#include +#include +#include +#include #include "server/activeobjectmgr.h" +#include "server/serveractiveobject.h" -#include "profiler.h" +class TestServerActiveObjectMgr { + server::ActiveObjectMgr saomgr; + std::vector ids; - -class TestServerActiveObjectMgr : public TestBase -{ public: - TestServerActiveObjectMgr() { TestManager::registerTestModule(this); } - const char *getName() { return "TestServerActiveObjectMgr"; } - void runTests(IGameDef *gamedef); + u16 getFreeId() const { return saomgr.getFreeId(); } - void testFreeID(); - void testRegisterObject(); - void testRemoveObject(); - void testGetObjectsInsideRadius(); - void testGetAddedActiveObjectsAroundPos(); + bool registerObject(std::unique_ptr obj) + { + auto *ptr = obj.get(); + if (!saomgr.registerObject(std::move(obj))) + return false; + ids.push_back(ptr->getId()); + return true; + } + + void removeObject(u16 id) + { + const auto it = std::find(ids.begin(), ids.end(), id); + REQUIRE(it != ids.end()); + ids.erase(it); + saomgr.removeObject(id); + } + + void updateObjectPos(u16 id, const v3f &pos) + { + auto *obj = saomgr.getActiveObject(id); + REQUIRE(obj != nullptr); + obj->setPos(pos); + saomgr.updateObjectPos(id, pos); // HACK work around m_env == nullptr + } + + void clear() + { + saomgr.clear(); + ids.clear(); + } + + ServerActiveObject *getActiveObject(u16 id) + { + return saomgr.getActiveObject(id); + } + + template + void getObjectsInsideRadius(T&& arg) + { + saomgr.getObjectsInsideRadius(std::forward(arg)); + } + + template + void getAddedActiveObjectsAroundPos(T&& arg) + { + saomgr.getAddedActiveObjectsAroundPos(std::forward(arg)); + } + + // Testing + + bool empty() { return ids.empty(); } + + template + u16 randomId(T &random) + { + REQUIRE(!ids.empty()); + std::uniform_int_distribution index(0, ids.size() - 1); + return ids[index(random)]; + } + + void getObjectsInsideRadiusNaive(const v3f &pos, float radius, + std::vector &result) + { + for (const auto &[id, obj] : saomgr.m_active_objects.iter()) { + if (obj->getBasePosition().getDistanceFromSQ(pos) <= radius * radius) { + result.push_back(obj.get()); + } + } + } + + void getObjectsInAreaNaive(const aabb3f &box, + std::vector &result) + { + for (const auto &[id, obj] : saomgr.m_active_objects.iter()) { + if (box.isPointInside(obj->getBasePosition())) { + result.push_back(obj.get()); + } + } + } + + constexpr static auto compare_by_id = [](auto *sao1, auto *sao2) -> bool { + return sao1->getId() < sao2->getId(); + }; + + static void sortById(std::vector &saos) + { + std::sort(saos.begin(), saos.end(), compare_by_id); + } + + void compareObjects(std::vector &actual, + std::vector &expected) + { + std::vector unexpected, missing; + sortById(actual); + sortById(expected); + + std::set_difference(actual.begin(), actual.end(), + expected.begin(), expected.end(), + std::back_inserter(unexpected), compare_by_id); + + assert(unexpected.empty()); + + std::set_difference(expected.begin(), expected.end(), + actual.begin(), actual.end(), + std::back_inserter(missing), compare_by_id); + assert(missing.empty()); + } + + void compareObjectsInsideRadius(const v3f &pos, float radius) + { + std::vector actual, expected; + saomgr.getObjectsInsideRadius(pos, radius, actual, nullptr); + getObjectsInsideRadiusNaive(pos, radius, expected); + compareObjects(actual, expected); + } + + void compareObjectsInArea(const aabb3f &box) + { + std::vector actual, expected; + saomgr.getObjectsInArea(box, actual, nullptr); + getObjectsInAreaNaive(box, expected); + compareObjects(actual, expected); + } }; -static TestServerActiveObjectMgr g_test_instance; -void TestServerActiveObjectMgr::runTests(IGameDef *gamedef) -{ - TEST(testFreeID); - TEST(testRegisterObject) - TEST(testRemoveObject) - TEST(testGetObjectsInsideRadius); - TEST(testGetAddedActiveObjectsAroundPos); -} +TEST_CASE("server active object manager") { -//////////////////////////////////////////////////////////////////////////////// - -void TestServerActiveObjectMgr::testFreeID() -{ - server::ActiveObjectMgr saomgr; +SECTION("free ID") { + TestServerActiveObjectMgr saomgr; std::vector aoids; u16 aoid = saomgr.getFreeId(); // Ensure it's not the same id - UASSERT(saomgr.getFreeId() != aoid); + REQUIRE(saomgr.getFreeId() != aoid); aoids.push_back(aoid); @@ -60,53 +170,50 @@ void TestServerActiveObjectMgr::testFreeID() aoids.push_back(sao->getId()); // Ensure next id is not in registered list - UASSERT(std::find(aoids.begin(), aoids.end(), saomgr.getFreeId()) == + REQUIRE(std::find(aoids.begin(), aoids.end(), saomgr.getFreeId()) == aoids.end()); } saomgr.clear(); } -void TestServerActiveObjectMgr::testRegisterObject() -{ - server::ActiveObjectMgr saomgr; +SECTION("register object") { + TestServerActiveObjectMgr saomgr; auto sao_u = std::make_unique(); auto sao = sao_u.get(); - UASSERT(saomgr.registerObject(std::move(sao_u))); + REQUIRE(saomgr.registerObject(std::move(sao_u))); u16 id = sao->getId(); auto saoToCompare = saomgr.getActiveObject(id); - UASSERT(saoToCompare->getId() == id); - UASSERT(saoToCompare == sao); + REQUIRE(saoToCompare->getId() == id); + REQUIRE(saoToCompare == sao); sao_u = std::make_unique(); sao = sao_u.get(); - UASSERT(saomgr.registerObject(std::move(sao_u))); - UASSERT(saomgr.getActiveObject(sao->getId()) == sao); - UASSERT(saomgr.getActiveObject(sao->getId()) != saoToCompare); + REQUIRE(saomgr.registerObject(std::move(sao_u))); + REQUIRE(saomgr.getActiveObject(sao->getId()) == sao); + REQUIRE(saomgr.getActiveObject(sao->getId()) != saoToCompare); saomgr.clear(); } -void TestServerActiveObjectMgr::testRemoveObject() -{ - server::ActiveObjectMgr saomgr; +SECTION("remove object") { + TestServerActiveObjectMgr saomgr; auto sao_u = std::make_unique(); auto sao = sao_u.get(); - UASSERT(saomgr.registerObject(std::move(sao_u))); + REQUIRE(saomgr.registerObject(std::move(sao_u))); u16 id = sao->getId(); - UASSERT(saomgr.getActiveObject(id) != nullptr) + REQUIRE(saomgr.getActiveObject(id) != nullptr); saomgr.removeObject(sao->getId()); - UASSERT(saomgr.getActiveObject(id) == nullptr); + REQUIRE(saomgr.getActiveObject(id) == nullptr); saomgr.clear(); } -void TestServerActiveObjectMgr::testGetObjectsInsideRadius() -{ +SECTION("get objects inside radius") { server::ActiveObjectMgr saomgr; static const v3f sao_pos[] = { v3f(10, 40, 10), @@ -122,15 +229,15 @@ void TestServerActiveObjectMgr::testGetObjectsInsideRadius() std::vector result; saomgr.getObjectsInsideRadius(v3f(), 50, result, nullptr); - UASSERTCMP(int, ==, result.size(), 1); + CHECK(result.size() == 1); result.clear(); saomgr.getObjectsInsideRadius(v3f(), 750, result, nullptr); - UASSERTCMP(int, ==, result.size(), 2); + CHECK(result.size() == 2); result.clear(); saomgr.getObjectsInsideRadius(v3f(), 750000, result, nullptr); - UASSERTCMP(int, ==, result.size(), 5); + CHECK(result.size() == 5); result.clear(); auto include_obj_cb = [](ServerActiveObject *obj) { @@ -138,13 +245,12 @@ void TestServerActiveObjectMgr::testGetObjectsInsideRadius() }; saomgr.getObjectsInsideRadius(v3f(), 750000, result, include_obj_cb); - UASSERTCMP(int, ==, result.size(), 4); + CHECK(result.size() == 4); saomgr.clear(); } -void TestServerActiveObjectMgr::testGetAddedActiveObjectsAroundPos() -{ +SECTION("get added active objects around pos") { server::ActiveObjectMgr saomgr; static const v3f sao_pos[] = { v3f(10, 40, 10), @@ -161,12 +267,64 @@ void TestServerActiveObjectMgr::testGetAddedActiveObjectsAroundPos() std::vector result; std::set cur_objects; saomgr.getAddedActiveObjectsAroundPos(v3f(), "singleplayer", 100, 50, cur_objects, result); - UASSERTCMP(int, ==, result.size(), 1); + CHECK(result.size() == 1); result.clear(); cur_objects.clear(); saomgr.getAddedActiveObjectsAroundPos(v3f(), "singleplayer", 740, 50, cur_objects, result); - UASSERTCMP(int, ==, result.size(), 2); + CHECK(result.size() == 2); saomgr.clear(); } + +SECTION("spatial index") { + TestServerActiveObjectMgr saomgr; + std::mt19937 gen(0xABCDEF); + std::uniform_int_distribution coordinate(-1000, 1000); + const auto random_pos = [&]() { + return v3f(coordinate(gen), coordinate(gen), coordinate(gen)); + }; + + std::uniform_int_distribution percent(0, 99); + const auto modify = [&](u32 p_insert, u32 p_delete, u32 p_update) { + const auto p = percent(gen); + if (p < p_insert) { + saomgr.registerObject(std::make_unique(nullptr, random_pos())); + } else if (p < p_insert + p_delete) { + if (!saomgr.empty()) + saomgr.removeObject(saomgr.randomId(gen)); + } else if (p < p_insert + p_delete + p_update) { + if (!saomgr.empty()) + saomgr.updateObjectPos(saomgr.randomId(gen), random_pos()); + } + }; + + const auto test_queries = [&]() { + std::uniform_real_distribution radius(0, 100); + saomgr.compareObjectsInsideRadius(random_pos(), radius(gen)); + + aabb3f box(random_pos(), random_pos()); + box.repair(); + saomgr.compareObjectsInArea(box); + }; + + // Grow: Insertion twice as likely as deletion + for (u32 i = 0; i < 3000; ++i) { + modify(50, 25, 25); + test_queries(); + } + + // Stagnate: Insertion and deletion equally likely + for (u32 i = 0; i < 3000; ++i) { + modify(25, 25, 50); + test_queries(); + } + + // Shrink: Deletion twice as likely as insertion + while (!saomgr.empty()) { + modify(25, 50, 25); + test_queries(); + } +} + +} diff --git a/src/util/k_d_tree.h b/src/util/k_d_tree.h new file mode 100644 index 000000000..f8e266a36 --- /dev/null +++ b/src/util/k_d_tree.h @@ -0,0 +1,515 @@ +// Copyright (C) 2024 Lars Müller +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +/* +This implements a dynamic forest of static k-d-trees. + +A k-d-tree is a k-dimensional binary search tree. +On the i-th level of the tree, you split by the (i mod k)-th coordinate. + +Building a balanced k-d-tree for n points is done in O(n log n) time: +Points are stored in a matrix, identified by indices. +These indices are presorted by all k axes. +To split, you simply pick the pivot index in the appropriate index array, +and mark all points left to it by index in a bitset. +This lets you then split the indices sorted by the other axes, +while preserving the sorted order. + +This however only gives us a static spatial index. +To make it dynamic, we keep a "forest" of k-d-trees of sizes of successive powers of two. +When we insert a new tree, we check whether there already is a k-d-tree of the same size. +If this is the case, we merge with that tree, giving us a tree of twice the size, +and so on, until we find a free size. + +This means our "forest" corresponds to a bit pattern, +where a set bit means a non-empty tree. +Inserting a point is equivalent to incrementing this bit pattern. + +To handle deletions, we simply mark the appropriate point as deleted using another bitset. +When more than half the points have been deleted, +we shrink the structure by removing all deleted points. +This is equivalent to shifting down the "bit pattern" by one. + +There are plenty variations that could be explored: + +* Keeping a small amount of points in a small pool to make updates faster - + avoid building and querying small k-d-trees. + This might be useful if the overhead for small sizes hurts performance. +* Keeping fewer trees to make queries faster, at the expense of updates. +* More eagerly removing entries marked as deleted (for example, on merge). +* Replacing the array-backed structure with a structure of dynamically allocated nodes. + This would make it possible to "let trees get out of shape". +* Shrinking the structure currently sorts the live points by all axes, + not leveraging the existing presorting of the subsets. + Cleverly done filtering followed by sorted merges should enable linear time. +* A special ray proximity query could be implemented. This is tricky however. +*/ + +namespace k_d_tree +{ + +using Idx = uint16_t; + +// We use size_t for sizes (but not for indices) +// to make sure there are no wraparounds when we approach the limit. +// This hardly affects performance or memory usage; +// the core arrays still only store indices. + +template +class Points +{ +public: + using Point = std::array; + //! Empty + Points() : n(0), coords(nullptr) {} + //! Allocating constructor; leaves coords uninitialized! + Points(size_t n) : n(n), coords(new Component[Dim * n]) {} + //! Copying constructor + Points(size_t n, const std::array &coords) + : Points(n) + { + for (uint8_t d = 0; d < Dim; ++d) + std::copy(coords[d], coords[d] + n, begin(d)); + } + + size_t size() const { return n; } + + void assign(Idx start, const Points &from) + { + for (uint8_t d = 0; d < Dim; ++d) + std::copy(from.begin(d), from.end(d), begin(d) + start); + } + + Point getPoint(Idx i) const + { + Point point; + for (uint8_t d = 0; d < Dim; ++d) + point[d] = begin(d)[i]; + return point; + } + + void setPoint(Idx i, const Point &point) + { + for (uint8_t d = 0; d < Dim; ++d) + begin(d)[i] = point[d]; + } + + Component *begin(uint8_t d) { return coords.get() + d * n; } + Component *end(uint8_t d) { return begin(d) + n; } + const Component *begin(uint8_t d) const { return coords.get() + d * n; } + const Component *end(uint8_t d) const { return begin(d) + n; } + +private: + size_t n; + std::unique_ptr coords; +}; + +template +class SortedIndices +{ +public: + //! empty + SortedIndices() : indices() {} + + //! uninitialized indices + static SortedIndices newUninitialized(size_t n) + { + return SortedIndices(Points(n)); + } + + //! Identity permutation on all axes + SortedIndices(size_t n) + : indices(n) + { + for (uint8_t d = 0; d < Dim; ++d) { + for (Idx i = 0; i < n; ++i) { + indices.begin(d)[i] = i; + } + } + } + + size_t size() const { return indices.size(); } + bool empty() const { return size() == 0; } + + struct SplitResult { + SortedIndices left, right; + Idx pivot; + }; + + //! Splits the sorted indices in the middle along the specified axis, + //! partitioning them into left (<=), the pivot, and right (>=). + SplitResult split(uint8_t axis, std::vector &markers) const + { + const auto begin = indices.begin(axis); + Idx left_n = indices.size() / 2; + const auto mid = begin + left_n; + + // Mark all points to be partitioned left + for (auto it = begin; it != mid; ++it) + markers[*it] = true; + + SortedIndices left(left_n); + std::copy(begin, mid, left.indices.begin(axis)); + SortedIndices right(indices.size() - left_n - 1); + std::copy(mid + 1, indices.end(axis), right.indices.begin(axis)); + + for (uint8_t d = 0; d < Dim; ++d) { + if (d == axis) + continue; + auto left_ptr = left.indices.begin(d); + auto right_ptr = right.indices.begin(d); + for (auto it = indices.begin(d); it != indices.end(d); ++it) { + if (*it != *mid) { // ignore pivot + if (markers[*it]) + *(left_ptr++) = *it; + else + *(right_ptr++) = *it; + } + } + } + + // Unmark points, since we want to reuse the storage for markers + for (auto it = begin; it != mid; ++it) + markers[*it] = false; + + return SplitResult{std::move(left), std::move(right), *mid}; + } + + Idx *begin(uint8_t d) { return indices.begin(d); } + Idx *end(uint8_t d) { return indices.end(d); } + const Idx *begin(uint8_t d) const { return indices.begin(d); } + const Idx *end(uint8_t d) const { return indices.end(d); } +private: + SortedIndices(Points &&indices) : indices(std::move(indices)) {} + Points indices; +}; + +template +class SortedPoints +{ +public: + SortedPoints() : points(), indices() {} + + //! Single point + SortedPoints(const std::array &point) + : points(1), indices(1) + { + points.setPoint(0, point); + } + + //! Sort points + SortedPoints(size_t n, const std::array ptrs) + : points(n, ptrs), indices(n) + { + for (uint8_t d = 0; d < Dim; ++d) { + const auto coord = points.begin(d); + std::sort(indices.begin(d), indices.end(d), [&](auto i, auto j) { + return coord[i] < coord[j]; + }); + } + } + + //! Merge two sets of sorted points + SortedPoints(const SortedPoints &a, const SortedPoints &b) + : points(a.size() + b.size()) + { + const auto n = points.size(); + indices = SortedIndices::newUninitialized(n); + for (uint8_t d = 0; d < Dim; ++d) { + points.assign(0, a.points); + points.assign(a.points.size(), b.points); + const auto coord = points.begin(d); + auto a_ptr = a.indices.begin(d); + auto b_ptr = b.indices.begin(d); + auto dst_ptr = indices.begin(d); + while (a_ptr != a.indices.end(d) && b_ptr != b.indices.end(d)) { + const auto i = *a_ptr; + const auto j = *b_ptr + a.size(); + if (coord[i] <= coord[j]) { + *(dst_ptr++) = i; + ++a_ptr; + } else { + *(dst_ptr++) = j; + ++b_ptr; + } + } + while (a_ptr != a.indices.end(d)) + *(dst_ptr++) = *(a_ptr++); + while (b_ptr != b.indices.end(d)) + *(dst_ptr++) = a.size() + *(b_ptr++); + } + } + + size_t size() const + { + // technically redundant with indices.size(), + // but that is irrelevant + return points.size(); + } + + Points points; + SortedIndices indices; +}; + +template +class KdTree +{ +public: + using Point = std::array; + + //! Empty tree + KdTree() + : items() + , ids(nullptr) + , tree(nullptr) + , deleted() + {} + + //! Build a tree containing just a single point + KdTree(const Point &point, const Id &id) + : items(point) + , ids(std::make_unique(1)) + , tree(std::make_unique(1)) + , deleted(1) + { + tree[0] = 0; + ids[0] = id; + } + + //! Build a tree + KdTree(size_t n, Id const *ids, std::array pts) + : items(n, pts) + , ids(std::make_unique(n)) + , tree(std::make_unique(n)) + , deleted(n) + { + std::copy(ids, ids + n, this->ids.get()); + init(0, 0, items.indices); + } + + //! Merge two trees. Both trees are assumed to have a power of two size. + KdTree(const KdTree &a, const KdTree &b) + : items(a.items, b.items) + { + tree = std::make_unique(cap()); + ids = std::make_unique(cap()); + std::copy(a.ids.get(), a.ids.get() + a.cap(), ids.get()); + std::copy(b.ids.get(), b.ids.get() + b.cap(), ids.get() + a.cap()); + // Note: Initialize `deleted` *before* calling `init`, + // since `init` abuses the `deleted` marks as left/right marks. + deleted = std::vector(cap()); + init(0, 0, items.indices); + std::copy(a.deleted.begin(), a.deleted.end(), deleted.begin()); + std::copy(b.deleted.begin(), b.deleted.end(), deleted.begin() + a.items.size()); + } + + template + void rangeQuery(const Point &min, const Point &max, + const F &cb) const + { + rangeQuery(0, 0, min, max, cb); + } + + void remove(Idx internalIdx) + { + assert(!deleted[internalIdx]); + deleted[internalIdx] = true; + } + + template + void foreach(F cb) const + { + for (Idx i = 0; i < cap(); ++i) { + if (!deleted[i]) { + cb(i, items.points.getPoint(i), ids[i]); + } + } + } + + //! Capacity, not size, since some items may be marked as deleted + size_t cap() const { return items.size(); } + +private: + void init(Idx root, uint8_t axis, const SortedIndices &sorted) + { + // Temporarily abuse "deleted" marks as left/right marks + const auto split = sorted.split(axis, deleted); + tree[root] = split.pivot; + const auto next_axis = (axis + 1) % Dim; + if (!split.left.empty()) + init(2 * root + 1, next_axis, split.left); + if (!split.right.empty()) + init(2 * root + 2, next_axis, split.right); + } + + template + // Note: root is of type size_t to avoid issues with wraparound + void rangeQuery(size_t root, uint8_t split, + const Point &min, const Point &max, + const F &cb) const + { + if (root >= cap()) + return; + const auto ptid = tree[root]; + const auto coord = items.points.begin(split)[ptid]; + const auto leftChild = 2*root + 1; + const auto rightChild = 2*root + 2; + const auto nextSplit = (split + 1) % Dim; + if (min[split] > coord) { + rangeQuery(rightChild, nextSplit, min, max, cb); + } else if (max[split] < coord) { + rangeQuery(leftChild, nextSplit, min, max, cb); + } else { + rangeQuery(rightChild, nextSplit, min, max, cb); + rangeQuery(leftChild, nextSplit, min, max, cb); + if (deleted[ptid]) + return; + const auto point = items.points.getPoint(ptid); + for (uint8_t d = 0; d < Dim; ++d) + if (point[d] < min[d] || point[d] > max[d]) + return; + cb(point, ids[ptid]); + } + } + SortedPoints items; + std::unique_ptr ids; + std::unique_ptr tree; + std::vector deleted; +}; + +template +class DynamicKdTrees +{ + using Tree = KdTree; + +public: + using Point = typename Tree::Point; + + void insert(const std::array &point, Id id) + { + Tree tree(point, id); + for (uint8_t tree_idx = 0;; ++tree_idx) { + if (tree_idx == trees.size()) { + trees.push_back(std::move(tree)); + updateDelEntries(tree_idx); + break; + } + // Can we use a free slot to "plant" the tree? + if (trees[tree_idx].cap() == 0) { + trees[tree_idx] = std::move(tree); + updateDelEntries(tree_idx); + break; + } + tree = Tree(tree, trees[tree_idx]); + trees[tree_idx] = std::move(Tree()); + } + ++n_entries; + } + + void remove(Id id) + { + const auto it = del_entries.find(id); + assert(it != del_entries.end()); + trees.at(it->second.tree_idx).remove(it->second.in_tree); + del_entries.erase(it); + ++deleted; + if (deleted >= (n_entries+1)/2) // "shift out" the last tree + shrink_to_half(); + } + + void update(const Point &newPos, Id id) + { + remove(id); + insert(newPos, id); + } + + template + void rangeQuery(const Point &min, const Point &max, + const F &cb) const + { + for (const auto &tree : trees) + tree.rangeQuery(min, max, cb); + } + + size_t size() const + { + return n_entries - deleted; + } + +private: + + void updateDelEntries(uint8_t tree_idx) + { + trees[tree_idx].foreach([&](Idx in_tree_idx, auto _, Id id) { + del_entries[id] = {tree_idx, in_tree_idx}; + }); + } + + // Shrink to half the size, equivalent to shifting down the "bit pattern". + void shrink_to_half() + { + assert(n_entries >= deleted); + assert(n_entries - deleted == (n_entries >> 1)); + n_entries -= deleted; + deleted = 0; + // Reset map, freeing memory (instead of clearing) + del_entries = std::unordered_map(); + + // Collect all live points and corresponding IDs. + const auto live_ids = std::make_unique(n_entries); + Points live_points(n_entries); + size_t i = 0; + for (const auto &tree : trees) { + tree.foreach([&](Idx _, auto point, Id id) { + assert(i < n_entries); + live_points.setPoint(static_cast(i), point); + live_ids[i] = id; + ++i; + }); + } + assert(i == n_entries); + + // Construct a new forest. + // The "tree pattern" will effectively just be shifted down by one. + auto id_ptr = live_ids.get(); + std::array point_ptrs; + size_t n = 1; + for (uint8_t d = 0; d < Dim; ++d) + point_ptrs[d] = live_points.begin(d); + for (uint8_t tree_idx = 0; tree_idx < trees.size() - 1; ++tree_idx, n *= 2) { + Tree tree; + // If there was a tree at the next position, there should be + // a tree at this position after shifting the pattern. + if (trees[tree_idx+1].cap() > 0) { + tree = std::move(Tree(n, id_ptr, point_ptrs)); + id_ptr += n; + for (uint8_t d = 0; d < Dim; ++d) + point_ptrs[d] += n; + } + trees[tree_idx] = std::move(tree); + updateDelEntries(tree_idx); + } + trees.pop_back(); // "shift out" tree with the most elements + } + // This could even use an array instead of a vector, + // since the number of trees is guaranteed to be logarithmic in the max of Idx + std::vector trees; + struct DelEntry { + uint8_t tree_idx; + Idx in_tree; + }; + std::unordered_map del_entries; + size_t n_entries = 0; + size_t deleted = 0; +}; + +} // end namespace k_d_tree \ No newline at end of file From 7689f1f0fd6a954ac061e683a332e3555f72c152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:24:00 +0200 Subject: [PATCH 117/284] Improve some warning messages (#15990) --- builtin/game/register.lua | 15 ++++++++++----- src/script/lua_api/l_mainmenu.cpp | 3 ++- src/script/lua_api/l_util.cpp | 8 +++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index dc9dcfb0e..d6ada6920 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -156,7 +156,8 @@ local function preprocess_craft(itemdef) -- BEGIN Legacy stuff if itemdef.inventory_image == nil and itemdef.image ~= nil then core.log("deprecated", "The `image` field in craftitem definitions " .. - "is deprecated. Use `inventory_image` instead.") + "is deprecated. Use `inventory_image` instead. " .. + "Craftitem name: " .. itemdef.name, 3) itemdef.inventory_image = itemdef.image end -- END Legacy stuff @@ -168,7 +169,8 @@ local function preprocess_tool(tooldef) -- BEGIN Legacy stuff if tooldef.inventory_image == nil and tooldef.image ~= nil then core.log("deprecated", "The `image` field in tool definitions " .. - "is deprecated. Use `inventory_image` instead.") + "is deprecated. Use `inventory_image` instead. " .. + "Tool name: " .. tooldef.name, 3) tooldef.inventory_image = tooldef.image end @@ -185,7 +187,8 @@ local function preprocess_tool(tooldef) tooldef.dd_crumbliness ~= nil or tooldef.dd_cuttability ~= nil) then core.log("deprecated", "Specifying tool capabilities directly in the tool " .. - "definition is deprecated. Use the `tool_capabilities` field instead.") + "definition is deprecated. Use the `tool_capabilities` field instead. " .. + "Tool name: " .. tooldef.name, 3) tooldef.tool_capabilities = { full_punch_interval = tooldef.full_punch_interval, basetime = tooldef.basetime, @@ -269,7 +272,8 @@ function core.register_item(name, itemdef) -- BEGIN Legacy stuff if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then core.log("deprecated", "The `cookresult_itemstring` item definition " .. - "field is deprecated. Use `core.register_craft` instead.") + "field is deprecated. Use `core.register_craft` instead. " .. + "Item name: " .. itemdef.name, 2) core.register_craft({ type="cooking", output=itemdef.cookresult_itemstring, @@ -279,7 +283,8 @@ function core.register_item(name, itemdef) end if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then core.log("deprecated", "The `furnace_burntime` item definition " .. - "field is deprecated. Use `core.register_craft` instead.") + "field is deprecated. Use `core.register_craft` instead. " .. + "Item name: " .. itemdef.name, 2) core.register_craft({ type="fuel", recipe=itemdef.name, diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index f6734c788..7070952e6 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -364,7 +364,8 @@ int ModApiMainMenu::l_get_content_info(lua_State *L) // being able to return type "unknown". // TODO inspect call sites and make sure this is handled, then we can // likely remove the warning. - warningstream << "Requested content info has type \"unknown\"" << std::endl; + warningstream << "Requested content info has type \"unknown\" " + << "(at " << path << ")" << std::endl; } lua_newtable(L); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index 86d3de316..5ac290b2e 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -54,7 +54,13 @@ int ModApiUtil::l_log(lua_State *L) auto name = readParam(L, 1); text = readParam(L, 2); if (name == "deprecated") { - log_deprecated(L, text, 2); + // core.log("deprecated", message [, stack_level]) + // Level 1 - immediate caller of core.log (probably engine code); + // Level 2 - caller of the function that called core.log, and so on + int stack_level = readParam(L, 3, 2); + if (stack_level < 1) + throw LuaError("invalid stack level"); + log_deprecated(L, text, stack_level); return 0; } level = Logger::stringToLevel(name); From 03affa1bbb9211011350a4a8f3ece1ed8963ee2b Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 31 Mar 2025 18:01:51 +0200 Subject: [PATCH 118/284] Some minor code cleanups --- .../shaders/nodes_shader/opengl_vertex.glsl | 2 - irr/include/IImage.h | 1 - irr/include/ITexture.h | 40 +------------------ irr/include/vector3d.h | 20 ---------- irr/src/CGUIFont.cpp | 4 +- irr/src/CImage.cpp | 5 ++- irr/src/CNullDriver.cpp | 16 ++------ irr/src/COpenGLCoreTexture.h | 20 ++++------ irr/src/OpenGL3/Driver.cpp | 3 +- irr/src/OpenGLES2/Driver.cpp | 2 +- src/client/content_mapblock.cpp | 2 +- src/client/mapblock_mesh.cpp | 2 +- src/client/meshgen/collector.cpp | 2 +- src/irrlicht_changes/CGUITTFont.cpp | 2 + src/irrlicht_changes/CGUITTFont.h | 6 ++- 15 files changed, 29 insertions(+), 98 deletions(-) diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl index 0f508dc6a..6fe7acd85 100644 --- a/client/shaders/nodes_shader/opengl_vertex.glsl +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -44,8 +44,6 @@ centroid varying float nightRatio; varying float perspective_factor; #endif -varying float area_enable_parallax; - varying highp vec3 eyeVec; // Color of the light emitted by the light sources. const vec3 artificialLight = vec3(1.04, 1.04, 1.04); diff --git a/irr/include/IImage.h b/irr/include/IImage.h index a303201a9..a0adae506 100644 --- a/irr/include/IImage.h +++ b/irr/include/IImage.h @@ -56,7 +56,6 @@ public: //! Returns bits per pixel. u32 getBitsPerPixel() const { - return getBitsPerPixelFromFormat(Format); } diff --git a/irr/include/ITexture.h b/irr/include/ITexture.h index 869a325e0..fec69e4b1 100644 --- a/irr/include/ITexture.h +++ b/irr/include/ITexture.h @@ -68,30 +68,12 @@ enum E_TEXTURE_CREATION_FLAG not recommended to enable this flag. */ ETCF_NO_ALPHA_CHANNEL = 0x00000020, - //! Allow the Driver to use Non-Power-2-Textures - /** BurningVideo can handle Non-Power-2 Textures in 2D (GUI), but not in 3D. */ - ETCF_ALLOW_NON_POWER_2 = 0x00000040, - //! Allow the driver to keep a copy of the texture in memory /** Enabling this makes calls to ITexture::lock a lot faster, but costs main memory. This is disabled by default. */ ETCF_ALLOW_MEMORY_COPY = 0x00000080, - //! Enable automatic updating mip maps when the base texture changes. - /** Default is true. - This flag is only used when ETCF_CREATE_MIP_MAPS is also enabled and if the driver supports it. - Please note: - - On D3D (and maybe older OGL?) you can no longer manually set mipmap data when enabled - (for example mips from image loading will be ignored). - - On D3D (and maybe older OGL?) texture locking for mipmap levels usually won't work anymore. - - On new OGL this flag is ignored. - - When disabled you do _not_ get hardware mipmaps on D3D, so mipmap generation can be slower. - - When disabled you can still update your mipmaps when the texture changed by manually calling regenerateMipMapLevels. - - You can still call regenerateMipMapLevels when this flag is enabled (it will be a hint on d3d to update mips immediately) - */ - ETCF_AUTO_GENERATE_MIP_MAPS = 0x00000100, - /** This flag is never used, it only forces the compiler to compile these enumeration values to 32 bit. */ ETCF_FORCE_32_BIT_DO_NOT_USE = 0x7fffffff @@ -137,19 +119,6 @@ enum E_TEXTURE_LOCK_FLAGS ETLF_FLIP_Y_UP_RTT = 1 }; -//! Where did the last IVideoDriver::getTexture call find this texture -enum E_TEXTURE_SOURCE -{ - //! IVideoDriver::getTexture was never called (texture created otherwise) - ETS_UNKNOWN, - - //! Texture has been found in cache - ETS_FROM_CACHE, - - //! Texture had to be loaded - ETS_FROM_FILE -}; - //! Enumeration describing the type of ITexture. enum E_TEXTURE_TYPE { @@ -178,7 +147,7 @@ public: //! constructor ITexture(const io::path &name, E_TEXTURE_TYPE type) : NamedPath(name), DriverType(EDT_NULL), OriginalColorFormat(ECF_UNKNOWN), - ColorFormat(ECF_UNKNOWN), Pitch(0), HasMipMaps(false), IsRenderTarget(false), Source(ETS_UNKNOWN), Type(type) + ColorFormat(ECF_UNKNOWN), Pitch(0), HasMipMaps(false), IsRenderTarget(false), Type(type) { } @@ -275,12 +244,6 @@ public: //! Get name of texture (in most cases this is the filename) const io::SNamedPath &getName() const { return NamedPath; } - //! Check where the last IVideoDriver::getTexture found this texture - E_TEXTURE_SOURCE getSource() const { return Source; } - - //! Used internally by the engine to update Source status on IVideoDriver::getTexture calls. - void updateSource(E_TEXTURE_SOURCE source) { Source = source; } - //! Returns if the texture has an alpha channel bool hasAlpha() const { @@ -329,7 +292,6 @@ protected: u32 Pitch; bool HasMipMaps; bool IsRenderTarget; - E_TEXTURE_SOURCE Source; E_TEXTURE_TYPE Type; }; diff --git a/irr/include/vector3d.h b/irr/include/vector3d.h index 562efb2d6..9bacf977e 100644 --- a/irr/include/vector3d.h +++ b/irr/include/vector3d.h @@ -464,26 +464,6 @@ public: forwards.Z * pseudoMatrix[8])); } - //! Fills an array of 4 values with the vector data (usually floats). - /** Useful for setting in shader constants for example. The fourth value - will always be 0. */ - void getAs4Values(T *array) const - { - array[0] = X; - array[1] = Y; - array[2] = Z; - array[3] = 0; - } - - //! Fills an array of 3 values with the vector data (usually floats). - /** Useful for setting in shader constants for example.*/ - void getAs3Values(T *array) const - { - array[0] = X; - array[1] = Y; - array[2] = Z; - } - //! X coordinate of the vector T X; diff --git a/irr/src/CGUIFont.cpp b/irr/src/CGUIFont.cpp index 951304476..9d8b1d488 100644 --- a/irr/src/CGUIFont.cpp +++ b/irr/src/CGUIFont.cpp @@ -71,18 +71,16 @@ void CGUIFont::setMaxHeight() void CGUIFont::pushTextureCreationFlags(bool (&flags)[3]) { - flags[0] = Driver->getTextureCreationFlag(video::ETCF_ALLOW_NON_POWER_2); + flags[0] = false; flags[1] = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); flags[2] = Driver->getTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY); - Driver->setTextureCreationFlag(video::ETCF_ALLOW_NON_POWER_2, true); Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); Driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, true); } void CGUIFont::popTextureCreationFlags(const bool (&flags)[3]) { - Driver->setTextureCreationFlag(video::ETCF_ALLOW_NON_POWER_2, flags[0]); Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flags[1]); Driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, flags[2]); } diff --git a/irr/src/CImage.cpp b/irr/src/CImage.cpp index 0c6d705f3..de4df0c0f 100644 --- a/irr/src/CImage.cpp +++ b/irr/src/CImage.cpp @@ -20,7 +20,10 @@ CImage::CImage(ECOLOR_FORMAT format, const core::dimension2d &size, void *d IImage(format, size, deleteMemory) { if (ownForeignMemory) { - Data = (u8 *)data; + _IRR_DEBUG_BREAK_IF(!data) + Data = reinterpret_cast(data); + if (reinterpret_cast(data) % sizeof(u32) != 0) + os::Printer::log("CImage created with foreign memory that's not aligned", ELL_WARNING); } else { const u32 dataSize = getDataSizeFromFormat(Format, Size.Width, Size.Height); const u32 allocSize = align_next(dataSize, 16); diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 6f44fc4e6..59487c5b0 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -71,7 +71,6 @@ CNullDriver::CNullDriver(io::IFileSystem *io, const core::dimension2d &scre setTextureCreationFlag(ETCF_ALWAYS_32_BIT, true); setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, true); - setTextureCreationFlag(ETCF_AUTO_GENERATE_MIP_MAPS, true); setTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY, false); ViewPort = core::rect(core::position2d(0, 0), core::dimension2di(screenSize)); @@ -406,17 +405,13 @@ ITexture *CNullDriver::getTexture(const io::path &filename) const io::path absolutePath = FileSystem->getAbsolutePath(filename); ITexture *texture = findTexture(absolutePath); - if (texture) { - texture->updateSource(ETS_FROM_CACHE); + if (texture) return texture; - } // Then try the raw filename, which might be in an Archive texture = findTexture(filename); - if (texture) { - texture->updateSource(ETS_FROM_CACHE); + if (texture) return texture; - } // Now try to open the file using the complete path. io::IReadFile *file = FileSystem->createAndOpenFile(absolutePath); @@ -430,7 +425,6 @@ ITexture *CNullDriver::getTexture(const io::path &filename) // Re-check name for actual archive names texture = findTexture(file->getFileName()); if (texture) { - texture->updateSource(ETS_FROM_CACHE); file->drop(); return texture; } @@ -439,7 +433,6 @@ ITexture *CNullDriver::getTexture(const io::path &filename) file->drop(); if (texture) { - texture->updateSource(ETS_FROM_FILE); addTexture(texture); texture->drop(); // drop it because we created it, one grab too much } else @@ -459,15 +452,12 @@ ITexture *CNullDriver::getTexture(io::IReadFile *file) if (file) { texture = findTexture(file->getFileName()); - if (texture) { - texture->updateSource(ETS_FROM_CACHE); + if (texture) return texture; - } texture = loadTextureFromFile(file); if (texture) { - texture->updateSource(ETS_FROM_FILE); addTexture(texture); texture->drop(); // drop it because we created it, one grab too much } diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 1b02c9234..506d078cb 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -51,7 +51,7 @@ public: _IRR_DEBUG_BREAK_IF(srcImages.empty()) DriverType = Driver->getDriverType(); - _IRR_DEBUG_BREAK_IF(Type == ETT_2D_MS); // not supported by this constructor + _IRR_DEBUG_BREAK_IF(Type == ETT_2D_MS) // not supported by this constructor TextureType = TextureTypeIrrToGL(Type); HasMipMaps = Driver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); KeepImage = Driver->getTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY); @@ -60,7 +60,6 @@ public: if (!InternalFormat) return; -#ifdef _DEBUG char lbuf[128]; snprintf_irr(lbuf, sizeof(lbuf), "COpenGLCoreTexture: Type = %d Size = %dx%d (%dx%d) ColorFormat = %d (%d)%s -> %#06x %#06x %#06x%s", @@ -70,7 +69,6 @@ public: InternalFormat, PixelFormat, PixelType, Converter ? " (c)" : "" ); os::Printer::log(lbuf, ELL_DEBUG); -#endif const auto *tmpImages = &srcImages; @@ -111,7 +109,6 @@ public: GL.TexParameteri(TextureType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); GL.TexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -#ifdef GL_GENERATE_MIPMAP_HINT if (HasMipMaps) { if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED)) GL.Hint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST); @@ -120,8 +117,6 @@ public: else GL.Hint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); } -#endif - TEST_GL_ERROR(Driver); for (size_t i = 0; i < tmpImages->size(); ++i) @@ -189,7 +184,6 @@ public: } #endif -#ifdef _DEBUG char lbuf[100]; snprintf_irr(lbuf, sizeof(lbuf), "COpenGLCoreTexture: RTT Type = %d Size = %dx%d ColorFormat = %d -> %#06x %#06x %#06x%s", @@ -197,7 +191,6 @@ public: InternalFormat, PixelFormat, PixelType, Converter ? " (c)" : "" ); os::Printer::log(lbuf, ELL_DEBUG); -#endif GL.GenTextures(1, &TextureName); TEST_GL_ERROR(Driver); @@ -218,10 +211,7 @@ public: GL.TexParameteri(TextureType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - -#if defined(GL_VERSION_1_2) GL.TexParameteri(TextureType, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); -#endif StatesCache.WrapU = ETC_CLAMP_TO_EDGE; StatesCache.WrapV = ETC_CLAMP_TO_EDGE; @@ -258,6 +248,9 @@ public: GL.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); GL.TexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); break; + default: + _IRR_DEBUG_BREAK_IF(1) + break; } if (!name.empty()) @@ -306,6 +299,7 @@ public: if (!LockImage) { core::dimension2d lockImageSize(IImage::getMipMapsSize(Size, MipLevelStored)); + _IRR_DEBUG_BREAK_IF(lockImageSize.Width == 0 || lockImageSize.Height == 0) // note: we save mipmap data also in the image because IImage doesn't allow saving single mipmap levels to the mipmap data LockImage = Driver->createImage(ColorFormat, lockImageSize); @@ -321,7 +315,7 @@ public: if (use_gl_impl) { - IImage *tmpImage = LockImage; // not sure yet if the size required by glGetTexImage is always correct, if not we might have to allocate a different tmpImage and convert colors later on. + IImage *tmpImage = LockImage; Driver->getCacheHandler()->getTextureCache().set(0, this); TEST_GL_ERROR(Driver); @@ -620,6 +614,7 @@ protected: TEST_GL_ERROR(Driver); break; default: + _IRR_DEBUG_BREAK_IF(1) break; } @@ -637,6 +632,7 @@ protected: TEST_GL_ERROR(Driver); break; default: + _IRR_DEBUG_BREAK_IF(1) break; } } diff --git a/irr/src/OpenGL3/Driver.cpp b/irr/src/OpenGL3/Driver.cpp index 7a62f4a12..43ee9ba45 100644 --- a/irr/src/OpenGL3/Driver.cpp +++ b/irr/src/OpenGL3/Driver.cpp @@ -50,8 +50,7 @@ void COpenGL3Driver::initFeatures() TextureFormats[ECF_A1R5G5B5] = {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}; // WARNING: may not be renderable TextureFormats[ECF_R5G6B5] = {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}; // GL_RGB565 is an extension until 4.1 TextureFormats[ECF_R8G8B8] = {GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE}; // WARNING: may not be renderable - // FIXME: shouldn't this simply be GL_UNSIGNED_BYTE? - TextureFormats[ECF_A8R8G8B8] = {GL_RGBA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV}; + TextureFormats[ECF_A8R8G8B8] = {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}; TextureFormats[ECF_R16F] = {GL_R16F, GL_RED, GL_HALF_FLOAT}; TextureFormats[ECF_G16R16F] = {GL_RG16F, GL_RG, GL_HALF_FLOAT}; TextureFormats[ECF_A16B16G16R16F] = {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}; diff --git a/irr/src/OpenGLES2/Driver.cpp b/irr/src/OpenGLES2/Driver.cpp index 6b234842a..7c98fca8d 100644 --- a/irr/src/OpenGLES2/Driver.cpp +++ b/irr/src/OpenGLES2/Driver.cpp @@ -146,7 +146,7 @@ void COpenGLES2Driver::initFeatures() MaxTextureSize = GetInteger(GL_MAX_TEXTURE_SIZE); if (LODBiasSupported) GL.GetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &MaxTextureLODBias); - GL.GetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, DimAliasedLine); // NOTE: this is not in the OpenGL ES 2.0 spec... + GL.GetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, DimAliasedLine); GL.GetFloatv(GL_ALIASED_POINT_SIZE_RANGE, DimAliasedPoint); } diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 71068d13e..f5b528287 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -103,7 +103,7 @@ void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile_ret, bool a for (auto &layernum : tile_ret->layers) { TileLayer *layer = &layernum; - if (layer->texture_id == 0) + if (layer->empty()) continue; top_layer = layer; if (!layer->has_color) diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index a53a5c073..a84747188 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -347,7 +347,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, tile = f.tiles[tileindex]; bool has_crack = p == data->m_crack_pos_relative; for (TileLayer &layer : tile.layers) { - if (layer.texture_id == 0) + if (layer.empty()) continue; if (!layer.has_color) mn.getColor(f, &(layer.color)); diff --git a/src/client/meshgen/collector.cpp b/src/client/meshgen/collector.cpp index 5a4fb8489..c8b726cde 100644 --- a/src/client/meshgen/collector.cpp +++ b/src/client/meshgen/collector.cpp @@ -12,7 +12,7 @@ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertice { for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { const TileLayer *layer = &tile.layers[layernum]; - if (layer->texture_id == 0) + if (layer->empty()) continue; append(*layer, vertices, numVertices, indices, numIndices, layernum, tile.world_aligned); diff --git a/src/irrlicht_changes/CGUITTFont.cpp b/src/irrlicht_changes/CGUITTFont.cpp index 4c07da140..ab5aeb091 100644 --- a/src/irrlicht_changes/CGUITTFont.cpp +++ b/src/irrlicht_changes/CGUITTFont.cpp @@ -897,6 +897,8 @@ video::IImage* CGUITTFont::createTextureFromChar(const char32_t& ch) // Acquire a read-only lock of the corresponding page texture. void* ptr = tex->lock(video::ETLM_READ_ONLY); + if (!ptr) + return nullptr; video::ECOLOR_FORMAT format = tex->getColorFormat(); core::dimension2du tex_size = tex->getOriginalSize(); diff --git a/src/irrlicht_changes/CGUITTFont.h b/src/irrlicht_changes/CGUITTFont.h index ffdd6a6ca..082942856 100644 --- a/src/irrlicht_changes/CGUITTFont.h +++ b/src/irrlicht_changes/CGUITTFont.h @@ -200,9 +200,13 @@ namespace gui //! Updates the texture atlas with new glyphs. void updateTexture() { - if (!dirty) return; + if (!dirty) + return; void* ptr = texture->lock(); + if (!ptr) + return; + video::ECOLOR_FORMAT format = texture->getColorFormat(); core::dimension2du size = texture->getOriginalSize(); video::IImage* pageholder = driver->createImageFromData(format, size, ptr, true, false); From 38c3876c4ef79f310e445cba06af804b1db52fb0 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 6 Apr 2025 17:47:18 +0200 Subject: [PATCH 119/284] Drop support for storing mipmap data alongside IImage --- irr/include/IImage.h | 99 ++---------------------------------- irr/include/ITexture.h | 11 +--- irr/src/CNullDriver.h | 2 +- irr/src/COpenGLCoreTexture.h | 71 +++++--------------------- 4 files changed, 19 insertions(+), 164 deletions(-) diff --git a/irr/include/IImage.h b/irr/include/IImage.h index a0adae506..ef8f6934b 100644 --- a/irr/include/IImage.h +++ b/irr/include/IImage.h @@ -25,7 +25,7 @@ class IImage : public virtual IReferenceCounted public: //! constructor IImage(ECOLOR_FORMAT format, const core::dimension2d &size, bool deleteMemory) : - Format(format), Size(size), Data(0), MipMapsData(0), BytesPerPixel(0), Pitch(0), DeleteMemory(deleteMemory), DeleteMipMapsMemory(false) + Format(format), Size(size), Data(0), BytesPerPixel(0), Pitch(0), DeleteMemory(deleteMemory) { BytesPerPixel = getBitsPerPixelFromFormat(Format) / 8; Pitch = BytesPerPixel * Size.Width; @@ -36,9 +36,6 @@ public: { if (DeleteMemory) delete[] Data; - - if (DeleteMipMapsMemory) - delete[] MipMapsData; } //! Returns the color format @@ -188,87 +185,6 @@ public: return result; } - //! Get mipmaps data. - /** Note that different mip levels are just behind each other in memory block. - So if you just get level 1 you also have the data for all other levels. - There is no level 0 - use getData to get the original image data. - */ - void *getMipMapsData(irr::u32 mipLevel = 1) const - { - if (MipMapsData && mipLevel > 0) { - size_t dataSize = 0; - core::dimension2du mipSize(Size); - u32 i = 1; // We want the start of data for this level, not end. - - while (i != mipLevel) { - if (mipSize.Width > 1) - mipSize.Width >>= 1; - - if (mipSize.Height > 1) - mipSize.Height >>= 1; - - dataSize += getDataSizeFromFormat(Format, mipSize.Width, mipSize.Height); - - ++i; - if (mipSize.Width == 1 && mipSize.Height == 1 && i < mipLevel) - return 0; - } - - return MipMapsData + dataSize; - } - - return 0; - } - - //! Set mipmaps data. - /** This method allows you to put custom mipmaps data for - image. - \param data A byte array with pixel color information - \param ownForeignMemory If true, the image will use the data - pointer directly and own it afterward. If false, the memory - will by copied internally. - \param deleteMemory Whether the memory is deallocated upon - destruction. */ - void setMipMapsData(void *data, bool ownForeignMemory) - { - if (data != MipMapsData) { - if (DeleteMipMapsMemory) { - delete[] MipMapsData; - - DeleteMipMapsMemory = false; - } - - if (data) { - if (ownForeignMemory) { - MipMapsData = static_cast(data); - - DeleteMipMapsMemory = false; - } else { - u32 dataSize = 0; - u32 width = Size.Width; - u32 height = Size.Height; - - do { - if (width > 1) - width >>= 1; - - if (height > 1) - height >>= 1; - - dataSize += getDataSizeFromFormat(Format, width, height); - } while (width != 1 || height != 1); - - MipMapsData = new u8[dataSize]; - memcpy(MipMapsData, data, dataSize); - - DeleteMipMapsMemory = true; - } - } else { - MipMapsData = 0; - } - } - } - //! Returns a pixel virtual SColor getPixel(u32 x, u32 y) const = 0; @@ -276,30 +192,24 @@ public: virtual void setPixel(u32 x, u32 y, const SColor &color, bool blend = false) = 0; //! Copies this surface into another, if it has the exact same size and format. - /** NOTE: mipmaps are ignored - \return True if it was copied, false otherwise. + /** \return True if it was copied, false otherwise. */ virtual bool copyToNoScaling(void *target, u32 width, u32 height, ECOLOR_FORMAT format = ECF_A8R8G8B8, u32 pitch = 0) const = 0; //! Copies the image into the target, scaling the image to fit - /** NOTE: mipmaps are ignored */ virtual void copyToScaling(void *target, u32 width, u32 height, ECOLOR_FORMAT format = ECF_A8R8G8B8, u32 pitch = 0) = 0; //! Copies the image into the target, scaling the image to fit - /** NOTE: mipmaps are ignored */ virtual void copyToScaling(IImage *target) = 0; //! copies this surface into another - /** NOTE: mipmaps are ignored */ virtual void copyTo(IImage *target, const core::position2d &pos = core::position2d(0, 0)) = 0; //! copies this surface into another - /** NOTE: mipmaps are ignored */ virtual void copyTo(IImage *target, const core::position2d &pos, const core::rect &sourceRect, const core::rect *clipRect = 0) = 0; //! copies this surface into another, using the alpha mask and cliprect and a color to add with - /** NOTE: mipmaps are ignored - \param combineAlpha - When true then combine alpha channels. When false replace target image alpha with source image alpha. + /** \param combineAlpha - When true then combine alpha channels. When false replace target image alpha with source image alpha. */ virtual void copyToWithAlpha(IImage *target, const core::position2d &pos, const core::rect &sourceRect, const SColor &color, @@ -307,7 +217,6 @@ public: bool combineAlpha = false) = 0; //! copies this surface into another, scaling it to fit, applying a box filter - /** NOTE: mipmaps are ignored */ virtual void copyToScalingBoxFilter(IImage *target, s32 bias = 0, bool blend = false) = 0; //! fills the surface with given color @@ -415,13 +324,11 @@ protected: core::dimension2d Size; u8 *Data; - u8 *MipMapsData; u32 BytesPerPixel; u32 Pitch; bool DeleteMemory; - bool DeleteMipMapsMemory; }; } // end namespace video diff --git a/irr/include/ITexture.h b/irr/include/ITexture.h index fec69e4b1..1a65a34f1 100644 --- a/irr/include/ITexture.h +++ b/irr/include/ITexture.h @@ -166,9 +166,7 @@ public: only mode or read from in write only mode. Support for this feature depends on the driver, so don't rely on the texture being write-protected when locking with read-only, etc. - \param mipmapLevel NOTE: Currently broken, sorry, we try if we can repair it for 1.9 release. - Number of the mipmapLevel to lock. 0 is main texture. - Non-existing levels will silently fail and return 0. + \param mipmapLevel Number of the mipmapLevel to lock. 0 is main texture. \param layer It determines which cubemap face or texture array layer should be locked. \param lockFlags See E_TEXTURE_LOCK_FLAGS documentation. \return Returns a pointer to the pixel data. The format of the pixel can @@ -184,14 +182,9 @@ public: //! Regenerates the mip map levels of the texture. /** Required after modifying the texture, usually after calling unlock(). - \param data Optional parameter to pass in image data which will be - used instead of the previously stored or automatically generated mipmap - data. The data has to be a continuous pixel data for all mipmaps until - 1x1 pixel. Each mipmap has to be half the width and height of the previous - level. At least one pixel will be always kept. \param layer It informs a texture about which cubemap or texture array layer needs mipmap regeneration. */ - virtual void regenerateMipMapLevels(void *data = 0, u32 layer = 0) = 0; + virtual void regenerateMipMapLevels(u32 layer = 0) = 0; //! Get original size of the texture. /** The texture is usually scaled, if it was created with an unoptimal diff --git a/irr/src/CNullDriver.h b/irr/src/CNullDriver.h index 0b4167e83..00ae0b79b 100644 --- a/irr/src/CNullDriver.h +++ b/irr/src/CNullDriver.h @@ -616,7 +616,7 @@ protected: void *lock(E_TEXTURE_LOCK_MODE mode = ETLM_READ_WRITE, u32 mipmapLevel = 0, u32 layer = 0, E_TEXTURE_LOCK_FLAGS lockFlags = ETLF_FLIP_Y_UP_RTT) override { return 0; } void unlock() override {} - void regenerateMipMapLevels(void *data = 0, u32 layer = 0) override {} + void regenerateMipMapLevels(u32 layer = 0) override {} }; core::array Textures; diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 506d078cb..d087b5ae7 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -46,7 +46,7 @@ public: COpenGLCoreTexture(const io::path &name, const std::vector &srcImages, E_TEXTURE_TYPE type, TOpenGLDriver *driver) : ITexture(name, type), Driver(driver), TextureType(GL_TEXTURE_2D), TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), MSAA(0), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), - KeepImage(false), MipLevelStored(0), LegacyAutoGenerateMipMaps(false) + KeepImage(false), MipLevelStored(0) { _IRR_DEBUG_BREAK_IF(srcImages.empty()) @@ -82,15 +82,6 @@ public: srcImages[i]->copyTo(Images[i]); else srcImages[i]->copyToScaling(Images[i]); - - if (srcImages[i]->getMipMapsData()) { - if (OriginalSize == Size && OriginalColorFormat == ColorFormat) { - Images[i]->setMipMapsData(srcImages[i]->getMipMapsData(), false); - } else { - // TODO: handle at least mipmap with changing color format - os::Printer::log("COpenGLCoreTexture: Can't handle format changes for mipmap data. Mipmap data dropped", ELL_WARNING); - } - } } tmpImages = &Images; @@ -122,12 +113,9 @@ public: for (size_t i = 0; i < tmpImages->size(); ++i) uploadTexture(true, i, 0, (*tmpImages)[i]->getData()); - if (HasMipMaps && !LegacyAutoGenerateMipMaps) { - // Create mipmaps (either from image mipmaps or generate them) - for (size_t i = 0; i < tmpImages->size(); ++i) { - void *mipmapsData = (*tmpImages)[i]->getMipMapsData(); - regenerateMipMapLevels(mipmapsData, i); - } + if (HasMipMaps) { + for (size_t i = 0; i < tmpImages->size(); ++i) + regenerateMipMapLevels(i); } if (!KeepImage) { @@ -149,7 +137,7 @@ public: ITexture(name, type), Driver(driver), TextureType(GL_TEXTURE_2D), TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), MSAA(msaa), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), - MipLevelStored(0), LegacyAutoGenerateMipMaps(false) + MipLevelStored(0) { DriverType = Driver->getDriverType(); TextureType = TextureTypeIrrToGL(Type); @@ -279,7 +267,7 @@ public: void *lock(E_TEXTURE_LOCK_MODE mode = ETLM_READ_WRITE, u32 mipmapLevel = 0, u32 layer = 0, E_TEXTURE_LOCK_FLAGS lockFlags = ETLF_FLIP_Y_UP_RTT) override { if (LockImage) - return getLockImageData(MipLevelStored); + return LockImage->getData(); if (IImage::isCompressedFormat(ColorFormat)) return 0; @@ -291,7 +279,7 @@ public: if (KeepImage) { _IRR_DEBUG_BREAK_IF(LockLayer > Images.size()) - if (mipmapLevel == 0 || (Images[LockLayer] && Images[LockLayer]->getMipMapsData(mipmapLevel))) { + if (mipmapLevel == 0) { LockImage = Images[LockLayer]; LockImage->grab(); } @@ -301,7 +289,6 @@ public: core::dimension2d lockImageSize(IImage::getMipMapsSize(Size, MipLevelStored)); _IRR_DEBUG_BREAK_IF(lockImageSize.Width == 0 || lockImageSize.Height == 0) - // note: we save mipmap data also in the image because IImage doesn't allow saving single mipmap levels to the mipmap data LockImage = Driver->createImage(ColorFormat, lockImageSize); if (LockImage && mode != ETLM_WRITE_ONLY) { @@ -403,7 +390,7 @@ public: TEST_GL_ERROR(Driver); } - return (LockImage) ? getLockImageData(MipLevelStored) : 0; + return (LockImage) ? LockImage->getData() : 0; } void unlock() override @@ -415,7 +402,7 @@ public: const COpenGLCoreTexture *prevTexture = Driver->getCacheHandler()->getTextureCache().get(0); Driver->getCacheHandler()->getTextureCache().set(0, this); - uploadTexture(false, LockLayer, MipLevelStored, getLockImageData(MipLevelStored)); + uploadTexture(false, LockLayer, MipLevelStored, LockImage->getData()); Driver->getCacheHandler()->getTextureCache().set(0, prevTexture); } @@ -427,39 +414,16 @@ public: LockLayer = 0; } - void regenerateMipMapLevels(void *data = 0, u32 layer = 0) override + void regenerateMipMapLevels(u32 layer = 0) override { - if (!HasMipMaps || LegacyAutoGenerateMipMaps || (Size.Width <= 1 && Size.Height <= 1)) + if (!HasMipMaps || (Size.Width <= 1 && Size.Height <= 1)) return; const COpenGLCoreTexture *prevTexture = Driver->getCacheHandler()->getTextureCache().get(0); Driver->getCacheHandler()->getTextureCache().set(0, this); - if (data) { - u32 width = Size.Width; - u32 height = Size.Height; - u8 *tmpData = static_cast(data); - u32 dataSize = 0; - u32 level = 0; - - do { - if (width > 1) - width >>= 1; - - if (height > 1) - height >>= 1; - - dataSize = IImage::getDataSizeFromFormat(ColorFormat, width, height); - ++level; - - uploadTexture(true, layer, level, tmpData); - - tmpData += dataSize; - } while (width != 1 || height != 1); - } else { - Driver->irrGlGenerateMipmap(TextureType); - TEST_GL_ERROR(Driver); - } + Driver->irrGlGenerateMipmap(TextureType); + TEST_GL_ERROR(Driver); Driver->getCacheHandler()->getTextureCache().set(0, prevTexture); } @@ -480,14 +444,6 @@ public: } protected: - void *getLockImageData(irr::u32 miplevel) const - { - if (KeepImage && MipLevelStored > 0 && LockImage->getMipMapsData(MipLevelStored)) { - return LockImage->getMipMapsData(MipLevelStored); - } - return LockImage->getData(); - } - ECOLOR_FORMAT getBestColorFormat(ECOLOR_FORMAT format) { // We only try for to adapt "simple" formats @@ -671,7 +627,6 @@ protected: std::vector Images; u8 MipLevelStored; - bool LegacyAutoGenerateMipMaps; mutable SStatesCache StatesCache; }; From 9ff07df45e10490e71868b6c48b67b31ba94b11f Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 6 Apr 2025 17:48:42 +0200 Subject: [PATCH 120/284] Fix GLES texture download to handle mipmaps and cubemap type --- irr/src/COpenGLCoreTexture.h | 47 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index d087b5ae7..e6fa96400 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -307,13 +307,7 @@ public: Driver->getCacheHandler()->getTextureCache().set(0, this); TEST_GL_ERROR(Driver); - GLenum tmpTextureType = TextureType; - - if (tmpTextureType == GL_TEXTURE_CUBE_MAP) { - _IRR_DEBUG_BREAK_IF(layer > 5) - - tmpTextureType = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer; - } + GLenum tmpTextureType = getTextureTarget(layer); GL.GetTexImage(tmpTextureType, MipLevelStored, PixelFormat, PixelType, tmpImage->getData()); TEST_GL_ERROR(Driver); @@ -346,20 +340,31 @@ public: Driver->getCacheHandler()->getFBO(prevFBO); Driver->getCacheHandler()->setFBO(tmpFBO); - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, getOpenGLTextureName(), 0); + GLenum tmpTextureType = getTextureTarget(layer); - IImage *tmpImage = Driver->createImage(ECF_A8R8G8B8, Size); - GL.ReadPixels(0, 0, Size.Width, Size.Height, GL_RGBA, GL_UNSIGNED_BYTE, tmpImage->getData()); + // Warning: on GLES 2.0 this call will only work with mipmapLevel == 0 + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + tmpTextureType, getOpenGLTextureName(), mipmapLevel); + TEST_GL_ERROR(Driver); - Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + IImage *tmpImage = Driver->createImage(ECF_A8R8G8B8, lockImageSize); + GL.ReadPixels(0, 0, lockImageSize.Width, lockImageSize.Height, + GL_RGBA, GL_UNSIGNED_BYTE, tmpImage->getData()); + + Driver->irrGlFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, tmpTextureType, 0, 0); Driver->getCacheHandler()->setFBO(prevFBO); Driver->irrGlDeleteFramebuffers(1, &tmpFBO); + TEST_GL_ERROR(Driver); + void *src = tmpImage->getData(); void *dest = LockImage->getData(); + // FIXME: what about ETLF_FLIP_Y_UP_RTT + switch (ColorFormat) { case ECF_A1R5G5B5: CColorConverter::convert_A8R8G8B8toA1B5G5R5(src, tmpImage->getDimension().getArea(), dest); @@ -386,8 +391,6 @@ public: LockImage = 0; } } - - TEST_GL_ERROR(Driver); } return (LockImage) ? LockImage->getData() : 0; @@ -539,13 +542,7 @@ protected: if (height < 1) height = 1; - GLenum tmpTextureType = TextureType; - - if (tmpTextureType == GL_TEXTURE_CUBE_MAP) { - _IRR_DEBUG_BREAK_IF(layer > 5) - - tmpTextureType = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer; - } + GLenum tmpTextureType = getTextureTarget(layer); if (!IImage::isCompressedFormat(ColorFormat)) { CImage *tmpImage = 0; @@ -609,6 +606,16 @@ protected: return GL_TEXTURE_2D; } + GLenum getTextureTarget(u32 layer) const + { + GLenum tmp = TextureType; + if (tmp == GL_TEXTURE_CUBE_MAP) { + _IRR_DEBUG_BREAK_IF(layer > 5) + tmp = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer; + } + return tmp; + } + TOpenGLDriver *Driver; GLenum TextureType; From 427a7e49980956f5e2c20f5ec6c154484dfb725e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 6 Apr 2025 19:40:12 +0200 Subject: [PATCH 121/284] Split texture initialization code from upload --- irr/src/COpenGLCoreTexture.h | 106 ++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index e6fa96400..28394886c 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -110,8 +110,10 @@ public: } TEST_GL_ERROR(Driver); + initTexture(); + for (size_t i = 0; i < tmpImages->size(); ++i) - uploadTexture(true, i, 0, (*tmpImages)[i]->getData()); + uploadTexture(i, 0, (*tmpImages)[i]->getData()); if (HasMipMaps) { for (size_t i = 0; i < tmpImages->size(); ++i) @@ -206,50 +208,13 @@ public: StatesCache.WrapW = ETC_CLAMP_TO_EDGE; } - switch (Type) { - case ETT_2D: - GL.TexImage2D(GL_TEXTURE_2D, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); - break; - case ETT_2D_MS: { - // glTexImage2DMultisample is supported by OpenGL 3.2+ - // glTexStorage2DMultisample is supported by OpenGL 4.3+ and OpenGL ES 3.1+ -#ifdef IRR_COMPILE_GL_COMMON // legacy driver - constexpr bool use_gl_impl = true; -#else - const bool use_gl_impl = Driver->Version.Spec != OpenGLSpec::ES; -#endif - GLint max_samples = 0; - GL.GetIntegerv(GL_MAX_SAMPLES, &max_samples); - MSAA = core::min_(MSAA, (u8)max_samples); - - if (use_gl_impl) - GL.TexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA, InternalFormat, Size.Width, Size.Height, GL_TRUE); - else - GL.TexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA, InternalFormat, Size.Width, Size.Height, GL_TRUE); - break; - } - case ETT_CUBEMAP: - GL.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); - GL.TexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); - GL.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); - GL.TexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); - GL.TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); - GL.TexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, InternalFormat, Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); - break; - default: - _IRR_DEBUG_BREAK_IF(1) - break; - } + initTexture(); if (!name.empty()) Driver->irrGlObjectLabel(GL_TEXTURE, TextureName, name.c_str()); Driver->getCacheHandler()->getTextureCache().set(0, prevTexture); - if (TEST_GL_ERROR(Driver)) { - char msg[256]; - snprintf_irr(msg, 256, "COpenGLCoreTexture: InternalFormat:0x%04x PixelFormat:0x%04x", (int)InternalFormat, (int)PixelFormat); - os::Printer::log(msg, ELL_ERROR); - } + TEST_GL_ERROR(Driver); } virtual ~COpenGLCoreTexture() @@ -405,7 +370,7 @@ public: const COpenGLCoreTexture *prevTexture = Driver->getCacheHandler()->getTextureCache().get(0); Driver->getCacheHandler()->getTextureCache().set(0, this); - uploadTexture(false, LockLayer, MipLevelStored, LockImage->getData()); + uploadTexture(LockLayer, MipLevelStored, LockImage->getData()); Driver->getCacheHandler()->getTextureCache().set(0, prevTexture); } @@ -530,7 +495,54 @@ protected: Pitch = Size.Width * IImage::getBitsPerPixelFromFormat(ColorFormat) / 8; } - void uploadTexture(bool initTexture, u32 layer, u32 level, void *data) + void initTexture() + { + // Compressed textures cannot be pre-allocated and are initialized on upload + if (IImage::isCompressedFormat(ColorFormat)) { + _IRR_DEBUG_BREAK_IF(IsRenderTarget) + return; + } + + switch (Type) { + case ETT_2D: + GL.TexImage2D(TextureType, 0, InternalFormat, + Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); + TEST_GL_ERROR(Driver); + break; + case ETT_2D_MS: { + GLint max_samples = 0; + GL.GetIntegerv(GL_MAX_SAMPLES, &max_samples); + MSAA = core::min_(MSAA, (u8)max_samples); + + // glTexImage2DMultisample is supported by OpenGL 3.2+ + // glTexStorage2DMultisample is supported by OpenGL 4.3+ and OpenGL ES 3.1+ +#ifdef IRR_COMPILE_GL_COMMON // legacy driver + constexpr bool use_gl_impl = true; +#else + const bool use_gl_impl = Driver->Version.Spec != OpenGLSpec::ES; +#endif + + if (use_gl_impl) + GL.TexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA, InternalFormat, Size.Width, Size.Height, GL_TRUE); + else + GL.TexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA, InternalFormat, Size.Width, Size.Height, GL_TRUE); + TEST_GL_ERROR(Driver); + break; + } + case ETT_CUBEMAP: + for (u32 i = 0; i < 6; i++) { + GL.TexImage2D(getTextureTarget(i), 0, InternalFormat, + Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); + TEST_GL_ERROR(Driver); + } + break; + default: + _IRR_DEBUG_BREAK_IF(1) + break; + } + } + + void uploadTexture(u32 layer, u32 level, void *data) { if (!data) return; @@ -560,10 +572,7 @@ protected: switch (TextureType) { case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: - if (initTexture) - GL.TexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, PixelFormat, PixelType, tmpData); - else - GL.TexSubImage2D(tmpTextureType, level, 0, 0, width, height, PixelFormat, PixelType, tmpData); + GL.TexSubImage2D(tmpTextureType, level, 0, 0, width, height, PixelFormat, PixelType, tmpData); TEST_GL_ERROR(Driver); break; default: @@ -578,10 +587,7 @@ protected: switch (TextureType) { case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: - if (initTexture) - Driver->irrGlCompressedTexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, dataSize, data); - else - Driver->irrGlCompressedTexSubImage2D(tmpTextureType, level, 0, 0, width, height, PixelFormat, dataSize, data); + Driver->irrGlCompressedTexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, dataSize, data); TEST_GL_ERROR(Driver); break; default: From d5bf094f9aed411d5d54f22795e06706d18f2d3c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 6 Apr 2025 20:25:03 +0200 Subject: [PATCH 122/284] Prefer immutable texture storage when available --- irr/include/irrMath.h | 11 +++++++++++ irr/src/CImage.cpp | 2 +- irr/src/COpenGLCoreFeature.h | 14 ++++++-------- irr/src/COpenGLCoreTexture.h | 26 ++++++++++++++++++++++---- irr/src/OpenGL3/Driver.cpp | 1 + irr/src/OpenGLES2/Driver.cpp | 1 + irr/src/SoftwareDriver2_helper.h | 11 ----------- 7 files changed, 42 insertions(+), 24 deletions(-) diff --git a/irr/include/irrMath.h b/irr/include/irrMath.h index e9c86156d..e11d47360 100644 --- a/irr/include/irrMath.h +++ b/irr/include/irrMath.h @@ -283,6 +283,17 @@ inline s32 s32_clamp(s32 value, s32 low, s32 high) return clamp(value, low, high); } +// integer log2 of an integer. returning 0 if denormal +inline s32 u32_log2(u32 in) +{ + s32 ret = 0; + while (in > 1) { + in >>= 1; + ret++; + } + return ret; +} + /* float IEEE-754 bit representation diff --git a/irr/src/CImage.cpp b/irr/src/CImage.cpp index de4df0c0f..29c115c8a 100644 --- a/irr/src/CImage.cpp +++ b/irr/src/CImage.cpp @@ -363,7 +363,7 @@ inline SColor CImage::getPixelBox(s32 x, s32 y, s32 fx, s32 fy, s32 bias) const } } - s32 sdiv = s32_log2_s32(fx * fy); + s32 sdiv = core::u32_log2(fx * fy); a = core::s32_clamp((a >> sdiv) + bias, 0, 255); r = core::s32_clamp((r >> sdiv) + bias, 0, 255); diff --git a/irr/src/COpenGLCoreFeature.h b/irr/src/COpenGLCoreFeature.h index dc3d40e04..629bec2a1 100644 --- a/irr/src/COpenGLCoreFeature.h +++ b/irr/src/COpenGLCoreFeature.h @@ -14,20 +14,18 @@ namespace video class COpenGLCoreFeature { public: - COpenGLCoreFeature() : - BlendOperation(false), ColorAttachment(0), MultipleRenderTarget(0), MaxTextureUnits(1) - { - } + COpenGLCoreFeature() = default; virtual ~COpenGLCoreFeature() { } - bool BlendOperation; + bool BlendOperation = false; + bool TexStorage = false; - u8 ColorAttachment; - u8 MultipleRenderTarget; - u8 MaxTextureUnits; + u8 ColorAttachment = 0; + u8 MultipleRenderTarget = 0; + u8 MaxTextureUnits = 0; }; } diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 28394886c..439f786c6 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -503,10 +503,22 @@ protected: return; } + u32 levels = 1; + if (HasMipMaps) { + levels = core::u32_log2(core::max_(Size.Width, Size.Height)) + 1; + } + + // reference: + switch (Type) { case ETT_2D: - GL.TexImage2D(TextureType, 0, InternalFormat, - Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); + if (Driver->getFeature().TexStorage) { + GL.TexStorage2D(TextureType, levels, InternalFormat, + Size.Width, Size.Height); + } else { + GL.TexImage2D(TextureType, 0, InternalFormat, + Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); + } TEST_GL_ERROR(Driver); break; case ETT_2D_MS: { @@ -531,8 +543,14 @@ protected: } case ETT_CUBEMAP: for (u32 i = 0; i < 6; i++) { - GL.TexImage2D(getTextureTarget(i), 0, InternalFormat, - Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); + GLenum target = getTextureTarget(i); + if (Driver->getFeature().TexStorage) { + GL.TexStorage2D(target, levels, InternalFormat, + Size.Width, Size.Height); + } else { + GL.TexImage2D(target, 0, InternalFormat, + Size.Width, Size.Height, 0, PixelFormat, PixelType, 0); + } TEST_GL_ERROR(Driver); } break; diff --git a/irr/src/OpenGL3/Driver.cpp b/irr/src/OpenGL3/Driver.cpp index 43ee9ba45..8339068a6 100644 --- a/irr/src/OpenGL3/Driver.cpp +++ b/irr/src/OpenGL3/Driver.cpp @@ -78,6 +78,7 @@ void COpenGL3Driver::initFeatures() // COGLESCoreExtensionHandler::Feature static_assert(MATERIAL_MAX_TEXTURES <= 16, "Only up to 16 textures are guaranteed"); Feature.BlendOperation = true; + Feature.TexStorage = isVersionAtLeast(4, 2) || queryExtension("GL_ARB_texture_storage"); Feature.ColorAttachment = GetInteger(GL_MAX_COLOR_ATTACHMENTS); Feature.MaxTextureUnits = MATERIAL_MAX_TEXTURES; Feature.MultipleRenderTarget = GetInteger(GL_MAX_DRAW_BUFFERS); diff --git a/irr/src/OpenGLES2/Driver.cpp b/irr/src/OpenGLES2/Driver.cpp index 7c98fca8d..6000059ed 100644 --- a/irr/src/OpenGLES2/Driver.cpp +++ b/irr/src/OpenGLES2/Driver.cpp @@ -131,6 +131,7 @@ void COpenGLES2Driver::initFeatures() // COGLESCoreExtensionHandler::Feature static_assert(MATERIAL_MAX_TEXTURES <= 8, "Only up to 8 textures are guaranteed"); Feature.BlendOperation = true; + Feature.TexStorage = Version.Major >= 3 || queryExtension("GL_ARB_texture_storage"); Feature.ColorAttachment = 1; if (MRTSupported) Feature.ColorAttachment = GetInteger(GL_MAX_COLOR_ATTACHMENTS); diff --git a/irr/src/SoftwareDriver2_helper.h b/irr/src/SoftwareDriver2_helper.h index 602f9e295..76ea249ea 100644 --- a/irr/src/SoftwareDriver2_helper.h +++ b/irr/src/SoftwareDriver2_helper.h @@ -93,17 +93,6 @@ inline void memset16(void *dest, const u16 value, size_t bytesize) } } -// integer log2 of an integer. returning 0 as denormal -static inline s32 s32_log2_s32(u32 in) -{ - s32 ret = 0; - while (in > 1) { - in >>= 1; - ret++; - } - return ret; -} - // ------------------ Video--------------------------------------- /*! Pixel = dest * ( 1 - alpha ) + source * alpha From 46db688cc8773e2aefe22bbb3f12f20622734b74 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 6 Apr 2025 22:16:17 +0200 Subject: [PATCH 123/284] Implement support for array textures in GL driver note: feature detection was not implemented in the legacy driver, but the code itself probably works. --- irr/include/EDriverFeatures.h | 3 ++ irr/include/ITexture.h | 5 ++- irr/include/IVideoDriver.h | 9 ++++ irr/src/CNullDriver.cpp | 66 ++++++++++++++++------------ irr/src/CNullDriver.h | 7 +-- irr/src/COGLESCoreExtensionHandler.h | 4 +- irr/src/COpenGLCoreTexture.h | 40 ++++++++++++++--- irr/src/COpenGLDriver.cpp | 15 +------ irr/src/COpenGLDriver.h | 4 +- irr/src/OpenGL/Driver.cpp | 16 ++----- irr/src/OpenGL/Driver.h | 5 +-- irr/src/OpenGL/ExtensionHandler.h | 3 ++ irr/src/OpenGL3/Driver.cpp | 3 ++ irr/src/OpenGLES2/Driver.cpp | 3 ++ 14 files changed, 111 insertions(+), 72 deletions(-) diff --git a/irr/include/EDriverFeatures.h b/irr/include/EDriverFeatures.h index 0a35161cf..c1fcd464d 100644 --- a/irr/include/EDriverFeatures.h +++ b/irr/include/EDriverFeatures.h @@ -132,6 +132,9 @@ enum E_VIDEO_DRIVER_FEATURE //! Support for multisample textures. EVDF_TEXTURE_MULTISAMPLE, + //! Support for 2D array textures. + EVDF_TEXTURE_2D_ARRAY, + //! Only used for counting the elements of this enum EVDF_COUNT }; diff --git a/irr/include/ITexture.h b/irr/include/ITexture.h index 1a65a34f1..bdbb72727 100644 --- a/irr/include/ITexture.h +++ b/irr/include/ITexture.h @@ -129,7 +129,10 @@ enum E_TEXTURE_TYPE ETT_2D_MS, //! Cubemap texture. - ETT_CUBEMAP + ETT_CUBEMAP, + + //! 2D array texture + ETT_2D_ARRAY }; //! Interface of a Video Driver dependent Texture. diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index 29ce8678a..950723941 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -232,6 +232,15 @@ public: information. */ virtual ITexture *addTexture(const io::path &name, IImage *image) = 0; + /** + * Creates an array texture from IImages. + * @param name A name for the texture. + * @param images Pointer to array of images + * @param count Number of images (must be at least 1) + * @return Pointer to the newly created texture + */ + virtual ITexture *addArrayTexture(const io::path &name, IImage **images, u32 count) = 0; + //! Creates a cubemap texture from loaded IImages. /** \param name A name for the texture. Later calls of getTexture() with this name will return this texture. The name can _not_ be empty. diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 59487c5b0..21346e6ed 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -294,25 +294,9 @@ u32 CNullDriver::getTextureCount() const ITexture *CNullDriver::addTexture(const core::dimension2d &size, const io::path &name, ECOLOR_FORMAT format) { - if (0 == name.size()) { - os::Printer::log("Could not create ITexture, texture needs to have a non-empty name.", ELL_WARNING); - return 0; - } - IImage *image = new CImage(format, size); - ITexture *t = 0; - - if (checkImage(image)) { - t = createDeviceDependentTexture(name, image); - } - + ITexture *t = addTexture(name, image); image->drop(); - - if (t) { - addTexture(t); - t->drop(); - } - return t; } @@ -329,7 +313,8 @@ ITexture *CNullDriver::addTexture(const io::path &name, IImage *image) ITexture *t = 0; if (checkImage(image)) { - t = createDeviceDependentTexture(name, image); + std::vector tmp { image }; + t = createDeviceDependentTexture(name, ETT_2D, tmp); } if (t) { @@ -340,6 +325,27 @@ ITexture *CNullDriver::addTexture(const io::path &name, IImage *image) return t; } +ITexture *CNullDriver::addArrayTexture(const io::path &name, IImage **images, u32 count) +{ + if (0 == name.size()) { + os::Printer::log("Could not create ITexture, texture needs to have a non-empty name.", ELL_WARNING); + return 0; + } + + // this is stupid but who cares + std::vector tmp(images, images + count); + + ITexture *t = nullptr; + if (checkImage(tmp)) { + t = createDeviceDependentTexture(name, ETT_2D_ARRAY, tmp); + } + if (t) { + addTexture(t); + t->drop(); + } + return t; +} + ITexture *CNullDriver::addTextureCubemap(const io::path &name, IImage *imagePosX, IImage *imageNegX, IImage *imagePosY, IImage *imageNegY, IImage *imagePosZ, IImage *imageNegZ) { @@ -357,7 +363,7 @@ ITexture *CNullDriver::addTextureCubemap(const io::path &name, IImage *imagePosX imageArray.push_back(imageNegZ); if (checkImage(imageArray)) { - t = createDeviceDependentTextureCubemap(name, imageArray); + t = createDeviceDependentTexture(name, ETT_CUBEMAP, imageArray); } if (t) { @@ -384,7 +390,7 @@ ITexture *CNullDriver::addTextureCubemap(const irr::u32 sideLen, const io::path ITexture *t = 0; if (checkImage(imageArray)) { - t = createDeviceDependentTextureCubemap(name, imageArray); + t = createDeviceDependentTexture(name, ETT_CUBEMAP, imageArray); if (t) { addTexture(t); @@ -479,7 +485,8 @@ video::ITexture *CNullDriver::loadTextureFromFile(io::IReadFile *file, const io: return nullptr; if (checkImage(image)) { - texture = createDeviceDependentTexture(hashName.size() ? hashName : file->getFileName(), image); + std::vector tmp { image }; + texture = createDeviceDependentTexture(hashName.size() ? hashName : file->getFileName(), ETT_2D, tmp); if (texture) os::Printer::log("Loaded texture", file->getFileName(), ELL_DEBUG); } @@ -519,18 +526,16 @@ video::ITexture *CNullDriver::findTexture(const io::path &filename) return 0; } -ITexture *CNullDriver::createDeviceDependentTexture(const io::path &name, IImage *image) +ITexture *CNullDriver::createDeviceDependentTexture(const io::path &name, E_TEXTURE_TYPE type, + const std::vector &images) { - SDummyTexture *dummy = new SDummyTexture(name, ETT_2D); - dummy->setSize(image->getDimension()); + if (type != ETT_2D && type != ETT_CUBEMAP) + return nullptr; + SDummyTexture *dummy = new SDummyTexture(name, type); + dummy->setSize(images[0]->getDimension()); return dummy; } -ITexture *CNullDriver::createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) -{ - return new SDummyTexture(name, ETT_CUBEMAP); -} - bool CNullDriver::setRenderTargetEx(IRenderTarget *target, u16 clearFlag, SColor clearColor, f32 clearDepth, u8 clearStencil) { return false; @@ -883,6 +888,9 @@ bool CNullDriver::checkImage(const std::vector &image) const auto lastSize = image[0]->getDimension(); for (size_t i = 0; i < image.size(); ++i) { + if (!image[i]) + return false; + ECOLOR_FORMAT format = image[i]->getColorFormat(); auto size = image[i]->getDimension(); diff --git a/irr/src/CNullDriver.h b/irr/src/CNullDriver.h index 00ae0b79b..3ef8c642d 100644 --- a/irr/src/CNullDriver.h +++ b/irr/src/CNullDriver.h @@ -84,6 +84,8 @@ public: ITexture *addTexture(const io::path &name, IImage *image) override; + ITexture *addArrayTexture(const io::path &name, IImage **images, u32 count) override; + virtual ITexture *addTextureCubemap(const io::path &name, IImage *imagePosX, IImage *imageNegX, IImage *imagePosY, IImage *imageNegY, IImage *imagePosZ, IImage *imageNegZ) override; @@ -549,9 +551,8 @@ protected: //! adds a surface, not loaded or created by the Irrlicht Engine void addTexture(ITexture *surface); - virtual ITexture *createDeviceDependentTexture(const io::path &name, IImage *image); - - virtual ITexture *createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image); + virtual ITexture *createDeviceDependentTexture(const io::path &name, E_TEXTURE_TYPE type, + const std::vector &images); //! checks triangle count and print warning if wrong bool checkPrimitiveCount(u32 prmcnt) const; diff --git a/irr/src/COGLESCoreExtensionHandler.h b/irr/src/COGLESCoreExtensionHandler.h index 80a7bb061..5c760e8ca 100644 --- a/irr/src/COGLESCoreExtensionHandler.h +++ b/irr/src/COGLESCoreExtensionHandler.h @@ -39,7 +39,8 @@ public: COGLESCoreExtensionHandler() : MaxAnisotropy(1), MaxIndices(0xffff), - MaxTextureSize(1), MaxTextureLODBias(0.f), StencilBuffer(false) + MaxTextureSize(1), MaxArrayTextureLayers(1), + MaxTextureLODBias(0.f), StencilBuffer(false) { for (u32 i = 0; i < IRR_OGLES_Feature_Count; ++i) FeatureAvailable[i] = false; @@ -87,6 +88,7 @@ protected: u8 MaxAnisotropy; u32 MaxIndices; u32 MaxTextureSize; + u32 MaxArrayTextureLayers; f32 MaxTextureLODBias; //! Minimal and maximal supported thickness for lines without smoothing float DimAliasedLine[2]; diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 439f786c6..51b122075 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -110,7 +110,7 @@ public: } TEST_GL_ERROR(Driver); - initTexture(); + initTexture(tmpImages->size()); for (size_t i = 0; i < tmpImages->size(); ++i) uploadTexture(i, 0, (*tmpImages)[i]->getData()); @@ -142,6 +142,7 @@ public: MipLevelStored(0) { DriverType = Driver->getDriverType(); + _IRR_DEBUG_BREAK_IF(Type == ETT_2D_ARRAY) // not supported by this constructor TextureType = TextureTypeIrrToGL(Type); HasMipMaps = false; IsRenderTarget = true; @@ -208,7 +209,7 @@ public: StatesCache.WrapW = ETC_CLAMP_TO_EDGE; } - initTexture(); + initTexture(0); if (!name.empty()) Driver->irrGlObjectLabel(GL_TEXTURE, TextureName, name.c_str()); @@ -265,7 +266,19 @@ public: const bool use_gl_impl = Driver->Version.Spec != OpenGLSpec::ES; #endif - if (use_gl_impl) { + if (Type == ETT_2D_ARRAY) { + + // For OpenGL an array texture is basically just a 3D texture internally. + // So if we call glGetTexImage() we would download the entire array, + // except the caller only wants a single layer. + // To do this properly we could have to use glGetTextureSubImage() [4.5] + // or some trickery with glTextureView() [4.3]. + // Also neither of those will work on GLES. + + os::Printer::log("lock: read or read/write unimplemented for ETT_2D_ARRAY", ELL_WARNING); + passed = false; + + } else if (use_gl_impl) { IImage *tmpImage = LockImage; @@ -495,7 +508,7 @@ protected: Pitch = Size.Width * IImage::getBitsPerPixelFromFormat(ColorFormat) / 8; } - void initTexture() + void initTexture(u32 layers) { // Compressed textures cannot be pre-allocated and are initialized on upload if (IImage::isCompressedFormat(ColorFormat)) { @@ -554,6 +567,16 @@ protected: TEST_GL_ERROR(Driver); } break; + case ETT_2D_ARRAY: + if (Driver->getFeature().TexStorage) { + GL.TexStorage3D(TextureType, levels, InternalFormat, + Size.Width, Size.Height, layers); + } else { + GL.TexImage3D(TextureType, 0, InternalFormat, + Size.Width, Size.Height, layers, 0, PixelFormat, PixelType, 0); + } + TEST_GL_ERROR(Driver); + break; default: _IRR_DEBUG_BREAK_IF(1) break; @@ -591,12 +614,15 @@ protected: case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: GL.TexSubImage2D(tmpTextureType, level, 0, 0, width, height, PixelFormat, PixelType, tmpData); - TEST_GL_ERROR(Driver); + break; + case GL_TEXTURE_2D_ARRAY: + GL.TexSubImage3D(tmpTextureType, level, 0, 0, layer, width, height, 1, PixelFormat, PixelType, tmpData); break; default: _IRR_DEBUG_BREAK_IF(1) break; } + TEST_GL_ERROR(Driver); delete tmpImage; } else { @@ -606,12 +632,12 @@ protected: case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: Driver->irrGlCompressedTexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, dataSize, data); - TEST_GL_ERROR(Driver); break; default: _IRR_DEBUG_BREAK_IF(1) break; } + TEST_GL_ERROR(Driver); } } @@ -624,6 +650,8 @@ protected: return GL_TEXTURE_2D_MULTISAMPLE; case ETT_CUBEMAP: return GL_TEXTURE_CUBE_MAP; + case ETT_2D_ARRAY: + return GL_TEXTURE_2D_ARRAY; } os::Printer::log("COpenGLCoreTexture::TextureTypeIrrToGL unknown texture type", ELL_WARNING); diff --git a/irr/src/COpenGLDriver.cpp b/irr/src/COpenGLDriver.cpp index e9cd10b49..50e097362 100644 --- a/irr/src/COpenGLDriver.cpp +++ b/irr/src/COpenGLDriver.cpp @@ -1609,20 +1609,9 @@ inline void COpenGLDriver::getGLTextureMatrix(GLfloat *o, const core::matrix4 &m o[15] = 1.f; } -ITexture *COpenGLDriver::createDeviceDependentTexture(const io::path &name, IImage *image) +ITexture *COpenGLDriver::createDeviceDependentTexture(const io::path &name, E_TEXTURE_TYPE type, const std::vector &images) { - std::vector tmp { image }; - - COpenGLTexture *texture = new COpenGLTexture(name, tmp, ETT_2D, this); - - return texture; -} - -ITexture *COpenGLDriver::createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) -{ - COpenGLTexture *texture = new COpenGLTexture(name, image, ETT_CUBEMAP, this); - - return texture; + return new COpenGLTexture(name, images, ETT_2D, this); } void COpenGLDriver::disableFeature(E_VIDEO_DRIVER_FEATURE feature, bool flag) diff --git a/irr/src/COpenGLDriver.h b/irr/src/COpenGLDriver.h index 1b5c0c6d3..67c328f58 100644 --- a/irr/src/COpenGLDriver.h +++ b/irr/src/COpenGLDriver.h @@ -326,9 +326,7 @@ private: //! inits the parts of the open gl driver used on all platforms bool genericDriverInit(); - ITexture *createDeviceDependentTexture(const io::path &name, IImage *image) override; - - ITexture *createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) override; + ITexture *createDeviceDependentTexture(const io::path &name, E_TEXTURE_TYPE type, const std::vector &images) override; //! creates a transposed matrix in supplied GLfloat array to pass to OpenGL inline void getGLMatrix(GLfloat gl_matrix[16], const core::matrix4 &m); diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index e83a5b3ec..0057611d4 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -267,6 +267,7 @@ bool COpenGL3DriverBase::genericDriverInit(const core::dimension2d &screenS DriverAttributes->setAttribute("MaxAnisotropy", MaxAnisotropy); DriverAttributes->setAttribute("MaxIndices", (s32)MaxIndices); DriverAttributes->setAttribute("MaxTextureSize", (s32)MaxTextureSize); + DriverAttributes->setAttribute("MaxArrayTextureLayers", (s32)MaxArrayTextureLayers); DriverAttributes->setAttribute("MaxTextureLODBias", MaxTextureLODBias); DriverAttributes->setAttribute("Version", 100 * Version.Major + Version.Minor); DriverAttributes->setAttribute("AntiAlias", AntiAlias); @@ -1072,20 +1073,9 @@ void COpenGL3DriverBase::endDraw(const VertexType &vertexType) GL.DisableVertexAttribArray(attr.Index); } -ITexture *COpenGL3DriverBase::createDeviceDependentTexture(const io::path &name, IImage *image) +ITexture *COpenGL3DriverBase::createDeviceDependentTexture(const io::path &name, E_TEXTURE_TYPE type, const std::vector &images) { - std::vector tmp { image }; - - COpenGL3Texture *texture = new COpenGL3Texture(name, tmp, ETT_2D, this); - - return texture; -} - -ITexture *COpenGL3DriverBase::createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) -{ - COpenGL3Texture *texture = new COpenGL3Texture(name, image, ETT_CUBEMAP, this); - - return texture; + return new COpenGL3Texture(name, images, type, this); } // Same as COpenGLDriver::TextureFlipMatrix diff --git a/irr/src/OpenGL/Driver.h b/irr/src/OpenGL/Driver.h index 6154e3fa9..7b399cf93 100644 --- a/irr/src/OpenGL/Driver.h +++ b/irr/src/OpenGL/Driver.h @@ -267,9 +267,8 @@ protected: void chooseMaterial2D(); - ITexture *createDeviceDependentTexture(const io::path &name, IImage *image) override; - - ITexture *createDeviceDependentTextureCubemap(const io::path &name, const std::vector &image) override; + ITexture *createDeviceDependentTexture(const io::path &name, E_TEXTURE_TYPE type, + const std::vector &images) override; //! Map Irrlicht wrap mode to OpenGL enum GLint getTextureWrapMode(u8 clamp) const; diff --git a/irr/src/OpenGL/ExtensionHandler.h b/irr/src/OpenGL/ExtensionHandler.h index 209fd415b..413ab7f23 100644 --- a/irr/src/OpenGL/ExtensionHandler.h +++ b/irr/src/OpenGL/ExtensionHandler.h @@ -76,6 +76,8 @@ public: return StencilBuffer; case EVDF_TEXTURE_MULTISAMPLE: return TextureMultisampleSupported; + case EVDF_TEXTURE_2D_ARRAY: + return Texture2DArraySupported; default: return false; }; @@ -176,6 +178,7 @@ public: bool AnisotropicFilterSupported = false; bool BlendMinMaxSupported = false; bool TextureMultisampleSupported = false; + bool Texture2DArraySupported = false; bool KHRDebugSupported = false; u32 MaxLabelLength = 0; }; diff --git a/irr/src/OpenGL3/Driver.cpp b/irr/src/OpenGL3/Driver.cpp index 8339068a6..09286b027 100644 --- a/irr/src/OpenGL3/Driver.cpp +++ b/irr/src/OpenGL3/Driver.cpp @@ -71,6 +71,7 @@ void COpenGL3Driver::initFeatures() LODBiasSupported = true; BlendMinMaxSupported = true; TextureMultisampleSupported = true; + Texture2DArraySupported = Version.Major >= 3 || queryExtension("GL_EXT_texture_array"); KHRDebugSupported = isVersionAtLeast(4, 6) || queryExtension("GL_KHR_debug"); if (KHRDebugSupported) MaxLabelLength = GetInteger(GL.MAX_LABEL_LENGTH); @@ -88,6 +89,8 @@ void COpenGL3Driver::initFeatures() MaxAnisotropy = GetInteger(GL.MAX_TEXTURE_MAX_ANISOTROPY); MaxIndices = GetInteger(GL_MAX_ELEMENTS_INDICES); MaxTextureSize = GetInteger(GL_MAX_TEXTURE_SIZE); + if (Texture2DArraySupported) + MaxArrayTextureLayers = GetInteger(GL_MAX_ARRAY_TEXTURE_LAYERS); GL.GetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &MaxTextureLODBias); GL.GetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, DimAliasedLine); DimAliasedPoint[0] = 1.0f; diff --git a/irr/src/OpenGLES2/Driver.cpp b/irr/src/OpenGLES2/Driver.cpp index 6000059ed..2db021088 100644 --- a/irr/src/OpenGLES2/Driver.cpp +++ b/irr/src/OpenGLES2/Driver.cpp @@ -124,6 +124,7 @@ void COpenGLES2Driver::initFeatures() AnisotropicFilterSupported = queryExtension("GL_EXT_texture_filter_anisotropic"); BlendMinMaxSupported = (Version.Major >= 3) || FeatureAvailable[IRR_GL_EXT_blend_minmax]; TextureMultisampleSupported = isVersionAtLeast(3, 1); + Texture2DArraySupported = Version.Major >= 3 || queryExtension("GL_EXT_texture_array"); KHRDebugSupported = queryExtension("GL_KHR_debug"); if (KHRDebugSupported) MaxLabelLength = GetInteger(GL.MAX_LABEL_LENGTH); @@ -145,6 +146,8 @@ void COpenGLES2Driver::initFeatures() if (Version.Major >= 3 || queryExtension("GL_EXT_draw_range_elements")) MaxIndices = GetInteger(GL_MAX_ELEMENTS_INDICES); MaxTextureSize = GetInteger(GL_MAX_TEXTURE_SIZE); + if (Texture2DArraySupported) + MaxArrayTextureLayers = GetInteger(GL_MAX_ARRAY_TEXTURE_LAYERS); if (LODBiasSupported) GL.GetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &MaxTextureLODBias); GL.GetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, DimAliasedLine); From a00b9cab36fa116e8af97df6036ad0e634775447 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 8 Apr 2025 22:25:45 +0200 Subject: [PATCH 124/284] Fix operator[] for vector2d and vector3d being potentially UB (#15977) We don't have a C++ expert on hand, but taking a pointer to one member and expecting to access another by an offset is very fishy: - for one, there could theoretically be padding - the compiler might assume that we are only writing to that first member The new code has shown to be free for constant parameter values. Non-constant ones cause the assembly to have branches (why?), but we don't use that much. --- irr/include/irrTypes.h | 16 ++++++++++++++++ irr/include/vector2d.h | 16 ++++++++++------ irr/include/vector3d.h | 18 ++++++++++++------ irr/src/OpenGL/Driver.cpp | 3 +-- irr/src/os.h | 16 ---------------- 5 files changed, 39 insertions(+), 30 deletions(-) diff --git a/irr/include/irrTypes.h b/irr/include/irrTypes.h index 6c0ee661c..910991689 100644 --- a/irr/include/irrTypes.h +++ b/irr/include/irrTypes.h @@ -85,6 +85,22 @@ typedef char fschar_t; /** prefer to use the override keyword for new code */ #define _IRR_OVERRIDE_ override +// Invokes undefined behavior for unreachable code optimization +// Note: an assert(false) is included first to catch this in debug builds +#if defined(__cpp_lib_unreachable) +#include +#define IRR_CODE_UNREACHABLE() do { _IRR_DEBUG_BREAK_IF(1) std::unreachable(); } while(0) +#elif defined(__has_builtin) +#if __has_builtin(__builtin_unreachable) +#define IRR_CODE_UNREACHABLE() do { _IRR_DEBUG_BREAK_IF(1) __builtin_unreachable(); } while(0) +#endif +#elif defined(_MSC_VER) +#define IRR_CODE_UNREACHABLE() do { _IRR_DEBUG_BREAK_IF(1) __assume(false); } while(0) +#endif +#ifndef IRR_CODE_UNREACHABLE +#define IRR_CODE_UNREACHABLE() (void)0 +#endif + //! creates four CC codes used in Irrlicht for simple ids /** some compilers can create those by directly writing the code like 'code', but some generate warnings so we use this macro here */ diff --git a/irr/include/vector2d.h b/irr/include/vector2d.h index 182965295..63be1f246 100644 --- a/irr/include/vector2d.h +++ b/irr/include/vector2d.h @@ -131,16 +131,20 @@ public: T &operator[](u32 index) { - _IRR_DEBUG_BREAK_IF(index > 1) // access violation - - return *(&X + index); + switch (index) { + case 0: return X; + case 1: return Y; + default: IRR_CODE_UNREACHABLE(); + } } const T &operator[](u32 index) const { - _IRR_DEBUG_BREAK_IF(index > 1) // access violation - - return *(&X + index); + switch (index) { + case 0: return X; + case 1: return Y; + default: IRR_CODE_UNREACHABLE(); + } } //! sort in order X, Y. diff --git a/irr/include/vector3d.h b/irr/include/vector3d.h index 9bacf977e..780686c7a 100644 --- a/irr/include/vector3d.h +++ b/irr/include/vector3d.h @@ -117,16 +117,22 @@ public: T &operator[](u32 index) { - _IRR_DEBUG_BREAK_IF(index > 2) // access violation - - return *(&X + index); + switch (index) { + case 0: return X; + case 1: return Y; + case 2: return Z; + default: IRR_CODE_UNREACHABLE(); + } } const T &operator[](u32 index) const { - _IRR_DEBUG_BREAK_IF(index > 2) // access violation - - return *(&X + index); + switch (index) { + case 0: return X; + case 1: return Y; + case 2: return Z; + default: IRR_CODE_UNREACHABLE(); + } } //! sort in order X, Y, Z. diff --git a/irr/src/OpenGL/Driver.cpp b/irr/src/OpenGL/Driver.cpp index 0057611d4..6e30063aa 100644 --- a/irr/src/OpenGL/Driver.cpp +++ b/irr/src/OpenGL/Driver.cpp @@ -104,8 +104,7 @@ static const VertexType &getVertexTypeDescription(E_VERTEX_TYPE type) case EVT_TANGENTS: return vtTangents; default: - assert(false); - CODE_UNREACHABLE(); + IRR_CODE_UNREACHABLE(); } } diff --git a/irr/src/os.h b/irr/src/os.h index bcc096f95..238b71653 100644 --- a/irr/src/os.h +++ b/irr/src/os.h @@ -10,22 +10,6 @@ #include "ILogger.h" #include "ITimer.h" -// CODE_UNREACHABLE(): Invokes undefined behavior for unreachable code optimization -#if defined(__cpp_lib_unreachable) -#include -#define CODE_UNREACHABLE() std::unreachable() -#elif defined(__has_builtin) -#if __has_builtin(__builtin_unreachable) -#define CODE_UNREACHABLE() __builtin_unreachable() -#endif -#elif defined(_MSC_VER) -#define CODE_UNREACHABLE() __assume(false) -#endif - -#ifndef CODE_UNREACHABLE -#define CODE_UNREACHABLE() (void)0 -#endif - namespace irr { From 9d81c02f27f6b34400630a135e427b731210692c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 7 Apr 2025 20:23:13 +0200 Subject: [PATCH 125/284] Add/remove/change some log messages for clarity --- builtin/game/chat.lua | 2 ++ src/client/client.cpp | 2 ++ src/client/shader.cpp | 5 +--- src/content/mod_configuration.cpp | 2 ++ src/gettext.h | 18 +++++++---- src/nodedef.cpp | 29 +++++++----------- src/pathfinder.cpp | 4 +-- src/script/lua_api/l_mapgen.cpp | 4 +-- src/server.cpp | 26 ++++++++-------- src/server/mods.cpp | 10 +++++-- src/serverenvironment.cpp | 50 +++++++++++++------------------ 11 files changed, 73 insertions(+), 79 deletions(-) diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua index 64f14cc40..80c9a6e81 100644 --- a/builtin/game/chat.lua +++ b/builtin/game/chat.lua @@ -60,6 +60,8 @@ core.register_on_chat_message(function(name, message) param = param or "" + core.log("verbose", string.format("Handling chat command %q with params %q", cmd, param)) + -- Run core.registered_on_chatcommands callbacks. if core.run_callbacks(core.registered_on_chatcommands, 5, name, cmd, param) then return true diff --git a/src/client/client.cpp b/src/client/client.cpp index cf19d7e58..1859356b9 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -869,6 +869,8 @@ bool Client::loadMedia(const std::string &data, const std::string &filename, const char *font_ext[] = {".ttf", ".woff", NULL}; name = removeStringEnd(filename, font_ext); if (!name.empty()) { + verbosestream<<"Client: Loading file as font: \"" + << filename << "\"" << std::endl; g_fontengine->setMediaFont(name, data); return true; } diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 5924e935a..3ecbb4f38 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -451,9 +451,6 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name, u32 id = m_shaderinfo_cache.size(); m_shaderinfo_cache.push_back(info); - infostream<<"getShaderIdDirect(): " - <<"Returning id="< @@ -81,14 +82,19 @@ inline std::wstring fwgettext(const char *src, Args&&... args) template inline std::string fmtgettext(const char *format, Args&&... args) { - std::string buf; - std::size_t buf_size = 256; - buf.resize(buf_size); - format = gettext(format); - int len = porting::mt_snprintf(&buf[0], buf_size, format, std::forward(args)...); - if (len <= 0) throw std::runtime_error("gettext format error: " + std::string(format)); + std::string buf; + { + size_t default_size = strlen(format); + if (default_size < 256) + default_size = 256; + buf.resize(default_size); + } + + int len = porting::mt_snprintf(&buf[0], buf.size(), format, std::forward(args)...); + if (len <= 0) + throw std::runtime_error("gettext format error: " + std::string(format)); if ((size_t)len >= buf.size()) { buf.resize(len+1); // extra null byte porting::mt_snprintf(&buf[0], buf.size(), format, std::forward(args)...); diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 2fc3264bc..8ef6f8493 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -1299,7 +1299,7 @@ content_t NodeDefManager::set(const std::string &name, const ContentFeatures &de // Get new id id = allocateId(); if (id == CONTENT_IGNORE) { - warningstream << "NodeDefManager: Absolute " + errorstream << "NodeDefManager: Absolute " "limit reached" << std::endl; return CONTENT_IGNORE; } @@ -1307,15 +1307,14 @@ content_t NodeDefManager::set(const std::string &name, const ContentFeatures &de addNameIdMapping(id, name); } - // If there is already ContentFeatures registered for this id, clear old groups - if (id < m_content_features.size()) - eraseIdFromGroups(id); + // Clear old groups in case of re-registration + eraseIdFromGroups(id); m_content_features[id] = def; m_content_features[id].floats = itemgroup_get(def.groups, "float") != 0; m_content_lighting_flag_cache[id] = def.getLightingFlags(); - verbosestream << "NodeDefManager: registering content id \"" << id - << "\": name=\"" << def.name << "\""<= m_content_features.size()) - m_content_features.resize((u32)(i) + 1); + m_content_features.resize((size_t)(i) + 1); m_content_features[i] = f; m_content_features[i].floats = itemgroup_get(f.groups, "float") != 0; m_content_lighting_flag_cache[i] = f.getLightingFlags(); @@ -1598,13 +1596,6 @@ void NodeDefManager::resetNodeResolveState() m_pending_resolve_callbacks.clear(); } -static void removeDupes(std::vector &list) -{ - std::sort(list.begin(), list.end()); - auto new_end = std::unique(list.begin(), list.end()); - list.erase(new_end, list.end()); -} - void NodeDefManager::resolveCrossrefs() { for (ContentFeatures &f : m_content_features) { @@ -1619,7 +1610,7 @@ void NodeDefManager::resolveCrossrefs() for (const std::string &name : f.connects_to) { getIds(name, f.connects_to_ids); } - removeDupes(f.connects_to_ids); + SORT_AND_UNIQUE(f.connects_to_ids); } } diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp index 9509ba88a..d2cfebb4f 100644 --- a/src/pathfinder.cpp +++ b/src/pathfinder.cpp @@ -879,10 +879,10 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir) DEBUG_OUT("Pathfinder cost below height found" << std::endl); } else { - INFO_TARGET << "Pathfinder:" + DEBUG_OUT("Pathfinder:" " distance to surface below too big: " << (testpos.Y - pos2.Y) << " max: " << m_maxdrop - << std::endl; + << std::endl); } } else { diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 8d3df7213..49c28172b 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -428,7 +428,7 @@ size_t get_biome_list(lua_State *L, int index, if (is_single) { Biome *biome = get_or_load_biome(L, index, biomemgr); if (!biome) { - infostream << "get_biome_list: failed to get biome '" + warningstream << "get_biome_list: failed to get biome '" << (lua_isstring(L, index) ? lua_tostring(L, index) : "") << "'." << std::endl; return 1; @@ -445,7 +445,7 @@ size_t get_biome_list(lua_State *L, int index, Biome *biome = get_or_load_biome(L, -1, biomemgr); if (!biome) { fail_count++; - infostream << "get_biome_list: failed to get biome '" + warningstream << "get_biome_list: failed to get biome '" << (lua_isstring(L, -1) ? lua_tostring(L, -1) : "") << "'" << std::endl; continue; diff --git a/src/server.cpp b/src/server.cpp index 68b27513d..13e46883a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -567,8 +567,7 @@ void Server::start() { init(); - infostream << "Starting server on " << m_bind_addr.serializeString() - << "..." << std::endl; + infostream << "Starting server thread..." << std::endl; // Stop thread if already running m_thread->stop(); @@ -967,6 +966,7 @@ void Server::AsyncRunStep(float dtime, bool initial_step) // We'll log the amount of each Profiler prof; + size_t block_count = 0; std::unordered_set node_meta_updates; while (!m_unsent_map_edit_queue.empty()) { @@ -1015,6 +1015,8 @@ void Server::AsyncRunStep(float dtime, bool initial_step) break; } + block_count += event->modified_blocks.size(); + /* Set blocks not sent to far players */ @@ -1026,11 +1028,9 @@ void Server::AsyncRunStep(float dtime, bool initial_step) delete event; } - if (event_count >= 5) { - infostream << "Server: MapEditEvents:" << std::endl; - prof.print(infostream); - } else if (event_count != 0) { - verbosestream << "Server: MapEditEvents:" << std::endl; + if (event_count != 0) { + verbosestream << "Server: MapEditEvents modified total " + << block_count << " blocks:" << std::endl; prof.print(verbosestream); } @@ -1310,9 +1310,7 @@ void Server::ProcessData(NetworkPacket *pkt) } if (m_clients.getClientState(peer_id) < CS_Active) { - if (command == TOSERVER_PLAYERPOS) return; - - errorstream << "Server: Got packet command " + warningstream << "Server: Got packet command " << static_cast(command) << " for peer id " << peer_id << " but client isn't active yet. Dropping packet." << std::endl; @@ -2166,10 +2164,6 @@ void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersa } Send(&pkt); - - verbosestream << "Server::SendActiveObjectRemoveAdd(): " - << removed_objects.size() << " removed, " << added_objects.size() - << " added, packet size is " << pkt.getSize() << std::endl; } void Server::SendActiveObjectMessages(session_t peer_id, const std::string &datas, @@ -3921,7 +3915,11 @@ std::string Server::getBuiltinLuaPath() void Server::setAsyncFatalError(const std::string &error) { + // print error right here in the thread that set it, for clearer logging + infostream << "setAsyncFatalError: " << error << std::endl; + m_async_fatal_error.set(error); + // make sure server steps stop happening immediately if (m_thread) m_thread->stop(); diff --git a/src/server/mods.cpp b/src/server/mods.cpp index bfafe701d..1aee40a1a 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -42,21 +42,25 @@ void ServerModManager::loadMods(ServerScripting &script) for (const ModSpec &mod : configuration.getMods()) { infostream << mod.name << " "; } - infostream << std::endl; + // Load and run "mod" scripts + auto t0 = porting::getTimeMs(); for (const ModSpec &mod : configuration.getMods()) { mod.checkAndLog(); + auto t1 = porting::getTimeMs(); std::string script_path = mod.path + DIR_DELIM + "init.lua"; - auto t = porting::getTimeMs(); script.loadMod(script_path, mod.name); infostream << "Mod \"" << mod.name << "\" loaded after " - << (porting::getTimeMs() - t) << " ms" << std::endl; + << (porting::getTimeMs() - t1) << " ms" << std::endl; } // Run a callback when mods are loaded script.on_mods_loaded(); + + infostream << "All mods loaded after " << (porting::getTimeMs() - t0) + << " ms" << std::endl; } const ModSpec *ServerModManager::getModSpec(const std::string &modname) const diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 55306ee59..9014c7017 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -472,28 +472,25 @@ void ServerEnvironment::loadMeta() SANITY_CHECK(!m_meta_loaded); m_meta_loaded = true; + // This has nothing to do with this method but it's nice to know + infostream << "ServerEnvironment: " << m_abms.size() << " ABMs are registered" << std::endl; + std::string path = m_server->getWorldPath() + DIR_DELIM "env_meta.txt"; // If file doesn't exist, load default environment metadata if (!fs::PathExists(path)) { - infostream << "ServerEnvironment: Loading default environment metadata" - << std::endl; loadDefaultMeta(); return; } - infostream << "ServerEnvironment: Loading environment metadata" << std::endl; + infostream << "ServerEnvironment: Loading environment metadata from file" << std::endl; // Open file and deserialize - std::ifstream is(path.c_str(), std::ios_base::binary); - if (!is.good()) { - infostream << "ServerEnvironment::loadMeta(): Failed to open " - << path << std::endl; + auto is = open_ifstream(path.c_str(), true); + if (!is.good()) throw SerializationError("Couldn't load env meta"); - } Settings args("EnvArgsEnd"); - if (!args.parseConfigLines(is)) { throw SerializationError("ServerEnvironment::loadMeta(): " "EnvArgsEnd not found!"); @@ -503,32 +500,32 @@ void ServerEnvironment::loadMeta() m_game_time = args.getU64("game_time"); } catch (SettingNotFoundException &e) { // Getting this is crucial, otherwise timestamps are useless - throw SerializationError("Couldn't load env meta game_time"); + throw SerializationError("Couldn't read game_time from env meta"); } setTimeOfDay(args.exists("time_of_day") ? - // set day to early morning by default + // if it's missing for some reason, set early morning args.getU64("time_of_day") : 5250); m_last_clear_objects_time = args.exists("last_clear_objects_time") ? // If missing, do as if clearObjects was never called args.getU64("last_clear_objects_time") : 0; + m_day_count = args.exists("day_count") ? args.getU32("day_count") : 0; + std::string lbm_introduction_times; try { - u64 ver = args.getU64("lbm_introduction_times_version"); + u32 ver = args.getU32("lbm_introduction_times_version"); if (ver == 1) { lbm_introduction_times = args.get("lbm_introduction_times"); } else { - infostream << "ServerEnvironment::loadMeta(): Non-supported" + warningstream << "ServerEnvironment::loadMeta(): Unsupported" << " introduction time version " << ver << std::endl; } } catch (SettingNotFoundException &e) { // No problem, this is expected. Just continue with an empty string } m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_server, m_game_time); - - m_day_count = args.exists("day_count") ? args.getU32("day_count") : 0; } /** @@ -536,6 +533,8 @@ void ServerEnvironment::loadMeta() */ void ServerEnvironment::loadDefaultMeta() { + infostream << "ServerEnvironment: Using default environment metadata" + << std::endl; m_lbm_mgr.loadIntroductionTimes("", m_server, m_game_time); } @@ -1694,13 +1693,14 @@ void ServerEnvironment::deactivateFarObjects(const bool _force_delete) if (!force_delete && still_active) return false; - verbosestream << "ServerEnvironment::deactivateFarObjects(): " - << "deactivating object id=" << id << " on inactive block " - << blockpos_o << std::endl; - // If known by some client, don't immediately delete. bool pending_delete = (obj->m_known_by_count > 0 && !force_delete); + verbosestream << "ServerEnvironment::deactivateFarObjects(): " + << "deactivating object id=" << id << " on inactive block " + << blockpos_o << (pending_delete ? " (pending)" : "") + << std::endl; + /* Update the static data */ @@ -1759,17 +1759,9 @@ void ServerEnvironment::deactivateFarObjects(const bool _force_delete) // This ensures that LuaEntity on_deactivate is always called. obj->markForDeactivation(); - /* - If known by some client, set pending deactivation. - Otherwise delete it immediately. - */ - if (pending_delete && !force_delete) { - verbosestream << "ServerEnvironment::deactivateFarObjects(): " - << "object id=" << id << " is known by clients" - << "; not deleting yet" << std::endl; - + // If known by some client, don't delete yet. + if (pending_delete && !force_delete) return false; - } processActiveObjectRemove(obj); From 124d77082337e77a031ca681c6ec035a2ca918d9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 8 Apr 2025 12:15:16 +0200 Subject: [PATCH 126/284] Fix edge-case where manually set gameid isn't used --- src/server.cpp | 2 +- src/server/mods.cpp | 9 +-------- src/server/mods.h | 7 ++++--- src/unittest/test_servermodmanager.cpp | 24 ++++++++++++++---------- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index 13e46883a..4fe2d3c4a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -461,7 +461,7 @@ void Server::init() m_mod_storage_database = openModStorageDatabase(m_path_world); m_mod_storage_database->beginSave(); - m_modmgr = std::make_unique(m_path_world); + m_modmgr = std::make_unique(m_path_world, m_gamespec); // complain about mods with unsatisfied dependencies if (!m_modmgr->isConsistent()) { diff --git a/src/server/mods.cpp b/src/server/mods.cpp index 1aee40a1a..e2d5debba 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -15,15 +15,8 @@ * All new calls to this class must be tested in test_servermodmanager.cpp */ -/** - * Creates a ServerModManager which targets worldpath - * @param worldpath - */ -ServerModManager::ServerModManager(const std::string &worldpath): - configuration() +ServerModManager::ServerModManager(const std::string &worldpath, SubgameSpec gamespec) { - SubgameSpec gamespec = findWorldSubgame(worldpath); - // Add all game mods and all world mods configuration.addGameMods(gamespec); configuration.addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods"); diff --git a/src/server/mods.h b/src/server/mods.h index a82d4d572..b10b232b2 100644 --- a/src/server/mods.h +++ b/src/server/mods.h @@ -21,10 +21,11 @@ class ServerModManager public: /** - * Creates a ServerModManager which targets worldpath - * @param worldpath + * Creates a ServerModManager + * @param worldpath path to world + * @param gamespec game used by the world */ - ServerModManager(const std::string &worldpath); + ServerModManager(const std::string &worldpath, SubgameSpec gamespec); /** * Creates an empty ServerModManager. For testing purposes. diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index 500d0f288..5689658d9 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -20,6 +20,10 @@ public: std::string m_worlddir; + static ServerModManager makeManager(const std::string &worldpath) { + return ServerModManager(worldpath, findWorldSubgame(worldpath)); + } + void testCreation(); void testIsConsistent(); void testUnsatisfiedMods(); @@ -80,31 +84,31 @@ void TestServerModManager::testCreation() world_config.set("load_mod_test_mod", "true"); UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true); - ServerModManager sm(m_worlddir); + auto sm = makeManager(m_worlddir); } void TestServerModManager::testGetModsWrongDir() { // Test in non worlddir to ensure no mods are found - ServerModManager sm(m_worlddir + DIR_DELIM + ".."); + auto sm = makeManager(m_worlddir + DIR_DELIM ".."); UASSERTEQ(bool, sm.getMods().empty(), true); } void TestServerModManager::testUnsatisfiedMods() { - ServerModManager sm(m_worlddir); + auto sm = makeManager(m_worlddir); UASSERTEQ(bool, sm.getUnsatisfiedMods().empty(), true); } void TestServerModManager::testIsConsistent() { - ServerModManager sm(m_worlddir); + auto sm = makeManager(m_worlddir); UASSERTEQ(bool, sm.isConsistent(), true); } void TestServerModManager::testGetMods() { - ServerModManager sm(m_worlddir); + auto sm = makeManager(m_worlddir); const auto &mods = sm.getMods(); // `ls ./games/devtest/mods | wc -l` + 1 (test mod) UASSERTEQ(std::size_t, mods.size(), 34 + 1); @@ -132,14 +136,14 @@ void TestServerModManager::testGetMods() void TestServerModManager::testGetModspec() { - ServerModManager sm(m_worlddir); + auto sm = makeManager(m_worlddir); UASSERTEQ(const ModSpec *, sm.getModSpec("wrongmod"), NULL); UASSERT(sm.getModSpec("basenodes") != NULL); } void TestServerModManager::testGetModNamesWrongDir() { - ServerModManager sm(m_worlddir + DIR_DELIM + ".."); + auto sm = makeManager(m_worlddir + DIR_DELIM ".."); std::vector result; sm.getModNames(result); UASSERTEQ(bool, result.empty(), true); @@ -147,7 +151,7 @@ void TestServerModManager::testGetModNamesWrongDir() void TestServerModManager::testGetModNames() { - ServerModManager sm(m_worlddir); + auto sm = makeManager(m_worlddir); std::vector result; sm.getModNames(result); UASSERTEQ(bool, result.empty(), false); @@ -156,7 +160,7 @@ void TestServerModManager::testGetModNames() void TestServerModManager::testGetModMediaPathsWrongDir() { - ServerModManager sm(m_worlddir + DIR_DELIM + ".."); + auto sm = makeManager(m_worlddir + DIR_DELIM ".."); std::vector result; sm.getModsMediaPaths(result); UASSERTEQ(bool, result.empty(), true); @@ -164,7 +168,7 @@ void TestServerModManager::testGetModMediaPathsWrongDir() void TestServerModManager::testGetModMediaPaths() { - ServerModManager sm(m_worlddir); + auto sm = makeManager(m_worlddir); std::vector result; sm.getModsMediaPaths(result); UASSERTEQ(bool, result.empty(), false); From 372e37faf255b60415d6fe8969d60d2f79c24c4d Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 8 Apr 2025 18:19:08 +0200 Subject: [PATCH 127/284] Minor correction to how buildbot finds packages --- util/buildbot/common.sh | 2 +- util/buildbot/toolchain_i686-w64-mingw32.cmake | 1 + util/buildbot/toolchain_x86_64-w64-mingw32.cmake | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/util/buildbot/common.sh b/util/buildbot/common.sh index 27815f349..08d01da44 100644 --- a/util/buildbot/common.sh +++ b/util/buildbot/common.sh @@ -88,7 +88,7 @@ add_cmake_libs () { -DJPEG_INCLUDE_DIR=$libdir/libjpeg/include -DJPEG_DLL="$(_dlls $libdir/libjpeg/bin/libjpeg*)" - -DCMAKE_PREFIX_PATH=$libdir/sdl2/lib/cmake + -DSDL2_DIR=$libdir/sdl2/lib/cmake/SDL2 -DSDL2_DLL="$(_dlls $libdir/sdl2/bin/*)" -DZLIB_INCLUDE_DIR=$libdir/zlib/include diff --git a/util/buildbot/toolchain_i686-w64-mingw32.cmake b/util/buildbot/toolchain_i686-w64-mingw32.cmake index 015682394..a8649ec51 100644 --- a/util/buildbot/toolchain_i686-w64-mingw32.cmake +++ b/util/buildbot/toolchain_i686-w64-mingw32.cmake @@ -15,3 +15,4 @@ SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/util/buildbot/toolchain_x86_64-w64-mingw32.cmake b/util/buildbot/toolchain_x86_64-w64-mingw32.cmake index 113cc0e78..d09322bae 100644 --- a/util/buildbot/toolchain_x86_64-w64-mingw32.cmake +++ b/util/buildbot/toolchain_x86_64-w64-mingw32.cmake @@ -15,3 +15,4 @@ SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) From 78293404c74e6c920f530046bc05dddd0a616959 Mon Sep 17 00:00:00 2001 From: Erich Schubert Date: Thu, 10 Apr 2025 14:39:40 +0200 Subject: [PATCH 128/284] Rename perlin noise to value noise (#15858) --- .luacheckrc | 2 +- builtin/emerge/env.lua | 16 ++-- builtin/game/deprecated.lua | 7 ++ doc/lua_api.md | 103 ++++++++++++++----------- src/client/clouds.cpp | 2 +- src/mapgen/cavegen.cpp | 8 +- src/mapgen/dungeongen.cpp | 2 +- src/mapgen/mapgen.cpp | 4 +- src/mapgen/mapgen_carpathian.cpp | 50 ++++++------- src/mapgen/mapgen_flat.cpp | 4 +- src/mapgen/mapgen_fractal.cpp | 4 +- src/mapgen/mapgen_v5.cpp | 12 +-- src/mapgen/mapgen_v6.cpp | 44 +++++------ src/mapgen/mapgen_v7.cpp | 32 ++++---- src/mapgen/mapgen_valleys.cpp | 24 +++--- src/mapgen/mg_biome.cpp | 16 ++-- src/mapgen/mg_decoration.cpp | 2 +- src/mapgen/mg_ore.cpp | 22 +++--- src/noise.cpp | 52 ++++++------- src/noise.h | 36 ++++----- src/script/lua_api/l_env.cpp | 24 +++--- src/script/lua_api/l_env.h | 12 +-- src/script/lua_api/l_noise.cpp | 124 +++++++++++++++---------------- src/script/lua_api/l_noise.h | 24 +++--- src/script/scripting_emerge.cpp | 4 +- src/script/scripting_server.cpp | 8 +- src/unittest/test_noise.cpp | 10 +-- 27 files changed, 339 insertions(+), 309 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index 8121f6f53..de45f0413 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -19,7 +19,7 @@ read_globals = { "VoxelManip", "profiler", "Settings", - "PerlinNoise", "PerlinNoiseMap", + "ValueNoise", "ValueNoiseMap", string = {fields = {"split", "trim"}}, table = {fields = {"copy", "copy_with_metatables", "getn", "indexof", "keyof", "insert_all"}}, diff --git a/builtin/emerge/env.lua b/builtin/emerge/env.lua index 5beb6285c..6d3fb0a3b 100644 --- a/builtin/emerge/env.lua +++ b/builtin/emerge/env.lua @@ -33,7 +33,7 @@ function core.get_node(pos) return core.vmanip:get_node_at(pos) end -function core.get_perlin(seed, octaves, persist, spread) +function core.get_value_noise(seed, octaves, persist, spread) local params if type(seed) == "table" then params = table.copy(seed) @@ -47,12 +47,18 @@ function core.get_perlin(seed, octaves, persist, spread) } end params.seed = core.get_seed(params.seed) -- add mapgen seed - return PerlinNoise(params) + return ValueNoise(params) end - -function core.get_perlin_map(params, size) +function core.get_value_noise_map(params, size) local params2 = table.copy(params) params2.seed = core.get_seed(params.seed) -- add mapgen seed - return PerlinNoiseMap(params2, size) + return ValueNoiseMap(params2, size) end + +-- deprecated as of 5.12, as it was not Perlin noise +-- but with no warnings (yet) for compatibility +core.get_perlin = core.get_value_noise +core.get_perlin_map = core.get_value_noise_map +PerlinNoise = ValueNoise +PerlinNoiseMap = ValueNoiseMap diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua index 87b785995..48e825ea2 100644 --- a/builtin/game/deprecated.lua +++ b/builtin/game/deprecated.lua @@ -61,3 +61,10 @@ function core.register_on_auth_fail(func) end end) end + +-- deprecated as of 5.12, as it was not Perlin noise +-- but with no warnings (yet) for compatibility +core.get_perlin = core.get_value_noise +core.get_perlin_map = core.get_value_noise_map +PerlinNoise = ValueNoise +PerlinNoiseMap = ValueNoiseMap diff --git a/doc/lua_api.md b/doc/lua_api.md index 12b412dbe..882bfe341 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -4520,22 +4520,25 @@ textdomain match the mod name, but this isn't required. -Perlin noise -============ +Fractal value noise +=================== + +Value noise creates a continuously-varying value depending on the input values. +It is similar to Perlin noise, but may exhibit more geometric artifacts, +as it interpolates between values and not between gradients as in Perlin noise. -Perlin noise creates a continuously-varying value depending on the input values. Usually in Luanti the input values are either 2D or 3D coordinates in nodes. The result is used during map generation to create the terrain shape, vary heat and humidity to distribute biomes, vary the density of decorations or vary the structure of ores. -Structure of perlin noise -------------------------- +Structure of fractal value noise +-------------------------------- An 'octave' is a simple noise generator that outputs a value between -1 and 1. The smooth wavy noise it generates has a single characteristic scale, almost like a 'wavelength', so on its own does not create fine detail. -Due to this perlin noise combines several octaves to create variation on +Due to this fractal value noise combines several octaves to create variation on multiple scales. Each additional octave has a smaller 'wavelength' than the previous. @@ -4543,7 +4546,7 @@ This combination results in noise varying very roughly between -2.0 and 2.0 and with an average value of 0.0, so `scale` and `offset` are then used to multiply and offset the noise variation. -The final perlin noise variation is created as follows: +The final fractal value noise variation is created as follows: noise = offset + scale * (octave1 + octave2 * persistence + @@ -4661,7 +4664,7 @@ with restraint. #### `absvalue` The absolute value of each octave's noise variation is used when combining the -octaves. The final perlin noise variation is created as follows: +octaves. The final value noise variation is created as follows: noise = offset + scale * (abs(octave1) + abs(octave2) * persistence + @@ -4671,7 +4674,7 @@ noise = offset + scale * (abs(octave1) + ### Format example -For 2D or 3D perlin noise or perlin noise maps: +For 2D or 3D value noise or value noise maps: ```lua np_terrain = { @@ -4706,13 +4709,13 @@ All default ores are of the uniformly-distributed scatter type. Randomly chooses a location and generates a cluster of ore. -If `noise_params` is specified, the ore will be placed if the 3D perlin noise +If `noise_params` is specified, the ore will be placed if the 3D value noise at that point is greater than the `noise_threshold`, giving the ability to create a non-equal distribution of ore. ### `sheet` -Creates a sheet of ore in a blob shape according to the 2D perlin noise +Creates a sheet of ore in a blob shape according to the 2D value noise described by `noise_params` and `noise_threshold`. This is essentially an improved version of the so-called "stratus" ore seen in some unofficial mods. @@ -4745,14 +4748,14 @@ noise parameters `np_puff_top` and `np_puff_bottom`, respectively. ### `blob` -Creates a deformed sphere of ore according to 3d perlin noise described by +Creates a deformed sphere of ore according to 3d value noise described by `noise_params`. The maximum size of the blob is `clust_size`, and `clust_scarcity` has the same meaning as with the `scatter` type. ### `vein` Creates veins of ore varying in density by according to the intersection of two -instances of 3d perlin noise with different seeds, both described by +instances of 3d value noise with different seeds, both described by `noise_params`. `random_factor` varies the influence random chance has on placement of an ore @@ -4787,8 +4790,8 @@ computationally expensive than any other ore. Creates a single undulating ore stratum that is continuous across mapchunk borders and horizontally spans the world. -The 2D perlin noise described by `noise_params` defines the Y coordinate of -the stratum midpoint. The 2D perlin noise described by `np_stratum_thickness` +The 2D value noise described by `noise_params` defines the Y coordinate of +the stratum midpoint. The 2D value noise described by `np_stratum_thickness` defines the stratum's vertical thickness (in units of nodes). Due to being continuous across mapchunk borders the stratum's vertical thickness is unlimited. @@ -5036,7 +5039,7 @@ and the array index for a position p contained completely in p1..p2 is: `(p.Z - p1.Z) * Ny * Nx + (p.Y - p1.Y) * Nx + (p.X - p1.X) + 1` Note that this is the same "flat 3D array" format as -`PerlinNoiseMap:get3dMap_flat()`. +`ValueNoiseMap:get3dMap_flat()`. VoxelArea objects (see section [`VoxelArea`]) can be used to simplify calculation of the index for a single point in a flat VoxelManip array. @@ -5227,7 +5230,7 @@ The coordinates are *inclusive*, like most other things in Luanti. * The position (x, y, z) is not checked for being inside the area volume, being outside can cause an incorrect index result. * Useful for things like `VoxelManip`, raw Schematic specifiers, - `PerlinNoiseMap:get2d`/`3dMap`, and so on. + `ValueNoiseMap:get2d`/`3dMap`, and so on. * `indexp(p)`: same functionality as `index(x, y, z)` but takes a vector. * As with `index(x, y, z)`, the components of `p` must be integers, and `p` is not checked for being inside the area volume. @@ -6518,12 +6521,18 @@ Environment access * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` * Return value: Table with all node positions with a node air above * Area volume is limited to 150,000,000 nodes -* `core.get_perlin(noiseparams)` - * Return world-specific perlin noise. +* `core.get_value_noise(noiseparams)` + * Return world-specific value noise. * The actual seed used is the noiseparams seed plus the world seed. +* `core.get_value_noise(seeddiff, octaves, persistence, spread)` + * Deprecated: use `core.get_value_noise(noiseparams)` instead. + * Return world-specific value noise +* `core.get_perlin(noiseparams)` + * Deprecated: use `core.get_value_noise(noiseparams)` instead. + * Return world-specific value noise (was not Perlin noise) * `core.get_perlin(seeddiff, octaves, persistence, spread)` - * Deprecated: use `core.get_perlin(noiseparams)` instead. - * Return world-specific perlin noise. + * Deprecated: use `core.get_value_noise(noiseparams)` instead. + * Return world-specific value noise (was not Perlin noise) * `core.get_voxel_manip([pos1, pos2])` * Return voxel manipulator object. * Loads the manipulator from the map if positions are passed. @@ -7098,8 +7107,8 @@ Classes: * `AreaStore` * `ItemStack` -* `PerlinNoise` -* `PerlinNoiseMap` +* `ValueNoise` +* `ValueNoiseMap` * `PseudoRandom` * `PcgRandom` * `SecureRandom` @@ -7111,8 +7120,8 @@ Classes: Class instances that can be transferred between environments: * `ItemStack` -* `PerlinNoise` -* `PerlinNoiseMap` +* `ValueNoise` +* `ValueNoiseMap` * `VoxelManip` Functions: @@ -7178,8 +7187,8 @@ Classes: * `AreaStore` * `ItemStack` -* `PerlinNoise` -* `PerlinNoiseMap` +* `ValueNoise` +* `ValueNoiseMap` * `PseudoRandom` * `PcgRandom` * `SecureRandom` @@ -9005,33 +9014,41 @@ offering very strong randomness. * `get_state()`: return generator state encoded in string * `set_state(state_string)`: restore generator state from encoded string -`PerlinNoise` +`ValueNoise` ------------- -A perlin noise generator. -It can be created via `PerlinNoise()` or `core.get_perlin()`. -For `core.get_perlin()`, the actual seed used is the noiseparams seed +A value noise generator. +It can be created via `ValueNoise()` or `core.get_value_noise()`. +For legacy reasons, it can also be created via `PerlinNoise()` or `core.get_perlin()`, +but the implemented noise is not Perlin noise. +For `core.get_value_noise()`, the actual seed used is the noiseparams seed plus the world seed, to create world-specific noise. -`PerlinNoise(noiseparams)` -`PerlinNoise(seed, octaves, persistence, spread)` (Deprecated). +* `ValueNoise(noiseparams) +* `ValueNoise(seed, octaves, persistence, spread)` (Deprecated) +* `PerlinNoise(noiseparams)` (Deprecated) +* `PerlinNoise(seed, octaves, persistence, spread)` (Deprecated) -`core.get_perlin(noiseparams)` -`core.get_perlin(seeddiff, octaves, persistence, spread)` (Deprecated). +* `core.get_value_noise(noiseparams)` +* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (Deprecated) +* `core.get_perlin(noiseparams)` (Deprecated) +* `core.get_perlin(seeddiff, octaves, persistence, spread)` (Deprecated) ### Methods * `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}` * `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}` -`PerlinNoiseMap` +`ValueNoiseMap` ---------------- -A fast, bulk perlin noise generator. +A fast, bulk noise generator. -It can be created via `PerlinNoiseMap(noiseparams, size)` or -`core.get_perlin_map(noiseparams, size)`. -For `core.get_perlin_map()`, the actual seed used is the noiseparams seed +It can be created via `ValueNoiseMap(noiseparams, size)` or +`core.get_value_noise_map(noiseparams, size)`. +For legacy reasons, it can also be created via `PerlinNoiseMap(noiseparams, size)` +or `core.get_perlin_map(noiseparams, size)`, but it is not Perlin noise. +For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed plus the world seed, to create world-specific noise. Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted @@ -9058,7 +9075,7 @@ table. * `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array, returns a slice of the most recently computed noise results. The result slice begins at coordinates `slice_offset` and takes a chunk of `slice_size`. - E.g. to grab a 2-slice high horizontal 2d plane of noise starting at buffer + E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer offset y = 20: `noisevals = noise:get_map_slice({y=20}, {y=2})` It is important to note that `slice_offset` offset coordinates begin at 1, @@ -10746,7 +10763,7 @@ See [Ores] section above for essential information. octaves = 3, persistence = 0.7 }, - -- NoiseParams structure describing one of the perlin noises used for + -- NoiseParams structure describing one of the noises used for -- ore distribution. -- Needed by "sheet", "puff", "blob" and "vein" ores. -- Omit from "scatter" ore for a uniform ore distribution. @@ -10933,7 +10950,7 @@ See [Decoration types]. Used by `core.register_decoration`. lacunarity = 2.0, flags = "absvalue" }, - -- NoiseParams structure describing the perlin noise used for decoration + -- NoiseParams structure describing the noise used for decoration -- distribution. -- A noise value is calculated for each square division and determines -- 'decorations per surface node' within each division. diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index 18b1d281c..b7f6a9a64 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -474,7 +474,7 @@ void Clouds::readSettings() bool Clouds::gridFilled(int x, int y) const { float cloud_size_noise = cloud_size / (BS * 200.f); - float noise = noise2d_perlin( + float noise = noise2d_fractal( (float)x * cloud_size_noise, (float)y * cloud_size_noise, m_seed, 3, 0.5); diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp index 0c3ec1f81..f31ec21fb 100644 --- a/src/mapgen/cavegen.cpp +++ b/src/mapgen/cavegen.cpp @@ -61,8 +61,8 @@ void CavesNoiseIntersection::generateCaves(MMVManip *vm, assert(vm); assert(biomemap); - noise_cave1->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); - noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); + noise_cave1->noiseMap3D(nmin.X, nmin.Y - 1, nmin.Z); + noise_cave2->noiseMap3D(nmin.X, nmin.Y - 1, nmin.Z); const v3s32 &em = vm->m_area.getExtent(); u32 index2d = 0; // Biomemap index @@ -218,7 +218,7 @@ bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax) assert(vm); // Calculate noise - noise_cavern->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); + noise_cavern->noiseMap3D(nmin.X, nmin.Y - 1, nmin.Z); // Cache cavern_amp values float *cavern_amp = new float[m_csize.Y + 1]; @@ -532,7 +532,7 @@ void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) // If cave liquid not defined by biome, fallback to old hardcoded behavior. // 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, + float nval = NoiseFractal3D(np_caveliquids, startp.X, startp.Y, startp.Z, seed); liquidnode = (nval < 0.40f && node_max.Y < water_level - 256) ? lavanode : waternode; diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp index 249c462ba..f99ed8348 100644 --- a/src/mapgen/dungeongen.cpp +++ b/src/mapgen/dungeongen.cpp @@ -114,7 +114,7 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) 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) + if (NoiseFractal3D(&dp.np_alt_wall, x, y, z, blockseed) > 0.0f) vm->m_data[i].setContent(dp.c_alt_wall); } i++; diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index 83c56ee8d..e8c60c0de 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -632,7 +632,7 @@ void MapgenBasic::generateBiomes() const v3s32 &em = vm->m_area.getExtent(); u32 index = 0; - noise_filler_depth->perlinMap2D(node_min.X, node_min.Z); + noise_filler_depth->noiseMap2D(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++) { @@ -897,7 +897,7 @@ void MapgenBasic::generateDungeons(s16 max_stone_y) return; u16 num_dungeons = std::fmax(std::floor( - NoisePerlin3D(&np_dungeons, node_min.X, node_min.Y, node_min.Z, seed)), 0.0f); + NoiseFractal3D(&np_dungeons, node_min.X, node_min.Y, node_min.Z, seed)), 0.0f); if (num_dungeons == 0) return; diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp index 5c7f9e1b1..7d00b3d38 100644 --- a/src/mapgen/mapgen_carpathian.cpp +++ b/src/mapgen/mapgen_carpathian.cpp @@ -329,34 +329,34 @@ 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)) - + float river = std::fabs(NoiseFractal2D(&noise_rivers->np, p.X, p.Y, seed)) - river_width; if (river < 0.0f) return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point } - float height1 = NoisePerlin2D(&noise_height1->np, p.X, p.Y, seed); - float height2 = NoisePerlin2D(&noise_height2->np, p.X, p.Y, seed); - float height3 = NoisePerlin2D(&noise_height3->np, p.X, p.Y, seed); - float height4 = NoisePerlin2D(&noise_height4->np, p.X, p.Y, seed); + float height1 = NoiseFractal2D(&noise_height1->np, p.X, p.Y, seed); + float height2 = NoiseFractal2D(&noise_height2->np, p.X, p.Y, seed); + float height3 = NoiseFractal2D(&noise_height3->np, p.X, p.Y, seed); + float height4 = NoiseFractal2D(&noise_height4->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 hterabs = std::fabs(NoiseFractal2D(&noise_hills_terrain->np, p.X, p.Y, seed)); + float n_hills = NoiseFractal2D(&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 n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, p.X, p.Y, seed); + float rterabs = std::fabs(NoiseFractal2D(&noise_ridge_terrain->np, p.X, p.Y, seed)); + float n_ridge_mnt = NoiseFractal2D(&noise_ridge_mnt->np, p.X, p.Y, seed); float ridge_mnt = rterabs * rterabs * rterabs * (1.0f - std::fabs(n_ridge_mnt)); - float sterabs = std::fabs(NoisePerlin2D(&noise_step_terrain->np, p.X, p.Y, seed)); - float n_step_mnt = NoisePerlin2D(&noise_step_mnt->np, p.X, p.Y, seed); + float sterabs = std::fabs(NoiseFractal2D(&noise_step_terrain->np, p.X, p.Y, seed)); + float n_step_mnt = NoiseFractal2D(&noise_step_mnt->np, p.X, p.Y, seed); float step_mnt = sterabs * sterabs * sterabs * getSteps(n_step_mnt); float valley = 1.0f; 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(NoiseFractal2D(&noise_rivers->np, p.X, p.Y, seed)) - river_width; if (river <= valley_width) { // Within river valley if (river < 0.0f) { @@ -376,7 +376,7 @@ int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p) u8 cons_non_solid = 0; // consecutive non-solid nodes for (s16 y = water_level; y <= water_level + 32; y++) { - float mnt_var = NoisePerlin3D(&noise_mnt_var->np, p.X, y, p.Y, seed); + float mnt_var = NoiseFractal3D(&noise_mnt_var->np, p.X, y, p.Y, seed); float hill1 = getLerp(height1, height2, mnt_var); float hill2 = getLerp(height3, height4, mnt_var); float hill3 = getLerp(height3, height2, mnt_var); @@ -429,20 +429,20 @@ int MapgenCarpathian::generateTerrain() MapNode mn_water(c_water_source); // Calculate noise for terrain generation - noise_height1->perlinMap2D(node_min.X, node_min.Z); - noise_height2->perlinMap2D(node_min.X, node_min.Z); - noise_height3->perlinMap2D(node_min.X, node_min.Z); - noise_height4->perlinMap2D(node_min.X, node_min.Z); - noise_hills_terrain->perlinMap2D(node_min.X, node_min.Z); - noise_ridge_terrain->perlinMap2D(node_min.X, node_min.Z); - noise_step_terrain->perlinMap2D(node_min.X, node_min.Z); - noise_hills->perlinMap2D(node_min.X, node_min.Z); - noise_ridge_mnt->perlinMap2D(node_min.X, node_min.Z); - noise_step_mnt->perlinMap2D(node_min.X, node_min.Z); - noise_mnt_var->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_height1->noiseMap2D(node_min.X, node_min.Z); + noise_height2->noiseMap2D(node_min.X, node_min.Z); + noise_height3->noiseMap2D(node_min.X, node_min.Z); + noise_height4->noiseMap2D(node_min.X, node_min.Z); + noise_hills_terrain->noiseMap2D(node_min.X, node_min.Z); + noise_ridge_terrain->noiseMap2D(node_min.X, node_min.Z); + noise_step_terrain->noiseMap2D(node_min.X, node_min.Z); + noise_hills->noiseMap2D(node_min.X, node_min.Z); + noise_ridge_mnt->noiseMap2D(node_min.X, node_min.Z); + noise_step_mnt->noiseMap2D(node_min.X, node_min.Z); + noise_mnt_var->noiseMap3D(node_min.X, node_min.Y - 1, node_min.Z); if (spflags & MGCARPATHIAN_RIVERS) - noise_rivers->perlinMap2D(node_min.X, node_min.Z); + noise_rivers->noiseMap2D(node_min.X, node_min.Z); //// Place nodes const v3s32 &em = vm->m_area.getExtent(); diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp index c0face7b9..cbd5202d0 100644 --- a/src/mapgen/mapgen_flat.cpp +++ b/src/mapgen/mapgen_flat.cpp @@ -164,7 +164,7 @@ 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) : + NoiseFractal2D(&noise_terrain->np, p.X, p.Y, seed) : 0.0f; if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) { @@ -284,7 +284,7 @@ s16 MapgenFlat::generateTerrain() bool use_noise = (spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS); if (use_noise) - noise_terrain->perlinMap2D(node_min.X, node_min.Z); + noise_terrain->noiseMap2D(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++) { diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp index 0ac72ac08..71c49b035 100644 --- a/src/mapgen/mapgen_fractal.cpp +++ b/src/mapgen/mapgen_fractal.cpp @@ -177,7 +177,7 @@ int MapgenFractal::getSpawnLevelAtPoint(v2s16 p) // If terrain present, don't start search below terrain or water level if (noise_seabed) { - s16 seabed_level = NoisePerlin2D(&noise_seabed->np, p.X, p.Y, seed); + s16 seabed_level = NoiseFractal2D(&noise_seabed->np, p.X, p.Y, seed); search_start = MYMAX(search_start, MYMAX(seabed_level, water_level)); } @@ -409,7 +409,7 @@ s16 MapgenFractal::generateTerrain() u32 index2d = 0; if (noise_seabed) - noise_seabed->perlinMap2D(node_min.X, node_min.Z); + noise_seabed->noiseMap2D(node_min.X, 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++) { diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp index 9cfd0cf9d..6d42f5833 100644 --- a/src/mapgen/mapgen_v5.cpp +++ b/src/mapgen/mapgen_v5.cpp @@ -150,12 +150,12 @@ void MapgenV5Params::setDefaultSettings(Settings *settings) int MapgenV5::getSpawnLevelAtPoint(v2s16 p) { - float f = 0.55 + NoisePerlin2D(&noise_factor->np, p.X, p.Y, seed); + float f = 0.55 + NoiseFractal2D(&noise_factor->np, p.X, p.Y, seed); if (f < 0.01) f = 0.01; else if (f >= 1.0) f *= 1.6; - float h = NoisePerlin2D(&noise_height->np, p.X, p.Y, seed); + float h = NoiseFractal2D(&noise_height->np, p.X, p.Y, seed); // noise_height 'offset' is the average level of terrain. At least 50% of // terrain will be below this. @@ -166,7 +166,7 @@ int MapgenV5::getSpawnLevelAtPoint(v2s16 p) // 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_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed); + float n_ground = NoiseFractal3D(&noise_ground->np, p.X, y, p.Y, seed); if (n_ground * f > y - h) { // If solid if (y < water_level || y > max_spawn_y) @@ -272,9 +272,9 @@ int MapgenV5::generateBaseTerrain() u32 index2d = 0; int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; - noise_factor->perlinMap2D(node_min.X, node_min.Z); - noise_height->perlinMap2D(node_min.X, node_min.Z); - noise_ground->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_factor->noiseMap2D(node_min.X, node_min.Z); + noise_height->noiseMap2D(node_min.X, node_min.Z); + noise_ground->noiseMap3D(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++) { diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index a698494cd..2b4d37c6b 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -289,13 +289,13 @@ float MapgenV6::baseTerrainLevelFromNoise(v2s16 p) if (spflags & MGV6_FLAT) return water_level; - float terrain_base = NoisePerlin2D_PO(&noise_terrain_base->np, + float terrain_base = NoiseFractal2D_PO(&noise_terrain_base->np, p.X, 0.5, p.Y, 0.5, seed); - float terrain_higher = NoisePerlin2D_PO(&noise_terrain_higher->np, + float terrain_higher = NoiseFractal2D_PO(&noise_terrain_higher->np, p.X, 0.5, p.Y, 0.5, seed); - float steepness = NoisePerlin2D_PO(&noise_steepness->np, + float steepness = NoiseFractal2D_PO(&noise_steepness->np, p.X, 0.5, p.Y, 0.5, seed); - float height_select = NoisePerlin2D_PO(&noise_height_select->np, + float height_select = NoiseFractal2D_PO(&noise_height_select->np, p.X, 0.5, p.Y, 0.5, seed); return baseTerrainLevel(terrain_base, terrain_higher, @@ -355,7 +355,7 @@ BiomeV6Type MapgenV6::getBiome(v2s16 p) float MapgenV6::getHumidity(v2s16 p) { - /*double noise = noise2d_perlin( + /*double noise = noise2d_fractal( 0.5+(float)p.X/500, 0.5+(float)p.Y/500, seed+72384, 4, 0.66); noise = (noise + 1.0)/2.0;*/ @@ -374,11 +374,11 @@ float MapgenV6::getHumidity(v2s16 p) float MapgenV6::getTreeAmount(v2s16 p) { - /*double noise = noise2d_perlin( + /*double noise = noise2d_fractal( 0.5+(float)p.X/125, 0.5+(float)p.Y/125, seed+2, 4, 0.66);*/ - float noise = NoisePerlin2D(np_trees, p.X, p.Y, seed); + float noise = NoiseFractal2D(np_trees, p.X, p.Y, seed); float zeroval = -0.39; if (noise < zeroval) return 0; @@ -389,11 +389,11 @@ float MapgenV6::getTreeAmount(v2s16 p) bool MapgenV6::getHaveAppleTree(v2s16 p) { - /*is_apple_tree = noise2d_perlin( + /*is_apple_tree = noise2d_fractal( 0.5+(float)p.X/100, 0.5+(float)p.Z/100, data->seed+342902, 3, 0.45) > 0.2;*/ - float noise = NoisePerlin2D(np_apple_trees, p.X, p.Y, seed); + float noise = NoiseFractal2D(np_apple_trees, p.X, p.Y, seed); return noise > 0.2; } @@ -404,7 +404,7 @@ float MapgenV6::getMudAmount(int index) if (spflags & MGV6_FLAT) return MGV6_AVERAGE_MUD_AMOUNT; - /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin( + /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_fractal( 0.5+(float)p.X/200, 0.5+(float)p.Y/200, seed+91013, 3, 0.55));*/ @@ -415,7 +415,7 @@ float MapgenV6::getMudAmount(int index) bool MapgenV6::getHaveBeach(int index) { // Determine whether to have sand here - /*double sandnoise = noise2d_perlin( + /*double sandnoise = noise2d_fractal( 0.2+(float)p2d.X/250, 0.7+(float)p2d.Y/250, seed+59420, 3, 0.50);*/ @@ -427,7 +427,7 @@ bool MapgenV6::getHaveBeach(int index) BiomeV6Type MapgenV6::getBiome(int index, v2s16 p) { // Just do something very simple as for now - /*double d = noise2d_perlin( + /*double d = noise2d_fractal( 0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250, seed+9130, 3, 0.50);*/ @@ -547,7 +547,7 @@ void MapgenV6::makeChunk(BlockMakeData *data) 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); + NoiseFractal3D(&np_dungeons, node_min.X, node_min.Y, node_min.Z, seed)), 0.0f); if (num_dungeons >= 1) { PseudoRandom ps(blockseed + 4713); @@ -633,17 +633,17 @@ void MapgenV6::calculateNoise() int fz = full_node_min.Z; if (!(spflags & MGV6_FLAT)) { - noise_terrain_base->perlinMap2D_PO(x, 0.5, z, 0.5); - noise_terrain_higher->perlinMap2D_PO(x, 0.5, z, 0.5); - noise_steepness->perlinMap2D_PO(x, 0.5, z, 0.5); - noise_height_select->perlinMap2D_PO(x, 0.5, z, 0.5); - noise_mud->perlinMap2D_PO(x, 0.5, z, 0.5); + noise_terrain_base->noiseMap2D_PO(x, 0.5, z, 0.5); + noise_terrain_higher->noiseMap2D_PO(x, 0.5, z, 0.5); + noise_steepness->noiseMap2D_PO(x, 0.5, z, 0.5); + noise_height_select->noiseMap2D_PO(x, 0.5, z, 0.5); + noise_mud->noiseMap2D_PO(x, 0.5, z, 0.5); } - noise_beach->perlinMap2D_PO(x, 0.2, z, 0.7); + noise_beach->noiseMap2D_PO(x, 0.2, z, 0.7); - noise_biome->perlinMap2D_PO(fx, 0.6, fz, 0.2); - noise_humidity->perlinMap2D_PO(fx, 0.0, fz, 0.0); + noise_biome->noiseMap2D_PO(fx, 0.6, fz, 0.2); + noise_humidity->noiseMap2D_PO(fx, 0.0, fz, 0.0); // Humidity map does not need range limiting 0 to 1, // only humidity at point does } @@ -1075,7 +1075,7 @@ void MapgenV6::growGrass() // Add surface nodes void MapgenV6::generateCaves(int max_stone_y) { - float cave_amount = NoisePerlin2D(np_cave, node_min.X, node_min.Y, seed); + float cave_amount = NoiseFractal2D(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; cave_amount = MYMAX(0.0, cave_amount); diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp index 8fc5b0c6f..fdd971d28 100644 --- a/src/mapgen/mapgen_v7.cpp +++ b/src/mapgen/mapgen_v7.cpp @@ -251,7 +251,7 @@ 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) * + float uwatern = NoiseFractal2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2.0f; if (std::fabs(uwatern) <= width) return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point @@ -390,16 +390,16 @@ void MapgenV7::makeChunk(BlockMakeData *data) float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z) { - float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed); + float hselect = NoiseFractal2D(&noise_height_select->np, x, z, seed); hselect = rangelim(hselect, 0.0f, 1.0f); - float persist = NoisePerlin2D(&noise_terrain_persist->np, x, z, seed); + float persist = NoiseFractal2D(&noise_terrain_persist->np, x, z, seed); noise_terrain_base->np.persist = persist; - float height_base = NoisePerlin2D(&noise_terrain_base->np, x, z, seed); + float height_base = NoiseFractal2D(&noise_terrain_base->np, x, z, seed); noise_terrain_alt->np.persist = persist; - float height_alt = NoisePerlin2D(&noise_terrain_alt->np, x, z, seed); + float height_alt = NoiseFractal2D(&noise_terrain_alt->np, x, z, seed); if (height_alt > height_base) return height_alt; @@ -424,9 +424,9 @@ float MapgenV7::baseTerrainLevelFromMap(int index) 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); + std::fmax(NoiseFractal2D(&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); + float mnt_n = NoiseFractal3D(&noise_mountain->np, x, y, z, seed); return mnt_n + density_gradient >= 0.0f; } @@ -472,16 +472,16 @@ int MapgenV7::generateTerrain() MapNode n_water(c_water_source); //// Calculate noise for terrain generation - noise_terrain_persist->perlinMap2D(node_min.X, node_min.Z); + noise_terrain_persist->noiseMap2D(node_min.X, node_min.Z); float *persistmap = noise_terrain_persist->result; - noise_terrain_base->perlinMap2D(node_min.X, node_min.Z, persistmap); - noise_terrain_alt->perlinMap2D(node_min.X, node_min.Z, persistmap); - noise_height_select->perlinMap2D(node_min.X, node_min.Z); + noise_terrain_base->noiseMap2D(node_min.X, node_min.Z, persistmap); + noise_terrain_alt->noiseMap2D(node_min.X, node_min.Z, persistmap); + noise_height_select->noiseMap2D(node_min.X, node_min.Z); if (spflags & MGV7_MOUNTAINS) { - noise_mount_height->perlinMap2D(node_min.X, node_min.Z); - noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_mount_height->noiseMap2D(node_min.X, node_min.Z); + noise_mountain->noiseMap3D(node_min.X, node_min.Y - 1, node_min.Z); } //// Floatlands @@ -497,7 +497,7 @@ int MapgenV7::generateTerrain() 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); + noise_floatland->noiseMap3D(node_min.X, node_min.Y - 1, node_min.Z); // Cache floatland noise offset values, for floatland tapering for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++, cache_index++) { @@ -518,8 +518,8 @@ int MapgenV7::generateTerrain() 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); + noise_ridge->noiseMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_ridge_uwater->noiseMap2D(node_min.X, node_min.Z); } //// Place nodes diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp index 0d91765b6..9d17e50e4 100644 --- a/src/mapgen/mapgen_valleys.cpp +++ b/src/mapgen/mapgen_valleys.cpp @@ -280,15 +280,15 @@ void MapgenValleys::makeChunk(BlockMakeData *data) int MapgenValleys::getSpawnLevelAtPoint(v2s16 p) { // Check if in a river channel - float n_rivers = NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed); + float n_rivers = NoiseFractal2D(&noise_rivers->np, p.X, p.Y, seed); if (std::fabs(n_rivers) <= river_size_factor) // Unsuitable spawn point return MAX_MAP_GENERATION_LIMIT; - 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_profile = NoisePerlin2D(&noise_valley_profile->np, p.X, p.Y, seed); + float n_slope = NoiseFractal2D(&noise_inter_valley_slope->np, p.X, p.Y, seed); + float n_terrain_height = NoiseFractal2D(&noise_terrain_height->np, p.X, p.Y, seed); + float n_valley = NoiseFractal2D(&noise_valley_depth->np, p.X, p.Y, seed); + float n_valley_profile = NoiseFractal2D(&noise_valley_profile->np, p.X, p.Y, seed); float valley_d = n_valley * n_valley; float base = n_terrain_height + valley_d; @@ -309,7 +309,7 @@ int MapgenValleys::getSpawnLevelAtPoint(v2s16 p) // 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 = NoiseFractal3D(&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; @@ -336,13 +336,13 @@ int MapgenValleys::generateTerrain() MapNode n_stone(c_stone); MapNode n_water(c_water_source); - noise_inter_valley_slope->perlinMap2D(node_min.X, node_min.Z); - noise_rivers->perlinMap2D(node_min.X, node_min.Z); - noise_terrain_height->perlinMap2D(node_min.X, node_min.Z); - noise_valley_depth->perlinMap2D(node_min.X, node_min.Z); - noise_valley_profile->perlinMap2D(node_min.X, node_min.Z); + noise_inter_valley_slope->noiseMap2D(node_min.X, node_min.Z); + noise_rivers->noiseMap2D(node_min.X, node_min.Z); + noise_terrain_height->noiseMap2D(node_min.X, node_min.Z); + noise_valley_depth->noiseMap2D(node_min.X, node_min.Z); + noise_valley_profile->noiseMap2D(node_min.X, node_min.Z); - noise_inter_valley_fill->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_inter_valley_fill->noiseMap3D(node_min.X, node_min.Y - 1, node_min.Z); const v3s32 &em = vm->m_area.getExtent(); s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT; diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp index a0bb6dee2..42b4b0862 100644 --- a/src/mapgen/mg_biome.cpp +++ b/src/mapgen/mg_biome.cpp @@ -165,14 +165,14 @@ BiomeGen *BiomeGenOriginal::clone(BiomeManager *biomemgr) const float BiomeGenOriginal::calcHeatAtPoint(v3s16 pos) const { - return 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); + return NoiseFractal2D(&m_params->np_heat, pos.X, pos.Z, m_params->seed) + + NoiseFractal2D(&m_params->np_heat_blend, pos.X, pos.Z, m_params->seed); } float BiomeGenOriginal::calcHumidityAtPoint(v3s16 pos) const { - return 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 NoiseFractal2D(&m_params->np_humidity, pos.X, pos.Z, m_params->seed) + + NoiseFractal2D(&m_params->np_humidity_blend, pos.X, pos.Z, m_params->seed); } Biome *BiomeGenOriginal::calcBiomeAtPoint(v3s16 pos) const @@ -185,10 +185,10 @@ void BiomeGenOriginal::calcBiomeNoise(v3s16 pmin) { m_pmin = pmin; - noise_heat->perlinMap2D(pmin.X, pmin.Z); - noise_humidity->perlinMap2D(pmin.X, pmin.Z); - noise_heat_blend->perlinMap2D(pmin.X, pmin.Z); - noise_humidity_blend->perlinMap2D(pmin.X, pmin.Z); + noise_heat->noiseMap2D(pmin.X, pmin.Z); + noise_humidity->noiseMap2D(pmin.X, pmin.Z); + noise_heat_blend->noiseMap2D(pmin.X, pmin.Z); + noise_humidity_blend->noiseMap2D(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]; diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index 9d6b73070..8810a654d 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -148,7 +148,7 @@ void Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) bool cover = false; // Amount of decorations float nval = (flags & DECO_USE_NOISE) ? - NoisePerlin2D(&np, p2d_min.X + sidelen / 2, p2d_min.Y + sidelen / 2, mapseed) : + NoiseFractal2D(&np, p2d_min.X + sidelen / 2, p2d_min.Y + sidelen / 2, mapseed) : fill_ratio; u32 deco_count = 0; diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp index 3ab908d75..d3b943f8a 100644 --- a/src/mapgen/mg_ore.cpp +++ b/src/mapgen/mg_ore.cpp @@ -150,7 +150,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)) + (NoiseFractal3D(&np, x0, y0, z0, mapseed) < nthresh)) continue; if (biomemap && !biomes.empty()) { @@ -212,7 +212,7 @@ void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, noise = new Noise(&np, 0, sx, sz); } noise->seed = mapseed + y_start; - noise->perlinMap2D(nmin.X, nmin.Z); + noise->noiseMap2D(nmin.X, nmin.Z); size_t index = 0; for (int z = nmin.Z; z <= nmax.Z; z++) @@ -286,7 +286,7 @@ void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed, } noise->seed = mapseed + y_start; - noise->perlinMap2D(nmin.X, nmin.Z); + noise->noiseMap2D(nmin.X, nmin.Z); bool noise_generated = false; size_t index = 0; @@ -304,8 +304,8 @@ void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed, if (!noise_generated) { noise_generated = true; - noise_puff_top->perlinMap2D(nmin.X, nmin.Z); - noise_puff_bottom->perlinMap2D(nmin.X, nmin.Z); + noise_puff_top->noiseMap2D(nmin.X, nmin.Z); + noise_puff_bottom->noiseMap2D(nmin.X, nmin.Z); } float ntop = noise_puff_top->result[index]; @@ -393,7 +393,7 @@ void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed, // This simple optimization makes calls 6x faster on average if (!noise_generated) { noise_generated = true; - noise->perlinMap3D(x0, y0, z0); + noise->noiseMap3D(x0, y0, z0); } float noiseval = noise->result[index]; @@ -443,7 +443,7 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, int sizex = nmax.X - nmin.X + 1; int sizey = nmax.Y - nmin.Y + 1; - // Because this ore uses 3D noise the perlinmap Y size can be different in + // Because this ore uses 3D noise the noisemap Y size can be different in // different mapchunks due to ore Y limits. So recreate the noise objects // if Y size has changed. // Because these noise objects are created multiple times for this ore type @@ -478,8 +478,8 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, // 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); + noise->noiseMap3D(nmin.X, nmin.Y, nmin.Z); + noise2->noiseMap3D(nmin.X, nmin.Y, nmin.Z); } // randval ranges from -1..1 @@ -532,7 +532,7 @@ void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed, int sz = nmax.Z - nmin.Z + 1; noise = new Noise(&np, 0, sx, sz); } - noise->perlinMap2D(nmin.X, nmin.Z); + noise->noiseMap2D(nmin.X, nmin.Z); } if (flags & OREFLAG_USE_NOISE2) { @@ -541,7 +541,7 @@ void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed, int sz = nmax.Z - nmin.Z + 1; noise_stratum_thickness = new Noise(&np_stratum_thickness, 0, sx, sz); } - noise_stratum_thickness->perlinMap2D(nmin.X, nmin.Z); + noise_stratum_thickness->noiseMap2D(nmin.X, nmin.Z); } size_t index = 0; diff --git a/src/noise.cpp b/src/noise.cpp index d81e0bbba..347780b1a 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -234,7 +234,7 @@ inline float triLinearInterpolation( return linearInterpolation(u, v, z); } -float noise2d_gradient(float x, float y, s32 seed, bool eased) +float noise2d_value(float x, float y, s32 seed, bool eased) { // Calculate the integer coordinates int x0 = myfloor(x); @@ -252,7 +252,7 @@ float noise2d_gradient(float x, float y, s32 seed, bool eased) } -float noise3d_gradient(float x, float y, float z, s32 seed, bool eased) +float noise3d_value(float x, float y, float z, s32 seed, bool eased) { // Calculate the integer coordinates int x0 = myfloor(x); @@ -280,7 +280,7 @@ float noise3d_gradient(float x, float y, float z, s32 seed, bool eased) } -float noise2d_perlin(float x, float y, s32 seed, +float noise2d_fractal(float x, float y, s32 seed, int octaves, float persistence, bool eased) { float a = 0; @@ -288,7 +288,7 @@ float noise2d_perlin(float x, float y, s32 seed, float g = 1.0; for (int i = 0; i < octaves; i++) { - a += g * noise2d_gradient(x * f, y * f, seed + i, eased); + a += g * noise2d_value(x * f, y * f, seed + i, eased); f *= 2.0; g *= persistence; } @@ -305,10 +305,10 @@ float contour(float v) } -///////////////////////// [ New noise ] //////////////////////////// +///////////////////////// [ Fractal value noise ] //////////////////////////// -float NoisePerlin2D(const NoiseParams *np, float x, float y, s32 seed) +float NoiseFractal2D(const NoiseParams *np, float x, float y, s32 seed) { float a = 0; float f = 1.0; @@ -319,7 +319,7 @@ float NoisePerlin2D(const NoiseParams *np, float x, float y, s32 seed) seed += np->seed; for (size_t i = 0; i < np->octaves; i++) { - float noiseval = noise2d_gradient(x * f, y * f, seed + i, + float noiseval = noise2d_value(x * f, y * f, seed + i, np->flags & (NOISE_FLAG_DEFAULTS | NOISE_FLAG_EASED)); if (np->flags & NOISE_FLAG_ABSVALUE) @@ -334,7 +334,7 @@ float NoisePerlin2D(const NoiseParams *np, float x, float y, s32 seed) } -float NoisePerlin3D(const NoiseParams *np, float x, float y, float z, s32 seed) +float NoiseFractal3D(const NoiseParams *np, float x, float y, float z, s32 seed) { float a = 0; float f = 1.0; @@ -346,7 +346,7 @@ float NoisePerlin3D(const NoiseParams *np, float x, float y, float z, s32 seed) seed += np->seed; for (size_t i = 0; i < np->octaves; i++) { - float noiseval = noise3d_gradient(x * f, y * f, z * f, seed + i, + float noiseval = noise3d_value(x * f, y * f, z * f, seed + i, np->flags & NOISE_FLAG_EASED); if (np->flags & NOISE_FLAG_ABSVALUE) @@ -375,7 +375,7 @@ Noise::Noise(const NoiseParams *np_, s32 seed, u32 sx, u32 sy, u32 sz) Noise::~Noise() { - delete[] gradient_buf; + delete[] value_buf; delete[] persist_buf; delete[] noise_buf; delete[] result; @@ -394,15 +394,15 @@ void Noise::allocBuffers() this->noise_buf = NULL; resizeNoiseBuf(sz > 1); - delete[] gradient_buf; + delete[] value_buf; delete[] persist_buf; delete[] result; try { size_t bufsize = sx * sy * sz; - this->persist_buf = NULL; - this->gradient_buf = new float[bufsize]; - this->result = new float[bufsize]; + this->persist_buf = NULL; + this->value_buf = new float[bufsize]; + this->result = new float[bufsize]; } catch (std::bad_alloc &e) { throw InvalidNoiseParamsException(); } @@ -490,7 +490,7 @@ void Noise::resizeNoiseBuf(bool is3d) * next octave. */ #define idx(x, y) ((y) * nlx + (x)) -void Noise::gradientMap2D( +void Noise::valueMap2D( float x, float y, float step_x, float step_y, s32 seed) @@ -527,7 +527,7 @@ void Noise::gradientMap2D( u = orig_u; noisex = 0; for (i = 0; i != sx; i++) { - gradient_buf[index++] = + value_buf[index++] = biLinearInterpolation(v00, v10, v01, v11, u, v, eased); u += step_x; @@ -552,7 +552,7 @@ void Noise::gradientMap2D( #define idx(x, y, z) ((z) * nly * nlx + (y) * nlx + (x)) -void Noise::gradientMap3D( +void Noise::valueMap3D( float x, float y, float z, float step_x, float step_y, float step_z, s32 seed) @@ -605,7 +605,7 @@ void Noise::gradientMap3D( u = orig_u; noisex = 0; for (i = 0; i != sx; i++) { - gradient_buf[index++] = triLinearInterpolation( + value_buf[index++] = triLinearInterpolation( v000, v100, v010, v110, v001, v101, v011, v111, u, v, w, @@ -643,7 +643,7 @@ void Noise::gradientMap3D( #undef idx -float *Noise::perlinMap2D(float x, float y, float *persistence_map) +float *Noise::noiseMap2D(float x, float y, float *persistence_map) { float f = 1.0, g = 1.0; size_t bufsize = sx * sy; @@ -661,7 +661,7 @@ float *Noise::perlinMap2D(float x, float y, float *persistence_map) } for (size_t oct = 0; oct < np.octaves; oct++) { - gradientMap2D(x * f, y * f, + valueMap2D(x * f, y * f, f / np.spread.X, f / np.spread.Y, seed + np.seed + oct); @@ -680,7 +680,7 @@ float *Noise::perlinMap2D(float x, float y, float *persistence_map) } -float *Noise::perlinMap3D(float x, float y, float z, float *persistence_map) +float *Noise::noiseMap3D(float x, float y, float z, float *persistence_map) { float f = 1.0, g = 1.0; size_t bufsize = sx * sy * sz; @@ -699,7 +699,7 @@ 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, + valueMap3D(x * f, y * f, z * f, f / np.spread.X, f / np.spread.Y, f / np.spread.Z, seed + np.seed + oct); @@ -726,22 +726,22 @@ void Noise::updateResults(float g, float *gmap, if (np.flags & NOISE_FLAG_ABSVALUE) { if (persistence_map) { for (size_t i = 0; i != bufsize; i++) { - result[i] += gmap[i] * std::fabs(gradient_buf[i]); + result[i] += gmap[i] * std::fabs(value_buf[i]); gmap[i] *= persistence_map[i]; } } else { for (size_t i = 0; i != bufsize; i++) - result[i] += g * std::fabs(gradient_buf[i]); + result[i] += g * std::fabs(value_buf[i]); } } else { if (persistence_map) { for (size_t i = 0; i != bufsize; i++) { - result[i] += gmap[i] * gradient_buf[i]; + result[i] += gmap[i] * value_buf[i]; gmap[i] *= persistence_map[i]; } } else { for (size_t i = 0; i != bufsize; i++) - result[i] += g * gradient_buf[i]; + result[i] += g * value_buf[i]; } } } diff --git a/src/noise.h b/src/noise.h index acd8d555d..154c7fe94 100644 --- a/src/noise.h +++ b/src/noise.h @@ -151,7 +151,7 @@ public: u32 sy; u32 sz; float *noise_buf = nullptr; - float *gradient_buf = nullptr; + float *value_buf = nullptr; float *persist_buf = nullptr; float *result = nullptr; @@ -162,31 +162,31 @@ public: void setSpreadFactor(v3f spread); void setOctaves(int octaves); - void gradientMap2D( + void valueMap2D( float x, float y, float step_x, float step_y, s32 seed); - void gradientMap3D( + void valueMap3D( 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 *noiseMap2D(float x, float y, float *persistence_map=NULL); + float *noiseMap3D(float x, float y, float z, float *persistence_map=NULL); - inline float *perlinMap2D_PO(float x, float xoff, float y, float yoff, + inline float *noiseMap2D_PO(float x, float xoff, float y, float yoff, float *persistence_map=NULL) { - return perlinMap2D( + return noiseMap2D( x + xoff * np.spread.X, y + yoff * np.spread.Y, persistence_map); } - inline float *perlinMap3D_PO(float x, float xoff, float y, float yoff, + inline float *noiseMap3D_PO(float x, float xoff, float y, float yoff, float z, float zoff, float *persistence_map=NULL) { - return perlinMap3D( + return noiseMap3D( x + xoff * np.spread.X, y + yoff * np.spread.Y, z + zoff * np.spread.Z, @@ -201,22 +201,22 @@ private: }; -float NoisePerlin2D(const NoiseParams *np, float x, float y, s32 seed); -float NoisePerlin3D(const NoiseParams *np, float x, float y, float z, s32 seed); +float NoiseFractal2D(const NoiseParams *np, float x, float y, s32 seed); +float NoiseFractal3D(const NoiseParams *np, float x, float y, float z, s32 seed); -inline float NoisePerlin2D_PO(NoiseParams *np, float x, float xoff, +inline float NoiseFractal2D_PO(NoiseParams *np, float x, float xoff, float y, float yoff, s32 seed) { - return NoisePerlin2D(np, + return NoiseFractal2D(np, x + xoff * np->spread.X, y + yoff * np->spread.Y, seed); } -inline float NoisePerlin3D_PO(NoiseParams *np, float x, float xoff, +inline float NoiseFractal3D_PO(NoiseParams *np, float x, float xoff, float y, float yoff, float z, float zoff, s32 seed) { - return NoisePerlin3D(np, + return NoiseFractal3D(np, x + xoff * np->spread.X, y + yoff * np->spread.Y, z + zoff * np->spread.Z, @@ -227,10 +227,10 @@ inline float NoisePerlin3D_PO(NoiseParams *np, float x, float xoff, 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_value(float x, float y, s32 seed, bool eased=true); +float noise3d_value(float x, float y, float z, s32 seed, bool eased=false); -float noise2d_perlin(float x, float y, s32 seed, +float noise2d_fractal(float x, float y, s32 seed, int octaves, float persistence, bool eased=true); inline float easeCurve(float t) diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 51604fff0..195e002ec 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -1023,9 +1023,9 @@ int ModApiEnv::l_find_nodes_in_area_under_air(lua_State *L) return findNodesInAreaUnderAir(L, minp, maxp, filter, getNode); } -// get_perlin(seeddiff, octaves, persistence, scale) -// returns world-specific PerlinNoise -int ModApiEnv::l_get_perlin(lua_State *L) +// get_value_noise(seeddiff, octaves, persistence, scale) +// returns world-specific ValueNoise +int ModApiEnv::l_get_value_noise(lua_State *L) { GET_ENV_PTR_NO_MAP_LOCK; @@ -1042,16 +1042,16 @@ int ModApiEnv::l_get_perlin(lua_State *L) params.seed += (int)env->getServerMap().getSeed(); - LuaPerlinNoise *n = new LuaPerlinNoise(¶ms); + LuaValueNoise *n = new LuaValueNoise(¶ms); *(void **)(lua_newuserdata(L, sizeof(void *))) = n; - luaL_getmetatable(L, "PerlinNoise"); + luaL_getmetatable(L, "ValueNoise"); lua_setmetatable(L, -2); return 1; } -// get_perlin_map(noiseparams, size) -// returns world-specific PerlinNoiseMap -int ModApiEnv::l_get_perlin_map(lua_State *L) +// get_value_noise_map(noiseparams, size) +// returns world-specific ValueNoiseMap +int ModApiEnv::l_get_value_noise_map(lua_State *L) { GET_ENV_PTR_NO_MAP_LOCK; @@ -1061,9 +1061,9 @@ int ModApiEnv::l_get_perlin_map(lua_State *L) v3s16 size = read_v3s16(L, 2); s32 seed = (s32)(env->getServerMap().getSeed()); - LuaPerlinNoiseMap *n = new LuaPerlinNoiseMap(&np, seed, size); + LuaValueNoiseMap *n = new LuaValueNoiseMap(&np, seed, size); *(void **)(lua_newuserdata(L, sizeof(void *))) = n; - luaL_getmetatable(L, "PerlinNoiseMap"); + luaL_getmetatable(L, "ValueNoiseMap"); lua_setmetatable(L, -2); return 1; } @@ -1415,8 +1415,8 @@ void ModApiEnv::Initialize(lua_State *L, int top) API_FCT(load_area); API_FCT(emerge_area); API_FCT(delete_area); - API_FCT(get_perlin); - API_FCT(get_perlin_map); + API_FCT(get_value_noise); + API_FCT(get_value_noise_map); API_FCT(get_voxel_manip); API_FCT(clear_objects); API_FCT(spawn_tree); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 49b3458a6..e755166a7 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -179,13 +179,13 @@ private: // delete_area(p1, p2) -> true/false static int l_delete_area(lua_State *L); - // get_perlin(seeddiff, octaves, persistence, scale) - // returns world-specific PerlinNoise - static int l_get_perlin(lua_State *L); + // get_value_noise(seeddiff, octaves, persistence, scale) + // returns world-specific ValueNoise + static int l_get_value_noise(lua_State *L); - // get_perlin_map(noiseparams, size) - // returns world-specific PerlinNoiseMap - static int l_get_perlin_map(lua_State *L); + // get_value_noise_map(noiseparams, size) + // returns world-specific ValueNoiseMap + static int l_get_value_noise_map(lua_State *L); // get_voxel_manip() // returns world-specific voxel manipulator diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index 3818218ee..5ad57835b 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -13,38 +13,38 @@ /////////////////////////////////////// /* - LuaPerlinNoise + LuaValueNoise */ -LuaPerlinNoise::LuaPerlinNoise(const NoiseParams *params) : +LuaValueNoise::LuaValueNoise(const NoiseParams *params) : np(*params) { } -int LuaPerlinNoise::l_get_2d(lua_State *L) +int LuaValueNoise::l_get_2d(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaPerlinNoise *o = checkObject(L, 1); + LuaValueNoise *o = checkObject(L, 1); v2f p = readParam(L, 2); - lua_Number val = NoisePerlin2D(&o->np, p.X, p.Y, 0); + lua_Number val = NoiseFractal2D(&o->np, p.X, p.Y, 0); lua_pushnumber(L, val); return 1; } -int LuaPerlinNoise::l_get_3d(lua_State *L) +int LuaValueNoise::l_get_3d(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaPerlinNoise *o = checkObject(L, 1); + LuaValueNoise *o = checkObject(L, 1); v3f p = check_v3f(L, 2); - lua_Number val = NoisePerlin3D(&o->np, p.X, p.Y, p.Z, 0); + lua_Number val = NoiseFractal3D(&o->np, p.X, p.Y, p.Z, 0); lua_pushnumber(L, val); return 1; } -int LuaPerlinNoise::create_object(lua_State *L) +int LuaValueNoise::create_object(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -59,7 +59,7 @@ int LuaPerlinNoise::create_object(lua_State *L) params.spread = v3f(1, 1, 1) * readParam(L, 4); } - LuaPerlinNoise *o = new LuaPerlinNoise(¶ms); + LuaValueNoise *o = new LuaValueNoise(¶ms); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); @@ -68,25 +68,25 @@ int LuaPerlinNoise::create_object(lua_State *L) } -int LuaPerlinNoise::gc_object(lua_State *L) +int LuaValueNoise::gc_object(lua_State *L) { - LuaPerlinNoise *o = *(LuaPerlinNoise **)(lua_touserdata(L, 1)); + LuaValueNoise *o = *(LuaValueNoise **)(lua_touserdata(L, 1)); delete o; return 0; } -void *LuaPerlinNoise::packIn(lua_State *L, int idx) +void *LuaValueNoise::packIn(lua_State *L, int idx) { - LuaPerlinNoise *o = checkObject(L, idx); + LuaValueNoise *o = checkObject(L, idx); return new NoiseParams(o->np); } -void LuaPerlinNoise::packOut(lua_State *L, void *ptr) +void LuaValueNoise::packOut(lua_State *L, void *ptr) { NoiseParams *np = reinterpret_cast(ptr); if (L) { - LuaPerlinNoise *o = new LuaPerlinNoise(np); + LuaValueNoise *o = new LuaValueNoise(np); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); @@ -95,7 +95,7 @@ void LuaPerlinNoise::packOut(lua_State *L, void *ptr) } -void LuaPerlinNoise::Register(lua_State *L) +void LuaValueNoise::Register(lua_State *L) { static const luaL_Reg metamethods[] = { {"__gc", gc_object}, @@ -109,19 +109,19 @@ void LuaPerlinNoise::Register(lua_State *L) } -const char LuaPerlinNoise::className[] = "PerlinNoise"; -luaL_Reg LuaPerlinNoise::methods[] = { - luamethod_aliased(LuaPerlinNoise, get_2d, get2d), - luamethod_aliased(LuaPerlinNoise, get_3d, get3d), +const char LuaValueNoise::className[] = "ValueNoise"; +luaL_Reg LuaValueNoise::methods[] = { + luamethod_aliased(LuaValueNoise, get_2d, get2d), + luamethod_aliased(LuaValueNoise, get_3d, get3d), {0,0} }; /////////////////////////////////////// /* - LuaPerlinNoiseMap + LuaValueNoiseMap */ -LuaPerlinNoiseMap::LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size) +LuaValueNoiseMap::LuaValueNoiseMap(const NoiseParams *np, s32 seed, v3s16 size) { try { noise = new Noise(np, seed, size.X, size.Y, size.Z); @@ -131,22 +131,22 @@ LuaPerlinNoiseMap::LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size } -LuaPerlinNoiseMap::~LuaPerlinNoiseMap() +LuaValueNoiseMap::~LuaValueNoiseMap() { delete noise; } -int LuaPerlinNoiseMap::l_get_2d_map(lua_State *L) +int LuaValueNoiseMap::l_get_2d_map(lua_State *L) { NO_MAP_LOCK_REQUIRED; size_t i = 0; - LuaPerlinNoiseMap *o = checkObject(L, 1); + LuaValueNoiseMap *o = checkObject(L, 1); v2f p = readParam(L, 2); Noise *n = o->noise; - n->perlinMap2D(p.X, p.Y); + n->noiseMap2D(p.X, p.Y); lua_createtable(L, n->sy, 0); for (u32 y = 0; y != n->sy; y++) { @@ -161,16 +161,16 @@ int LuaPerlinNoiseMap::l_get_2d_map(lua_State *L) } -int LuaPerlinNoiseMap::l_get_2d_map_flat(lua_State *L) +int LuaValueNoiseMap::l_get_2d_map_flat(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaPerlinNoiseMap *o = checkObject(L, 1); + LuaValueNoiseMap *o = checkObject(L, 1); v2f p = readParam(L, 2); bool use_buffer = lua_istable(L, 3); Noise *n = o->noise; - n->perlinMap2D(p.X, p.Y); + n->noiseMap2D(p.X, p.Y); size_t maplen = n->sx * n->sy; @@ -187,19 +187,19 @@ int LuaPerlinNoiseMap::l_get_2d_map_flat(lua_State *L) } -int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L) +int LuaValueNoiseMap::l_get_3d_map(lua_State *L) { NO_MAP_LOCK_REQUIRED; size_t i = 0; - LuaPerlinNoiseMap *o = checkObject(L, 1); + LuaValueNoiseMap *o = checkObject(L, 1); v3f p = check_v3f(L, 2); if (!o->is3D()) return 0; Noise *n = o->noise; - n->perlinMap3D(p.X, p.Y, p.Z); + n->noiseMap3D(p.X, p.Y, p.Z); lua_createtable(L, n->sz, 0); for (u32 z = 0; z != n->sz; z++) { @@ -218,11 +218,11 @@ int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L) } -int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) +int LuaValueNoiseMap::l_get_3d_map_flat(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaPerlinNoiseMap *o = checkObject(L, 1); + LuaValueNoiseMap *o = checkObject(L, 1); v3f p = check_v3f(L, 2); bool use_buffer = lua_istable(L, 3); @@ -230,7 +230,7 @@ int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) return 0; Noise *n = o->noise; - n->perlinMap3D(p.X, p.Y, p.Z); + n->noiseMap3D(p.X, p.Y, p.Z); size_t maplen = n->sx * n->sy * n->sz; @@ -247,41 +247,41 @@ int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L) } -int LuaPerlinNoiseMap::l_calc_2d_map(lua_State *L) +int LuaValueNoiseMap::l_calc_2d_map(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaPerlinNoiseMap *o = checkObject(L, 1); + LuaValueNoiseMap *o = checkObject(L, 1); v2f p = readParam(L, 2); Noise *n = o->noise; - n->perlinMap2D(p.X, p.Y); + n->noiseMap2D(p.X, p.Y); return 0; } -int LuaPerlinNoiseMap::l_calc_3d_map(lua_State *L) +int LuaValueNoiseMap::l_calc_3d_map(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaPerlinNoiseMap *o = checkObject(L, 1); + LuaValueNoiseMap *o = checkObject(L, 1); v3f p = check_v3f(L, 2); if (!o->is3D()) return 0; Noise *n = o->noise; - n->perlinMap3D(p.X, p.Y, p.Z); + n->noiseMap3D(p.X, p.Y, p.Z); return 0; } -int LuaPerlinNoiseMap::l_get_map_slice(lua_State *L) +int LuaValueNoiseMap::l_get_map_slice(lua_State *L) { NO_MAP_LOCK_REQUIRED; - LuaPerlinNoiseMap *o = checkObject(L, 1); + LuaValueNoiseMap *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); @@ -302,14 +302,14 @@ int LuaPerlinNoiseMap::l_get_map_slice(lua_State *L) } -int LuaPerlinNoiseMap::create_object(lua_State *L) +int LuaValueNoiseMap::create_object(lua_State *L) { NoiseParams np; if (!read_noiseparams(L, 1, &np)) return 0; v3s16 size = read_v3s16(L, 2); - LuaPerlinNoiseMap *o = new LuaPerlinNoiseMap(&np, 0, size); + LuaValueNoiseMap *o = new LuaValueNoiseMap(&np, 0, size); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); @@ -317,9 +317,9 @@ int LuaPerlinNoiseMap::create_object(lua_State *L) } -int LuaPerlinNoiseMap::gc_object(lua_State *L) +int LuaValueNoiseMap::gc_object(lua_State *L) { - LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)(lua_touserdata(L, 1)); + LuaValueNoiseMap *o = *(LuaValueNoiseMap **)(lua_touserdata(L, 1)); delete o; return 0; } @@ -331,9 +331,9 @@ struct NoiseMapParams { v3s16 size; }; -void *LuaPerlinNoiseMap::packIn(lua_State *L, int idx) +void *LuaValueNoiseMap::packIn(lua_State *L, int idx) { - LuaPerlinNoiseMap *o = checkObject(L, idx); + LuaValueNoiseMap *o = checkObject(L, idx); NoiseMapParams *ret = new NoiseMapParams(); ret->np = o->noise->np; ret->seed = o->noise->seed; @@ -341,11 +341,11 @@ void *LuaPerlinNoiseMap::packIn(lua_State *L, int idx) return ret; } -void LuaPerlinNoiseMap::packOut(lua_State *L, void *ptr) +void LuaValueNoiseMap::packOut(lua_State *L, void *ptr) { NoiseMapParams *p = reinterpret_cast(ptr); if (L) { - LuaPerlinNoiseMap *o = new LuaPerlinNoiseMap(&p->np, p->seed, p->size); + LuaValueNoiseMap *o = new LuaValueNoiseMap(&p->np, p->seed, p->size); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); @@ -354,7 +354,7 @@ void LuaPerlinNoiseMap::packOut(lua_State *L, void *ptr) } -void LuaPerlinNoiseMap::Register(lua_State *L) +void LuaValueNoiseMap::Register(lua_State *L) { static const luaL_Reg metamethods[] = { {"__gc", gc_object}, @@ -368,15 +368,15 @@ void LuaPerlinNoiseMap::Register(lua_State *L) } -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), +const char LuaValueNoiseMap::className[] = "ValueNoiseMap"; +luaL_Reg LuaValueNoiseMap::methods[] = { + luamethod_aliased(LuaValueNoiseMap, get_2d_map, get2dMap), + luamethod_aliased(LuaValueNoiseMap, get_2d_map_flat, get2dMap_flat), + luamethod_aliased(LuaValueNoiseMap, calc_2d_map, calc2dMap), + luamethod_aliased(LuaValueNoiseMap, get_3d_map, get3dMap), + luamethod_aliased(LuaValueNoiseMap, get_3d_map_flat, get3dMap_flat), + luamethod_aliased(LuaValueNoiseMap, calc_3d_map, calc3dMap), + luamethod_aliased(LuaValueNoiseMap, get_map_slice, getMapSlice), {0,0} }; diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index 595aa0694..9bcbd98b9 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -9,9 +9,9 @@ #include "noise.h" /* - LuaPerlinNoise + LuaValueNoise */ -class LuaPerlinNoise : public ModApiBase +class LuaValueNoise : public ModApiBase { private: NoiseParams np; @@ -27,11 +27,11 @@ private: static int l_get_3d(lua_State *L); public: - LuaPerlinNoise(const NoiseParams *params); - ~LuaPerlinNoise() = default; + LuaValueNoise(const NoiseParams *params); + ~LuaValueNoise() = default; - // LuaPerlinNoise(seed, octaves, persistence, scale) - // Creates an LuaPerlinNoise and leaves it on top of stack + // LuaValueNoise(seed, octaves, persistence, scale) + // Creates an LuaValueNoise and leaves it on top of stack static int create_object(lua_State *L); static void *packIn(lua_State *L, int idx); @@ -43,9 +43,9 @@ public: }; /* - LuaPerlinNoiseMap + LuaValueNoiseMap */ -class LuaPerlinNoiseMap : public ModApiBase +class LuaValueNoiseMap : public ModApiBase { Noise *noise; @@ -66,13 +66,13 @@ class LuaPerlinNoiseMap : public ModApiBase static int l_get_map_slice(lua_State *L); public: - LuaPerlinNoiseMap(const NoiseParams *np, s32 seed, v3s16 size); - ~LuaPerlinNoiseMap(); + LuaValueNoiseMap(const NoiseParams *np, s32 seed, v3s16 size); + ~LuaValueNoiseMap(); inline bool is3D() const { return noise->sz > 1; } - // LuaPerlinNoiseMap(np, size) - // Creates an LuaPerlinNoiseMap and leaves it on top of stack + // LuaValueNoiseMap(np, size) + // Creates an LuaValueNoiseMap and leaves it on top of stack static int create_object(lua_State *L); static void *packIn(lua_State *L, int idx); diff --git a/src/script/scripting_emerge.cpp b/src/script/scripting_emerge.cpp index cc2c350d9..e60ec35d5 100644 --- a/src/script/scripting_emerge.cpp +++ b/src/script/scripting_emerge.cpp @@ -60,8 +60,8 @@ void EmergeScripting::InitializeModApi(lua_State *L, int top) ItemStackMetaRef::Register(L); LuaAreaStore::Register(L); LuaItemStack::Register(L); - LuaPerlinNoise::Register(L); - LuaPerlinNoiseMap::Register(L); + LuaValueNoise::Register(L); + LuaValueNoiseMap::Register(L); LuaPseudoRandom::Register(L); LuaPcgRandom::Register(L); LuaSecureRandom::Register(L); diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index db1dfc34b..f30def03d 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -134,8 +134,8 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ItemStackMetaRef::Register(L); LuaAreaStore::Register(L); LuaItemStack::Register(L); - LuaPerlinNoise::Register(L); - LuaPerlinNoiseMap::Register(L); + LuaValueNoise::Register(L); + LuaValueNoiseMap::Register(L); LuaPseudoRandom::Register(L); LuaPcgRandom::Register(L); LuaRaycast::Register(L); @@ -172,8 +172,8 @@ void ServerScripting::InitializeAsync(lua_State *L, int top) ItemStackMetaRef::Register(L); LuaAreaStore::Register(L); LuaItemStack::Register(L); - LuaPerlinNoise::Register(L); - LuaPerlinNoiseMap::Register(L); + LuaValueNoise::Register(L); + LuaValueNoiseMap::Register(L); LuaPseudoRandom::Register(L); LuaPcgRandom::Register(L); LuaSecureRandom::Register(L); diff --git a/src/unittest/test_noise.cpp b/src/unittest/test_noise.cpp index fec4da0d3..cd181cedd 100644 --- a/src/unittest/test_noise.cpp +++ b/src/unittest/test_noise.cpp @@ -78,7 +78,7 @@ void TestNoise::testNoise2dPoint() u32 i = 0; for (u32 y = 0; y != 10; y++) for (u32 x = 0; x != 10; x++, i++) { - float actual = NoisePerlin2D(&np_normal, x, y, 1337); + float actual = NoiseFractal2D(&np_normal, x, y, 1337); float expected = expected_2d_results[i]; UASSERT(std::fabs(actual - expected) <= 0.00001); } @@ -88,7 +88,7 @@ void TestNoise::testNoise2dBulk() { NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0); Noise noise_normal_2d(&np_normal, 1337, 10, 10); - float *noisevals = noise_normal_2d.perlinMap2D(0, 0, NULL); + float *noisevals = noise_normal_2d.noiseMap2D(0, 0, NULL); for (u32 i = 0; i != 10 * 10; i++) { float actual = noisevals[i]; @@ -126,7 +126,7 @@ void TestNoise::testNoise3dPoint() for (u32 z = 0; z != 10; z++) for (u32 y = 0; y != 10; y++) for (u32 x = 0; x != 10; x++, i++) { - float actual = NoisePerlin3D(&np_normal, x, y, z, 1337); + float actual = NoiseFractal3D(&np_normal, x, y, z, 1337); float expected = expected_3d_results[i]; UASSERT(std::fabs(actual - expected) <= 0.00001); } @@ -136,7 +136,7 @@ void TestNoise::testNoise3dBulk() { NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0); Noise noise_normal_3d(&np_normal, 1337, 10, 10, 10); - float *noisevals = noise_normal_3d.perlinMap3D(0, 0, 0, NULL); + float *noisevals = noise_normal_3d.noiseMap3D(0, 0, 0, NULL); for (u32 i = 0; i != 10 * 10 * 10; i++) { float actual = noisevals[i]; @@ -152,7 +152,7 @@ void TestNoise::testNoiseInvalidParams() try { NoiseParams np_highmem(4, 70, v3f(1, 1, 1), 5, 60, 0.7, 10.0); Noise noise_highmem_3d(&np_highmem, 1337, 200, 200, 200); - noise_highmem_3d.perlinMap3D(0, 0, 0, NULL); + noise_highmem_3d.noiseMap3D(0, 0, 0, NULL); } catch (InvalidNoiseParamsException &) { exception_thrown = true; } From 75862e33b6bfe6b3805310152c66af1430ac3666 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 13 Apr 2025 16:07:01 +0100 Subject: [PATCH 129/284] ContentDB: Add reviews tab (#15254) --- LICENSE.txt | 5 ++ builtin/async/mainmenu.lua | 13 +++ builtin/mainmenu/content/contentdb.lua | 93 +++++++++----------- builtin/mainmenu/content/dlg_package.lua | 65 +++++++++++--- textures/base/pack/contentdb_neutral.png | Bin 0 -> 169 bytes textures/base/pack/contentdb_thumb_down.png | Bin 0 -> 348 bytes textures/base/pack/contentdb_thumb_up.png | Bin 0 -> 411 bytes 7 files changed, 110 insertions(+), 66 deletions(-) create mode 100644 textures/base/pack/contentdb_neutral.png create mode 100644 textures/base/pack/contentdb_thumb_down.png create mode 100644 textures/base/pack/contentdb_thumb_up.png diff --git a/LICENSE.txt b/LICENSE.txt index 772f86728..5c01e2c3b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -106,6 +106,11 @@ grorp: textures/base/pack/place_btn.png derived by editing the text in aux1_btn.svg +Material Design, Google (Apache license v2.0): + textures/base/pack/contentdb_thumb_up.png + textures/base/pack/contentdb_thumb_down.png + textures/base/pack/contentdb_neutral.png + License of Luanti source code ------------------------------- diff --git a/builtin/async/mainmenu.lua b/builtin/async/mainmenu.lua index 0e9c222d1..c1d8618b4 100644 --- a/builtin/async/mainmenu.lua +++ b/builtin/async/mainmenu.lua @@ -1,5 +1,6 @@ core.log("info", "Initializing asynchronous environment") + function core.job_processor(func, serialized_param) local param = core.deserialize(serialized_param) @@ -7,3 +8,15 @@ function core.job_processor(func, serialized_param) return retval or core.serialize(nil) end + + +function core.get_http_accept_languages() + local languages + local current_language = core.get_language() + if current_language ~= "" then + languages = { current_language, "en;q=0.8" } + else + languages = { "en" } + end + return "Accept-Language: " .. table.concat(languages, ", ") +end diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua index 963400a12..856924bd4 100644 --- a/builtin/mainmenu/content/contentdb.lua +++ b/builtin/mainmenu/content/contentdb.lua @@ -41,6 +41,7 @@ contentdb = { REASON_DEPENDENCY = "dependency", } +-- API documentation: https://content.luanti.org/help/api/ local function get_download_url(package, reason) local base_url = core.settings:get("contentdb_url") @@ -398,7 +399,6 @@ local function fetch_pkgs() local url = base_url .. "/api/packages/?type=mod&type=game&type=txp&protocol_version=" .. core.get_max_supp_proto() .. "&engine_version=" .. core.urlencode(version.string) - for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do item = item:trim() if item ~= "" then @@ -406,19 +406,11 @@ local function fetch_pkgs() end end - local languages - local current_language = core.get_language() - if current_language ~= "" then - languages = { current_language, "en;q=0.8" } - else - languages = { "en" } - end - local http = core.get_http_api() local response = http.fetch_sync({ url = url, extra_headers = { - "Accept-Language: " .. table.concat(languages, ", ") + core.get_http_accept_languages() }, }) if not response.succeeded then @@ -596,57 +588,54 @@ function contentdb.filter_packages(query, by_type) end -function contentdb.get_full_package_info(package, callback) - assert(package) - if package.full_info then - callback(package.full_info) - return - end - - local function fetch(params) - local version = core.get_version() - local base_url = core.settings:get("contentdb_url") - - local languages - local current_language = core.get_language() - if current_language ~= "" then - languages = { current_language, "en;q=0.8" } - else - languages = { "en" } +local function get_package_info(key, path) + return function(package, callback) + assert(package) + if package[key] then + callback(package[key]) + return end - local url = base_url .. - "/api/packages/" .. params.package.url_part .. "/for-client/?" .. - "protocol_version=" .. core.urlencode(core.get_max_supp_proto()) .. - "&engine_version=" .. core.urlencode(version.string) .. - "&formspec_version=" .. core.urlencode(core.get_formspec_version()) .. - "&include_images=false" - local http = core.get_http_api() - local response = http.fetch_sync({ - url = url, - extra_headers = { - "Accept-Language: " .. table.concat(languages, ", ") - }, - }) - if not response.succeeded then - return nil + local function fetch(params) + local version = core.get_version() + local base_url = core.settings:get("contentdb_url") + local url = base_url .. + "/api/packages/" .. params.package.url_part .. params.path .. "?" .. + "protocol_version=" .. core.urlencode(core.get_max_supp_proto()) .. + "&engine_version=" .. core.urlencode(version.string) .. + "&formspec_version=" .. core.urlencode(core.get_formspec_version()) .. + "&include_images=false" + local http = core.get_http_api() + local response = http.fetch_sync({ + url = url, + extra_headers = { + core.get_http_accept_languages() + }, + }) + if not response.succeeded then + return nil + end + + return core.parse_json(response.data) end - return core.parse_json(response.data) - end + local function my_callback(value) + package[key] = value + callback(value) + end - local function my_callback(value) - package.full_info = value - callback(value) - end - - if not core.handle_async(fetch, { package = package }, my_callback) then - core.log("error", "ERROR: async event failed") - callback(nil) + if not core.handle_async(fetch, { package = package, path = path }, my_callback) then + core.log("error", "ERROR: async event failed") + callback(nil) + end end end +contentdb.get_full_package_info = get_package_info("full_info", "/for-client/") +contentdb.get_package_reviews = get_package_info("reviews", "/for-client/reviews/") + + function contentdb.get_formspec_padding() -- Padding is increased on Android to account for notches -- TODO: use Android API to determine size of cut outs diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua index 7edbf678f..d8fc057c1 100644 --- a/builtin/mainmenu/content/dlg_package.lua +++ b/builtin/mainmenu/content/dlg_package.lua @@ -32,6 +32,7 @@ end local function get_formspec(data) + local package = data.package local window_padding = contentdb.get_formspec_padding() local size = contentdb.get_formspec_size() size.x = math.min(size.x, 20) @@ -42,7 +43,7 @@ local function get_formspec(data) if not data.loading and not data.loading_error then data.loading = true - contentdb.get_full_package_info(data.package, function(info) + contentdb.get_full_package_info(package, function(info) data.loading = false if info == nil then @@ -61,7 +62,7 @@ local function get_formspec(data) -- check to see if that happened if not data.info then if data.loading_error then - return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved")) + return get_info_formspec(size, window_padding, fgettext("Error loading package information")) end return get_info_formspec(size, window_padding, fgettext("Loading...")) end @@ -103,15 +104,15 @@ local function get_formspec(data) local left_button_rect = "0,0;2.875,1" local right_button_rect = "3.125,0;2.875,1" - if data.package.downloading then + if package.downloading then formspec[#formspec + 1] = "animated_image[5,0;1,1;downloading;" formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir) formspec[#formspec + 1] = "cdb_downloading.png;3;400;]" - elseif data.package.queued then + elseif package.queued then formspec[#formspec + 1] = "style[queued;border=false]" formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir) formspec[#formspec + 1] = "cdb_queued.png;queued;]" - elseif not data.package.path then + elseif not package.path then formspec[#formspec + 1] = "style[install;bgcolor=green]" formspec[#formspec + 1] = "button[" formspec[#formspec + 1] = right_button_rect @@ -119,7 +120,7 @@ local function get_formspec(data) formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size) formspec[#formspec + 1] = "]" else - if data.package.installed_release < data.package.release then + if package.installed_release < package.release then -- The install_ action also handles updating formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]" formspec[#formspec + 1] = "button[" @@ -137,10 +138,12 @@ local function get_formspec(data) formspec[#formspec + 1] = "]" end + local review_count = info.reviews.positive + info.reviews.neutral + info.reviews.negative local current_tab = data.current_tab or 1 local tab_titles = { fgettext("Description"), fgettext("Information"), + fgettext("Reviews") .. core.formspec_escape(" [" .. review_count .. "]"), } local tab_body_height = bottom_buttons_y - 2.8 @@ -162,8 +165,8 @@ local function get_formspec(data) local winfo = core.get_window_info() local fs_to_px = winfo.size.x / winfo.max_formspec_size.x for i, ss in ipairs(info.screenshots) do - local path = get_screenshot(data.package, ss.url, 2) - hypertext = hypertext .. "" if i ~= #info.screenshots then @@ -194,22 +197,54 @@ local function get_formspec(data) hypertext = hypertext .. "\n\n" .. info.long_description.body + -- Fix the path to blank.png. This is needed for bullet indentation. hypertext = hypertext:gsub("", + "") + hypertext = hypertext:gsub("", + "") + hypertext = hypertext:gsub("", + "") + table.insert_all(formspec, { + "hypertext[0,0;", W, ",", tab_body_height - 0.375, + ";reviews;", core.formspec_escape(hypertext), "]", + }) + elseif data.reviews_error then + table.insert_all(formspec, {"label[2,2;", fgettext("Error loading reviews"), "]"} ) + else + table.insert_all(formspec, {"label[2,2;", fgettext("Loading..."), "]"} ) + end else error("Unknown tab " .. current_tab) end @@ -269,9 +304,10 @@ local function handle_submit(this, fields) end if fields.open_contentdb then - local url = ("%s/packages/%s/?protocol_version=%d"):format( - core.settings:get("contentdb_url"), package.url_part, - core.get_max_supp_proto()) + local version = core.get_version() + local url = core.settings:get("contentdb_url") .. "/packages/" .. package.url_part .. + "/?protocol_version=" .. core.urlencode(core.get_max_supp_proto()) .. + "&engine_version=" .. core.urlencode(version.string) core.open_url(url) return true end @@ -295,7 +331,8 @@ local function handle_submit(this, fields) end if handle_hypertext_event(this, fields.desc, info.long_description) or - handle_hypertext_event(this, fields.info, info.info_hypertext) then + handle_hypertext_event(this, fields.info, info.info_hypertext) or + (package.reviews and handle_hypertext_event(this, fields.reviews, package.reviews)) then return true end end diff --git a/textures/base/pack/contentdb_neutral.png b/textures/base/pack/contentdb_neutral.png new file mode 100644 index 0000000000000000000000000000000000000000..a73e30bcd34066231558709f701435395026b2ee GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^8$g(a8A!&?{Fw`+_yc@GT!HlSS8rdwe*668>ld%y zuKD}b4=BV^666=m5L(|b=XcHxAm7!~#W5tK@$DH$-UA8(3`nEQl-ild6t3eGAn zDy}QIskrMntl+5Pq~fgNvVyCMn~M7q4l0f+PAfR8xTv_QxUJy+103G`XIK{SWHl>u zVb08n`JCpC`MUKj6F!)CW`A>j<&D`fTjr1A<&|Z^2lLMCneXCtV8?8k4YN%6D&Azs z&g_}DIrotrvt>5SGU02UF!IjqnKx#~d=-x)8)ljC!Mrnj_zrRxCw9!1*)Yq54=i{V uNA}DcJ?zD#Vqw|Nn2Ic!IWa$&e`B7Vr4%?A@oSO*0000ufGF(HyNCAsbdCI3ApSuR7c}S=?#GzF|Yv;iLd!DIRE5GeTnc)K+Adg4BXk_?`%elF{r5}E+JOt9Pl literal 0 HcmV?d00001 From 60c47c51e0b41bc99cb78654a65688c0ad6978ef Mon Sep 17 00:00:00 2001 From: DS Date: Mon, 14 Apr 2025 17:18:21 +0200 Subject: [PATCH 130/284] Optimize name-id-lookup for MapBlock serialization (#16000) --- src/mapblock.cpp | 86 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 6987c36a6..348c02a1e 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -24,6 +24,55 @@ #include "util/serialize.h" #include "util/basic_macros.h" +// Like a std::unordered_map, but faster. +// +// Unassigned entries are marked with 0xFFFF. +// +// The static memory requires about 65535 * 2 bytes RAM in order to be +// sure we can handle all content ids. +class IdIdMapping +{ + static_assert(sizeof(content_t) == 2, "content_t must be 16-bit"); + +private: + std::unique_ptr m_mapping; + std::vector m_dirty; + +public: + IdIdMapping() + { + m_mapping = std::make_unique(CONTENT_MAX + 1); + memset(m_mapping.get(), 0xFF, (CONTENT_MAX + 1) * sizeof(content_t)); + } + + DISABLE_CLASS_COPY(IdIdMapping) + + content_t get(content_t k) const + { + return m_mapping[k]; + } + + void set(content_t k, content_t v) + { + m_mapping[k] = v; + m_dirty.push_back(k); + } + + void clear() + { + for (auto k : m_dirty) + m_mapping[k] = 0xFFFF; + m_dirty.clear(); + } + + static IdIdMapping &giveClearedThreadLocalInstance() + { + static thread_local IdIdMapping tl_ididmapping; + tl_ididmapping.clear(); + return tl_ididmapping; + } +}; + static const char *modified_reason_strings[] = { "reallocate or initial", "setIsUnderground", @@ -212,16 +261,7 @@ void MapBlock::expireIsAirCache() static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, const NodeDefManager *nodedef) { - // The static memory requires about 65535 * 2 bytes RAM in order to be - // sure we can handle all content ids. But it's absolutely worth it as it's - // a speedup of 4 for one of the major time consuming functions on storing - // mapblocks. - thread_local std::unique_ptr mapping; - static_assert(sizeof(content_t) == 2, "content_t must be 16-bit"); - if (!mapping) - mapping = std::make_unique(CONTENT_MAX + 1); - - memset(mapping.get(), 0xFF, (CONTENT_MAX + 1) * sizeof(content_t)); + IdIdMapping &mapping = IdIdMapping::giveClearedThreadLocalInstance(); content_t id_counter = 0; for (u32 i = 0; i < MapBlock::nodecount; i++) { @@ -229,12 +269,12 @@ static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, content_t id = CONTENT_IGNORE; // Try to find an existing mapping - if (mapping[global_id] != 0xFFFF) { - id = mapping[global_id]; + if (auto found = mapping.get(global_id); found != 0xFFFF) { + id = found; } else { // We have to assign a new mapping id = id_counter++; - mapping[global_id] = id; + mapping.set(global_id, id); const auto &name = nodedef->get(global_id).name; nimap->set(id, name); @@ -259,25 +299,20 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, std::unordered_set unnamed_contents; std::unordered_set unallocatable_contents; - bool previous_exists = false; - content_t previous_local_id = CONTENT_IGNORE; - content_t previous_global_id = CONTENT_IGNORE; + // Used to cache local to global id lookup. + IdIdMapping &mapping_cache = IdIdMapping::giveClearedThreadLocalInstance(); 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_exists && local_id == previous_local_id) { - nodes[i].setContent(previous_global_id); + + if (auto found = mapping_cache.get(local_id); found != 0xFFFF) { + nodes[i].setContent(found); continue; } std::string name; if (!nimap->getName(local_id, name)) { unnamed_contents.insert(local_id); - previous_exists = false; continue; } @@ -286,16 +321,13 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, global_id = gamedef->allocateUnknownNodeId(name); if (global_id == CONTENT_IGNORE) { unallocatable_contents.insert(name); - previous_exists = false; continue; } } nodes[i].setContent(global_id); // Save previous node local_id & global_id result - previous_local_id = local_id; - previous_global_id = global_id; - previous_exists = true; + mapping_cache.set(local_id, global_id); } for (const content_t c: unnamed_contents) { From bdaabad53c3e15c19163e2ecc6ce3952a298e9a9 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 14 Apr 2025 17:18:33 +0200 Subject: [PATCH 131/284] Warn if async engine seems stuck (#16010) --- src/script/cpp_api/s_async.cpp | 47 ++++++++++++++++++++++++++-------- src/script/cpp_api/s_async.h | 23 +++++++++++++++++ 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index 4cb46f6bb..982fb825e 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -24,6 +24,11 @@ extern "C" { #endif #include "lua_api/l_base.h" +// if a job is waiting for this duration, an additional thread will be spawned +static constexpr int AUTOSCALE_DELAY_MS = 1000; +// if jobs are waiting for this duration, a warning is printed +static constexpr int STUCK_DELAY_MS = 11500; + /******************************************************************************/ AsyncEngine::~AsyncEngine() { @@ -156,6 +161,7 @@ void AsyncEngine::step(lua_State *L) { stepJobResults(L); stepAutoscale(); + stepStuckWarning(); } void AsyncEngine::stepJobResults(lua_State *L) @@ -203,11 +209,9 @@ void AsyncEngine::stepAutoscale() if (autoscaleTimer && porting::getTimeMs() >= autoscaleTimer) { autoscaleTimer = 0; // Determine overlap with previous snapshot - unsigned int n = 0; - for (const auto &it : jobQueue) - n += autoscaleSeenJobs.count(it.id); - autoscaleSeenJobs.clear(); - infostream << "AsyncEngine: " << n << " jobs were still waiting after 1s" << std::endl; + size_t n = compareJobs(autoscaleSeenJobs); + infostream << "AsyncEngine: " << n << " jobs were still waiting after " + << AUTOSCALE_DELAY_MS << "ms, adding more threads." << std::endl; // Start this many new threads while (workerThreads.size() < autoscaleMaxWorkers && n > 0) { addWorkerThread(); @@ -216,13 +220,34 @@ void AsyncEngine::stepAutoscale() return; } - // 1) Check if there's anything in the queue + // 1) Check queue contents if (!autoscaleTimer && !jobQueue.empty()) { - // Take a snapshot of all jobs we have seen - for (const auto &it : jobQueue) - autoscaleSeenJobs.emplace(it.id); - // and set a timer for 1 second - autoscaleTimer = porting::getTimeMs() + 1000; + autoscaleSeenJobs.clear(); + snapshotJobs(autoscaleSeenJobs); + autoscaleTimer = porting::getTimeMs() + AUTOSCALE_DELAY_MS; + } +} + +void AsyncEngine::stepStuckWarning() +{ + MutexAutoLock autolock(jobQueueMutex); + + // 2) If the timer elapsed, check again + if (stuckTimer && porting::getTimeMs() >= stuckTimer) { + stuckTimer = 0; + size_t n = compareJobs(stuckSeenJobs); + if (n > 0) { + warningstream << "AsyncEngine: " << n << " jobs seem to be stuck in queue" + " (" << workerThreads.size() << " workers active)" << std::endl; + } + // fallthrough + } + + // 1) Check queue contents + if (!stuckTimer && !jobQueue.empty()) { + stuckSeenJobs.clear(); + snapshotJobs(stuckSeenJobs); + stuckTimer = porting::getTimeMs() + STUCK_DELAY_MS; } } diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index d2a6913ef..1b6743dea 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -138,6 +138,11 @@ protected: */ void stepAutoscale(); + /** + * Print warning message if too many jobs are stuck + */ + void stepStuckWarning(); + /** * Initialize environment with current registred functions * this function adds all functions registred by registerFunction to the @@ -149,6 +154,21 @@ protected: bool prepareEnvironment(lua_State* L, int top); private: + template + inline void snapshotJobs(T &to) + { + for (const auto &it : jobQueue) + to.emplace(it.id); + } + template + inline size_t compareJobs(const T &from) + { + size_t overlap = 0; + for (const auto &it : jobQueue) + overlap += from.count(it.id); + return overlap; + } + // Variable locking the engine against further modification bool initDone = false; @@ -158,6 +178,9 @@ private: u64 autoscaleTimer = 0; std::unordered_set autoscaleSeenJobs; + u64 stuckTimer = 0; + std::unordered_set stuckSeenJobs; + // Only set for the server async environment (duh) Server *server = nullptr; From a9c197b1e5d7265e88219aacef53965a22257fda Mon Sep 17 00:00:00 2001 From: sfence Date: Tue, 15 Apr 2025 21:42:12 +0200 Subject: [PATCH 132/284] Expand usable range of fill_ratio to about 2.3e-10 (#16026) --- src/mapgen/mg_decoration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index 8810a654d..298184a10 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -163,7 +163,7 @@ void Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) 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) + if (ps.next() <= deco_count_f * PcgRandom::RANDOM_RANGE) deco_count = 1; } } From 37d2bc8a5fad4fe84b1361ba38c1c9faac2c1660 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 15 Apr 2025 21:42:39 +0200 Subject: [PATCH 133/284] Reuse some allocations in ClientMap rendering --- src/client/clientmap.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 497c3452f..5098818ec 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -120,6 +120,11 @@ namespace { inline T subtract_or_zero(T a, T b) { return b >= a ? T(0) : (a - b); } + + // file-scope thread-local instances of the above two data structures, because + // allocating memory in a hot path can be expensive. + thread_local MeshBufListMaps tl_meshbuflistmaps; + thread_local DrawDescriptorList tl_drawdescriptorlist; } void CachedMeshBuffer::drop() @@ -978,8 +983,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) */ TimeTaker tt_collect(""); - MeshBufListMaps grouped_buffers; - DrawDescriptorList draw_order; + MeshBufListMaps &grouped_buffers = tl_meshbuflistmaps; + DrawDescriptorList &draw_order = tl_drawdescriptorlist; + grouped_buffers.clear(); + draw_order.clear(); auto is_frustum_culled = m_client->getCamera()->getFrustumCuller(); @@ -1375,8 +1382,10 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver, return intToFloat(mesh_grid.getMeshPos(pos) * MAP_BLOCKSIZE - m_camera_offset, BS); }; - MeshBufListMaps grouped_buffers; - DrawDescriptorList draw_order; + MeshBufListMaps &grouped_buffers = tl_meshbuflistmaps; + DrawDescriptorList &draw_order = tl_drawdescriptorlist; + grouped_buffers.clear(); + draw_order.clear(); std::size_t count = 0; std::size_t meshes_per_frame = m_drawlist_shadow.size() / total_frames + 1; From cf07b56235dfd14148f614bf535838023dbef143 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 13 Apr 2025 17:20:41 +0200 Subject: [PATCH 134/284] Expand workarounds for format inconsistency with BGRA8888 extension on GLES fixes #16011 --- irr/src/COpenGLCoreTexture.h | 22 +++++++++++++++------- irr/src/OpenGLES2/Driver.cpp | 4 ++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index 51b122075..ba9ad89c3 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -165,9 +165,8 @@ public: } #ifndef IRR_COMPILE_GL_COMMON - // On GLES 3.0 we must use sized internal formats for textures in certain - // cases (e.g. with ETT_2D_MS). However ECF_A8R8G8B8 is mapped to GL_BGRA - // (an unsized format). + // On GLES 3.0 we must use sized internal formats for textures when calling + // glTexStorage. But ECF_A8R8G8B8 might be mapped to GL_BGRA (an unsized format). // Since we don't upload to RTT we can safely pick a different combo that works. if (InternalFormat == GL_BGRA && Driver->Version.Major >= 3) { InternalFormat = GL_RGBA8; @@ -271,7 +270,7 @@ public: // For OpenGL an array texture is basically just a 3D texture internally. // So if we call glGetTexImage() we would download the entire array, // except the caller only wants a single layer. - // To do this properly we could have to use glGetTextureSubImage() [4.5] + // To do this properly we could use glGetTextureSubImage() [4.5] // or some trickery with glTextureView() [4.3]. // Also neither of those will work on GLES. @@ -522,10 +521,18 @@ protected: } // reference: + bool use_tex_storage = Driver->getFeature().TexStorage; + +#ifndef IRR_COMPILE_GL_COMMON + // On GLES 3.0 if we don't have a sized format suitable for glTexStorage, + // just avoid using it. Only affects the extension that provides BGRA. + if (InternalFormat == GL_BGRA && Driver->Version.Major >= 3) + use_tex_storage = false; +#endif switch (Type) { case ETT_2D: - if (Driver->getFeature().TexStorage) { + if (use_tex_storage) { GL.TexStorage2D(TextureType, levels, InternalFormat, Size.Width, Size.Height); } else { @@ -541,6 +548,7 @@ protected: // glTexImage2DMultisample is supported by OpenGL 3.2+ // glTexStorage2DMultisample is supported by OpenGL 4.3+ and OpenGL ES 3.1+ + // so pick the most compatible one #ifdef IRR_COMPILE_GL_COMMON // legacy driver constexpr bool use_gl_impl = true; #else @@ -557,7 +565,7 @@ protected: case ETT_CUBEMAP: for (u32 i = 0; i < 6; i++) { GLenum target = getTextureTarget(i); - if (Driver->getFeature().TexStorage) { + if (use_tex_storage) { GL.TexStorage2D(target, levels, InternalFormat, Size.Width, Size.Height); } else { @@ -568,7 +576,7 @@ protected: } break; case ETT_2D_ARRAY: - if (Driver->getFeature().TexStorage) { + if (use_tex_storage) { GL.TexStorage3D(TextureType, levels, InternalFormat, Size.Width, Size.Height, layers); } else { diff --git a/irr/src/OpenGLES2/Driver.cpp b/irr/src/OpenGLES2/Driver.cpp index 2db021088..84e702a8f 100644 --- a/irr/src/OpenGLES2/Driver.cpp +++ b/irr/src/OpenGLES2/Driver.cpp @@ -63,8 +63,8 @@ void COpenGLES2Driver::initFeatures() TextureFormats[ECF_D24S8] = {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}; // NOTE a recent (2024) revision of EXT_texture_format_BGRA8888 also - // adds a sized format GL_BGRA8_EXT. We have a workaround in place to - // fix up the InternalFormat in case of render targets. + // adds a sized format GL_BGRA8_EXT. Because we can't rely on that we + // have stupid workarounds in place on texture creation... if (FeatureAvailable[IRR_GL_EXT_texture_format_BGRA8888] || FeatureAvailable[IRR_GL_APPLE_texture_format_BGRA8888]) TextureFormats[ECF_A8R8G8B8] = {GL_BGRA, GL_BGRA, GL_UNSIGNED_BYTE}; From 04e82749db3c3d08d525e2ab05296da593c27010 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 13 Apr 2025 17:34:45 +0200 Subject: [PATCH 135/284] Make ETLF_FLIP_Y_UP_RTT work for texture download on GLES --- irr/src/COpenGLCoreTexture.h | 41 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index ba9ad89c3..bc9a7e919 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -289,24 +289,8 @@ public: GL.GetTexImage(tmpTextureType, MipLevelStored, PixelFormat, PixelType, tmpImage->getData()); TEST_GL_ERROR(Driver); - if (IsRenderTarget && lockFlags == ETLF_FLIP_Y_UP_RTT) { - const s32 pitch = tmpImage->getPitch(); - - u8 *srcA = static_cast(tmpImage->getData()); - u8 *srcB = srcA + (tmpImage->getDimension().Height - 1) * pitch; - - u8 *tmpBuffer = new u8[pitch]; - - for (u32 i = 0; i < tmpImage->getDimension().Height; i += 2) { - memcpy(tmpBuffer, srcA, pitch); - memcpy(srcA, srcB, pitch); - memcpy(srcB, tmpBuffer, pitch); - srcA += pitch; - srcB -= pitch; - } - - delete[] tmpBuffer; - } + if (IsRenderTarget && lockFlags == ETLF_FLIP_Y_UP_RTT) + flipImageY(tmpImage); } else { @@ -337,11 +321,12 @@ public: TEST_GL_ERROR(Driver); + if (IsRenderTarget && lockFlags == ETLF_FLIP_Y_UP_RTT) + flipImageY(tmpImage); + void *src = tmpImage->getData(); void *dest = LockImage->getData(); - // FIXME: what about ETLF_FLIP_Y_UP_RTT - switch (ColorFormat) { case ECF_A1R5G5B5: CColorConverter::convert_A8R8G8B8toA1B5G5R5(src, tmpImage->getDimension().getArea(), dest); @@ -507,6 +492,22 @@ protected: Pitch = Size.Width * IImage::getBitsPerPixelFromFormat(ColorFormat) / 8; } + static void flipImageY(IImage *image) + { + const u32 pitch = image->getPitch(); + u8 *srcA = static_cast(image->getData()); + u8 *srcB = srcA + (image->getDimension().Height - 1) * pitch; + + std::vector tmpBuffer(pitch); + for (u32 i = 0; i < image->getDimension().Height; i += 2) { + memcpy(tmpBuffer.data(), srcA, pitch); + memcpy(srcA, srcB, pitch); + memcpy(srcB, tmpBuffer.data(), pitch); + srcA += pitch; + srcB -= pitch; + } + } + void initTexture(u32 layers) { // Compressed textures cannot be pre-allocated and are initialized on upload From fd857374603e63ab1092034dc8b82f7a3927caff Mon Sep 17 00:00:00 2001 From: Vincent Robinson Date: Wed, 16 Apr 2025 16:20:39 -0700 Subject: [PATCH 136/284] Add `allow_close[]` element to formspecs (#15971) --- README.md | 1 + doc/lua_api.md | 11 +++- games/devtest/mods/testformspec/formspec.lua | 9 ++- src/client/game.cpp | 8 +-- src/client/game_formspec.cpp | 3 + src/client/inputhandler.cpp | 4 ++ src/gui/guiEngine.cpp | 2 +- src/gui/guiFormSpecMenu.cpp | 67 ++++++++++---------- src/gui/guiFormSpecMenu.h | 10 +-- 9 files changed, 71 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 33340053a..2d5cdf890 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Some can be changed in the key config dialog in the settings tab. | T | Chat | | / | Command | | Esc | Pause menu/abort/exit (pauses only singleplayer game) | +| Ctrl + Esc | Exit directly to main menu from anywhere, bypassing pause menu | | + | Increase view range | | - | Decrease view range | | K | Enable/disable fly mode (needs fly privilege) | diff --git a/doc/lua_api.md b/doc/lua_api.md index 882bfe341..d8e58fcde 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2903,6 +2903,13 @@ Elements * For information on converting forms to the new coordinate system, see `Migrating to Real Coordinates`. +### `allow_close[]` + +* When set to false, the formspec will not close when the user tries to close + it with the Escape key or similar. Default true. +* The formspec can still be closed with `*_exit[]` elements and + `core.close_formspec()`, regardless of this setting. + ### `container[,]` * Start of a container block, moves all physical elements in the container by @@ -6206,8 +6213,10 @@ Call these functions only at load time! * `table`: See `core.explode_table_event` * `scrollbar`: See `core.explode_scrollbar_event` * Special case: `["quit"]="true"` is sent when the user actively - closed the form by mouse click, keypress or through a button_exit[] + closed the form by mouse click, keypress or through a `button_exit[]` element. + * Special case: `["try_quit"]="true"` is sent when the user tries to + close the formspec, but the formspec used `allow_close[false]`. * Special case: `["key_enter"]="true"` is sent when the user pressed the Enter key and the focus was either nowhere (causing the formspec to be closed) or on a button. If the focus was on a text field, diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua index 29014f1f2..f2f632fa0 100644 --- a/games/devtest/mods/testformspec/formspec.lua +++ b/games/devtest/mods/testformspec/formspec.lua @@ -339,10 +339,11 @@ local pages = { [[ formspec_version[3] size[12,13] + allow_close[false] image_button[0,0;1,1;logo.png;rc_image_button_1x1;1x1] - image_button[1,0;2,2;logo.png;rc_image_button_2x2;2x2] + image_button_exit[1,0;2,2;logo.png;rc_image_button_2x2;2x2 exit] button[0,2;1,1;rc_button_1x1;1x1] - button[1,2;2,2;rc_button_2x2;2x2] + button_exit[1,2;2,2;rc_button_2x2;2x2 exit] item_image[0,4;1,1;air] item_image[1,4;2,2;air] item_image_button[0,6;1,1;testformspec:node;rc_item_image_button_1x1;1x1] @@ -575,6 +576,10 @@ core.register_on_player_receive_fields(function(player, formname, fields) if fields.submit_window then show_test_formspec(player:get_player_name()) end + + if fields.try_quit then + core.chat_send_player(player:get_player_name(), "Quit attempt received") + end end) core.register_chatcommand("test_formspec", { diff --git a/src/client/game.cpp b/src/client/game.cpp index ee6cd0af4..78d009c7e 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1050,10 +1050,6 @@ void Game::shutdown() if (g_touchcontrols) g_touchcontrols->hide(); - // only if the shutdown progress bar isn't shown yet - if (m_shutdown_progress == 0.0f) - showOverlayMessage(N_("Shutting down..."), 0, 0); - clouds.reset(); gui_chat_console.reset(); @@ -1065,6 +1061,10 @@ void Game::shutdown() g_menumgr.deleteFront(); } + // only if the shutdown progress bar isn't shown yet + if (m_shutdown_progress == 0.0f) + showOverlayMessage(N_("Shutting down..."), 0, 0); + chat_backend->addMessage(L"", L"# Disconnected."); chat_backend->addMessage(L"", L""); diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp index 4bd1a42bd..dc4be3699 100644 --- a/src/client/game_formspec.cpp +++ b/src/client/game_formspec.cpp @@ -205,6 +205,9 @@ void GameFormSpec::init(Client *client, RenderingEngine *rendering_engine, Input m_input = input; m_pause_script = std::make_unique(client); m_pause_script->loadBuiltin(); + + // Make sure any remaining game callback requests are cleared out. + *g_gamecallback = MainGameCallback(); } void GameFormSpec::deleteFormspec() diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 492f2fd6c..e7bdd4f30 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -148,6 +148,10 @@ bool MyEventReceiver::OnEvent(const SEvent &event) } fullscreen_is_down = event.KeyInput.PressedDown; return true; + } else if (keyCode == EscapeKey && + event.KeyInput.PressedDown && event.KeyInput.Control) { + g_gamecallback->disconnect(); + return true; } } diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index a83a913ec..860fce665 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -168,7 +168,7 @@ GUIEngine::GUIEngine(JoystickController *joystick, "", false); - m_menu->allowClose(false); + m_menu->defaultAllowClose(false); m_menu->lockSize(true,v2u32(800,600)); // Initialize scripting diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 59d6dc5a7..39d8797b4 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -105,7 +105,6 @@ GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick, current_keys_pending.key_down = false; current_keys_pending.key_up = false; current_keys_pending.key_enter = false; - current_keys_pending.key_escape = false; m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay"); m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname"); @@ -2833,6 +2832,11 @@ void GUIFormSpecMenu::parseModel(parserData *data, const std::string &element) m_fields.push_back(spec); } +void GUIFormSpecMenu::parseAllowClose(parserData *data, const std::string &element) +{ + m_allowclose = is_yes(element); +} + void GUIFormSpecMenu::removeAll() { // Remove children @@ -2901,6 +2905,7 @@ const std::unordered_mapgotText(fields); return; + } else if (quitmode == quit_mode_try) { + fields["try_quit"] = "true"; } if (current_keys_pending.key_down) { @@ -3816,11 +3822,6 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode) current_field_enter_pending.clear(); } - if (current_keys_pending.key_escape) { - fields["key_escape"] = "true"; - current_keys_pending.key_escape = false; - } - for (const GUIFormSpecMenu::FieldSpec &s : m_fields) { if (s.send) { std::string name = s.fname; @@ -3997,6 +3998,8 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) if (m_allowclose) { acceptInput(quit_mode_accept); quitMenu(); + } else { + acceptInput(quit_mode_try); } } } @@ -4013,6 +4016,7 @@ void GUIFormSpecMenu::tryClose() acceptInput(quit_mode_cancel); quitMenu(); } else { + acceptInput(quit_mode_try); m_text_dst->gotText(L"MenuQuit"); } } @@ -4059,9 +4063,13 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) 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); - quitMenu(); + if (current_keys_pending.key_enter) { + if (m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } else { + acceptInput(quit_mode_try); + } } else { acceptInput(); } @@ -4806,14 +4814,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s32 caller_id = event.GUIEvent.Caller->getID(); if (caller_id == 257) { - if (m_allowclose) { - acceptInput(quit_mode_accept); - quitMenu(); - } else { - acceptInput(); - m_text_dst->gotText(L"ExitButton"); - } - // quitMenu deallocates menu + acceptInput(quit_mode_accept); + m_text_dst->gotText(L"ExitButton"); + quitMenu(); return true; } @@ -4842,12 +4845,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } if (s.is_exit) { - if (m_allowclose) { - acceptInput(quit_mode_accept); - quitMenu(); - } else { - m_text_dst->gotText(L"ExitButton"); - } + acceptInput(quit_mode_accept); + m_text_dst->gotText(L"ExitButton"); + quitMenu(); return true; } @@ -4910,15 +4910,18 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } - if (m_allowclose && close_on_enter) { - current_keys_pending.key_enter = true; - acceptInput(quit_mode_accept); - quitMenu(); + current_keys_pending.key_enter = true; + + if (close_on_enter) { + if (m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } else { + acceptInput(quit_mode_try); + } } else { - current_keys_pending.key_enter = true; acceptInput(); } - // quitMenu deallocates menu return true; } } diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index 04b65a967..3cfd79cae 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -47,7 +47,8 @@ enum FormspecFieldType { enum FormspecQuitMode { quit_mode_no, quit_mode_accept, - quit_mode_cancel + quit_mode_cancel, + quit_mode_try, }; enum ButtonEventType : u8 @@ -203,9 +204,9 @@ public: m_text_dst = text_dst; } - void allowClose(bool value) + void defaultAllowClose(bool value) { - m_allowclose = value; + m_default_allowclose = value; } void setDebugView(bool value) @@ -363,6 +364,7 @@ protected: u64 m_hovered_time = 0; s32 m_old_tooltip_id = -1; + bool m_default_allowclose = true; bool m_allowclose = true; bool m_lock = false; v2u32 m_lockscreensize; @@ -422,7 +424,6 @@ private: bool key_up; bool key_down; bool key_enter; - bool key_escape; }; fs_key_pending current_keys_pending; @@ -484,6 +485,7 @@ private: void parseStyle(parserData *data, const std::string &element); void parseSetFocus(parserData *, const std::string &element); void parseModel(parserData *data, const std::string &element); + void parseAllowClose(parserData *data, const std::string &element); bool parseMiddleRect(const std::string &value, core::rect *parsed_rect); From 7375358afd6cdea664a52da60d8b89ad0269e316 Mon Sep 17 00:00:00 2001 From: sfence Date: Thu, 17 Apr 2025 12:35:14 +0200 Subject: [PATCH 137/284] Fix warning in mg_decoration.cpp --- src/mapgen/mg_decoration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index 298184a10..1e3b7ec53 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -163,7 +163,7 @@ void Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) deco_count = deco_count_f; } else if (deco_count_f > 0.0f) { // For very low density calculate a chance for 1 decoration - if (ps.next() <= deco_count_f * PcgRandom::RANDOM_RANGE) + if (ps.next() <= deco_count_f * static_cast(PcgRandom::RANDOM_RANGE)) deco_count = 1; } } From 0695541bf59b305cc6661deb5d57a30e92cb9c6c Mon Sep 17 00:00:00 2001 From: Travis Wrightsman Date: Thu, 17 Apr 2025 12:35:31 +0200 Subject: [PATCH 138/284] Fix cross-building by ensuring output path is set --- src/CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 88c0c5a45..d772c10ad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -591,9 +591,12 @@ if(USE_CURL) endif() -# When cross-compiling assume the user doesn't want to run the executable anyway, -# otherwise place it in /bin/ since Luanti can only run from there. -if(NOT CMAKE_CROSSCOMPILING) +# When cross-compiling place the executable in /bin so that multiple +# targets can be built from the same source folder. Otherwise, place it in +# /bin/ since Luanti can only run from there. +if(CMAKE_CROSSCOMPILING) + set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin") +else() set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin") endif() From 2bb7ed208c75c3f0bcfd0fc6caa5dc62ef46a271 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Sun, 20 Apr 2025 13:28:31 +0200 Subject: [PATCH 139/284] Add vcpkg.json (#15863) --- .github/workflows/windows.yml | 10 ++-------- vcpkg.json | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 vcpkg.json diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index e9698c93b..979ab0144 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -71,9 +71,7 @@ jobs: name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }} runs-on: windows-2019 env: - VCPKG_VERSION: d5ec528843d29e3a52d745a64b469f810b2cedbf - # 2025.02.14 - vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp sdl2 + VCPKG_DEFAULT_TRIPLET: ${{matrix.config.vcpkg_triplet}} strategy: fail-fast: false matrix: @@ -97,13 +95,9 @@ jobs: - uses: actions/checkout@v4 - name: Restore from cache and run vcpkg - uses: lukka/run-vcpkg@v7 + uses: lukka/run-vcpkg@v11 with: - vcpkgArguments: ${{env.vcpkg_packages}} vcpkgDirectory: '${{ github.workspace }}\vcpkg' - appendedCacheKey: ${{ matrix.config.vcpkg_triplet }} - vcpkgGitCommitId: ${{ env.VCPKG_VERSION }} - vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }} - name: CMake # Note: See #15976 for why CMAKE_POLICY_VERSION_MINIMUM=3.5 is set. diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..213f4514a --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,29 @@ +{ + "builtin-baseline": "d5ec528843d29e3a52d745a64b469f810b2cedbf", + "dependencies": [ + "zlib", + "zstd", + { + "name": "curl", + "features": [ + "winssl" + ] + }, + "openal-soft", + "libvorbis", + "libogg", + "libjpeg-turbo", + "sqlite3", + "freetype", + "luajit", + "gmp", + "jsoncpp", + { + "name": "gettext", + "features": [ + "tools" + ] + }, + "sdl2" + ] +} From bf15036831e5bfcf548bc6d4b2f93c93fa8f373a Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:20:22 +0200 Subject: [PATCH 140/284] Show SDL version in the About tab (#16046) --- irr/include/IrrlichtDevice.h | 7 +++++++ irr/src/CIrrDeviceSDL.h | 8 ++++++++ src/script/lua_api/l_mainmenu.cpp | 9 ++++++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/irr/include/IrrlichtDevice.h b/irr/include/IrrlichtDevice.h index 6ae21a212..d159142c4 100644 --- a/irr/include/IrrlichtDevice.h +++ b/irr/include/IrrlichtDevice.h @@ -16,6 +16,7 @@ #include "IrrCompileConfig.h" #include "position2d.h" #include "SColor.h" // video::ECOLOR_FORMAT +#include #include namespace irr @@ -332,6 +333,12 @@ public: used. */ virtual E_DEVICE_TYPE getType() const = 0; + //! Get the version string of the underlying system (e.g. SDL) + virtual std::string getVersionString() const + { + return ""; + } + //! Get the display density in dots per inch. //! Returns 0.0f on failure. virtual float getDisplayDensity() const = 0; diff --git a/irr/src/CIrrDeviceSDL.h b/irr/src/CIrrDeviceSDL.h index 8a7e5f680..33d97ce56 100644 --- a/irr/src/CIrrDeviceSDL.h +++ b/irr/src/CIrrDeviceSDL.h @@ -109,6 +109,14 @@ public: return EIDT_SDL; } + //! Get the SDL version + std::string getVersionString() const override + { + SDL_version ver; + SDL_GetVersion(&ver); + return std::to_string(ver.major) + "." + std::to_string(ver.minor) + "." + std::to_string(ver.patch); + } + //! Get the display density in dots per inch. float getDisplayDensity() const override; diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 7070952e6..240927a4a 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -945,8 +945,9 @@ int ModApiMainMenu::l_get_active_renderer(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_active_irrlicht_device(lua_State *L) { - const char *device_name = [] { - switch (RenderingEngine::get_raw_device()->getType()) { + auto device = RenderingEngine::get_raw_device(); + std::string device_name = [device] { + switch (device->getType()) { case EIDT_WIN32: return "WIN32"; case EIDT_X11: return "X11"; case EIDT_OSX: return "OSX"; @@ -955,7 +956,9 @@ int ModApiMainMenu::l_get_active_irrlicht_device(lua_State *L) default: return "Unknown"; } }(); - lua_pushstring(L, device_name); + if (auto version = device->getVersionString(); !version.empty()) + device_name.append(" " + version); + lua_pushstring(L, device_name.c_str()); return 1; } From c1d2124102f1dc29c7daa2d21532fa6f170477c5 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:20:33 +0200 Subject: [PATCH 141/284] SDL: Send events for X1 and X2 mouse buttons (#16025) --- irr/src/CIrrDeviceSDL.cpp | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index 536eda96e..28a92f450 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -766,12 +766,7 @@ bool CIrrDeviceSDL::run() SDL_Keymod keymod = SDL_GetModState(); irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT; - irrevent.MouseInput.X = static_cast(SDL_event.button.x * ScaleX); - irrevent.MouseInput.Y = static_cast(SDL_event.button.y * ScaleY); - irrevent.MouseInput.Shift = (keymod & KMOD_SHIFT) != 0; - irrevent.MouseInput.Control = (keymod & KMOD_CTRL) != 0; - - irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED; + irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED; // value to be ignored #ifdef _IRR_EMSCRIPTEN_PLATFORM_ // Handle mouselocking in emscripten in Windowed mode. @@ -834,11 +829,29 @@ bool CIrrDeviceSDL::run() MouseButtonStates &= ~irr::EMBSM_MIDDLE; } break; + + // Since Irrlicht does not have event types for X1/X2 buttons, we simply pass + // those as keycodes instead. This is relatively hacky but avoids the effort of + // adding more mouse events that will be discarded anyway once we switch to SDL + case SDL_BUTTON_X1: + irrevent.EventType = irr::EET_KEY_INPUT_EVENT; + irrevent.KeyInput.Key = irr::KEY_XBUTTON1; + break; + + case SDL_BUTTON_X2: + irrevent.EventType = irr::EET_KEY_INPUT_EVENT; + irrevent.KeyInput.Key = irr::KEY_XBUTTON2; + break; } - irrevent.MouseInput.ButtonStates = MouseButtonStates; - - if (irrevent.MouseInput.Event != irr::EMIE_MOUSE_MOVED) { + bool shift = (keymod & KMOD_SHIFT) != 0; + bool control = (keymod & KMOD_CTRL) != 0; + if (irrevent.EventType == irr::EET_MOUSE_INPUT_EVENT && irrevent.MouseInput.Event != irr::EMIE_MOUSE_MOVED) { + irrevent.MouseInput.ButtonStates = MouseButtonStates; + irrevent.MouseInput.X = static_cast(SDL_event.button.x * ScaleX); + irrevent.MouseInput.Y = static_cast(SDL_event.button.y * ScaleY); + irrevent.MouseInput.Shift = shift; + irrevent.MouseInput.Control = control; postEventFromUser(irrevent); if (irrevent.MouseInput.Event >= EMIE_LMOUSE_PRESSED_DOWN && irrevent.MouseInput.Event <= EMIE_MMOUSE_PRESSED_DOWN) { @@ -851,6 +864,12 @@ bool CIrrDeviceSDL::run() postEventFromUser(irrevent); } } + } else if (irrevent.EventType == irr::EET_KEY_INPUT_EVENT) { + irrevent.KeyInput.Char = 0; + irrevent.KeyInput.PressedDown = SDL_event.type == SDL_MOUSEBUTTONDOWN; + irrevent.KeyInput.Shift = shift; + irrevent.KeyInput.Control = control; + postEventFromUser(irrevent); } break; } From 23bfb2db72638f4de5681e0b69d41fd8b0a8bda7 Mon Sep 17 00:00:00 2001 From: y5nw <37980625+y5nw@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:20:49 +0200 Subject: [PATCH 142/284] Move keybinding settings to (Lua-based) setting menu (#15791) --- .luacheckrc | 12 + builtin/common/menu.lua | 11 + builtin/common/settings/components.lua | 54 +++ builtin/common/settings/dlg_settings.lua | 33 +- builtin/common/settings/settingtypes.lua | 6 +- builtin/mainmenu/init.lua | 9 +- builtin/pause_menu/init.lua | 1 + builtin/settingtypes.txt | 467 ++++++++++++----------- doc/menu_lua_api.md | 1 - src/client/game_formspec.cpp | 12 - src/client/inputhandler.cpp | 2 + src/client/inputhandler.h | 14 + src/gui/CMakeLists.txt | 2 +- src/gui/guiButtonKey.cpp | 141 +++++++ src/gui/guiButtonKey.h | 75 ++++ src/gui/guiFormSpecMenu.cpp | 14 +- src/gui/guiKeyChangeMenu.cpp | 401 ------------------- src/gui/guiKeyChangeMenu.h | 63 --- src/gui/mainmenumanager.h | 14 - src/script/lua_api/l_mainmenu.cpp | 18 - src/script/lua_api/l_mainmenu.h | 2 - src/script/lua_api/l_menu_common.cpp | 10 + src/script/lua_api/l_menu_common.h | 1 + src/script/lua_api/l_pause_menu.cpp | 9 +- src/script/lua_api/l_pause_menu.h | 1 - 25 files changed, 591 insertions(+), 782 deletions(-) create mode 100644 builtin/common/menu.lua create mode 100644 src/gui/guiButtonKey.cpp create mode 100644 src/gui/guiButtonKey.h delete mode 100644 src/gui/guiKeyChangeMenu.cpp delete mode 100644 src/gui/guiKeyChangeMenu.h diff --git a/.luacheckrc b/.luacheckrc index de45f0413..670c84325 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -33,6 +33,13 @@ globals = { "_", } +stds.menu_common = { + globals = { + "mt_color_grey", "mt_color_blue", "mt_color_lightblue", "mt_color_green", + "mt_color_dark_green", "mt_color_orange", "mt_color_red", + }, +} + files["builtin/client/register.lua"] = { globals = { debug = {fields={"getinfo"}}, @@ -73,11 +80,16 @@ files["builtin/common/filterlist.lua"] = { } files["builtin/mainmenu"] = { + std = "+menu_common", globals = { "gamedata", }, } +files["builtin/common/settings"] = { + std = "+menu_common", +} + files["builtin/common/tests"] = { read_globals = { "describe", diff --git a/builtin/common/menu.lua b/builtin/common/menu.lua new file mode 100644 index 000000000..165286470 --- /dev/null +++ b/builtin/common/menu.lua @@ -0,0 +1,11 @@ +-- Luanti +-- SPDX-License-Identifier: LGPL-2.1-or-later + +-- These colors are used by the main menu and the settings menu +mt_color_grey = "#AAAAAA" +mt_color_blue = "#6389FF" +mt_color_lightblue = "#99CCFF" +mt_color_green = "#72FF63" +mt_color_dark_green = "#25C191" +mt_color_orange = "#FF8800" +mt_color_red = "#FF3300" diff --git a/builtin/common/settings/components.lua b/builtin/common/settings/components.lua index de7a63fee..b3035fd51 100644 --- a/builtin/common/settings/components.lua +++ b/builtin/common/settings/components.lua @@ -37,6 +37,7 @@ local make = {} -- * `fs` is a string for the formspec. -- Components should be relative to `0,0`, and not exceed `avail_w` or the returned `used_height`. -- * `used_height` is the space used by components in `fs`. +-- * `spacing`: (Optional) the vertical margin to be added before the component (default 0.25) -- * `on_submit = function(self, fields, parent)`: -- * `fields`: submitted formspec fields -- * `parent`: the fstk element for the settings UI, use to show dialogs @@ -442,6 +443,59 @@ local function make_noise_params(setting) } end +function make.key(setting) + local btn_bind = "bind_" .. setting.name + local btn_clear = "unbind_" .. setting.name + local function add_conflict_warnings(fs, height) + local value = core.settings:get(setting.name) + if value == "" then + return height + end + for _, o in ipairs(core.full_settingtypes) do + if o.type == "key" and o.name ~= setting.name and core.are_keycodes_equal(core.settings:get(o.name), value) then + table.insert(fs, ("label[0,%f;%s]"):format(height + 0.3, + core.colorize(mt_color_orange, fgettext([[Conflicts with "$1"]], fgettext(o.readable_name))))) + height = height + 0.6 + end + end + return height + end + return { + info_text = setting.comment, + setting = setting, + spacing = 0.1, + + get_formspec = function(self, avail_w) + self.resettable = core.settings:has(setting.name) + local btn_bind_width = math.max(2.5, avail_w/2) + local value = core.settings:get(setting.name) + local fs = { + ("label[0,0.4;%s]"):format(get_label(setting)), + ("button_key[%f,0;%f,0.8;%s;%s]"):format( + btn_bind_width, btn_bind_width-0.8, + btn_bind, core.formspec_escape(value)), + ("image_button[%f,0;0.8,0.8;%s;%s;]"):format(avail_w - 0.8, + core.formspec_escape(defaulttexturedir .. "clear.png"), + btn_clear), + ("tooltip[%s;%s]"):format(btn_clear, fgettext("Remove keybinding")), + } + local height = 0.8 + height = add_conflict_warnings(fs, height) + return table.concat(fs), height + end, + + on_submit = function(self, fields) + if fields[btn_bind] then + core.settings:set(setting.name, fields[btn_bind]) + return true + elseif fields[btn_clear] then + core.settings:set(setting.name, "") + return true + end + end, + } +end + if INIT == "pause_menu" then -- Making the noise parameter dialog work in the pause menu settings would -- require porting "FSTK" (at least the dialog API) from the mainmenu formspec diff --git a/builtin/common/settings/dlg_settings.lua b/builtin/common/settings/dlg_settings.lua index b43d9ebdc..8d4a8b6de 100644 --- a/builtin/common/settings/dlg_settings.lua +++ b/builtin/common/settings/dlg_settings.lua @@ -22,7 +22,6 @@ local component_funcs = dofile(path .. "components.lua") local shadows_component = dofile(path .. "shadows_component.lua") local loaded = false -local full_settings local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png") local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png") local all_pages = {} @@ -32,7 +31,7 @@ local filtered_page_by_id = page_by_id local function get_setting_info(name) - for _, entry in ipairs(full_settings) do + for _, entry in ipairs(core.full_settingtypes) do if entry.type ~= "category" and entry.name == name then return entry end @@ -70,7 +69,7 @@ local function load_settingtypes() end end - for _, entry in ipairs(full_settings) do + for _, entry in ipairs(core.full_settingtypes) do if entry.type == "category" then if entry.level == 0 then section = entry.name @@ -104,24 +103,7 @@ local function load() end loaded = true - full_settings = settingtypes.parse_config_file(false, true) - - local change_keys = { - query_text = "Controls", - requires = { - keyboard_mouse = true, - }, - context = "client", - get_formspec = function(self, avail_w) - local btn_w = math.min(avail_w, 3) - return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8 - end, - on_submit = function(self, fields) - if fields.btn_change_keys then - core.show_keys_menu() - end - end, - } + core.full_settingtypes = settingtypes.parse_config_file(false, true) local touchscreen_layout = { query_text = "Touchscreen layout", @@ -166,7 +148,6 @@ local function load() load_settingtypes() - table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) -- insert after "touch_controls" table.insert(page_by_id.controls_touchscreen.content, 2, touchscreen_layout) do @@ -665,7 +646,13 @@ local function get_formspec(dialogdata) fs[#fs + 1] = "container_end[]" if used_h > 0 then - y = y + used_h + 0.25 + local spacing = 0.25 + local next_comp = dialogdata.components[i + 1] + if next_comp and next_comp.spacing then + spacing = next_comp.spacing + end + + y = y + used_h + spacing end end diff --git a/builtin/common/settings/settingtypes.lua b/builtin/common/settings/settingtypes.lua index 39a50e1f4..90ff8975c 100644 --- a/builtin/common/settings/settingtypes.lua +++ b/builtin/common/settings/settingtypes.lua @@ -249,9 +249,9 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se if not default then return "Invalid string setting" end - if setting_type == "key" and not read_all then - -- ignore key type if read_all is false - return + + if setting_type == "key" then + requires.keyboard_mouse = true end table.insert(settings, { diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index ec33f33b3..acf1f738d 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -15,14 +15,6 @@ --with this program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -mt_color_grey = "#AAAAAA" -mt_color_blue = "#6389FF" -mt_color_lightblue = "#99CCFF" -mt_color_green = "#72FF63" -mt_color_dark_green = "#25C191" -mt_color_orange = "#FF8800" -mt_color_red = "#FF3300" - MAIN_TAB_W = 15.5 MAIN_TAB_H = 7.1 TABHEADER_H = 0.85 @@ -35,6 +27,7 @@ local basepath = core.get_builtin_path() defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. DIR_DELIM .. "pack" .. DIR_DELIM +dofile(basepath .. "common" .. DIR_DELIM .. "menu.lua") dofile(basepath .. "common" .. DIR_DELIM .. "filterlist.lua") dofile(basepath .. "fstk" .. DIR_DELIM .. "buttonbar.lua") dofile(basepath .. "fstk" .. DIR_DELIM .. "dialog.lua") diff --git a/builtin/pause_menu/init.lua b/builtin/pause_menu/init.lua index 035d2ba99..01c5dc856 100644 --- a/builtin/pause_menu/init.lua +++ b/builtin/pause_menu/init.lua @@ -8,5 +8,6 @@ defaulttexturedir = "" local builtin_shared = {} assert(loadfile(commonpath .. "register.lua"))(builtin_shared) +assert(loadfile(commonpath .. "menu.lua"))(builtin_shared) assert(loadfile(pausepath .. "register.lua"))(builtin_shared) dofile(commonpath .. "settings" .. DIR_DELIM .. "init.lua") diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 825a85b97..83d0b48e0 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -31,7 +31,7 @@ # - enum # - path # - filepath -# - key (will be ignored in GUI, since a special key change dialog exists) +# - key # - flags # - noise_params_2d # - noise_params_3d @@ -91,6 +91,8 @@ # * touchscreen / keyboard_mouse # * opengl / gles # * You can negate any requirement by prepending with ! +# * The "keyboard_mouse" requirement is automatically added to settings with the +# "key" type. # # Sections are marked by a single line in the format: [Section Name] # Sub-section are marked by adding * in front of the section name: [*Sub-section] @@ -178,6 +180,228 @@ enable_hotbar_mouse_wheel (Hotbar: Enable mouse wheel for selection) bool true # Requires: keyboard_mouse invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false +[**Keybindings] + +# Key for moving the player forward. +keymap_forward (Move forward) key KEY_KEY_W + +# Key for moving the player backward. +# Will also disable autoforward, when active. +keymap_backward (Move backward) key KEY_KEY_S + +# Key for moving the player left. +keymap_left (Move left) key KEY_KEY_A + +# Key for moving the player right. +keymap_right (Move right) key KEY_KEY_D + +# Key for jumping. +keymap_jump (Jump) key KEY_SPACE + +# Key for sneaking. +# Also used for climbing down and descending in water if aux1_descends is disabled. +keymap_sneak (Sneak) key KEY_LSHIFT + +# Key for digging, punching or using something. +# (Note: The actual meaning might vary on a per-game basis.) +keymap_dig (Dig/punch/use) key KEY_LBUTTON + +# Key for placing an item/block or for using something. +# (Note: The actual meaning might vary on a per-game basis.) +keymap_place (Place/use) key KEY_RBUTTON + +# Key for opening the inventory. +keymap_inventory (Open inventory) key KEY_KEY_I + +# Key for moving fast in fast mode. +keymap_aux1 (Aux1) key KEY_KEY_E + +# Key for opening the chat window. +keymap_chat (Open chat) key KEY_KEY_T + +# Key for opening the chat window to type commands. +keymap_cmd (Command) key / + +# Key for opening the chat window to type local commands. +keymap_cmd_local (Local command) key . + +# Key for toggling unlimited view range. +keymap_rangeselect (Range select) key + +# Key for toggling flying. +keymap_freemove (Toggle fly) key KEY_KEY_K + +# Key for toggling pitch move mode. +keymap_pitchmove (Toggle pitchmove) key + +# Key for toggling fast mode. +keymap_fastmove (Toggle fast) key KEY_KEY_J + +# Key for toggling noclip mode. +keymap_noclip (Toggle noclip) key KEY_KEY_H + +# Key for selecting the next item in the hotbar. +keymap_hotbar_next (Hotbar: select next item) key KEY_KEY_N + +# Key for selecting the previous item in the hotbar. +keymap_hotbar_previous (Hotbar: select previous item) key KEY_KEY_B + +# Key for muting the game. +keymap_mute (Mute) key KEY_KEY_M + +# Key for increasing the volume. +keymap_increase_volume (Increase volume) key + +# Key for decreasing the volume. +keymap_decrease_volume (Decrease volume) key + +# Key for toggling autoforward. +keymap_autoforward (Toggle automatic forward) key + +# Key for toggling cinematic mode. +keymap_cinematic (Toggle cinematic mode) key + +# Key for toggling display of minimap. +keymap_minimap (Toggle minimap) key KEY_KEY_V + +# Key for taking screenshots. +keymap_screenshot (Screenshot) key KEY_F12 + +# Key for toggling fullscreen mode. +keymap_fullscreen (Toggle fullscreen) key KEY_F11 + +# Key for dropping the currently selected item. +keymap_drop (Drop item) key KEY_KEY_Q + +# Key to use view zoom when possible. +keymap_zoom (Zoom) key KEY_KEY_Z + +# Key for toggling the display of the HUD. +keymap_toggle_hud (Toggle HUD) key KEY_F1 + +# Key for toggling the display of chat. +keymap_toggle_chat (Toggle chat log) key KEY_F2 + +# Key for toggling the display of the large chat console. +keymap_console (Large chat console) key KEY_F10 + +# Key for toggling the display of fog. +keymap_toggle_fog (Toggle fog) key KEY_F3 + +# Key for toggling the display of debug info. +keymap_toggle_debug (Toggle debug info) key KEY_F5 + +# Key for toggling the display of the profiler. Used for development. +keymap_toggle_profiler (Toggle profiler) key KEY_F6 + +# Key for toggling the display of mapblock boundaries. +keymap_toggle_block_bounds (Toggle block bounds) key + +# Key for switching between first- and third-person camera. +keymap_camera_mode (Toggle camera mode) key KEY_KEY_C + +# Key for increasing the viewing range. +keymap_increase_viewing_range_min (Increase view range) key + + +# Key for decreasing the viewing range. +keymap_decrease_viewing_range_min (Decrease view range) key - + +# Key for selecting the first hotbar slot. +keymap_slot1 (Hotbar slot 1) key KEY_KEY_1 + +# Key for selecting the second hotbar slot. +keymap_slot2 (Hotbar slot 2) key KEY_KEY_2 + +# Key for selecting the third hotbar slot. +keymap_slot3 (Hotbar slot 3) key KEY_KEY_3 + +# Key for selecting the fourth hotbar slot. +keymap_slot4 (Hotbar slot 4) key KEY_KEY_4 + +# Key for selecting the fifth hotbar slot. +keymap_slot5 (Hotbar slot 5) key KEY_KEY_5 + +# Key for selecting the sixth hotbar slot. +keymap_slot6 (Hotbar slot 6) key KEY_KEY_6 + +# Key for selecting the seventh hotbar slot. +keymap_slot7 (Hotbar slot 7) key KEY_KEY_7 + +# Key for selecting the eighth hotbar slot. +keymap_slot8 (Hotbar slot 8) key KEY_KEY_8 + +# Key for selecting the ninth hotbar slot. +keymap_slot9 (Hotbar slot 9) key KEY_KEY_9 + +# Key for selecting the tenth hotbar slot. +keymap_slot10 (Hotbar slot 10) key KEY_KEY_0 + +# Key for selecting the 11th hotbar slot. +keymap_slot11 (Hotbar slot 11) key + +# Key for selecting the 12th hotbar slot. +keymap_slot12 (Hotbar slot 12) key + +# Key for selecting the 13th hotbar slot. +keymap_slot13 (Hotbar slot 13) key + +# Key for selecting the 14th hotbar slot. +keymap_slot14 (Hotbar slot 14) key + +# Key for selecting the 15th hotbar slot. +keymap_slot15 (Hotbar slot 15) key + +# Key for selecting the 16th hotbar slot. +keymap_slot16 (Hotbar slot 16) key + +# Key for selecting the 17th hotbar slot. +keymap_slot17 (Hotbar slot 17) key + +# Key for selecting the 18th hotbar slot. +keymap_slot18 (Hotbar slot 18) key + +# Key for selecting the 19th hotbar slot. +keymap_slot19 (Hotbar slot 19) key + +# Key for selecting the 20th hotbar slot. +keymap_slot20 (Hotbar slot 20) key + +# Key for selecting the 21st hotbar slot. +keymap_slot21 (Hotbar slot 21) key + +# Key for selecting the 22nd hotbar slot. +keymap_slot22 (Hotbar slot 22) key + +# Key for selecting the 23rd hotbar slot. +keymap_slot23 (Hotbar slot 23) key + +# Key for selecting the 24th hotbar slot. +keymap_slot24 (Hotbar slot 24) key + +# Key for selecting the 25th hotbar slot. +keymap_slot25 (Hotbar slot 25) key + +# Key for selecting the 26th hotbar slot. +keymap_slot26 (Hotbar slot 26) key + +# Key for selecting the 27th hotbar slot. +keymap_slot27 (Hotbar slot 27) key + +# Key for selecting the 28th hotbar slot. +keymap_slot28 (Hotbar slot 28) key + +# Key for selecting the 29th hotbar slot. +keymap_slot29 (Hotbar slot 29) key + +# Key for selecting the 30th hotbar slot. +keymap_slot30 (Hotbar slot 30) key + +# Key for selecting the 31st hotbar slot. +keymap_slot31 (Hotbar slot 31) key + +# Key for selecting the 32nd hotbar slot. +keymap_slot32 (Hotbar slot 32) key + [*Touchscreen] # Enables the touchscreen controls, allowing you to play the game with a touchscreen. @@ -1825,7 +2049,6 @@ instrument.profiler (Profiler) bool false # 0 = disable. Useful for developers. profiler_print_interval (Engine profiling data print interval) int 0 0 - [*Advanced] [**Graphics] [client] @@ -2219,6 +2442,23 @@ curl_parallel_limit (cURL parallel limit) int 8 1 2147483647 # Maximum time a file download (e.g. a mod download) may take, stated in milliseconds. curl_file_download_timeout (cURL file download timeout) int 300000 5000 2147483647 +[**Client Debugging] [client] + +# Key for toggling the camera update. Only usable with 'debug' privilege. +keymap_toggle_update_camera (Toggle camera update) key + +# Key for switching to the previous entry in Quicktune. +keymap_quicktune_prev (Quicktune: select previous entry) key + +# Key for switching to the next entry in Quicktune. +keymap_quicktune_next (Quicktune: select next entry) key + +# Key for decrementing the selected value in Quicktune. +keymap_quicktune_dec (Quicktune: decrement value) key + +# Key for incrementing the selected value in Quicktune. +keymap_quicktune_inc (Quicktune: increment value) key + [**Miscellaneous] # Clickable weblinks (middle-click or Ctrl+left-click) enabled in chat console output. @@ -2345,226 +2585,3 @@ show_technical_names (Show technical names) bool false # Controlled by a checkbox in the settings menu. show_advanced (Show advanced settings) bool false - -# Key for moving the player forward. -keymap_forward (Forward key) key KEY_KEY_W - -# Key for moving the player backward. -# Will also disable autoforward, when active. -keymap_backward (Backward key) key KEY_KEY_S - -# Key for moving the player left. -keymap_left (Left key) key KEY_KEY_A - -# Key for moving the player right. -keymap_right (Right key) key KEY_KEY_D - -# Key for jumping. -keymap_jump (Jump key) key KEY_SPACE - -# Key for sneaking. -# Also used for climbing down and descending in water if aux1_descends is disabled. -keymap_sneak (Sneak key) key KEY_LSHIFT - -# Key for digging, punching or using something. -# (Note: The actual meaning might vary on a per-game basis.) -keymap_dig (Dig/punch/use key) key KEY_LBUTTON - -# Key for placing an item/block or for using something. -# (Note: The actual meaning might vary on a per-game basis.) -keymap_place (Place/use key) key KEY_RBUTTON - -# Key for opening the inventory. -keymap_inventory (Inventory key) key KEY_KEY_I - -# Key for moving fast in fast mode. -keymap_aux1 (Aux1 key) key KEY_KEY_E - -# Key for opening the chat window. -keymap_chat (Chat key) key KEY_KEY_T - -# Key for opening the chat window to type commands. -keymap_cmd (Command key) key / - -# Key for opening the chat window to type local commands. -keymap_cmd_local (Command key) key . - -# Key for toggling unlimited view range. -keymap_rangeselect (Range select key) key - -# Key for toggling flying. -keymap_freemove (Fly key) key KEY_KEY_K - -# Key for toggling pitch move mode. -keymap_pitchmove (Pitch move key) key - -# Key for toggling fast mode. -keymap_fastmove (Fast key) key KEY_KEY_J - -# Key for toggling noclip mode. -keymap_noclip (Noclip key) key KEY_KEY_H - -# Key for selecting the next item in the hotbar. -keymap_hotbar_next (Hotbar next key) key KEY_KEY_N - -# Key for selecting the previous item in the hotbar. -keymap_hotbar_previous (Hotbar previous key) key KEY_KEY_B - -# Key for muting the game. -keymap_mute (Mute key) key KEY_KEY_M - -# Key for increasing the volume. -keymap_increase_volume (Inc. volume key) key - -# Key for decreasing the volume. -keymap_decrease_volume (Dec. volume key) key - -# Key for toggling autoforward. -keymap_autoforward (Automatic forward key) key - -# Key for toggling cinematic mode. -keymap_cinematic (Cinematic mode key) key - -# Key for toggling display of minimap. -keymap_minimap (Minimap key) key KEY_KEY_V - -# Key for taking screenshots. -keymap_screenshot (Screenshot) key KEY_F12 - -# Key for toggling fullscreen mode. -keymap_fullscreen (Fullscreen key) key KEY_F11 - -# Key for dropping the currently selected item. -keymap_drop (Drop item key) key KEY_KEY_Q - -# Key to use view zoom when possible. -keymap_zoom (View zoom key) key KEY_KEY_Z - -# Key for selecting the first hotbar slot. -keymap_slot1 (Hotbar slot 1 key) key KEY_KEY_1 - -# Key for selecting the second hotbar slot. -keymap_slot2 (Hotbar slot 2 key) key KEY_KEY_2 - -# Key for selecting the third hotbar slot. -keymap_slot3 (Hotbar slot 3 key) key KEY_KEY_3 - -# Key for selecting the fourth hotbar slot. -keymap_slot4 (Hotbar slot 4 key) key KEY_KEY_4 - -# Key for selecting the fifth hotbar slot. -keymap_slot5 (Hotbar slot 5 key) key KEY_KEY_5 - -# Key for selecting the sixth hotbar slot. -keymap_slot6 (Hotbar slot 6 key) key KEY_KEY_6 - -# Key for selecting the seventh hotbar slot. -keymap_slot7 (Hotbar slot 7 key) key KEY_KEY_7 - -# Key for selecting the eighth hotbar slot. -keymap_slot8 (Hotbar slot 8 key) key KEY_KEY_8 - -# Key for selecting the ninth hotbar slot. -keymap_slot9 (Hotbar slot 9 key) key KEY_KEY_9 - -# Key for selecting the tenth hotbar slot. -keymap_slot10 (Hotbar slot 10 key) key KEY_KEY_0 - -# Key for selecting the 11th hotbar slot. -keymap_slot11 (Hotbar slot 11 key) key - -# Key for selecting the 12th hotbar slot. -keymap_slot12 (Hotbar slot 12 key) key - -# Key for selecting the 13th hotbar slot. -keymap_slot13 (Hotbar slot 13 key) key - -# Key for selecting the 14th hotbar slot. -keymap_slot14 (Hotbar slot 14 key) key - -# Key for selecting the 15th hotbar slot. -keymap_slot15 (Hotbar slot 15 key) key - -# Key for selecting the 16th hotbar slot. -keymap_slot16 (Hotbar slot 16 key) key - -# Key for selecting the 17th hotbar slot. -keymap_slot17 (Hotbar slot 17 key) key - -# Key for selecting the 18th hotbar slot. -keymap_slot18 (Hotbar slot 18 key) key - -# Key for selecting the 19th hotbar slot. -keymap_slot19 (Hotbar slot 19 key) key - -# Key for selecting the 20th hotbar slot. -keymap_slot20 (Hotbar slot 20 key) key - -# Key for selecting the 21st hotbar slot. -keymap_slot21 (Hotbar slot 21 key) key - -# Key for selecting the 22nd hotbar slot. -keymap_slot22 (Hotbar slot 22 key) key - -# Key for selecting the 23rd hotbar slot. -keymap_slot23 (Hotbar slot 23 key) key - -# Key for selecting the 24th hotbar slot. -keymap_slot24 (Hotbar slot 24 key) key - -# Key for selecting the 25th hotbar slot. -keymap_slot25 (Hotbar slot 25 key) key - -# Key for selecting the 26th hotbar slot. -keymap_slot26 (Hotbar slot 26 key) key - -# Key for selecting the 27th hotbar slot. -keymap_slot27 (Hotbar slot 27 key) key - -# Key for selecting the 28th hotbar slot. -keymap_slot28 (Hotbar slot 28 key) key - -# Key for selecting the 29th hotbar slot. -keymap_slot29 (Hotbar slot 29 key) key - -# Key for selecting the 30th hotbar slot. -keymap_slot30 (Hotbar slot 30 key) key - -# Key for selecting the 31st hotbar slot. -keymap_slot31 (Hotbar slot 31 key) key - -# Key for selecting the 32nd hotbar slot. -keymap_slot32 (Hotbar slot 32 key) key - -# Key for toggling the display of the HUD. -keymap_toggle_hud (HUD toggle key) key KEY_F1 - -# Key for toggling the display of chat. -keymap_toggle_chat (Chat toggle key) key KEY_F2 - -# Key for toggling the display of the large chat console. -keymap_console (Large chat console key) key KEY_F10 - -# Key for toggling the display of fog. -keymap_toggle_fog (Fog toggle key) key KEY_F3 - -# Key for toggling the camera update. Only usable with 'debug' privilege. -keymap_toggle_update_camera (Camera update toggle key) key - -# Key for toggling the display of debug info. -keymap_toggle_debug (Debug info toggle key) key KEY_F5 - -# Key for toggling the display of the profiler. Used for development. -keymap_toggle_profiler (Profiler toggle key) key KEY_F6 - -# Key for toggling the display of mapblock boundaries. -keymap_toggle_block_bounds (Block bounds toggle key) key - -# Key for switching between first- and third-person camera. -keymap_camera_mode (Toggle camera mode key) key KEY_KEY_C - -# Key for increasing the viewing range. -keymap_increase_viewing_range_min (View range increase key) key + - -# Key for decreasing the viewing range. -keymap_decrease_viewing_range_min (View range decrease key) key - diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index 0fccf37a3..38940a7ed 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -217,7 +217,6 @@ GUI doing tiling (background only) * `core.set_clouds()` * `core.set_topleft_text(text)` -* `core.show_keys_menu()` * `core.show_touchscreen_layout()` * `core.show_path_select_dialog(formname, caption, is_file_select)` * shows a path select dialog diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp index dc4be3699..3b86ae7e3 100644 --- a/src/client/game_formspec.cpp +++ b/src/client/game_formspec.cpp @@ -16,7 +16,6 @@ #include "gui/touchcontrols.h" #include "gui/touchscreeneditor.h" #include "gui/guiPasswordChange.h" -#include "gui/guiKeyChangeMenu.h" #include "gui/guiPasswordChange.h" #include "gui/guiOpenURL.h" #include "gui/guiVolumeChange.h" @@ -538,12 +537,6 @@ bool GameFormSpec::handleCallbacks() g_gamecallback->changevolume_requested = false; } - if (g_gamecallback->keyconfig_requested) { - (void)make_irr(guienv, guiroot, -1, - &g_menumgr, texture_src); - g_gamecallback->keyconfig_requested = false; - } - if (g_gamecallback->touchscreenlayout_requested) { (new GUITouchscreenLayout(guienv, guiroot, -1, &g_menumgr, texture_src))->drop(); @@ -556,11 +549,6 @@ bool GameFormSpec::handleCallbacks() g_gamecallback->show_open_url_dialog.clear(); } - if (g_gamecallback->keyconfig_changed) { - m_input->reloadKeybindings(); // update the cache with new settings - g_gamecallback->keyconfig_changed = false; - } - return true; } diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index e7bdd4f30..64c9cc968 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -14,6 +14,8 @@ void MyEventReceiver::reloadKeybindings() { + clearKeyCache(); + keybindings[KeyType::FORWARD] = getKeySetting("keymap_forward"); keybindings[KeyType::BACKWARD] = getKeySetting("keymap_backward"); keybindings[KeyType::LEFT] = getKeySetting("keymap_left"); diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index fec52a74d..85da30ff8 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -12,6 +12,8 @@ #include #include #include "keycode.h" +#include "settings.h" +#include "util/string.h" class InputHandler; @@ -132,6 +134,13 @@ private: class InputHandler { public: + InputHandler() + { + for (const auto &name: Settings::getLayer(SL_DEFAULTS)->getNames()) + if (str_starts_with(name, "keymap_")) + g_settings->registerChangedCallback(name, &settingChangedCallback, this); + } + virtual ~InputHandler() = default; virtual bool isRandom() const @@ -163,6 +172,11 @@ public: virtual void clear() {} virtual void releaseAllKeys() {} + static void settingChangedCallback(const std::string &name, void *data) + { + static_cast(data)->reloadKeybindings(); + } + JoystickController joystick; }; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 26fd5bfcf..0c1db2ae0 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -5,6 +5,7 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiButton.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonItemImage.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guiButtonKey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp @@ -12,7 +13,6 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiFormSpecMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiInventoryList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiItemImage.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiOpenURL.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp diff --git a/src/gui/guiButtonKey.cpp b/src/gui/guiButtonKey.cpp new file mode 100644 index 000000000..85e3719b1 --- /dev/null +++ b/src/gui/guiButtonKey.cpp @@ -0,0 +1,141 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "guiButtonKey.h" +using namespace irr::gui; + +GUIButtonKey *GUIButtonKey::addButton(IGUIEnvironment *environment, + const core::rect &rectangle, ISimpleTextureSource *tsrc, + IGUIElement *parent, s32 id, const wchar_t *text, + const wchar_t *tooltiptext) +{ + auto button = make_irr(environment, + parent ? parent : environment->getRootGUIElement(), id, rectangle, tsrc); + + if (text) + button->setText(text); + + if (tooltiptext) + button->setToolTipText(tooltiptext); + + return button.get(); +} + +void GUIButtonKey::setKey(KeyPress kp) +{ + key_value = kp; + keysym = utf8_to_wide(kp.sym()); + super::setText(wstrgettext(kp.name()).c_str()); +} + +void GUIButtonKey::sendKey() +{ + if (Parent) { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = nullptr; + e.GUIEvent.EventType = EGET_BUTTON_CLICKED; + Parent->OnEvent(e); + } +} + +bool GUIButtonKey::OnEvent(const SEvent & event) +{ + switch(event.EventType) + { + case EET_KEY_INPUT_EVENT: + if (!event.KeyInput.PressedDown) { + bool wasPressed = isPressed(); + setPressed(false); + if (capturing) { + cancelCapture(); + if (event.KeyInput.Key != KEY_ESCAPE) + sendKey(); + return true; + } else if (wasPressed && (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE)) { + startCapture(); + return true; + } + break; + } else if (capturing) { + if (event.KeyInput.Key != KEY_ESCAPE) { + setPressed(true); + setKey(KeyPress(event.KeyInput)); + } + return true; + } else if (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE) { + setPressed(true); + return true; + } + break; + case EET_MOUSE_INPUT_EVENT: { + auto in_rect = AbsoluteClippingRect.isPointInside( + core::position2d(event.MouseInput.X, event.MouseInput.Y)); + switch (event.MouseInput.Event) + { + case EMIE_LMOUSE_LEFT_UP: + if (!capturing && in_rect) { + setPressed(false); + startCapture(); + return true; + } + [[fallthrough]]; + case EMIE_MMOUSE_LEFT_UP: [[fallthrough]]; + case EMIE_RMOUSE_LEFT_UP: + setPressed(false); + if (capturing) { + cancelCapture(); + sendKey(); + return true; + } + break; + case EMIE_LMOUSE_PRESSED_DOWN: + if (capturing) { + if (event.MouseInput.Simulated) { + cancelCapture(true); + if (in_rect) + return true; + } else { + setPressed(true); + setKey(LMBKey); + return true; + } + } else if (in_rect) { + Environment->setFocus(this); + setPressed(true); + return true; + } + break; + case EMIE_MMOUSE_PRESSED_DOWN: + if (capturing) { + setPressed(true); + setKey(MMBKey); + return true; + } + break; + case EMIE_RMOUSE_PRESSED_DOWN: + if (capturing) { + setPressed(true); + setKey(RMBKey); + return true; + } + break; + default: + break; + } + break; + } + case EET_GUI_EVENT: + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) { + if (capturing) + return true; + else + nostart = false; // lift nostart restriction if "mouse" (finger) is released outside the button + } + default: + break; + } + + return Parent ? Parent->OnEvent(event) : false; +} diff --git a/src/gui/guiButtonKey.h b/src/gui/guiButtonKey.h new file mode 100644 index 000000000..75d0d56ee --- /dev/null +++ b/src/gui/guiButtonKey.h @@ -0,0 +1,75 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include "guiButton.h" +#include "client/keycode.h" +#include "util/string.h" +#include "gettext.h" + +using namespace irr; + +class GUIButtonKey : public GUIButton +{ + using super = GUIButton; + +public: + //! Constructor + GUIButtonKey(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, + s32 id, core::rect rectangle, ISimpleTextureSource *tsrc, + bool noclip = false) + : GUIButton(environment, parent, id, rectangle, tsrc, noclip) {} + + //! Sets the text for the key field + virtual void setText(const wchar_t *text) override + { + setKey(wide_to_utf8(text)); + } + + //! Gets the value for the key field + virtual const wchar_t *getText() const override + { + return keysym.c_str(); + } + + //! Do not drop returned handle + static GUIButtonKey *addButton(gui::IGUIEnvironment *environment, + const core::rect &rectangle, ISimpleTextureSource *tsrc, + IGUIElement *parent, s32 id, const wchar_t *text = L"", + const wchar_t *tooltiptext = L""); + + //! Called if an event happened + virtual bool OnEvent(const SEvent &event) override; + +private: + void sendKey(); + + //! Start key capture + void startCapture() + { + if (nostart) { + nostart = false; + return; + } + capturing = true; + super::setText(wstrgettext("Press Button").c_str()); + } + + //! Cancel key capture + // inhibit_restart: whether the next call to startCapture should be inhibited + void cancelCapture(bool inhibit_restart = false) + { + capturing = false; + nostart |= inhibit_restart; + super::setText(wstrgettext(key_value.name()).c_str()); + } + + //! Sets the captured key and stop capturing + void setKey(KeyPress key); + + bool capturing = false; + bool nostart = false; + KeyPress key_value = {}; + std::wstring keysym; +}; diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 39d8797b4..3c5a9ffde 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -47,6 +47,7 @@ #include "guiButton.h" #include "guiButtonImage.h" #include "guiButtonItemImage.h" +#include "guiButtonKey.h" #include "guiEditBoxWithScrollbar.h" #include "guiInventoryList.h" #include "guiItemImage.h" @@ -1025,8 +1026,16 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element) if (data->type == "button_url" || data->type == "button_url_exit") spec.url = url; - GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, - data->current_parent, spec.fid, spec.flabel.c_str()); + GUIButton *e; + + if (data->type == "button_key") { + spec.ftype = f_Unknown; + e = GUIButtonKey::addButton(Environment, rect, m_tsrc, + data->current_parent, spec.fid, spec.flabel.c_str()); + } else { + e = GUIButton::addButton(Environment, rect, m_tsrc, + data->current_parent, spec.fid, spec.flabel.c_str()); + } auto style = getStyleForElement(data->type, name, (data->type != "button") ? "button" : ""); @@ -2873,6 +2882,7 @@ const std::unordered_map -// Copyright (C) 2013 Ciaran Gultnieks -// Copyright (C) 2013 teddydestodes - -#include "guiKeyChangeMenu.h" -#include "debug.h" -#include "guiButton.h" -#include -#include -#include -#include -#include -#include -#include -#include "settings.h" -#include "gettext.h" - -#include "mainmenumanager.h" // for g_gamecallback - -#define KMaxButtonPerColumns 12 - -extern MainGameCallback *g_gamecallback; - -enum -{ - GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR, - // buttons - GUI_ID_KEY_FORWARD_BUTTON, - GUI_ID_KEY_BACKWARD_BUTTON, - GUI_ID_KEY_LEFT_BUTTON, - GUI_ID_KEY_RIGHT_BUTTON, - GUI_ID_KEY_AUX1_BUTTON, - GUI_ID_KEY_FLY_BUTTON, - GUI_ID_KEY_FAST_BUTTON, - GUI_ID_KEY_JUMP_BUTTON, - GUI_ID_KEY_NOCLIP_BUTTON, - GUI_ID_KEY_PITCH_MOVE, - GUI_ID_KEY_CHAT_BUTTON, - GUI_ID_KEY_CMD_BUTTON, - GUI_ID_KEY_CMD_LOCAL_BUTTON, - GUI_ID_KEY_CONSOLE_BUTTON, - GUI_ID_KEY_SNEAK_BUTTON, - GUI_ID_KEY_DROP_BUTTON, - GUI_ID_KEY_INVENTORY_BUTTON, - GUI_ID_KEY_HOTBAR_PREV_BUTTON, - GUI_ID_KEY_HOTBAR_NEXT_BUTTON, - GUI_ID_KEY_MUTE_BUTTON, - GUI_ID_KEY_DEC_VOLUME_BUTTON, - GUI_ID_KEY_INC_VOLUME_BUTTON, - GUI_ID_KEY_RANGE_BUTTON, - GUI_ID_KEY_ZOOM_BUTTON, - GUI_ID_KEY_CAMERA_BUTTON, - GUI_ID_KEY_MINIMAP_BUTTON, - GUI_ID_KEY_SCREENSHOT_BUTTON, - GUI_ID_KEY_CHATLOG_BUTTON, - GUI_ID_KEY_BLOCK_BOUNDS_BUTTON, - GUI_ID_KEY_HUD_BUTTON, - GUI_ID_KEY_FOG_BUTTON, - GUI_ID_KEY_DEC_RANGE_BUTTON, - GUI_ID_KEY_INC_RANGE_BUTTON, - GUI_ID_KEY_AUTOFWD_BUTTON, - // other - GUI_ID_CB_AUX1_DESCENDS, - GUI_ID_CB_DOUBLETAP_JUMP, - GUI_ID_CB_AUTOJUMP, -}; - -GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, - gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, - ISimpleTextureSource *tsrc) : - GUIModalMenu(env, parent, id, menumgr), - m_tsrc(tsrc) -{ - init_keys(); -} - -GUIKeyChangeMenu::~GUIKeyChangeMenu() -{ - removeAllChildren(); - key_used_text = nullptr; - - for (key_setting *ks : key_settings) { - delete ks; - } - key_settings.clear(); -} - -void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) -{ - removeAllChildren(); - key_used_text = nullptr; - - ScalingInfo info = getScalingInfo(screensize, v2u32(835, 430)); - const float s = info.scale; - DesiredRect = info.rect; - recalculateAbsolutePosition(false); - - v2s32 size = DesiredRect.getSize(); - v2s32 topleft(0, 0); - - { - core::rect rect(0, 0, 600 * s, 40 * s); - rect += topleft + v2s32(25 * s, 3 * s); - //gui::IGUIStaticText *t = - gui::StaticText::add(Environment, wstrgettext("Keybindings."), rect, - false, true, this, -1); - //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++) - { - key_setting *k = key_settings.at(i); - { - core::rect rect(0, 0, 150 * s, 20 * s); - rect += topleft + v2s32(offset.X, offset.Y); - gui::StaticText::add(Environment, 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); - k->button = GUIButton::addButton(Environment, rect, m_tsrc, this, k->id, - wstrgettext(k->key.name()).c_str()); - } - if ((i + 1) % KMaxButtonPerColumns == 0) { - offset.X += 260 * s; - offset.Y = 60 * s; - } else { - offset += v2s32(0, 25 * s); - } - } - - { - s32 option_x = offset.X; - s32 option_y = offset.Y + 5 * s; - u32 option_w = 180 * s; - { - core::rect rect(0, 0, option_w, 30 * s); - rect += topleft + v2s32(option_x, option_y); - Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this, - GUI_ID_CB_AUX1_DESCENDS, wstrgettext("\"Aux1\" = climb down").c_str()); - } - offset += v2s32(0, 25 * s); - } - - { - s32 option_x = offset.X; - s32 option_y = offset.Y + 5 * s; - u32 option_w = 280 * s; - { - core::rect rect(0, 0, option_w, 30 * s); - rect += topleft + v2s32(option_x, option_y); - Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this, - GUI_ID_CB_DOUBLETAP_JUMP, wstrgettext("Double tap \"jump\" to toggle fly").c_str()); - } - offset += v2s32(0, 25 * s); - } - - { - s32 option_x = offset.X; - s32 option_y = offset.Y + 5 * s; - u32 option_w = 280; - { - core::rect rect(0, 0, option_w, 30 * s); - rect += topleft + v2s32(option_x, option_y); - Environment->addCheckBox(g_settings->getBool("autojump"), rect, this, - GUI_ID_CB_AUTOJUMP, wstrgettext("Automatic jumping").c_str()); - } - offset += v2s32(0, 25); - } - - { - core::rect rect(0, 0, 100 * s, 30 * s); - rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s); - GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_BACK_BUTTON, - wstrgettext("Save").c_str()); - } - { - core::rect rect(0, 0, 100 * s, 30 * s); - rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s); - GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_ABORT_BUTTON, - wstrgettext("Cancel").c_str()); - } -} - -void GUIKeyChangeMenu::drawMenu() -{ - gui::IGUISkin* skin = Environment->getSkin(); - if (!skin) - return; - video::IVideoDriver* driver = Environment->getVideoDriver(); - - video::SColor bgcolor(140, 0, 0, 0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); - - gui::IGUIElement::draw(); -} - -bool GUIKeyChangeMenu::acceptInput() -{ - clearKeyCache(); - - for (key_setting *k : key_settings) { - std::string default_key; - Settings::getLayer(SL_DEFAULTS)->getNoEx(k->setting_name, default_key); - - if (k->key.sym() != default_key) - g_settings->set(k->setting_name, k->key.sym()); - else - g_settings->remove(k->setting_name); - } - - { - 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()); - } - { - 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()); - } - { - gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUTOJUMP); - if(e && e->getType() == gui::EGUIET_CHECK_BOX) - g_settings->setBool("autojump", ((gui::IGUICheckBox*)e)->isChecked()); - } - - g_gamecallback->signalKeyConfigChange(); - - return true; -} - -bool GUIKeyChangeMenu::resetMenu() -{ - if (active_key) { - active_key->button->setText(wstrgettext(active_key->key.name()).c_str()); - active_key = nullptr; - return false; - } - return true; -} -bool GUIKeyChangeMenu::OnEvent(const SEvent& event) -{ - if (event.EventType == EET_KEY_INPUT_EVENT && active_key - && event.KeyInput.PressedDown) { - - KeyPress kp(event.KeyInput); - - if (event.KeyInput.Key == irr::KEY_DELETE) - kp = KeyPress(""); // To erase key settings - else if (event.KeyInput.Key == irr::KEY_ESCAPE) - kp = active_key->key; // Cancel - - bool shift_went_down = false; - if(!shift_down && - (event.KeyInput.Key == irr::KEY_SHIFT || - event.KeyInput.Key == irr::KEY_LSHIFT || - event.KeyInput.Key == irr::KEY_RSHIFT)) - shift_went_down = true; - - // Display Key already in use message - bool key_in_use = false; - if (kp) { - for (key_setting *ks : key_settings) { - if (ks != active_key && ks->key == kp) { - key_in_use = true; - break; - } - } - } - - if (key_in_use && !this->key_used_text) { - core::rect rect(0, 0, 600, 40); - rect += v2s32(0, 0) + v2s32(25, 30); - this->key_used_text = gui::StaticText::add(Environment, - wstrgettext("Key already in use"), - rect, false, true, this, -1); - } else if (!key_in_use && this->key_used_text) { - this->key_used_text->remove(); - this->key_used_text = nullptr; - } - - // But go on - { - active_key->key = kp; - active_key->button->setText(wstrgettext(kp.name()).c_str()); - - // Allow characters made with shift - if (shift_went_down){ - shift_down = true; - return false; - } - - active_key = nullptr; - return true; - } - } 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; - // 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; - } - } - FATAL_ERROR_IF(!active_key, "Key setting not found"); - - shift_down = false; - active_key->button->setText(wstrgettext("press key").c_str()); - break; - } - Environment->setFocus(this); - } - } - return Parent ? Parent->OnEvent(event) : false; -} - -void GUIKeyChangeMenu::add_key(int id, std::wstring button_name, const std::string &setting_name) -{ - key_setting *k = new key_setting; - k->id = id; - - k->button_name = std::move(button_name); - k->setting_name = setting_name; - k->key = getKeySetting(k->setting_name.c_str()); - key_settings.push_back(k); -} - -// compare with button_titles in touchcontrols.cpp -void GUIKeyChangeMenu::init_keys() -{ - this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wstrgettext("Forward"), "keymap_forward"); - this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wstrgettext("Backward"), "keymap_backward"); - this->add_key(GUI_ID_KEY_LEFT_BUTTON, wstrgettext("Left"), "keymap_left"); - this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wstrgettext("Right"), "keymap_right"); - this->add_key(GUI_ID_KEY_AUX1_BUTTON, wstrgettext("Aux1"), "keymap_aux1"); - this->add_key(GUI_ID_KEY_JUMP_BUTTON, wstrgettext("Jump"), "keymap_jump"); - this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wstrgettext("Sneak"), "keymap_sneak"); - this->add_key(GUI_ID_KEY_DROP_BUTTON, wstrgettext("Drop"), "keymap_drop"); - this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wstrgettext("Inventory"), "keymap_inventory"); - this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON, wstrgettext("Prev. item"), "keymap_hotbar_previous"); - this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON, wstrgettext("Next item"), "keymap_hotbar_next"); - this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wstrgettext("Zoom"), "keymap_zoom"); - this->add_key(GUI_ID_KEY_CAMERA_BUTTON, wstrgettext("Change camera"), "keymap_camera_mode"); - this->add_key(GUI_ID_KEY_MINIMAP_BUTTON, wstrgettext("Toggle minimap"), "keymap_minimap"); - this->add_key(GUI_ID_KEY_FLY_BUTTON, wstrgettext("Toggle fly"), "keymap_freemove"); - this->add_key(GUI_ID_KEY_PITCH_MOVE, wstrgettext("Toggle pitchmove"), "keymap_pitchmove"); - this->add_key(GUI_ID_KEY_FAST_BUTTON, wstrgettext("Toggle fast"), "keymap_fastmove"); - this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wstrgettext("Toggle noclip"), "keymap_noclip"); - this->add_key(GUI_ID_KEY_MUTE_BUTTON, wstrgettext("Mute"), "keymap_mute"); - this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON, wstrgettext("Dec. volume"), "keymap_decrease_volume"); - this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON, wstrgettext("Inc. volume"), "keymap_increase_volume"); - this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON, wstrgettext("Autoforward"), "keymap_autoforward"); - this->add_key(GUI_ID_KEY_CHAT_BUTTON, wstrgettext("Chat"), "keymap_chat"); - this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON, wstrgettext("Screenshot"), "keymap_screenshot"); - this->add_key(GUI_ID_KEY_RANGE_BUTTON, wstrgettext("Range select"), "keymap_rangeselect"); - this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON, wstrgettext("Dec. range"), "keymap_decrease_viewing_range_min"); - this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON, wstrgettext("Inc. range"), "keymap_increase_viewing_range_min"); - this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wstrgettext("Console"), "keymap_console"); - this->add_key(GUI_ID_KEY_CMD_BUTTON, wstrgettext("Command"), "keymap_cmd"); - this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wstrgettext("Local command"), "keymap_cmd_local"); - this->add_key(GUI_ID_KEY_BLOCK_BOUNDS_BUTTON, wstrgettext("Block bounds"), "keymap_toggle_block_bounds"); - this->add_key(GUI_ID_KEY_HUD_BUTTON, wstrgettext("Toggle HUD"), "keymap_toggle_hud"); - this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wstrgettext("Toggle chat log"), "keymap_toggle_chat"); - this->add_key(GUI_ID_KEY_FOG_BUTTON, wstrgettext("Toggle fog"), "keymap_toggle_fog"); -} diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h deleted file mode 100644 index c858b2c93..000000000 --- a/src/gui/guiKeyChangeMenu.h +++ /dev/null @@ -1,63 +0,0 @@ -// Luanti -// SPDX-License-Identifier: LGPL-2.1-or-later -// Copyright (C) 2010-2013 celeron55, Perttu Ahola -// Copyright (C) 2013 Ciaran Gultnieks -// Copyright (C) 2013 teddydestodes - -#pragma once - -#include "modalMenu.h" -#include "client/keycode.h" -#include -#include -#include - -class ISimpleTextureSource; - -struct key_setting -{ - int id; - std::wstring button_name; - KeyPress key; - std::string setting_name; - gui::IGUIButton *button; -}; - -class GUIKeyChangeMenu : public GUIModalMenu -{ -public: - GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - IMenuManager *menumgr, ISimpleTextureSource *tsrc); - ~GUIKeyChangeMenu(); - - /* - Remove and re-add (or reposition) stuff - */ - void regenerateGui(v2u32 screensize); - - void drawMenu(); - - bool acceptInput(); - - bool OnEvent(const SEvent &event); - - bool pausesGame() { return true; } - -protected: - std::wstring getLabelByID(s32 id) { return L""; } - std::string getNameByID(s32 id) { return ""; } - -private: - void init_keys(); - - bool resetMenu(); - - void add_key(int id, std::wstring button_name, const std::string &setting_name); - - bool shift_down = false; - - key_setting *active_key = nullptr; - gui::IGUIStaticText *key_used_text = nullptr; - std::vector key_settings; - ISimpleTextureSource *m_tsrc; -}; diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 2ac00fee7..9b5bea5f7 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -22,12 +22,10 @@ class IGameCallback public: virtual void exitToOS() = 0; virtual void openSettings() = 0; - virtual void keyConfig() = 0; virtual void disconnect() = 0; virtual void changePassword() = 0; virtual void changeVolume() = 0; virtual void showOpenURLDialog(const std::string &url) = 0; - virtual void signalKeyConfigChange() = 0; virtual void touchscreenLayout() = 0; }; @@ -136,16 +134,6 @@ public: changevolume_requested = true; } - void keyConfig() override - { - keyconfig_requested = true; - } - - void signalKeyConfigChange() override - { - keyconfig_changed = true; - } - void touchscreenLayout() override { touchscreenlayout_requested = true; @@ -160,10 +148,8 @@ public: bool settings_requested = false; bool changepassword_requested = false; bool changevolume_requested = false; - bool keyconfig_requested = false; bool touchscreenlayout_requested = false; bool shutdown_requested = false; - bool keyconfig_changed = false; std::string show_open_url_dialog = ""; }; diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 240927a4a..3d096e018 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -9,7 +9,6 @@ #include "scripting_mainmenu.h" #include "gui/guiEngine.h" #include "gui/guiMainMenu.h" -#include "gui/guiKeyChangeMenu.h" #include "gui/guiPathSelectMenu.h" #include "gui/touchscreeneditor.h" #include "version.h" @@ -538,22 +537,6 @@ int ModApiMainMenu::l_get_content_translation(lua_State *L) return 1; } -/******************************************************************************/ -int ModApiMainMenu::l_show_keys_menu(lua_State *L) -{ - GUIEngine *engine = getGuiEngine(L); - sanity_check(engine != NULL); - - GUIKeyChangeMenu *kmenu = new GUIKeyChangeMenu( - engine->m_rendering_engine->get_gui_env(), - engine->m_parent, - -1, - engine->m_menumanager, - engine->m_texture_source.get()); - kmenu->drop(); - return 0; -} - /******************************************************************************/ int ModApiMainMenu::l_show_touchscreen_layout(lua_State *L) { @@ -1070,7 +1053,6 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_content_translation); API_FCT(start); API_FCT(close); - API_FCT(show_keys_menu); API_FCT(show_touchscreen_layout); API_FCT(create_world); API_FCT(delete_world); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 63f45d74b..fc2d90af8 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -65,8 +65,6 @@ private: //gui - static int l_show_keys_menu(lua_State *L); - static int l_show_touchscreen_layout(lua_State *L); static int l_show_path_select_dialog(lua_State *L); diff --git a/src/script/lua_api/l_menu_common.cpp b/src/script/lua_api/l_menu_common.cpp index 19c97c042..8b3ffda4e 100644 --- a/src/script/lua_api/l_menu_common.cpp +++ b/src/script/lua_api/l_menu_common.cpp @@ -35,11 +35,21 @@ int ModApiMenuCommon::l_irrlicht_device_supports_touch(lua_State *L) } +int ModApiMenuCommon::l_are_keycodes_equal(lua_State *L) +{ + auto k1 = luaL_checkstring(L, 1); + auto k2 = luaL_checkstring(L, 2); + lua_pushboolean(L, KeyPress(k1) == KeyPress(k2)); + return 1; +} + + void ModApiMenuCommon::Initialize(lua_State *L, int top) { API_FCT(gettext); API_FCT(get_active_driver); API_FCT(irrlicht_device_supports_touch); + API_FCT(are_keycodes_equal); } diff --git a/src/script/lua_api/l_menu_common.h b/src/script/lua_api/l_menu_common.h index e94e562e1..8cbd58f11 100644 --- a/src/script/lua_api/l_menu_common.h +++ b/src/script/lua_api/l_menu_common.h @@ -13,6 +13,7 @@ private: static int l_gettext(lua_State *L); static int l_get_active_driver(lua_State *L); static int l_irrlicht_device_supports_touch(lua_State *L); + static int l_are_keycodes_equal(lua_State *L); public: static void Initialize(lua_State *L, int top); diff --git a/src/script/lua_api/l_pause_menu.cpp b/src/script/lua_api/l_pause_menu.cpp index c2a9a81e5..b1aaae185 100644 --- a/src/script/lua_api/l_pause_menu.cpp +++ b/src/script/lua_api/l_pause_menu.cpp @@ -3,18 +3,12 @@ // Copyright (C) 2025 grorp #include "l_pause_menu.h" +#include "client/keycode.h" #include "gui/mainmenumanager.h" #include "lua_api/l_internal.h" #include "client/client.h" -int ModApiPauseMenu::l_show_keys_menu(lua_State *L) -{ - g_gamecallback->keyConfig(); - return 0; -} - - int ModApiPauseMenu::l_show_touchscreen_layout(lua_State *L) { g_gamecallback->touchscreenLayout(); @@ -31,7 +25,6 @@ int ModApiPauseMenu::l_is_internal_server(lua_State *L) void ModApiPauseMenu::Initialize(lua_State *L, int top) { - API_FCT(show_keys_menu); API_FCT(show_touchscreen_layout); API_FCT(is_internal_server); } diff --git a/src/script/lua_api/l_pause_menu.h b/src/script/lua_api/l_pause_menu.h index 2d7eb62d7..8bb6fe8c3 100644 --- a/src/script/lua_api/l_pause_menu.h +++ b/src/script/lua_api/l_pause_menu.h @@ -9,7 +9,6 @@ class ModApiPauseMenu: public ModApiBase { private: - static int l_show_keys_menu(lua_State *L); static int l_show_touchscreen_layout(lua_State *L); static int l_is_internal_server(lua_State *L); From 2f464843cb57832b71ff617f39aa274a8493a8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20R=C3=BChle?= Date: Sun, 20 Apr 2025 20:48:48 +0200 Subject: [PATCH 143/284] Make it more convenient to customize node drops (#15872) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Provide tool and digger to get_node_drops This gives games/mods the ability to modify node drops depending on item and/or player metadata without overriding node_dig or other workarounds. * Copy wielded item to prevent modification in get_node_drops * Also pass node pos to get_node_drops Allowing properties of the node and its surroundings to affect node drops. * Copy pos to prevent modification in get_node_drops Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> * Don't pass empty item stack to get_node_drops if wielded is nil --------- Co-authored-by: sfan5 Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> --- builtin/game/item.lua | 3 ++- doc/lua_api.md | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 5dd5312aa..4d68f1136 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -513,7 +513,8 @@ function core.node_dig(pos, node, digger) .. node.name .. " at " .. core.pos_to_string(pos)) local wielded = digger and digger:get_wielded_item() - local drops = core.get_node_drops(node, wielded and wielded:get_name()) + local drops = core.get_node_drops(node, wielded and wielded:get_name(), + wielded and ItemStack(wielded), digger, vector.copy(pos)) if wielded then local wdef = wielded:get_definition() diff --git a/doc/lua_api.md b/doc/lua_api.md index d8e58fcde..e2a55b7e1 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6910,11 +6910,16 @@ Item handling given `param2` value. * Returns `nil` if the given `paramtype2` does not contain color information. -* `core.get_node_drops(node, toolname)` - * Returns list of itemstrings that are dropped by `node` when dug - with the item `toolname` (not limited to tools). +* `core.get_node_drops(node, toolname[, tool, digger, pos])` + * Returns list of itemstrings that are dropped by `node` when dug with the + item `toolname` (not limited to tools). The default implementation doesn't + use `tool`, `digger`, and `pos`, but these are provided by `core.node_dig` + since 5.12.0 for games/mods implementing customized drops. * `node`: node as table or node name * `toolname`: name of the item used to dig (can be `nil`) + * `tool`: `ItemStack` used to dig (can be `nil`) + * `digger`: the ObjectRef that digs the node (can be `nil`) + * `pos`: the pos of the dug node (can be `nil`) * `core.get_craft_result(input)`: returns `output, decremented_input` * `input.method` = `"normal"` or `"cooking"` or `"fuel"` * `input.width` = for example `3` From 5f1ff453c95013af1017ed35c04e067eff7afbaa Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Fri, 4 Apr 2025 01:26:02 +0200 Subject: [PATCH 144/284] Replace `_IRR_DEBUG_BREAK_IF` with assertions --- irr/include/IMeshBuffer.h | 5 +++-- irr/include/IReferenceCounted.h | 3 ++- irr/include/ISceneNode.h | 3 ++- irr/include/SSkinMeshBuffer.h | 3 ++- irr/include/irrArray.h | 17 +++++++-------- irr/include/irrString.h | 7 ++++--- irr/include/irrTypes.h | 23 ++++---------------- irr/include/matrix4.h | 35 ++++++++++++++++--------------- irr/include/vector2d.h | 1 + irr/include/vector3d.h | 1 + irr/src/CGLTFMeshFileLoader.cpp | 3 ++- irr/src/CImage.cpp | 4 +++- irr/src/CIrrDeviceLinux.cpp | 4 ++-- irr/src/CIrrDeviceSDL.cpp | 8 ++++--- irr/src/CMakeLists.txt | 4 ++-- irr/src/CMeshManipulator.cpp | 6 ++++-- irr/src/CNullDriver.cpp | 8 ++++--- irr/src/COpenGLCoreCacheHandler.h | 4 +++- irr/src/COpenGLCoreTexture.h | 22 ++++++++++--------- irr/src/COpenGLDriver.cpp | 16 +++++++------- irr/src/CSceneManager.cpp | 3 ++- irr/src/SkinnedMesh.cpp | 7 ++++--- 22 files changed, 96 insertions(+), 91 deletions(-) diff --git a/irr/include/IMeshBuffer.h b/irr/include/IMeshBuffer.h index 55c05211a..afcf28943 100644 --- a/irr/include/IMeshBuffer.h +++ b/irr/include/IMeshBuffer.h @@ -11,6 +11,7 @@ #include "IIndexBuffer.h" #include "EHardwareBufferFlags.h" #include "EPrimitiveTypes.h" +#include namespace irr { @@ -121,7 +122,7 @@ public: /** \return Pointer to indices array. */ inline const u16 *getIndices() const { - _IRR_DEBUG_BREAK_IF(getIndexBuffer()->getType() != video::EIT_16BIT); + assert(getIndexBuffer()->getType() == video::EIT_16BIT); return static_cast(getIndexBuffer()->getData()); } @@ -129,7 +130,7 @@ public: /** \return Pointer to indices array. */ inline u16 *getIndices() { - _IRR_DEBUG_BREAK_IF(getIndexBuffer()->getType() != video::EIT_16BIT); + assert(getIndexBuffer()->getType() == video::EIT_16BIT); return static_cast(getIndexBuffer()->getData()); } diff --git a/irr/include/IReferenceCounted.h b/irr/include/IReferenceCounted.h index 65c991db2..80454f9ea 100644 --- a/irr/include/IReferenceCounted.h +++ b/irr/include/IReferenceCounted.h @@ -5,6 +5,7 @@ #pragma once #include "irrTypes.h" +#include namespace irr { @@ -118,7 +119,7 @@ public: bool drop() const { // someone is doing bad reference counting. - _IRR_DEBUG_BREAK_IF(ReferenceCounter <= 0) + assert(ReferenceCounter > 0); --ReferenceCounter; if (!ReferenceCounter) { diff --git a/irr/include/ISceneNode.h b/irr/include/ISceneNode.h index c80ff4b48..f91fd6499 100644 --- a/irr/include/ISceneNode.h +++ b/irr/include/ISceneNode.h @@ -16,6 +16,7 @@ #include #include #include +#include namespace irr { @@ -268,7 +269,7 @@ public: return false; // The iterator must be set since the parent is not null. - _IRR_DEBUG_BREAK_IF(!child->ThisIterator.has_value()); + assert(child->ThisIterator.has_value()); auto it = *child->ThisIterator; child->ThisIterator = std::nullopt; child->Parent = nullptr; diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index eb3f7371f..8b2e26882 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -8,6 +8,7 @@ #include "CVertexBuffer.h" #include "CIndexBuffer.h" #include "S3DVertex.h" +#include namespace irr { @@ -200,7 +201,7 @@ public: //! append the vertices and indices to the current buffer void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override { - _IRR_DEBUG_BREAK_IF(true); + assert(false); } //! Describe what kind of primitive geometry is used by the meshbuffer diff --git a/irr/include/irrArray.h b/irr/include/irrArray.h index 834dc825c..2542d0542 100644 --- a/irr/include/irrArray.h +++ b/irr/include/irrArray.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "irrTypes.h" #include "irrMath.h" @@ -108,7 +109,7 @@ public: \param index: Where position to insert the new element. */ void insert(const T &element, u32 index = 0) { - _IRR_DEBUG_BREAK_IF(index > m_data.size()) // access violation + assert(index <= m_data.size()); auto pos = std::next(m_data.begin(), index); m_data.insert(pos, element); is_sorted = false; @@ -190,32 +191,28 @@ public: //! Direct access operator T &operator[](u32 index) { - _IRR_DEBUG_BREAK_IF(index >= m_data.size()) // access violation - + assert(index < m_data.size()); return m_data[index]; } //! Direct const access operator const T &operator[](u32 index) const { - _IRR_DEBUG_BREAK_IF(index >= m_data.size()) // access violation - + assert(index < m_data.size()); return m_data[index]; } //! Gets last element. T &getLast() { - _IRR_DEBUG_BREAK_IF(m_data.empty()) // access violation - + assert(!m_data.empty()); return m_data.back(); } //! Gets last element const T &getLast() const { - _IRR_DEBUG_BREAK_IF(m_data.empty()) // access violation - + assert(!m_data.empty()); return m_data.back(); } @@ -365,7 +362,7 @@ public: \param index: Index of element to be erased. */ void erase(u32 index) { - _IRR_DEBUG_BREAK_IF(index >= m_data.size()) // access violation + assert(index < m_data.size()); auto it = std::next(m_data.begin(), index); m_data.erase(it); } diff --git a/irr/include/irrString.h b/irr/include/irrString.h index 76e0548d3..465664e87 100644 --- a/irr/include/irrString.h +++ b/irr/include/irrString.h @@ -11,6 +11,7 @@ #include #include #include +#include /* HACK: import these string methods from MT's util/string.h */ extern std::wstring utf8_to_wide(std::string_view input); @@ -174,9 +175,9 @@ public: } if constexpr (sizeof(T) != sizeof(B)) { - _IRR_DEBUG_BREAK_IF( - (uintptr_t)c >= (uintptr_t)(str.data()) && - (uintptr_t)c < (uintptr_t)(str.data() + str.size())); + assert( + (uintptr_t)c < (uintptr_t)(str.data()) || + (uintptr_t)c >= (uintptr_t)(str.data() + str.size())); } if ((void *)c == (void *)c_str()) diff --git a/irr/include/irrTypes.h b/irr/include/irrTypes.h index 910991689..9e71d0771 100644 --- a/irr/include/irrTypes.h +++ b/irr/include/irrTypes.h @@ -5,6 +5,7 @@ #pragma once #include +#include namespace irr { @@ -65,22 +66,6 @@ typedef char fschar_t; } // end namespace irr -//! define a break macro for debugging. -#if defined(_DEBUG) -#if defined(_IRR_WINDOWS_API_) && defined(_MSC_VER) -#include -#define _IRR_DEBUG_BREAK_IF(_CONDITION_) \ - if (_CONDITION_) { \ - _CrtDbgBreak(); \ - } -#else -#include -#define _IRR_DEBUG_BREAK_IF(_CONDITION_) assert(!(_CONDITION_)); -#endif -#else -#define _IRR_DEBUG_BREAK_IF(_CONDITION_) -#endif - //! deprecated macro for virtual function override /** prefer to use the override keyword for new code */ #define _IRR_OVERRIDE_ override @@ -89,13 +74,13 @@ typedef char fschar_t; // Note: an assert(false) is included first to catch this in debug builds #if defined(__cpp_lib_unreachable) #include -#define IRR_CODE_UNREACHABLE() do { _IRR_DEBUG_BREAK_IF(1) std::unreachable(); } while(0) +#define IRR_CODE_UNREACHABLE() do { assert(false); std::unreachable(); } while(0) #elif defined(__has_builtin) #if __has_builtin(__builtin_unreachable) -#define IRR_CODE_UNREACHABLE() do { _IRR_DEBUG_BREAK_IF(1) __builtin_unreachable(); } while(0) +#define IRR_CODE_UNREACHABLE() do { assert(false); __builtin_unreachable(); } while(0) #endif #elif defined(_MSC_VER) -#define IRR_CODE_UNREACHABLE() do { _IRR_DEBUG_BREAK_IF(1) __assume(false); } while(0) +#define IRR_CODE_UNREACHABLE() do { assert(false); __assume(false); } while(0) #endif #ifndef IRR_CODE_UNREACHABLE #define IRR_CODE_UNREACHABLE() (void)0 diff --git a/irr/include/matrix4.h b/irr/include/matrix4.h index 40fb41e57..6fc8cf6f8 100644 --- a/irr/include/matrix4.h +++ b/irr/include/matrix4.h @@ -12,6 +12,7 @@ #include "aabbox3d.h" #include "rect.h" #include "IrrCompileConfig.h" // for IRRLICHT_API +#include namespace irr { @@ -1198,10 +1199,10 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveFovRH( f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 zFar, bool zClipFromZero) { const f64 h = reciprocal(tan(fieldOfViewRadians * 0.5)); - _IRR_DEBUG_BREAK_IF(aspectRatio == 0.f); // divide by zero + assert(aspectRatio != 0.f); // divide by zero const T w = static_cast(h / aspectRatio); - _IRR_DEBUG_BREAK_IF(zNear == zFar); // divide by zero + assert(zNear != zFar); // divide by zero M[0] = w; M[1] = 0; M[2] = 0; @@ -1240,10 +1241,10 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveFovLH( f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 zFar, bool zClipFromZero) { const f64 h = reciprocal(tan(fieldOfViewRadians * 0.5)); - _IRR_DEBUG_BREAK_IF(aspectRatio == 0.f); // divide by zero + assert(aspectRatio != 0.f); // divide by zero const T w = static_cast(h / aspectRatio); - _IRR_DEBUG_BREAK_IF(zNear == zFar); // divide by zero + assert(zNear != zFar); // divide by zero M[0] = w; M[1] = 0; M[2] = 0; @@ -1282,7 +1283,7 @@ inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveFovInfinityLH( f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 epsilon) { const f64 h = reciprocal(tan(fieldOfViewRadians * 0.5)); - _IRR_DEBUG_BREAK_IF(aspectRatio == 0.f); // divide by zero + assert(aspectRatio != 0.f); // divide by zero const T w = static_cast(h / aspectRatio); M[0] = w; @@ -1313,9 +1314,9 @@ template inline CMatrix4 &CMatrix4::buildProjectionMatrixOrthoLH( f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar, bool zClipFromZero) { - _IRR_DEBUG_BREAK_IF(widthOfViewVolume == 0.f); // divide by zero - _IRR_DEBUG_BREAK_IF(heightOfViewVolume == 0.f); // divide by zero - _IRR_DEBUG_BREAK_IF(zNear == zFar); // divide by zero + assert(widthOfViewVolume != 0.f); // divide by zero + assert(heightOfViewVolume != 0.f); // divide by zero + assert(zNear != zFar); // divide by zero M[0] = (T)(2 / widthOfViewVolume); M[1] = 0; M[2] = 0; @@ -1352,9 +1353,9 @@ template inline CMatrix4 &CMatrix4::buildProjectionMatrixOrthoRH( f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar, bool zClipFromZero) { - _IRR_DEBUG_BREAK_IF(widthOfViewVolume == 0.f); // divide by zero - _IRR_DEBUG_BREAK_IF(heightOfViewVolume == 0.f); // divide by zero - _IRR_DEBUG_BREAK_IF(zNear == zFar); // divide by zero + assert(widthOfViewVolume != 0.f); // divide by zero + assert(heightOfViewVolume != 0.f); // divide by zero + assert(zNear != zFar); // divide by zero M[0] = (T)(2 / widthOfViewVolume); M[1] = 0; M[2] = 0; @@ -1391,9 +1392,9 @@ template inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveRH( f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar, bool zClipFromZero) { - _IRR_DEBUG_BREAK_IF(widthOfViewVolume == 0.f); // divide by zero - _IRR_DEBUG_BREAK_IF(heightOfViewVolume == 0.f); // divide by zero - _IRR_DEBUG_BREAK_IF(zNear == zFar); // divide by zero + assert(widthOfViewVolume != 0.f); // divide by zero + assert(heightOfViewVolume != 0.f); // divide by zero + assert(zNear != zFar); // divide by zero M[0] = (T)(2 * zNear / widthOfViewVolume); M[1] = 0; M[2] = 0; @@ -1431,9 +1432,9 @@ template inline CMatrix4 &CMatrix4::buildProjectionMatrixPerspectiveLH( f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar, bool zClipFromZero) { - _IRR_DEBUG_BREAK_IF(widthOfViewVolume == 0.f); // divide by zero - _IRR_DEBUG_BREAK_IF(heightOfViewVolume == 0.f); // divide by zero - _IRR_DEBUG_BREAK_IF(zNear == zFar); // divide by zero + assert(widthOfViewVolume != 0.f); // divide by zero + assert(heightOfViewVolume != 0.f); // divide by zero + assert(zNear != zFar); // divide by zero M[0] = (T)(2 * zNear / widthOfViewVolume); M[1] = 0; M[2] = 0; diff --git a/irr/include/vector2d.h b/irr/include/vector2d.h index 63be1f246..821885719 100644 --- a/irr/include/vector2d.h +++ b/irr/include/vector2d.h @@ -9,6 +9,7 @@ #include #include +#include namespace irr { diff --git a/irr/include/vector3d.h b/irr/include/vector3d.h index 780686c7a..c9c96fabf 100644 --- a/irr/include/vector3d.h +++ b/irr/include/vector3d.h @@ -8,6 +8,7 @@ #include #include +#include namespace irr { diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index b32ba5692..32fa4829c 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -797,7 +798,7 @@ std::optional> SelfType::MeshExtractor::getIndices( if (index == std::numeric_limits::max()) throw std::runtime_error("invalid index"); } else { - _IRR_DEBUG_BREAK_IF(!std::holds_alternative>(accessor)); + assert(std::holds_alternative>(accessor)); u32 indexWide = std::get>(accessor).get(elemIdx); // Use >= here for consistency. if (indexWide >= std::numeric_limits::max()) diff --git a/irr/src/CImage.cpp b/irr/src/CImage.cpp index 29c115c8a..4366809ae 100644 --- a/irr/src/CImage.cpp +++ b/irr/src/CImage.cpp @@ -9,6 +9,8 @@ #include "os.h" #include "SoftwareDriver2_helper.h" +#include + namespace irr { namespace video @@ -20,7 +22,7 @@ CImage::CImage(ECOLOR_FORMAT format, const core::dimension2d &size, void *d IImage(format, size, deleteMemory) { if (ownForeignMemory) { - _IRR_DEBUG_BREAK_IF(!data) + assert(data); Data = reinterpret_cast(data); if (reinterpret_cast(data) % sizeof(u32) != 0) os::Printer::log("CImage created with foreign memory that's not aligned", ELL_WARNING); diff --git a/irr/src/CIrrDeviceLinux.cpp b/irr/src/CIrrDeviceLinux.cpp index 7c5d9cf0b..f72ccc7a1 100644 --- a/irr/src/CIrrDeviceLinux.cpp +++ b/irr/src/CIrrDeviceLinux.cpp @@ -1680,10 +1680,10 @@ const c8 *CIrrDeviceLinux::getTextFromSelection(Atom selection, core::stringc &t }, (XPointer)&args); - _IRR_DEBUG_BREAK_IF(!(event_ret.type == SelectionNotify && + assert(event_ret.type == SelectionNotify && event_ret.xselection.requestor == XWindow && event_ret.xselection.selection == selection && - event_ret.xselection.target == X_ATOM_UTF8_STRING)); + event_ret.xselection.target == X_ATOM_UTF8_STRING); Atom property_set = event_ret.xselection.property; if (event_ret.xselection.property == None) { diff --git a/irr/src/CIrrDeviceSDL.cpp b/irr/src/CIrrDeviceSDL.cpp index 28a92f450..bc7627f73 100644 --- a/irr/src/CIrrDeviceSDL.cpp +++ b/irr/src/CIrrDeviceSDL.cpp @@ -16,11 +16,13 @@ #include "irrString.h" #include "Keycodes.h" #include "COSOperator.h" -#include -#include #include "SIrrCreationParameters.h" #include +#include +#include +#include + #ifdef _IRR_EMSCRIPTEN_PLATFORM_ #include #endif @@ -599,7 +601,7 @@ bool CIrrDeviceSDL::createWindowWithContext() SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); break; default: - _IRR_DEBUG_BREAK_IF(1); + assert(false); } if (CreationParams.DriverDebug) { diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 820af4fe9..8b6f71b3a 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -521,10 +521,10 @@ target_link_libraries(IrrlichtMt PRIVATE ) if(WIN32) - target_compile_definitions(IrrlichtMt INTERFACE _IRR_WINDOWS_API_) # used in _IRR_DEBUG_BREAK_IF definition in a public header + target_compile_definitions(IrrlichtMt INTERFACE _IRR_WINDOWS_API_) endif() if(CMAKE_BUILD_TYPE STREQUAL "Debug") - target_compile_definitions(IrrlichtMt INTERFACE _DEBUG) # same + target_compile_definitions(IrrlichtMt INTERFACE _DEBUG) endif() if(APPLE OR ANDROID OR EMSCRIPTEN) target_compile_definitions(IrrlichtMt PUBLIC IRR_MOBILE_PATHS) diff --git a/irr/src/CMeshManipulator.cpp b/irr/src/CMeshManipulator.cpp index 63157403b..659e8dff8 100644 --- a/irr/src/CMeshManipulator.cpp +++ b/irr/src/CMeshManipulator.cpp @@ -9,6 +9,8 @@ #include "SAnimatedMesh.h" #include "os.h" +#include + namespace irr { namespace scene @@ -118,14 +120,14 @@ void CMeshManipulator::recalculateNormals(scene::IMesh *mesh, bool smooth, bool template void copyVertices(const scene::IVertexBuffer *src, scene::CVertexBuffer *dst) { - _IRR_DEBUG_BREAK_IF(T::getType() != src->getType()); + assert(T::getType() == src->getType()); auto *data = static_cast(src->getData()); dst->Data.assign(data, data + src->getCount()); } static void copyIndices(const scene::IIndexBuffer *src, scene::SIndexBuffer *dst) { - _IRR_DEBUG_BREAK_IF(src->getType() != video::EIT_16BIT); + assert(src->getType() == video::EIT_16BIT); auto *data = static_cast(src->getData()); dst->Data.assign(data, data + src->getCount()); } diff --git a/irr/src/CNullDriver.cpp b/irr/src/CNullDriver.cpp index 21346e6ed..f54f7a418 100644 --- a/irr/src/CNullDriver.cpp +++ b/irr/src/CNullDriver.cpp @@ -17,6 +17,8 @@ #include "IReferenceCounted.h" #include "IRenderTarget.h" +#include + namespace irr { namespace video @@ -1092,7 +1094,7 @@ void CNullDriver::drawBuffers(const scene::IVertexBuffer *vb, if (vb->getHWBuffer() || ib->getHWBuffer()) { // subclass is supposed to override this if it supports hw buffers - _IRR_DEBUG_BREAK_IF(1); + assert(false); } drawVertexPrimitiveList(vb->getData(), vb->getCount(), ib->getData(), @@ -1138,7 +1140,7 @@ CNullDriver::SHWBufferLink *CNullDriver::getBufferLink(const scene::IIndexBuffer void CNullDriver::registerHardwareBuffer(SHWBufferLink *HWBuffer) { - _IRR_DEBUG_BREAK_IF(!HWBuffer) + assert(HWBuffer); HWBuffer->ListPosition = HWBufferList.size(); HWBufferList.push_back(HWBuffer); } @@ -1168,7 +1170,7 @@ void CNullDriver::deleteHardwareBuffer(SHWBufferLink *HWBuffer) if (!HWBuffer) return; const size_t pos = HWBuffer->ListPosition; - _IRR_DEBUG_BREAK_IF(HWBufferList.at(pos) != HWBuffer) + assert(HWBufferList.at(pos) == HWBuffer); if (HWBufferList.size() < 2 || pos == HWBufferList.size() - 1) { HWBufferList.erase(HWBufferList.begin() + pos); } else { diff --git a/irr/src/COpenGLCoreCacheHandler.h b/irr/src/COpenGLCoreCacheHandler.h index bf588aa8a..8302f182a 100644 --- a/irr/src/COpenGLCoreCacheHandler.h +++ b/irr/src/COpenGLCoreCacheHandler.h @@ -9,6 +9,8 @@ #include "mt_opengl.h" +#include + namespace irr { namespace video @@ -101,7 +103,7 @@ class COpenGLCoreCacheHandler #endif auto name = static_cast(texture)->getOpenGLTextureName(); - _IRR_DEBUG_BREAK_IF(name == 0) + assert(name != 0); GL.BindTexture(curTextureType, name); } else { texture = 0; diff --git a/irr/src/COpenGLCoreTexture.h b/irr/src/COpenGLCoreTexture.h index bc9a7e919..b2b7403c2 100644 --- a/irr/src/COpenGLCoreTexture.h +++ b/irr/src/COpenGLCoreTexture.h @@ -5,6 +5,8 @@ #pragma once #include +#include + #include "SMaterialLayer.h" #include "ITexture.h" #include "EDriverFeatures.h" @@ -48,10 +50,10 @@ public: TextureName(0), InternalFormat(GL_RGBA), PixelFormat(GL_RGBA), PixelType(GL_UNSIGNED_BYTE), MSAA(0), Converter(0), LockReadOnly(false), LockImage(0), LockLayer(0), KeepImage(false), MipLevelStored(0) { - _IRR_DEBUG_BREAK_IF(srcImages.empty()) + assert(!srcImages.empty()); DriverType = Driver->getDriverType(); - _IRR_DEBUG_BREAK_IF(Type == ETT_2D_MS) // not supported by this constructor + assert(Type != ETT_2D_MS); // not supported by this constructor TextureType = TextureTypeIrrToGL(Type); HasMipMaps = Driver->getTextureCreationFlag(ETCF_CREATE_MIP_MAPS); KeepImage = Driver->getTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY); @@ -142,7 +144,7 @@ public: MipLevelStored(0) { DriverType = Driver->getDriverType(); - _IRR_DEBUG_BREAK_IF(Type == ETT_2D_ARRAY) // not supported by this constructor + assert(Type != ETT_2D_ARRAY); // not supported by this constructor TextureType = TextureTypeIrrToGL(Type); HasMipMaps = false; IsRenderTarget = true; @@ -242,7 +244,7 @@ public: MipLevelStored = mipmapLevel; if (KeepImage) { - _IRR_DEBUG_BREAK_IF(LockLayer > Images.size()) + assert(LockLayer < Images.size()); if (mipmapLevel == 0) { LockImage = Images[LockLayer]; @@ -252,7 +254,7 @@ public: if (!LockImage) { core::dimension2d lockImageSize(IImage::getMipMapsSize(Size, MipLevelStored)); - _IRR_DEBUG_BREAK_IF(lockImageSize.Width == 0 || lockImageSize.Height == 0) + assert(lockImageSize.Width > 0 && lockImageSize.Height > 0); LockImage = Driver->createImage(ColorFormat, lockImageSize); @@ -512,7 +514,7 @@ protected: { // Compressed textures cannot be pre-allocated and are initialized on upload if (IImage::isCompressedFormat(ColorFormat)) { - _IRR_DEBUG_BREAK_IF(IsRenderTarget) + assert(!IsRenderTarget); return; } @@ -587,7 +589,7 @@ protected: TEST_GL_ERROR(Driver); break; default: - _IRR_DEBUG_BREAK_IF(1) + assert(false); break; } } @@ -628,7 +630,7 @@ protected: GL.TexSubImage3D(tmpTextureType, level, 0, 0, layer, width, height, 1, PixelFormat, PixelType, tmpData); break; default: - _IRR_DEBUG_BREAK_IF(1) + assert(false); break; } TEST_GL_ERROR(Driver); @@ -643,7 +645,7 @@ protected: Driver->irrGlCompressedTexImage2D(tmpTextureType, level, InternalFormat, width, height, 0, dataSize, data); break; default: - _IRR_DEBUG_BREAK_IF(1) + assert(false); break; } TEST_GL_ERROR(Driver); @@ -671,7 +673,7 @@ protected: { GLenum tmp = TextureType; if (tmp == GL_TEXTURE_CUBE_MAP) { - _IRR_DEBUG_BREAK_IF(layer > 5) + assert(layer < 6); tmp = GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer; } return tmp; diff --git a/irr/src/COpenGLDriver.cpp b/irr/src/COpenGLDriver.cpp index 50e097362..9c37e2f79 100644 --- a/irr/src/COpenGLDriver.cpp +++ b/irr/src/COpenGLDriver.cpp @@ -710,7 +710,7 @@ void COpenGLDriver::drawVertexPrimitiveList(const void *vertices, u32 vertexCoun } } else { // avoid passing broken pointer to OpenGL - _IRR_DEBUG_BREAK_IF(ColorBuffer.size() == 0); + assert(!ColorBuffer.empty()); glColorPointer(colorSize, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); } } @@ -983,7 +983,7 @@ void COpenGLDriver::draw2DVertexPrimitiveList(const void *vertices, u32 vertexCo } } else { // avoid passing broken pointer to OpenGL - _IRR_DEBUG_BREAK_IF(ColorBuffer.size() == 0); + assert(!ColorBuffer.empty()); glColorPointer(colorSize, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); } } @@ -1124,7 +1124,7 @@ void COpenGLDriver::draw2DImage(const video::ITexture *texture, const core::posi if (FeatureAvailable[IRR_ARB_vertex_array_bgra] || FeatureAvailable[IRR_EXT_vertex_array_bgra]) glColorPointer(colorSize, GL_UNSIGNED_BYTE, sizeof(S3DVertex), &(static_cast(Quad2DVertices))[0].Color); else { - _IRR_DEBUG_BREAK_IF(ColorBuffer.size() == 0); + assert(!ColorBuffer.empty()); glColorPointer(colorSize, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); } @@ -1204,7 +1204,7 @@ void COpenGLDriver::draw2DImage(const video::ITexture *texture, const core::rect if (FeatureAvailable[IRR_ARB_vertex_array_bgra] || FeatureAvailable[IRR_EXT_vertex_array_bgra]) glColorPointer(colorSize, GL_UNSIGNED_BYTE, sizeof(S3DVertex), &(static_cast(Quad2DVertices))[0].Color); else { - _IRR_DEBUG_BREAK_IF(ColorBuffer.size() == 0); + assert(!ColorBuffer.empty()); glColorPointer(colorSize, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); } @@ -1352,7 +1352,7 @@ void COpenGLDriver::draw2DImageBatch(const video::ITexture *texture, if (FeatureAvailable[IRR_ARB_vertex_array_bgra] || FeatureAvailable[IRR_EXT_vertex_array_bgra]) glColorPointer(colorSize, GL_UNSIGNED_BYTE, sizeof(S3DVertex), &(static_cast(Quad2DVertices))[0].Color); else { - _IRR_DEBUG_BREAK_IF(ColorBuffer.size() == 0); + assert(!ColorBuffer.empty()); glColorPointer(colorSize, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); } @@ -1519,7 +1519,7 @@ void COpenGLDriver::draw2DRectangle(const core::rect &position, if (FeatureAvailable[IRR_ARB_vertex_array_bgra] || FeatureAvailable[IRR_EXT_vertex_array_bgra]) glColorPointer(colorSize, GL_UNSIGNED_BYTE, sizeof(S3DVertex), &(static_cast(Quad2DVertices))[0].Color); else { - _IRR_DEBUG_BREAK_IF(ColorBuffer.size() == 0); + assert(!ColorBuffer.empty()); glColorPointer(colorSize, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); } @@ -1555,7 +1555,7 @@ void COpenGLDriver::draw2DLine(const core::position2d &start, if (FeatureAvailable[IRR_ARB_vertex_array_bgra] || FeatureAvailable[IRR_EXT_vertex_array_bgra]) glColorPointer(colorSize, GL_UNSIGNED_BYTE, sizeof(S3DVertex), &(static_cast(Quad2DVertices))[0].Color); else { - _IRR_DEBUG_BREAK_IF(ColorBuffer.size() == 0); + assert(!ColorBuffer.empty()); glColorPointer(colorSize, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); } @@ -2478,7 +2478,7 @@ void COpenGLDriver::draw3DLine(const core::vector3df &start, if (FeatureAvailable[IRR_ARB_vertex_array_bgra] || FeatureAvailable[IRR_EXT_vertex_array_bgra]) glColorPointer(colorSize, GL_UNSIGNED_BYTE, sizeof(S3DVertex), &(static_cast(Quad2DVertices))[0].Color); else { - _IRR_DEBUG_BREAK_IF(ColorBuffer.size() == 0); + assert(!ColorBuffer.empty()); glColorPointer(colorSize, GL_UNSIGNED_BYTE, 0, &ColorBuffer[0]); } diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp index 23e5335ce..41c59fe66 100644 --- a/irr/src/CSceneManager.cpp +++ b/irr/src/CSceneManager.cpp @@ -3,6 +3,7 @@ // For conditions of distribution and use, see copyright notice in irrlicht.h #include +#include #include "CSceneManager.h" #include "IVideoDriver.h" @@ -305,7 +306,7 @@ void CSceneManager::render() //! returns the axis aligned bounding box of this node const core::aabbox3d &CSceneManager::getBoundingBox() const { - _IRR_DEBUG_BREAK_IF(true) // Bounding Box of Scene Manager should never be used. + assert(false); // Bounding Box of Scene Manager should never be used. static const core::aabbox3d dummy{{0.0f, 0.0f, 0.0f}}; return dummy; diff --git a/irr/src/SkinnedMesh.cpp b/irr/src/SkinnedMesh.cpp index ebf84cec5..938a50e17 100644 --- a/irr/src/SkinnedMesh.cpp +++ b/irr/src/SkinnedMesh.cpp @@ -9,6 +9,7 @@ #include "SSkinMeshBuffer.h" #include "os.h" #include +#include namespace irr { @@ -620,19 +621,19 @@ SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent) void SkinnedMeshBuilder::addPositionKey(SJoint *joint, f32 frame, core::vector3df pos) { - _IRR_DEBUG_BREAK_IF(!joint); + assert(joint); joint->keys.position.pushBack(frame, pos); } void SkinnedMeshBuilder::addScaleKey(SJoint *joint, f32 frame, core::vector3df scale) { - _IRR_DEBUG_BREAK_IF(!joint); + assert(joint); joint->keys.scale.pushBack(frame, scale); } void SkinnedMeshBuilder::addRotationKey(SJoint *joint, f32 frame, core::quaternion rot) { - _IRR_DEBUG_BREAK_IF(!joint); + assert(joint); joint->keys.rotation.pushBack(frame, rot); } From 695d526764206b4a37ddb84ce0937314eb781422 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Fri, 4 Apr 2025 16:32:52 +0200 Subject: [PATCH 145/284] Get rid of `_IRR_OVERRIDE_` macro --- irr/include/irrTypes.h | 4 ---- irr/src/CIrrDeviceSDL.h | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/irr/include/irrTypes.h b/irr/include/irrTypes.h index 9e71d0771..9018dd151 100644 --- a/irr/include/irrTypes.h +++ b/irr/include/irrTypes.h @@ -66,10 +66,6 @@ typedef char fschar_t; } // end namespace irr -//! deprecated macro for virtual function override -/** prefer to use the override keyword for new code */ -#define _IRR_OVERRIDE_ override - // Invokes undefined behavior for unreachable code optimization // Note: an assert(false) is included first to catch this in debug builds #if defined(__cpp_lib_unreachable) diff --git a/irr/src/CIrrDeviceSDL.h b/irr/src/CIrrDeviceSDL.h index 33d97ce56..1faaad7a7 100644 --- a/irr/src/CIrrDeviceSDL.h +++ b/irr/src/CIrrDeviceSDL.h @@ -206,7 +206,7 @@ public: { } - virtual void setRelativeMode(bool relative) _IRR_OVERRIDE_ + virtual void setRelativeMode(bool relative) override { // Only change it when necessary, as it flushes mouse motion when enabled if (relative != static_cast(SDL_GetRelativeMouseMode())) { From e1143783e5b1a4021aa16293e232cd3ef9d72492 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Fri, 4 Apr 2025 17:09:12 +0200 Subject: [PATCH 146/284] Fix some (MSVC) compiler warnings --- irr/include/irrMath.h | 2 +- irr/src/CGLTFMeshFileLoader.cpp | 10 ++++------ irr/src/OpenGL/ExtensionHandler.h | 2 +- src/client/minimap.cpp | 4 ++-- src/client/shadows/dynamicshadowsrender.cpp | 4 ++-- src/client/shadows/dynamicshadowsrender.h | 2 +- src/script/common/c_content.cpp | 2 +- src/unittest/test_utilities.cpp | 2 +- 8 files changed, 13 insertions(+), 15 deletions(-) diff --git a/irr/include/irrMath.h b/irr/include/irrMath.h index e11d47360..4e74381a6 100644 --- a/irr/include/irrMath.h +++ b/irr/include/irrMath.h @@ -25,7 +25,7 @@ constexpr f64 ROUNDING_ERROR_f64 = 0.00000001; #undef PI #endif //! Constant for PI. -constexpr f32 PI = M_PI; +constexpr f32 PI = static_cast(M_PI); #ifdef PI64 // make sure we don't collide with a define #undef PI64 diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 32fa4829c..87712595a 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -549,12 +549,10 @@ void SelfType::MeshExtractor::deferAddMesh( static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMesh::SJoint *joint) { - // Note: Under the hood, this casts these doubles to floats. - core::matrix4 mat = convertHandedness(core::matrix4( - m[0], m[1], m[2], m[3], - m[4], m[5], m[6], m[7], - m[8], m[9], m[10], m[11], - m[12], m[13], m[14], m[15])); + core::matrix4 mat; + for (size_t i = 0; i < m.size(); ++i) + mat[i] = static_cast(m[i]); + mat = convertHandedness(mat); // Decompose the matrix into translation, scale, and rotation. joint->Animatedposition = mat.getTranslation(); diff --git a/irr/src/OpenGL/ExtensionHandler.h b/irr/src/OpenGL/ExtensionHandler.h index 413ab7f23..2ee3543d5 100644 --- a/irr/src/OpenGL/ExtensionHandler.h +++ b/irr/src/OpenGL/ExtensionHandler.h @@ -166,7 +166,7 @@ public: inline void irrGlObjectLabel(GLenum identifier, GLuint name, const char *label) { if (KHRDebugSupported) { - u32 len = strlen(label); + u32 len = static_cast(strlen(label)); // Since our texture strings can get quite long we also truncate // to a hardcoded limit of 82 len = std::min(len, std::min(MaxLabelLength, 82U)); diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index e60608688..0d44f9d00 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -704,8 +704,8 @@ 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.5f, + (1.0f - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5f); } } diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index a7d21d647..70bc2d2cf 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -36,7 +36,7 @@ ShadowRenderer::ShadowRenderer(IrrlichtDevice *device, Client *client) : m_shadow_map_max_distance = g_settings->getFloat("shadow_map_max_distance"); - m_shadow_map_texture_size = g_settings->getFloat("shadow_map_texture_size"); + m_shadow_map_texture_size = g_settings->getU32("shadow_map_texture_size"); m_shadow_map_texture_32bit = g_settings->getBool("shadow_map_texture_32bit"); m_shadow_map_colored = g_settings->getBool("shadow_map_color"); @@ -273,7 +273,7 @@ void ShadowRenderer::updateSMTextures() // Static shader values. for (auto cb : {m_shadow_depth_cb, m_shadow_depth_entity_cb, m_shadow_depth_trans_cb}) { if (cb) { - cb->MapRes = (f32)m_shadow_map_texture_size; + cb->MapRes = (u32)m_shadow_map_texture_size; cb->MaxFar = (f32)m_shadow_map_max_distance * BS; cb->PerspectiveBiasXY = getPerspectiveBiasXY(); cb->PerspectiveBiasZ = getPerspectiveBiasZ(); diff --git a/src/client/shadows/dynamicshadowsrender.h b/src/client/shadows/dynamicshadowsrender.h index 1c7b6e482..7026e39a9 100644 --- a/src/client/shadows/dynamicshadowsrender.h +++ b/src/client/shadows/dynamicshadowsrender.h @@ -124,7 +124,7 @@ private: video::SColor m_shadow_tint; float m_shadow_strength_gamma; float m_shadow_map_max_distance; - float m_shadow_map_texture_size; + u32 m_shadow_map_texture_size; float m_time_day; int m_shadow_samples; bool m_shadow_map_texture_32bit; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 0e802d9c7..e4d94b3fc 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -104,7 +104,7 @@ void read_item_definition(lua_State* L, int index, } else if (lua_isstring(L, -1)) { video::SColor color; read_color(L, -1, &color); - def.wear_bar_params = WearBarParams({{0.0, color}}, + def.wear_bar_params = WearBarParams({{0.0f, color}}, WearBarParams::BLEND_MODE_CONSTANT); } diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 1a810c06d..7a07c37fe 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -320,7 +320,7 @@ void TestUtilities::testUTF8() // try to check that the conversion function does not accidentally keep // its internal state across invocations. // \xC4\x81 is UTF-8 for \u0101 - utf8_to_wide("\xC4"); + static_cast(utf8_to_wide("\xC4")); UASSERT(utf8_to_wide("\x81") != L"\u0101"); } From c0d10b24a46b5473bad791f2689e4c97b75157e9 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Sat, 5 Apr 2025 01:19:52 +0200 Subject: [PATCH 147/284] Use SPDX-License-Identifiers in builtin --- builtin/common/filterlist.lua | 19 +++-------------- builtin/common/settings/components.lua | 19 +++-------------- .../settings/dlg_change_mapgen_flags.lua | 19 +++-------------- builtin/common/settings/dlg_settings.lua | 19 +++-------------- builtin/common/settings/init.lua | 19 +++-------------- builtin/common/settings/settingtypes.lua | 19 +++-------------- builtin/common/settings/shadows_component.lua | 21 ++++--------------- builtin/fstk/buttonbar.lua | 21 ++++--------------- builtin/fstk/dialog.lua | 19 +++-------------- builtin/fstk/tabview.lua | 19 +++-------------- builtin/fstk/ui.lua | 19 +++-------------- builtin/mainmenu/common.lua | 19 +++-------------- builtin/mainmenu/content/contentdb.lua | 19 +++-------------- builtin/mainmenu/content/dlg_contentdb.lua | 19 +++-------------- builtin/mainmenu/content/dlg_install.lua | 19 +++-------------- builtin/mainmenu/content/dlg_overwrite.lua | 19 +++-------------- builtin/mainmenu/content/dlg_package.lua | 19 +++-------------- builtin/mainmenu/content/init.lua | 19 +++-------------- builtin/mainmenu/content/pkgmgr.lua | 19 +++-------------- builtin/mainmenu/content/screenshots.lua | 19 +++-------------- .../mainmenu/content/tests/pkgmgr_spec.lua | 19 +++-------------- builtin/mainmenu/content/update_detector.lua | 19 +++-------------- builtin/mainmenu/dlg_config_world.lua | 19 +++-------------- builtin/mainmenu/dlg_create_world.lua | 19 +++-------------- builtin/mainmenu/dlg_delete_content.lua | 19 +++-------------- builtin/mainmenu/dlg_delete_world.lua | 19 +++-------------- builtin/mainmenu/dlg_register.lua | 19 +++-------------- builtin/mainmenu/dlg_reinstall_mtg.lua | 19 +++-------------- builtin/mainmenu/dlg_rename_modpack.lua | 19 +++-------------- builtin/mainmenu/game_theme.lua | 19 +++-------------- builtin/mainmenu/init.lua | 19 +++-------------- builtin/mainmenu/serverlistmgr.lua | 19 +++-------------- builtin/mainmenu/tab_about.lua | 19 +++-------------- builtin/mainmenu/tab_content.lua | 21 ++++--------------- builtin/mainmenu/tab_local.lua | 19 +++-------------- builtin/mainmenu/tab_online.lua | 19 +++-------------- builtin/profiler/init.lua | 19 +++-------------- builtin/profiler/instrumentation.lua | 19 +++-------------- builtin/profiler/reporter.lua | 19 +++-------------- builtin/profiler/sampling.lua | 19 +++-------------- 40 files changed, 123 insertions(+), 643 deletions(-) diff --git a/builtin/common/filterlist.lua b/builtin/common/filterlist.lua index c323a0d55..058a573c6 100644 --- a/builtin/common/filterlist.lua +++ b/builtin/common/filterlist.lua @@ -1,19 +1,6 @@ ---Luanti ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- -- TODO improve doc -- diff --git a/builtin/common/settings/components.lua b/builtin/common/settings/components.lua index b3035fd51..99fb0cd76 100644 --- a/builtin/common/settings/components.lua +++ b/builtin/common/settings/components.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2022 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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local make = {} diff --git a/builtin/common/settings/dlg_change_mapgen_flags.lua b/builtin/common/settings/dlg_change_mapgen_flags.lua index 31dd40bd8..de96c75e3 100644 --- a/builtin/common/settings/dlg_change_mapgen_flags.lua +++ b/builtin/common/settings/dlg_change_mapgen_flags.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2015 PilzAdam --- ---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. +-- Luanti +-- Copyright (C) 2015 PilzAdam +-- SPDX-License-Identifier: LGPL-2.1-or-later local checkboxes = {} diff --git a/builtin/common/settings/dlg_settings.lua b/builtin/common/settings/dlg_settings.lua index 8d4a8b6de..7f519f9c3 100644 --- a/builtin/common/settings/dlg_settings.lua +++ b/builtin/common/settings/dlg_settings.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2022 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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local path = core.get_builtin_path() .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM diff --git a/builtin/common/settings/init.lua b/builtin/common/settings/init.lua index d475e0c96..71a95424b 100644 --- a/builtin/common/settings/init.lua +++ b/builtin/common/settings/init.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2022 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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local path = core.get_builtin_path() .. "common" .. DIR_DELIM .. "settings" .. DIR_DELIM diff --git a/builtin/common/settings/settingtypes.lua b/builtin/common/settings/settingtypes.lua index 90ff8975c..db3b5a609 100644 --- a/builtin/common/settings/settingtypes.lua +++ b/builtin/common/settings/settingtypes.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2015 PilzAdam --- ---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. +-- Luanti +-- Copyright (C) 2015 PilzAdam +-- SPDX-License-Identifier: LGPL-2.1-or-later settingtypes = {} diff --git a/builtin/common/settings/shadows_component.lua b/builtin/common/settings/shadows_component.lua index 405517058..59b152ab8 100644 --- a/builtin/common/settings/shadows_component.lua +++ b/builtin/common/settings/shadows_component.lua @@ -1,20 +1,7 @@ ---Luanti ---Copyright (C) 2021-2 x2048 ---Copyright (C) 2022-3 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. +-- Luanti +-- Copyright (C) 2021-2 x2048 +-- Copyright (C) 2022-3 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local shadow_levels_labels = { diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua index 577eb7c6d..461babc1f 100644 --- a/builtin/fstk/buttonbar.lua +++ b/builtin/fstk/buttonbar.lua @@ -1,20 +1,7 @@ ---Luanti ---Copyright (C) 2014 sapier ---Copyright (C) 2023 Gregor Parzefall --- ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- Copyright (C) 2023 Gregor Parzefall +-- SPDX-License-Identifier: LGPL-2.1-or-later local BASE_SPACING = 0.1 diff --git a/builtin/fstk/dialog.lua b/builtin/fstk/dialog.lua index 7043646a4..f0b7cf14b 100644 --- a/builtin/fstk/dialog.lua +++ b/builtin/fstk/dialog.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function dialog_event_handler(self,event) if self.user_eventhandler == nil or diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua index c88c4917a..13a96abff 100644 --- a/builtin/fstk/tabview.lua +++ b/builtin/fstk/tabview.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua index b5b95d0e4..47b9d086c 100644 --- a/builtin/fstk/ui.lua +++ b/builtin/fstk/ui.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later ui = {} ui.childlist = {} diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index d9edd745b..e7b4d90d1 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -- Global menu data menudata = {} diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua index 856924bd4..be148841a 100644 --- a/builtin/mainmenu/content/contentdb.lua +++ b/builtin/mainmenu/content/contentdb.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-24 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. +-- Luanti +-- Copyright (C) 2018-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later if not core.get_http_api then return diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua index 3c3f7987d..872fab113 100644 --- a/builtin/mainmenu/content/dlg_contentdb.lua +++ b/builtin/mainmenu/content/dlg_contentdb.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-20 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. +-- Luanti +-- Copyright (C) 2018-20 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later if not core.get_http_api then function create_contentdb_dlg() diff --git a/builtin/mainmenu/content/dlg_install.lua b/builtin/mainmenu/content/dlg_install.lua index ef201f56a..3d9e45760 100644 --- a/builtin/mainmenu/content/dlg_install.lua +++ b/builtin/mainmenu/content/dlg_install.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-24 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. +-- Luanti +-- Copyright (C) 2018-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local function is_still_visible(dlg) local this = ui.find_by_name("install_dialog") diff --git a/builtin/mainmenu/content/dlg_overwrite.lua b/builtin/mainmenu/content/dlg_overwrite.lua index 5baaa5cd2..08d49bce3 100644 --- a/builtin/mainmenu/content/dlg_overwrite.lua +++ b/builtin/mainmenu/content/dlg_overwrite.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-24 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. +-- Luanti +-- Copyright (C) 2018-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later function get_formspec(data) local package = data.package diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua index d8fc057c1..500fb3f6c 100644 --- a/builtin/mainmenu/content/dlg_package.lua +++ b/builtin/mainmenu/content/dlg_package.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2018-24 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. +-- Luanti +-- Copyright (C) 2018-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local function get_info_formspec(size, padding, text) diff --git a/builtin/mainmenu/content/init.lua b/builtin/mainmenu/content/init.lua index dbf4cc888..e35ca1734 100644 --- a/builtin/mainmenu/content/init.lua +++ b/builtin/mainmenu/content/init.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2023 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. +-- Luanti +-- Copyright (C) 2023 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local path = core.get_mainmenu_path() .. DIR_DELIM .. "content" diff --git a/builtin/mainmenu/content/pkgmgr.lua b/builtin/mainmenu/content/pkgmgr.lua index 986d80398..2863e2a5f 100644 --- a/builtin/mainmenu/content/pkgmgr.lua +++ b/builtin/mainmenu/content/pkgmgr.lua @@ -1,19 +1,6 @@ ---Luanti ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- local function get_last_folder(text,count) diff --git a/builtin/mainmenu/content/screenshots.lua b/builtin/mainmenu/content/screenshots.lua index 718666085..e53f03ed0 100644 --- a/builtin/mainmenu/content/screenshots.lua +++ b/builtin/mainmenu/content/screenshots.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2023-24 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. +-- Luanti +-- Copyright (C) 2023-24 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later -- Screenshot diff --git a/builtin/mainmenu/content/tests/pkgmgr_spec.lua b/builtin/mainmenu/content/tests/pkgmgr_spec.lua index 8870bb68f..5532764d9 100644 --- a/builtin/mainmenu/content/tests/pkgmgr_spec.lua +++ b/builtin/mainmenu/content/tests/pkgmgr_spec.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2022 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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local mods_dir = "/tmp/.minetest/mods" local games_dir = "/tmp/.minetest/games" diff --git a/builtin/mainmenu/content/update_detector.lua b/builtin/mainmenu/content/update_detector.lua index 4d0c1196b..1479328f7 100644 --- a/builtin/mainmenu/content/update_detector.lua +++ b/builtin/mainmenu/content/update_detector.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2023 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. +-- Luanti +-- Copyright (C) 2023 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later update_detector = {} diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua index 416a4a6f3..36cc5580c 100644 --- a/builtin/mainmenu/dlg_config_world.lua +++ b/builtin/mainmenu/dlg_config_world.lua @@ -1,19 +1,6 @@ ---Luanti ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index 4844c85fe..27fe68050 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function table_to_flags(ftable) -- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves" diff --git a/builtin/mainmenu/dlg_delete_content.lua b/builtin/mainmenu/dlg_delete_content.lua index a36bcb2d7..799050d0b 100644 --- a/builtin/mainmenu/dlg_delete_content.lua +++ b/builtin/mainmenu/dlg_delete_content.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/dlg_delete_world.lua b/builtin/mainmenu/dlg_delete_world.lua index 531b4927d..25e9dd0fc 100644 --- a/builtin/mainmenu/dlg_delete_world.lua +++ b/builtin/mainmenu/dlg_delete_world.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function delete_world_formspec(dialogdata) diff --git a/builtin/mainmenu/dlg_register.lua b/builtin/mainmenu/dlg_register.lua index 88a449ae3..5047eda3c 100644 --- a/builtin/mainmenu/dlg_register.lua +++ b/builtin/mainmenu/dlg_register.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2022 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. +-- Luanti +-- Copyright (C) 2022 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/dlg_reinstall_mtg.lua b/builtin/mainmenu/dlg_reinstall_mtg.lua index 11b20f637..4defa6646 100644 --- a/builtin/mainmenu/dlg_reinstall_mtg.lua +++ b/builtin/mainmenu/dlg_reinstall_mtg.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2023 Gregor Parzefall --- ---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. +-- Luanti +-- Copyright (C) 2023 Gregor Parzefall +-- SPDX-License-Identifier: LGPL-2.1-or-later ---- IMPORTANT ---- -- This whole file can be removed after a while. diff --git a/builtin/mainmenu/dlg_rename_modpack.lua b/builtin/mainmenu/dlg_rename_modpack.lua index 09df92d25..830c3cef3 100644 --- a/builtin/mainmenu/dlg_rename_modpack.lua +++ b/builtin/mainmenu/dlg_rename_modpack.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later -------------------------------------------------------------------------------- diff --git a/builtin/mainmenu/game_theme.lua b/builtin/mainmenu/game_theme.lua index 7c6408157..729110025 100644 --- a/builtin/mainmenu/game_theme.lua +++ b/builtin/mainmenu/game_theme.lua @@ -1,19 +1,6 @@ ---Luanti ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later mm_game_theme = {} diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index acf1f738d..1394d13f1 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later MAIN_TAB_W = 15.5 MAIN_TAB_H = 7.1 diff --git a/builtin/mainmenu/serverlistmgr.lua b/builtin/mainmenu/serverlistmgr.lua index 1b69d7a55..34eca287a 100644 --- a/builtin/mainmenu/serverlistmgr.lua +++ b/builtin/mainmenu/serverlistmgr.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2020 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. +-- Luanti +-- Copyright (C) 2020 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later serverlistmgr = { -- continent code we detected for ourselves diff --git a/builtin/mainmenu/tab_about.lua b/builtin/mainmenu/tab_about.lua index 86c811457..5d2e606df 100644 --- a/builtin/mainmenu/tab_about.lua +++ b/builtin/mainmenu/tab_about.lua @@ -1,19 +1,6 @@ ---Luanti ---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. +-- Luanti +-- Copyright (C) 2013 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function prepare_credits(dest, source) diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua index cd384c905..8a6fc5b01 100644 --- a/builtin/mainmenu/tab_content.lua +++ b/builtin/mainmenu/tab_content.lua @@ -1,20 +1,7 @@ ---Luanti ---Copyright (C) 2014 sapier ---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. +-- Luanti +-- Copyright (C) 2014 sapier +-- Copyright (C) 2018 rubenwardy +-- SPDX-License-Identifier: LGPL-2.1-or-later local function get_content_icons(packages_with_updates) diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua index 083f9a50a..0d0b20ff1 100644 --- a/builtin/mainmenu/tab_local.lua +++ b/builtin/mainmenu/tab_local.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local current_game, singleplayer_refresh_gamebar diff --git a/builtin/mainmenu/tab_online.lua b/builtin/mainmenu/tab_online.lua index f3f19c50f..ab5ddfe11 100644 --- a/builtin/mainmenu/tab_online.lua +++ b/builtin/mainmenu/tab_online.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2014 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. +-- Luanti +-- Copyright (C) 2014 sapier +-- SPDX-License-Identifier: LGPL-2.1-or-later local function get_sorted_servers() local servers = { diff --git a/builtin/profiler/init.lua b/builtin/profiler/init.lua index f5b4b7c7e..ede9e0e7e 100644 --- a/builtin/profiler/init.lua +++ b/builtin/profiler/init.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2016 T4im --- ---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. +-- Luanti +-- Copyright (C) 2016 T4im +-- SPDX-License-Identifier: LGPL-2.1-or-later local S = core.get_translator("__builtin") diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua index e012f07a0..613d3d692 100644 --- a/builtin/profiler/instrumentation.lua +++ b/builtin/profiler/instrumentation.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2016 T4im --- ---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. +-- Luanti +-- Copyright (C) 2016 T4im +-- SPDX-License-Identifier: LGPL-2.1-or-later local format, pairs, type = string.format, pairs, type local core, get_current_modname = core, core.get_current_modname diff --git a/builtin/profiler/reporter.lua b/builtin/profiler/reporter.lua index 7bc1b235d..b2bad7560 100644 --- a/builtin/profiler/reporter.lua +++ b/builtin/profiler/reporter.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2016 T4im --- ---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. +-- Luanti +-- Copyright (C) 2016 T4im +-- SPDX-License-Identifier: LGPL-2.1-or-later local S = core.get_translator("__builtin") -- Note: In this file, only messages are translated diff --git a/builtin/profiler/sampling.lua b/builtin/profiler/sampling.lua index 16d6c2012..5f1f5414c 100644 --- a/builtin/profiler/sampling.lua +++ b/builtin/profiler/sampling.lua @@ -1,19 +1,6 @@ ---Luanti ---Copyright (C) 2016 T4im --- ---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. +-- Luanti +-- Copyright (C) 2016 T4im +-- SPDX-License-Identifier: LGPL-2.1-or-later local setmetatable = setmetatable local pairs, format = pairs, string.format local min, max, huge = math.min, math.max, math.huge From 1c5776d13a9d22241a08dd2611e6ec799e3a28ea Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Sat, 12 Apr 2025 02:29:53 +0200 Subject: [PATCH 148/284] `FATAL_ERROR` for orphan object refs in `objectrefGetOrCreate` --- src/script/cpp_api/s_base.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 9022cd8c3..ba931b22a 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -5,6 +5,7 @@ #include "cpp_api/s_base.h" #include "cpp_api/s_internal.h" #include "cpp_api/s_security.h" +#include "debug.h" #include "lua_api/l_object.h" #include "common/c_converter.h" #include "server/player_sao.h" @@ -467,12 +468,8 @@ void ScriptApiBase::objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj) if (!cobj) { ObjectRef::create(L, nullptr); // dummy reference } else if (cobj->getId() == 0) { - // TODO after 5.10.0: convert this to a FATAL_ERROR - errorstream << "ScriptApiBase::objectrefGetOrCreate(): " - << "Pushing orphan ObjectRef. Please open a bug report for this." - << std::endl; - assert(0); - ObjectRef::create(L, cobj); + FATAL_ERROR("ScriptApiBase::objectrefGetOrCreate(): " + "Pushing orphan ObjectRef. Please open a bug report for this."); } else { push_objectRef(L, cobj->getId()); if (cobj->isGone()) From 4c4e2962748b9f2b30c0b74b4527034b82891875 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 21 Apr 2025 12:31:44 +0200 Subject: [PATCH 149/284] Handle texture filtering sanely to avoid blurriness (#16034) --- builtin/settingtypes.txt | 4 +- doc/lua_api.md | 9 +++++ src/client/content_cao.cpp | 77 +++++++++++++++----------------------- src/client/imagesource.cpp | 25 ++++++++----- src/client/wieldmesh.cpp | 9 ++--- src/constants.h | 6 +++ src/defaultsettings.cpp | 2 +- src/nodedef.cpp | 3 +- 8 files changed, 70 insertions(+), 65 deletions(-) diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 83d0b48e0..9462f616d 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2106,7 +2106,7 @@ world_aligned_mode (World-aligned textures mode) enum enable disable,enable,forc # World-aligned textures may be scaled to span several nodes. However, # the server may not send the scale you want, especially if you use # a specially-designed texture pack; with this option, the client tries -# to determine the scale automatically basing on the texture size. +# to determine the scale automatically based on the texture size. # See also texture_min_size. # Warning: This option is EXPERIMENTAL! autoscale_mode (Autoscaling mode) enum disable disable,enable,force @@ -2118,7 +2118,7 @@ autoscale_mode (Autoscaling mode) enum disable disable,enable,force # This setting is ONLY applied if any of the mentioned filters are enabled. # This is also used as the base node texture size for world-aligned # texture autoscaling. -texture_min_size (Base texture size) int 64 1 32768 +texture_min_size (Base texture size) int 192 192 16384 # Side length of a cube of map blocks that the client will consider together # when generating meshes. diff --git a/doc/lua_api.md b/doc/lua_api.md index e2a55b7e1..31eb35dd2 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -518,6 +518,15 @@ stripping out the file extension: Supported texture formats are PNG (`.png`), JPEG (`.jpg`) and Targa (`.tga`). +Luanti generally uses nearest-neighbor upscaling for textures to preserve the crisp +look of pixel art (low-res textures). +Users can optionally enable bilinear and/or trilinear filtering. However, to avoid +everything becoming blurry, textures smaller than 192px will either not be filtered, +or will be upscaled to that minimum resolution first without filtering. + +This is subject to change to move more control to the Lua API, but you can rely on +low-res textures not suddenly becoming filtered. + Texture modifiers ----------------- diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 624ed4b5b..02e980cab 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -144,6 +144,31 @@ void SmoothTranslatorWrappedv3f::translate(f32 dtime) Other stuff */ +static bool setMaterialTextureAndFilters(video::SMaterial &material, + const std::string &texturestring, ITextureSource *tsrc) +{ + bool use_trilinear_filter = g_settings->getBool("trilinear_filter"); + bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); + bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); + + video::ITexture *texture = tsrc->getTextureForMesh(texturestring); + if (!texture) + return false; + + material.setTexture(0, texture); + + // don't filter low-res textures, makes them look blurry + const core::dimension2d &size = texture->getOriginalSize(); + if (std::min(size.Width, size.Height) < TEXTURE_FILTER_MIN_SIZE) + use_trilinear_filter = use_bilinear_filter = false; + + material.forEachTexture([=] (auto &tex) { + setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, + use_anisotropic_filter); + }); + return true; +} + static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, float txs, float tys, int col, int row) { @@ -1238,10 +1263,6 @@ void GenericCAO::updateTextures(std::string mod) { ITextureSource *tsrc = m_client->tsrc(); - bool use_trilinear_filter = g_settings->getBool("trilinear_filter"); - bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); - bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); - m_previous_texture_modifier = m_current_texture_modifier; m_current_texture_modifier = mod; @@ -1254,12 +1275,7 @@ void GenericCAO::updateTextures(std::string mod) video::SMaterial &material = m_spritenode->getMaterial(0); material.MaterialType = m_material_type; - material.setTexture(0, tsrc->getTextureForMesh(texturestring)); - - material.forEachTexture([=] (auto &tex) { - setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, - use_anisotropic_filter); - }); + setMaterialTextureAndFilters(material, texturestring, tsrc); } } @@ -1273,29 +1289,12 @@ void GenericCAO::updateTextures(std::string mod) if (texturestring.empty()) continue; // Empty texture string means don't modify that material texturestring += mod; - video::ITexture *texture = tsrc->getTextureForMesh(texturestring); - if (!texture) { - errorstream<<"GenericCAO::updateTextures(): Could not load texture "<getMaterial(i); material.MaterialType = m_material_type; - material.TextureLayers[0].Texture = texture; material.BackfaceCulling = 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 u32 res = std::min(size.Height, size.Width); - use_trilinear_filter &= res > 64; - use_bilinear_filter &= res > 64; - - material.forEachTexture([=] (auto &tex) { - setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, - use_anisotropic_filter); - }); + setMaterialTextureAndFilters(material, texturestring, tsrc); } } } @@ -1313,13 +1312,7 @@ void GenericCAO::updateTextures(std::string mod) // Set material flags and texture video::SMaterial &material = m_meshnode->getMaterial(i); material.MaterialType = m_material_type; - material.setTexture(0, tsrc->getTextureForMesh(texturestring)); - material.getTextureMatrix(0).makeIdentity(); - - material.forEachTexture([=] (auto &tex) { - setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, - use_anisotropic_filter); - }); + setMaterialTextureAndFilters(material, texturestring, tsrc); } } else if (m_prop.visual == OBJECTVISUAL_UPRIGHT_SPRITE) { scene::IMesh *mesh = m_meshnode->getMesh(); @@ -1330,12 +1323,7 @@ void GenericCAO::updateTextures(std::string mod) tname += mod; auto &material = m_meshnode->getMaterial(0); - material.setTexture(0, tsrc->getTextureForMesh(tname)); - - material.forEachTexture([=] (auto &tex) { - setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, - use_anisotropic_filter); - }); + setMaterialTextureAndFilters(material, tname, tsrc); } { std::string tname = "no_texture.png"; @@ -1346,12 +1334,7 @@ void GenericCAO::updateTextures(std::string mod) tname += mod; auto &material = m_meshnode->getMaterial(1); - material.setTexture(0, tsrc->getTextureForMesh(tname)); - - material.forEachTexture([=] (auto &tex) { - setMaterialFilters(tex, use_bilinear_filter, use_trilinear_filter, - use_anisotropic_filter); - }); + setMaterialTextureAndFilters(material, tname, tsrc); } // Set mesh color (only if lighting is disabled) if (m_prop.glow < 0) diff --git a/src/client/imagesource.cpp b/src/client/imagesource.cpp index 3e55dd386..e39dc2955 100644 --- a/src/client/imagesource.cpp +++ b/src/client/imagesource.cpp @@ -1479,21 +1479,28 @@ bool ImageSource::generateImagePart(std::string_view part_of_name, /* 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 + * 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. + * + * Q: why not just enable/disable filtering depending on texture size? + * A: large texture resolutions apparently allow getting rid of the Moire + * effect way better than anisotropic filtering alone could. + * see and related + * linked discussions. */ const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter; - const s32 scaleto = filter ? g_settings->getU16("texture_min_size") : 1; - if (scaleto > 1) { - const core::dimension2d dim = baseimg->getDimension(); + if (filter) { + const f32 scaleto = rangelim(g_settings->getU16("texture_min_size"), + TEXTURE_FILTER_MIN_SIZE, 16384); - /* 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 integer-scaling needed to make the shortest texture + * dimension equal to the target minimum. If this is e.g. a + * vertical frames animation, the short dimension will be the real size. */ - u32 xscale = scaleto / dim.Width; - u32 yscale = scaleto / dim.Height; + const auto &dim = baseimg->getDimension(); + u32 xscale = std::ceil(scaleto / dim.Width); + u32 yscale = std::ceil(scaleto / dim.Height); const s32 scale = std::max(xscale, yscale); // Never downscale; only scale up by 2x or more. diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 11116a5c3..baa72f1d9 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -281,12 +281,11 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, material.MaterialType = m_material_type; material.MaterialTypeParam = 0.5f; material.BackfaceCulling = true; - // Enable bi/trilinear filtering only for high resolution textures - bool bilinear_filter = dim.Width > 32 && m_bilinear_filter; - bool trilinear_filter = dim.Width > 32 && m_trilinear_filter; + // don't filter low-res textures, makes them look blurry + bool f_ok = std::min(dim.Width, dim.Height) >= TEXTURE_FILTER_MIN_SIZE; material.forEachTexture([=] (auto &tex) { - setMaterialFilters(tex, bilinear_filter, trilinear_filter, - m_anisotropic_filter); + setMaterialFilters(tex, m_bilinear_filter && f_ok, + m_trilinear_filter && f_ok, m_anisotropic_filter); }); // mipmaps cause "thin black line" artifacts material.UseMipMaps = false; diff --git a/src/constants.h b/src/constants.h index 4a8ed4471..e97bd0507 100644 --- a/src/constants.h +++ b/src/constants.h @@ -99,3 +99,9 @@ #define SCREENSHOT_MAX_SERIAL_TRIES 1000 #define TTF_DEFAULT_FONT_SIZE (16) + +// Minimum texture size enforced/checked for enabling linear filtering +// This serves as the minimum for `texture_min_size`. +// The intent is to ensure that the rendering doesn't turn terribly blurry +// when filtering is enabled. +#define TEXTURE_FILTER_MIN_SIZE 192U diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 205fe5934..67b090bc6 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -246,7 +246,7 @@ void set_default_settings() settings->setDefault("undersampling", "1"); settings->setDefault("world_aligned_mode", "enable"); settings->setDefault("autoscale_mode", "disable"); - settings->setDefault("texture_min_size", "64"); + settings->setDefault("texture_min_size", std::to_string(TEXTURE_FILTER_MIN_SIZE)); settings->setDefault("enable_fog", "true"); settings->setDefault("fog_start", "0.4"); settings->setDefault("3d_mode", "none"); diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 8ef6f8493..45fc8b55d 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -272,7 +272,8 @@ void TextureSettings::readSettings() connected_glass = g_settings->getBool("connected_glass"); translucent_liquids = g_settings->getBool("translucent_liquids"); enable_minimap = g_settings->getBool("enable_minimap"); - node_texture_size = std::max(g_settings->getU16("texture_min_size"), 1); + node_texture_size = rangelim(g_settings->getU16("texture_min_size"), + TEXTURE_FILTER_MIN_SIZE, 16384); 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"); From b2c2a6ff4708362986acc65c62e95a23c1312165 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 17 Apr 2025 22:15:14 +0200 Subject: [PATCH 150/284] Rename IShaderConstantSetter --- src/client/clientlauncher.cpp | 2 +- src/client/game.cpp | 30 +++++++++---------- src/client/renderingengine.cpp | 8 ++--- src/client/renderingengine.h | 6 ++-- src/client/shader.cpp | 30 +++++++++---------- src/client/shader.h | 16 +++++----- src/client/shadows/dynamicshadowsrender.cpp | 2 +- src/client/shadows/shadowsshadercallbacks.cpp | 2 +- src/client/shadows/shadowsshadercallbacks.h | 14 ++++----- 9 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 725ff4323..dc6a91831 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -126,7 +126,7 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) // This is only global so it can be used by RenderingEngine::draw_load_screen(). assert(!g_menucloudsmgr && !g_menuclouds); std::unique_ptr ssrc(createShaderSource()); - ssrc->addShaderConstantSetterFactory(new FogShaderConstantSetterFactory()); + ssrc->addShaderUniformSetterFactory(new FogShaderUniformSetterFactory()); g_menucloudsmgr = m_rendering_engine->get_scene_manager()->createNewSceneManager(); g_menuclouds = new Clouds(g_menucloudsmgr, ssrc.get(), -1, rand()); g_menuclouds->setHeight(100.0f); diff --git a/src/client/game.cpp b/src/client/game.cpp index 78d009c7e..61d067ef3 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -188,7 +188,7 @@ public: typedef s32 SamplerLayer_t; -class GameGlobalShaderConstantSetter : public IShaderConstantSetter +class GameGlobalShaderUniformSetter : public IShaderUniformSetter { Sky *m_sky; Client *m_client; @@ -247,12 +247,12 @@ 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, Client *client) : + GameGlobalShaderUniformSetter(Sky *sky, Client *client) : m_sky(sky), m_client(client) { @@ -264,12 +264,12 @@ public: m_volumetric_light_enabled = g_settings->getBool("enable_volumetric_lighting") && m_bloom_enabled; } - ~GameGlobalShaderConstantSetter() + ~GameGlobalShaderUniformSetter() { g_settings->deregisterAllChangedCallbacks(this); } - void onSetConstants(video::IMaterialRendererServices *services) override + void onSetUniforms(video::IMaterialRendererServices *services) override { u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio(); video::SColorf sunlight; @@ -395,28 +395,28 @@ public: }; -class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory +class GameGlobalShaderUniformSetterFactory : public IShaderUniformSetterFactory { Sky *m_sky = nullptr; Client *m_client; - std::vector created_nosky; + std::vector created_nosky; public: - GameGlobalShaderConstantSetterFactory(Client *client) : + GameGlobalShaderUniformSetterFactory(Client *client) : m_client(client) {} void setSky(Sky *sky) { m_sky = sky; - for (GameGlobalShaderConstantSetter *ggscs : created_nosky) { + for (GameGlobalShaderUniformSetter *ggscs : created_nosky) { ggscs->setSky(m_sky); } created_nosky.clear(); } - virtual IShaderConstantSetter* create() + virtual IShaderUniformSetter* create() { - auto *scs = new GameGlobalShaderConstantSetter(m_sky, m_client); + auto *scs = new GameGlobalShaderUniformSetter(m_sky, m_client); if (!m_sky) created_nosky.push_back(scs); return scs; @@ -1289,11 +1289,11 @@ bool Game::createClient(const GameStartData &start_data) return false; } - auto *scsf = new GameGlobalShaderConstantSetterFactory(client); - shader_src->addShaderConstantSetterFactory(scsf); + auto *scsf = new GameGlobalShaderUniformSetterFactory(client); + shader_src->addShaderUniformSetterFactory(scsf); - shader_src->addShaderConstantSetterFactory( - new FogShaderConstantSetterFactory()); + shader_src->addShaderUniformSetterFactory( + new FogShaderUniformSetterFactory()); ShadowRenderer::preInit(shader_src); diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 643898eac..3b3d114ea 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -67,14 +67,14 @@ void FpsControl::limit(IrrlichtDevice *device, f32 *dtime) last_time = time; } -class FogShaderConstantSetter : public IShaderConstantSetter +class FogShaderUniformSetter : public IShaderUniformSetter { CachedPixelShaderSetting m_fog_color{"fogColor"}; CachedPixelShaderSetting m_fog_distance{"fogDistance"}; CachedPixelShaderSetting m_fog_shading_parameter{"fogShadingParameter"}; public: - void onSetConstants(video::IMaterialRendererServices *services) override + void onSetUniforms(video::IMaterialRendererServices *services) override { auto *driver = services->getVideoDriver(); assert(driver); @@ -101,9 +101,9 @@ public: } }; -IShaderConstantSetter *FogShaderConstantSetterFactory::create() +IShaderUniformSetter *FogShaderUniformSetterFactory::create() { - return new FogShaderConstantSetter(); + return new FogShaderUniformSetter(); } /* Other helpers */ diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index d76b19abe..34918ec7a 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -54,11 +54,11 @@ struct FpsControl { }; // Populates fogColor, fogDistance, fogShadingParameter with values from Irrlicht -class FogShaderConstantSetterFactory : public IShaderConstantSetterFactory +class FogShaderUniformSetterFactory : public IShaderUniformSetterFactory { public: - FogShaderConstantSetterFactory() {}; - virtual IShaderConstantSetter *create(); + FogShaderUniformSetterFactory() {}; + virtual IShaderUniformSetter *create(); }; /* Rendering engine class */ diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 3ecbb4f38..39ea7c46d 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -159,7 +159,7 @@ private: class ShaderCallback : public video::IShaderConstantSetCallBack { - std::vector> m_setters; + std::vector> m_setters; public: template @@ -175,7 +175,7 @@ public: virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override { for (auto &&setter : m_setters) - setter->onSetConstants(services); + setter->onSetUniforms(services); } virtual void OnSetMaterial(const video::SMaterial& material) override @@ -187,10 +187,10 @@ public: /* - MainShaderConstantSetter: Set basic constants required for almost everything + MainShaderUniformSetter: Set basic uniforms required for almost everything */ -class MainShaderConstantSetter : public IShaderConstantSetter +class MainShaderUniformSetter : public IShaderUniformSetter { CachedVertexShaderSetting m_world_view_proj{"mWorldViewProj"}; CachedVertexShaderSetting m_world{"mWorld"}; @@ -205,14 +205,14 @@ class MainShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_material_color_setting{"materialColor"}; public: - ~MainShaderConstantSetter() = default; + ~MainShaderUniformSetter() = default; virtual void onSetMaterial(const video::SMaterial& material) override { m_material_color = material.ColorParam; } - virtual void onSetConstants(video::IMaterialRendererServices *services) override + virtual void onSetUniforms(video::IMaterialRendererServices *services) override { video::IVideoDriver *driver = services->getVideoDriver(); assert(driver); @@ -243,11 +243,11 @@ public: }; -class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory +class MainShaderUniformSetterFactory : public IShaderUniformSetterFactory { public: - virtual IShaderConstantSetter* create() - { return new MainShaderConstantSetter(); } + virtual IShaderUniformSetter* create() + { return new MainShaderUniformSetter(); } }; @@ -306,9 +306,9 @@ public: // Shall be called from the main thread. void rebuildShaders() override; - void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) override + void addShaderUniformSetterFactory(IShaderUniformSetterFactory *setter) override { - m_setter_factories.emplace_back(setter); + m_uniform_factories.emplace_back(setter); } private: @@ -331,8 +331,8 @@ private: RequestQueue m_get_shader_queue; #endif - // Global constant setter factories - std::vector> m_setter_factories; + // Global uniform setter factories + std::vector> m_uniform_factories; // Generate shader given the shader name. ShaderInfo generateShader(const std::string &name, @@ -352,7 +352,7 @@ ShaderSource::ShaderSource() m_shaderinfo_cache.emplace_back(); // Add main global constant setter - addShaderConstantSetterFactory(new MainShaderConstantSetterFactory()); + addShaderUniformSetterFactory(new MainShaderUniformSetterFactory()); } ShaderSource::~ShaderSource() @@ -773,7 +773,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, geometry_shader_ptr = geometry_shader.c_str(); } - auto cb = make_irr(m_setter_factories); + auto cb = make_irr(m_uniform_factories); infostream << "Compiling high level shaders for " << log_name << std::endl; s32 shadermat = gpu->addHighLevelShaderMaterial( vertex_shader.c_str(), fragment_shader.c_str(), geometry_shader_ptr, diff --git a/src/client/shader.h b/src/client/shader.h index c78b0078a..731cd4b21 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -40,7 +40,7 @@ struct ShaderInfo { }; /* - Setter of constants for shaders + Abstraction for updating uniforms used by shaders */ namespace irr::video { @@ -48,19 +48,19 @@ namespace irr::video { } -class IShaderConstantSetter { +class IShaderUniformSetter { public: - virtual ~IShaderConstantSetter() = default; - virtual void onSetConstants(video::IMaterialRendererServices *services) = 0; + virtual ~IShaderUniformSetter() = default; + virtual void onSetUniforms(video::IMaterialRendererServices *services) = 0; virtual void onSetMaterial(const video::SMaterial& material) { } }; -class IShaderConstantSetterFactory { +class IShaderUniformSetterFactory { public: - virtual ~IShaderConstantSetterFactory() = default; - virtual IShaderConstantSetter* create() = 0; + virtual ~IShaderUniformSetterFactory() = default; + virtual IShaderUniformSetter* create() = 0; }; @@ -236,7 +236,7 @@ public: virtual void rebuildShaders()=0; /// @note Takes ownership of @p setter. - virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0; + virtual void addShaderUniformSetterFactory(IShaderUniformSetterFactory *setter) = 0; }; IWritableShaderSource *createShaderSource(); diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 70bc2d2cf..1297bf175 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -107,7 +107,7 @@ void ShadowRenderer::disable() void ShadowRenderer::preInit(IWritableShaderSource *shsrc) { if (g_settings->getBool("enable_dynamic_shadows")) { - shsrc->addShaderConstantSetterFactory(new ShadowConstantSetterFactory()); + shsrc->addShaderUniformSetterFactory(new ShadowUniformSetterFactory()); } } diff --git a/src/client/shadows/shadowsshadercallbacks.cpp b/src/client/shadows/shadowsshadercallbacks.cpp index f662d21e7..add5f8a09 100644 --- a/src/client/shadows/shadowsshadercallbacks.cpp +++ b/src/client/shadows/shadowsshadercallbacks.cpp @@ -5,7 +5,7 @@ #include "client/shadows/shadowsshadercallbacks.h" #include "client/renderingengine.h" -void ShadowConstantSetter::onSetConstants(video::IMaterialRendererServices *services) +void ShadowUniformSetter::onSetUniforms(video::IMaterialRendererServices *services) { auto *shadow = RenderingEngine::get_shadow_renderer(); if (!shadow) diff --git a/src/client/shadows/shadowsshadercallbacks.h b/src/client/shadows/shadowsshadercallbacks.h index 4fc462b11..cd1ff3916 100644 --- a/src/client/shadows/shadowsshadercallbacks.h +++ b/src/client/shadows/shadowsshadercallbacks.h @@ -9,7 +9,7 @@ // Used by main game rendering -class ShadowConstantSetter : public IShaderConstantSetter +class ShadowUniformSetter : public IShaderUniformSetter { CachedPixelShaderSetting m_shadow_view_proj{"m_ShadowViewProj"}; CachedPixelShaderSetting m_light_direction{"v_LightDirection"}; @@ -33,17 +33,17 @@ class ShadowConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_perspective_zbias_pixel{"zPerspectiveBias"}; public: - ShadowConstantSetter() = default; - ~ShadowConstantSetter() = default; + ShadowUniformSetter() = default; + ~ShadowUniformSetter() = default; - virtual void onSetConstants(video::IMaterialRendererServices *services) override; + virtual void onSetUniforms(video::IMaterialRendererServices *services) override; }; -class ShadowConstantSetterFactory : public IShaderConstantSetterFactory +class ShadowUniformSetterFactory : public IShaderUniformSetterFactory { public: - virtual IShaderConstantSetter *create() { - return new ShadowConstantSetter(); + virtual IShaderUniformSetter *create() { + return new ShadowUniformSetter(); } }; From baa4c7cd21fe434a53e27a7de2d2df0159f964cb Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 17 Apr 2025 23:13:44 +0200 Subject: [PATCH 151/284] Introduce IShaderConstantSetter abstraction --- src/client/shader.cpp | 310 +++++++++++++++++++++++++----------------- src/client/shader.h | 26 +++- 2 files changed, 206 insertions(+), 130 deletions(-) diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 39ea7c46d..46dd9a1f2 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -186,6 +186,154 @@ public: }; +/* + MainShaderConstantSetter: Set some random general constants + NodeShaderConstantSetter: Set constants for node rendering +*/ + +class MainShaderConstantSetter : public IShaderConstantSetter +{ +public: + MainShaderConstantSetter() = default; + ~MainShaderConstantSetter() = default; + + void onGenerate(const std::string &name, ShaderConstants &constants) override + { + constants["ENABLE_TONE_MAPPING"] = g_settings->getBool("tone_mapping") ? 1 : 0; + + if (g_settings->getBool("enable_dynamic_shadows")) { + constants["ENABLE_DYNAMIC_SHADOWS"] = 1; + if (g_settings->getBool("shadow_map_color")) + constants["COLORED_SHADOWS"] = 1; + + if (g_settings->getBool("shadow_poisson_filter")) + constants["POISSON_FILTER"] = 1; + + if (g_settings->getBool("enable_water_reflections")) + constants["ENABLE_WATER_REFLECTIONS"] = 1; + + if (g_settings->getBool("enable_translucent_foliage")) + constants["ENABLE_TRANSLUCENT_FOLIAGE"] = 1; + + if (g_settings->getBool("enable_node_specular")) + constants["ENABLE_NODE_SPECULAR"] = 1; + + s32 shadow_filter = g_settings->getS32("shadow_filters"); + constants["SHADOW_FILTER"] = shadow_filter; + + float shadow_soft_radius = std::max(1.f, + g_settings->getFloat("shadow_soft_radius")); + constants["SOFTSHADOWRADIUS"] = shadow_soft_radius; + } + + if (g_settings->getBool("enable_bloom")) { + constants["ENABLE_BLOOM"] = 1; + if (g_settings->getBool("enable_bloom_debug")) + constants["ENABLE_BLOOM_DEBUG"] = 1; + } + + if (g_settings->getBool("enable_auto_exposure")) + constants["ENABLE_AUTO_EXPOSURE"] = 1; + + if (g_settings->get("antialiasing") == "ssaa") { + constants["ENABLE_SSAA"] = 1; + u16 ssaa_scale = std::max(2, g_settings->getU16("fsaa")); + constants["SSAA_SCALE"] = ssaa_scale; + } + + if (g_settings->getBool("debanding")) + constants["ENABLE_DITHERING"] = 1; + + if (g_settings->getBool("enable_volumetric_lighting")) + constants["VOLUMETRIC_LIGHT"] = 1; + } +}; + + +class NodeShaderConstantSetter : public IShaderConstantSetter +{ +public: + NodeShaderConstantSetter() = default; + ~NodeShaderConstantSetter() = default; + + void onGenerate(const std::string &name, ShaderConstants &constants) override + { + if (constants.find("DRAWTYPE") == constants.end()) + return; // not a node shader + [[maybe_unused]] const auto drawtype = + static_cast(std::get(constants["DRAWTYPE"])); + [[maybe_unused]] const auto material_type = + static_cast(std::get(constants["MATERIAL_TYPE"])); + +#define PROVIDE(constant) constants[ #constant ] = (int)constant + + PROVIDE(NDT_NORMAL); + PROVIDE(NDT_AIRLIKE); + PROVIDE(NDT_LIQUID); + PROVIDE(NDT_FLOWINGLIQUID); + PROVIDE(NDT_GLASSLIKE); + PROVIDE(NDT_ALLFACES); + PROVIDE(NDT_ALLFACES_OPTIONAL); + PROVIDE(NDT_TORCHLIKE); + PROVIDE(NDT_SIGNLIKE); + PROVIDE(NDT_PLANTLIKE); + PROVIDE(NDT_FENCELIKE); + PROVIDE(NDT_RAILLIKE); + PROVIDE(NDT_NODEBOX); + PROVIDE(NDT_GLASSLIKE_FRAMED); + PROVIDE(NDT_FIRELIKE); + PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL); + PROVIDE(NDT_PLANTLIKE_ROOTED); + + PROVIDE(TILE_MATERIAL_BASIC); + PROVIDE(TILE_MATERIAL_ALPHA); + PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT); + PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE); + PROVIDE(TILE_MATERIAL_WAVING_LEAVES); + PROVIDE(TILE_MATERIAL_WAVING_PLANTS); + PROVIDE(TILE_MATERIAL_OPAQUE); + PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC); + PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT); + PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE); + PROVIDE(TILE_MATERIAL_PLAIN); + PROVIDE(TILE_MATERIAL_PLAIN_ALPHA); + +#undef PROVIDE + + bool enable_waving_water = g_settings->getBool("enable_waving_water"); + constants["ENABLE_WAVING_WATER"] = enable_waving_water ? 1 : 0; + if (enable_waving_water) { + constants["WATER_WAVE_HEIGHT"] = g_settings->getFloat("water_wave_height"); + constants["WATER_WAVE_LENGTH"] = g_settings->getFloat("water_wave_length"); + constants["WATER_WAVE_SPEED"] = g_settings->getFloat("water_wave_speed"); + } + switch (material_type) { + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: + case TILE_MATERIAL_WAVING_LIQUID_BASIC: + constants["MATERIAL_WAVING_LIQUID"] = 1; + break; + default: + constants["MATERIAL_WAVING_LIQUID"] = 0; + break; + } + switch (material_type) { + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: + case TILE_MATERIAL_WAVING_LIQUID_BASIC: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + constants["MATERIAL_WATER_REFLECTIONS"] = 1; + break; + default: + constants["MATERIAL_WATER_REFLECTIONS"] = 0; + break; + } + + constants["ENABLE_WAVING_LEAVES"] = g_settings->getBool("enable_waving_leaves") ? 1 : 0; + constants["ENABLE_WAVING_PLANTS"] = g_settings->getBool("enable_waving_plants") ? 1 : 0; + } +}; + /* MainShaderUniformSetter: Set basic uniforms required for almost everything */ @@ -306,6 +454,11 @@ public: // Shall be called from the main thread. void rebuildShaders() override; + void addShaderConstantSetter(IShaderConstantSetter *setter) override + { + m_constant_setters.emplace_back(setter); + } + void addShaderUniformSetterFactory(IShaderUniformSetterFactory *setter) override { m_uniform_factories.emplace_back(setter); @@ -331,6 +484,9 @@ private: RequestQueue m_get_shader_queue; #endif + // Global constant setter factories + std::vector> m_constant_setters; + // Global uniform setter factories std::vector> m_uniform_factories; @@ -351,7 +507,9 @@ ShaderSource::ShaderSource() // Add a dummy ShaderInfo as the first index, named "" m_shaderinfo_cache.emplace_back(); - // Add main global constant setter + // Add global stuff + addShaderConstantSetter(new MainShaderConstantSetter()); + addShaderConstantSetter(new NodeShaderConstantSetter()); addShaderUniformSetterFactory(new MainShaderUniformSetterFactory()); } @@ -613,12 +771,16 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, /// Unique name of this shader, for debug/logging std::string log_name = name; - /* Define constants for node and object shaders */ - const bool node_shader = drawtype != NodeDrawType_END; - if (node_shader) { + ShaderConstants constants; - log_name.append(" mat=").append(itos(material_type)) - .append(" draw=").append(itos(drawtype)); + // Temporary plumbing <-> NodeShaderConstantSetter + if (drawtype != NodeDrawType_END) { + constants["DRAWTYPE"] = (int)drawtype; + constants["MATERIAL_TYPE"] = (int)material_type; + + log_name.append(" mat=").append(itos(material_type)) + .append(" draw=").append(itos(drawtype)); + } bool use_discard = fully_programmable; if (!use_discard) { @@ -629,133 +791,25 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, } if (use_discard) { if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL) - shaders_header << "#define USE_DISCARD 1\n"; + constants["USE_DISCARD"] = 1; else if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF) - shaders_header << "#define USE_DISCARD_REF 1\n"; + constants["USE_DISCARD_REF"] = 1; } -#define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n" - - PROVIDE(NDT_NORMAL); - PROVIDE(NDT_AIRLIKE); - PROVIDE(NDT_LIQUID); - PROVIDE(NDT_FLOWINGLIQUID); - PROVIDE(NDT_GLASSLIKE); - PROVIDE(NDT_ALLFACES); - PROVIDE(NDT_ALLFACES_OPTIONAL); - PROVIDE(NDT_TORCHLIKE); - PROVIDE(NDT_SIGNLIKE); - PROVIDE(NDT_PLANTLIKE); - PROVIDE(NDT_FENCELIKE); - PROVIDE(NDT_RAILLIKE); - PROVIDE(NDT_NODEBOX); - PROVIDE(NDT_GLASSLIKE_FRAMED); - PROVIDE(NDT_FIRELIKE); - PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL); - PROVIDE(NDT_PLANTLIKE_ROOTED); - - PROVIDE(TILE_MATERIAL_BASIC); - PROVIDE(TILE_MATERIAL_ALPHA); - PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT); - PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE); - PROVIDE(TILE_MATERIAL_WAVING_LEAVES); - PROVIDE(TILE_MATERIAL_WAVING_PLANTS); - PROVIDE(TILE_MATERIAL_OPAQUE); - PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC); - PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT); - PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE); - PROVIDE(TILE_MATERIAL_PLAIN); - PROVIDE(TILE_MATERIAL_PLAIN_ALPHA); - -#undef PROVIDE - - shaders_header << "#define MATERIAL_TYPE " << (int)material_type << "\n"; - shaders_header << "#define DRAW_TYPE " << (int)drawtype << "\n"; - - bool enable_waving_water = g_settings->getBool("enable_waving_water"); - shaders_header << "#define ENABLE_WAVING_WATER " << enable_waving_water << "\n"; - if (enable_waving_water) { - shaders_header << "#define WATER_WAVE_HEIGHT " << g_settings->getFloat("water_wave_height") << "\n"; - shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n"; - shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n"; - } - switch (material_type) { - case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: - case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: - case TILE_MATERIAL_WAVING_LIQUID_BASIC: - shaders_header << "#define MATERIAL_WAVING_LIQUID 1\n"; - break; - default: - shaders_header << "#define MATERIAL_WAVING_LIQUID 0\n"; - break; - } - switch (material_type) { - case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: - case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: - case TILE_MATERIAL_WAVING_LIQUID_BASIC: - case TILE_MATERIAL_LIQUID_TRANSPARENT: - shaders_header << "#define MATERIAL_WATER_REFLECTIONS 1\n"; - break; - default: - shaders_header << "#define MATERIAL_WATER_REFLECTIONS 0\n"; - break; + /* Let the constant setters do their job and emit constants */ + for (auto &setter : m_constant_setters) { + setter->onGenerate(name, constants); } - shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n"; - shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n"; - - } - - /* Other constants */ - - shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n"; - - if (g_settings->getBool("enable_dynamic_shadows")) { - shaders_header << "#define ENABLE_DYNAMIC_SHADOWS 1\n"; - if (g_settings->getBool("shadow_map_color")) - shaders_header << "#define COLORED_SHADOWS 1\n"; - - if (g_settings->getBool("shadow_poisson_filter")) - shaders_header << "#define POISSON_FILTER 1\n"; - - if (g_settings->getBool("enable_water_reflections")) - shaders_header << "#define ENABLE_WATER_REFLECTIONS 1\n"; - - if (g_settings->getBool("enable_translucent_foliage")) - shaders_header << "#define ENABLE_TRANSLUCENT_FOLIAGE 1\n"; - - if (g_settings->getBool("enable_node_specular")) - shaders_header << "#define ENABLE_NODE_SPECULAR 1\n"; - - s32 shadow_filter = g_settings->getS32("shadow_filters"); - shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n"; - - float shadow_soft_radius = g_settings->getFloat("shadow_soft_radius"); - if (shadow_soft_radius < 1.0f) - shadow_soft_radius = 1.0f; - shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n"; - } - - if (g_settings->getBool("enable_bloom")) { - shaders_header << "#define ENABLE_BLOOM 1\n"; - if (g_settings->getBool("enable_bloom_debug")) - shaders_header << "#define ENABLE_BLOOM_DEBUG 1\n"; - } - - if (g_settings->getBool("enable_auto_exposure")) - shaders_header << "#define ENABLE_AUTO_EXPOSURE 1\n"; - - if (g_settings->get("antialiasing") == "ssaa") { - shaders_header << "#define ENABLE_SSAA 1\n"; - u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa")); - shaders_header << "#define SSAA_SCALE " << ssaa_scale << ".\n"; - } - - if (g_settings->getBool("debanding")) - shaders_header << "#define ENABLE_DITHERING 1\n"; - - if (g_settings->getBool("enable_volumetric_lighting")) { - shaders_header << "#define VOLUMETRIC_LIGHT 1\n"; + for (auto &it : constants) { + // spaces could cause duplicates + assert(trim(it.first) == it.first); + shaders_header << "#define " << it.first << ' '; + if (auto *ival = std::get_if(&it.second); ival) + shaders_header << *ival; + else + shaders_header << std::get(it.second); + shaders_header << '\n'; } std::string common_header = shaders_header.str(); diff --git a/src/client/shader.h b/src/client/shader.h index 731cd4b21..0dc6d29ff 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -8,10 +8,10 @@ #include "irrlichttypes_bloated.h" #include #include +#include +#include #include "nodedef.h" -class IGameDef; - /* shader.{h,cpp}: Shader handling stuff. */ @@ -39,6 +39,25 @@ struct ShaderInfo { virtual ~ShaderInfo() = default; }; +/* + Abstraction for pushing constants (or what we pretend is) into + shaders. These end up as `#define` prepended to the shader source. +*/ + +// Shader constants are either an int or a float in GLSL +typedef std::map> ShaderConstants; + +class IShaderConstantSetter { +public: + virtual ~IShaderConstantSetter() = default; + /** + * Called when the final shader source is being generated + * @param name name of the shader + * @param constants current set of constants, free to modify + */ + virtual void onGenerate(const std::string &name, ShaderConstants &constants) = 0; +}; + /* Abstraction for updating uniforms used by shaders */ @@ -235,6 +254,9 @@ public: const std::string &filename, const std::string &program)=0; virtual void rebuildShaders()=0; + /// @note Takes ownership of @p setter. + virtual void addShaderConstantSetter(IShaderConstantSetter *setter) = 0; + /// @note Takes ownership of @p setter. virtual void addShaderUniformSetterFactory(IShaderUniformSetterFactory *setter) = 0; }; From f3c2bbfb4833fdbdd8b3bda0a0f102c72d12bcbc Mon Sep 17 00:00:00 2001 From: sfan5 Date: Thu, 17 Apr 2025 23:48:08 +0200 Subject: [PATCH 152/284] Change shaders to be defined by input constants rather than drawtype/material type --- src/client/shader.cpp | 161 +++++++++++++++++++++++------------------- src/client/shader.h | 55 ++++++++++----- 2 files changed, 127 insertions(+), 89 deletions(-) diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 46dd9a1f2..643c381c0 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -410,14 +410,13 @@ public: ~ShaderSource() override; /* - - If shader material specified by name is found from cache, - return the cached id. + - If shader material is found from cache, return the cached id. - Otherwise generate the shader material, add to cache and return id. The id 0 points to a null shader. Its material is EMT_SOLID. */ - u32 getShaderIdDirect(const std::string &name, - MaterialType material_type, NodeDrawType drawtype); + u32 getShaderIdDirect(const std::string &name, const ShaderConstants &input_const, + video::E_MATERIAL_TYPE base_mat); /* If shader specified by the name pointed by the id doesn't @@ -427,19 +426,10 @@ public: and not found in cache, the call is queued to the main thread for processing. */ - u32 getShader(const std::string &name, - MaterialType material_type, NodeDrawType drawtype) override; + u32 getShader(const std::string &name, const ShaderConstants &input_const, + video::E_MATERIAL_TYPE base_mat) override; - u32 getShaderRaw(const std::string &name, bool blendAlpha) override - { - // TODO: the shader system should be refactored to be much more generic. - // Just let callers pass arbitrary constants, this would also deal with - // runtime changes cleanly. - return getShader(name, blendAlpha ? TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC, - NodeDrawType_END); - } - - ShaderInfo getShaderInfo(u32 id) override; + const ShaderInfo &getShaderInfo(u32 id) override; // Processes queued shader requests from other threads. // Shall be called from the main thread. @@ -492,7 +482,16 @@ private: // Generate shader given the shader name. ShaderInfo generateShader(const std::string &name, - MaterialType material_type, NodeDrawType drawtype); + const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat); + + /// @brief outputs a constant to an ostream + inline void putConstant(std::ostream &os, const ShaderConstants::mapped_type &it) + { + if (auto *ival = std::get_if(&it); ival) + os << *ival; + else + os << std::get(it); + } }; IWritableShaderSource *createShaderSource() @@ -520,22 +519,27 @@ ShaderSource::~ShaderSource() // Delete materials auto *gpu = RenderingEngine::get_video_driver()->getGPUProgrammingServices(); assert(gpu); + u32 n = 0; for (ShaderInfo &i : m_shaderinfo_cache) { - if (!i.name.empty()) + if (!i.name.empty()) { gpu->deleteShaderMaterial(i.material); + n++; + } } m_shaderinfo_cache.clear(); + + infostream << "~ShaderSource() cleaned up " << n << " materials" << std::endl; } u32 ShaderSource::getShader(const std::string &name, - MaterialType material_type, NodeDrawType drawtype) + const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat) { /* Get shader */ if (std::this_thread::get_id() == m_main_thread) { - return getShaderIdDirect(name, material_type, drawtype); + return getShaderIdDirect(name, input_const, base_mat); } errorstream << "ShaderSource::getShader(): getting from " @@ -573,7 +577,7 @@ u32 ShaderSource::getShader(const std::string &name, This method generates all the shaders */ u32 ShaderSource::getShaderIdDirect(const std::string &name, - MaterialType material_type, NodeDrawType drawtype) + const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat) { // Empty name means shader 0 if (name.empty()) { @@ -582,10 +586,10 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name, } // Check if already have such instance - for(u32 i=0; iname == name && info->material_type == material_type && - info->drawtype == drawtype) + for (u32 i = 0; i < m_shaderinfo_cache.size(); i++) { + auto &info = m_shaderinfo_cache[i]; + if (info.name == name && info.base_material == base_mat && + info.input_constants == input_const) return i; } @@ -598,7 +602,7 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name, return 0; } - ShaderInfo info = generateShader(name, material_type, drawtype); + ShaderInfo info = generateShader(name, input_const, base_mat); /* Add shader to caches (add dummy shaders too) @@ -607,19 +611,19 @@ u32 ShaderSource::getShaderIdDirect(const std::string &name, MutexAutoLock lock(m_shaderinfo_cache_mutex); u32 id = m_shaderinfo_cache.size(); - m_shaderinfo_cache.push_back(info); - + m_shaderinfo_cache.push_back(std::move(info)); return id; } -ShaderInfo ShaderSource::getShaderInfo(u32 id) +const ShaderInfo &ShaderSource::getShaderInfo(u32 id) { MutexAutoLock lock(m_shaderinfo_cache_mutex); - if(id >= m_shaderinfo_cache.size()) - return ShaderInfo(); - + if (id >= m_shaderinfo_cache.size()) { + static ShaderInfo empty; + return empty; + } return m_shaderinfo_cache[id]; } @@ -655,46 +659,31 @@ void ShaderSource::rebuildShaders() } } + infostream << "ShaderSource: recreating " << m_shaderinfo_cache.size() + << " shaders" << std::endl; + // Recreate shaders for (ShaderInfo &i : m_shaderinfo_cache) { ShaderInfo *info = &i; if (!info->name.empty()) { - *info = generateShader(info->name, info->material_type, info->drawtype); + *info = generateShader(info->name, info->input_constants, info->base_material); } } } ShaderInfo ShaderSource::generateShader(const std::string &name, - MaterialType material_type, NodeDrawType drawtype) + const ShaderConstants &input_const, video::E_MATERIAL_TYPE base_mat) { ShaderInfo shaderinfo; shaderinfo.name = name; - shaderinfo.material_type = material_type; - shaderinfo.drawtype = drawtype; - switch (material_type) { - case TILE_MATERIAL_OPAQUE: - case TILE_MATERIAL_LIQUID_OPAQUE: - case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: - shaderinfo.base_material = video::EMT_SOLID; - break; - case TILE_MATERIAL_ALPHA: - case TILE_MATERIAL_PLAIN_ALPHA: - case TILE_MATERIAL_LIQUID_TRANSPARENT: - case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - break; - case TILE_MATERIAL_BASIC: - case TILE_MATERIAL_PLAIN: - case TILE_MATERIAL_WAVING_LEAVES: - case TILE_MATERIAL_WAVING_PLANTS: - case TILE_MATERIAL_WAVING_LIQUID_BASIC: - shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - break; - } + shaderinfo.input_constants = input_const; + // fixed pipeline materials don't make sense here + assert(base_mat != video::EMT_TRANSPARENT_VERTEX_ALPHA && base_mat != video::EMT_ONETEXTURE_BLEND); + shaderinfo.base_material = base_mat; shaderinfo.material = shaderinfo.base_material; - video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + auto *driver = RenderingEngine::get_video_driver(); // The null driver doesn't support shaders (duh), but we can pretend it does. if (driver->getDriverType() == video::EDT_NULL) return shaderinfo; @@ -770,18 +759,18 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, /// Unique name of this shader, for debug/logging std::string log_name = name; - - ShaderConstants constants; - - // Temporary plumbing <-> NodeShaderConstantSetter - if (drawtype != NodeDrawType_END) { - constants["DRAWTYPE"] = (int)drawtype; - constants["MATERIAL_TYPE"] = (int)material_type; - - log_name.append(" mat=").append(itos(material_type)) - .append(" draw=").append(itos(drawtype)); + for (auto &it : input_const) { + if (log_name.size() > 60) { // it shouldn't be too long + log_name.append("..."); + break; + } + std::ostringstream oss; + putConstant(oss, it.second); + log_name.append(" ").append(it.first).append("=").append(oss.str()); } + ShaderConstants constants = input_const; + bool use_discard = fully_programmable; if (!use_discard) { // workaround for a certain OpenGL implementation lacking GL_ALPHA_TEST @@ -805,10 +794,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, // spaces could cause duplicates assert(trim(it.first) == it.first); shaders_header << "#define " << it.first << ' '; - if (auto *ival = std::get_if(&it.second); ival) - shaders_header << *ival; - else - shaders_header << std::get(it.second); + putConstant(shaders_header, it.second); shaders_header << '\n'; } @@ -849,6 +835,39 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, return shaderinfo; } +/* + Other functions and helpers +*/ + +u32 IShaderSource::getShader(const std::string &name, + MaterialType material_type, NodeDrawType drawtype) +{ + ShaderConstants input_const; + input_const["MATERIAL_TYPE"] = (int)material_type; + input_const["DRAWTYPE"] = (int)drawtype; + + video::E_MATERIAL_TYPE base_mat = video::EMT_SOLID; + switch (material_type) { + case TILE_MATERIAL_ALPHA: + case TILE_MATERIAL_PLAIN_ALPHA: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + base_mat = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + break; + case TILE_MATERIAL_BASIC: + case TILE_MATERIAL_PLAIN: + case TILE_MATERIAL_WAVING_LEAVES: + case TILE_MATERIAL_WAVING_PLANTS: + case TILE_MATERIAL_WAVING_LIQUID_BASIC: + base_mat = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + break; + default: + break; + } + + return getShader(name, input_const, base_mat); +} + void dumpShaderProgram(std::ostream &output_stream, const std::string &program_type, std::string_view program) { diff --git a/src/client/shader.h b/src/client/shader.h index 0dc6d29ff..c4e37bb2e 100644 --- a/src/client/shader.h +++ b/src/client/shader.h @@ -28,17 +28,6 @@ std::string getShaderPath(const std::string &name_of_shader, const std::string &filename); -struct ShaderInfo { - std::string name = ""; - video::E_MATERIAL_TYPE base_material = video::EMT_SOLID; - video::E_MATERIAL_TYPE material = video::EMT_SOLID; - NodeDrawType drawtype = NDT_NORMAL; - MaterialType material_type = TILE_MATERIAL_BASIC; - - ShaderInfo() = default; - virtual ~ShaderInfo() = default; -}; - /* Abstraction for pushing constants (or what we pretend is) into shaders. These end up as `#define` prepended to the shader source. @@ -218,7 +207,18 @@ using CachedStructPixelShaderSetting = CachedStructShaderSetting Date: Fri, 18 Apr 2025 10:09:00 +0200 Subject: [PATCH 153/284] Move NodeShaderConstantSetter to game.cpp --- src/client/game.cpp | 105 ++++++++++++++++---- src/client/shader.cpp | 105 ++++---------------- src/client/shadows/dynamicshadowsrender.cpp | 3 + 3 files changed, 107 insertions(+), 106 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index 61d067ef3..08be9c809 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -204,10 +204,6 @@ class GameGlobalShaderUniformSetter : public IShaderUniformSetter CachedVertexShaderSetting m_camera_offset_vertex{"cameraOffset"}; CachedPixelShaderSetting m_camera_position_pixel{ "cameraPosition" }; CachedVertexShaderSetting m_camera_position_vertex{ "cameraPosition" }; - CachedPixelShaderSetting m_texture0{"texture0"}; - CachedPixelShaderSetting m_texture1{"texture1"}; - CachedPixelShaderSetting m_texture2{"texture2"}; - CachedPixelShaderSetting m_texture3{"texture3"}; CachedVertexShaderSetting m_texel_size0_vertex{"texelSize0"}; CachedPixelShaderSetting m_texel_size0_pixel{"texelSize0"}; v2f m_texel_size0; @@ -298,16 +294,6 @@ public: m_camera_position_pixel.set(camera_position, services); m_camera_position_pixel.set(camera_position, services); - SamplerLayer_t tex_id; - tex_id = 0; - m_texture0.set(&tex_id, services); - tex_id = 1; - m_texture1.set(&tex_id, services); - tex_id = 2; - m_texture2.set(&tex_id, services); - tex_id = 3; - m_texture3.set(&tex_id, services); - m_texel_size0_vertex.set(m_texel_size0, services); m_texel_size0_pixel.set(m_texel_size0, services); @@ -423,6 +409,90 @@ public: } }; +class NodeShaderConstantSetter : public IShaderConstantSetter +{ +public: + NodeShaderConstantSetter() = default; + ~NodeShaderConstantSetter() = default; + + void onGenerate(const std::string &name, ShaderConstants &constants) override + { + if (constants.find("DRAWTYPE") == constants.end()) + return; // not a node shader + [[maybe_unused]] const auto drawtype = + static_cast(std::get(constants["DRAWTYPE"])); + [[maybe_unused]] const auto material_type = + static_cast(std::get(constants["MATERIAL_TYPE"])); + +#define PROVIDE(constant) constants[ #constant ] = (int)constant + + PROVIDE(NDT_NORMAL); + PROVIDE(NDT_AIRLIKE); + PROVIDE(NDT_LIQUID); + PROVIDE(NDT_FLOWINGLIQUID); + PROVIDE(NDT_GLASSLIKE); + PROVIDE(NDT_ALLFACES); + PROVIDE(NDT_ALLFACES_OPTIONAL); + PROVIDE(NDT_TORCHLIKE); + PROVIDE(NDT_SIGNLIKE); + PROVIDE(NDT_PLANTLIKE); + PROVIDE(NDT_FENCELIKE); + PROVIDE(NDT_RAILLIKE); + PROVIDE(NDT_NODEBOX); + PROVIDE(NDT_GLASSLIKE_FRAMED); + PROVIDE(NDT_FIRELIKE); + PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL); + PROVIDE(NDT_PLANTLIKE_ROOTED); + + PROVIDE(TILE_MATERIAL_BASIC); + PROVIDE(TILE_MATERIAL_ALPHA); + PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT); + PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE); + PROVIDE(TILE_MATERIAL_WAVING_LEAVES); + PROVIDE(TILE_MATERIAL_WAVING_PLANTS); + PROVIDE(TILE_MATERIAL_OPAQUE); + PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC); + PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT); + PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE); + PROVIDE(TILE_MATERIAL_PLAIN); + PROVIDE(TILE_MATERIAL_PLAIN_ALPHA); + +#undef PROVIDE + + bool enable_waving_water = g_settings->getBool("enable_waving_water"); + constants["ENABLE_WAVING_WATER"] = enable_waving_water ? 1 : 0; + if (enable_waving_water) { + constants["WATER_WAVE_HEIGHT"] = g_settings->getFloat("water_wave_height"); + constants["WATER_WAVE_LENGTH"] = g_settings->getFloat("water_wave_length"); + constants["WATER_WAVE_SPEED"] = g_settings->getFloat("water_wave_speed"); + } + switch (material_type) { + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: + case TILE_MATERIAL_WAVING_LIQUID_BASIC: + constants["MATERIAL_WAVING_LIQUID"] = 1; + break; + default: + constants["MATERIAL_WAVING_LIQUID"] = 0; + break; + } + switch (material_type) { + case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: + case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: + case TILE_MATERIAL_WAVING_LIQUID_BASIC: + case TILE_MATERIAL_LIQUID_TRANSPARENT: + constants["MATERIAL_WATER_REFLECTIONS"] = 1; + break; + default: + constants["MATERIAL_WATER_REFLECTIONS"] = 0; + break; + } + + constants["ENABLE_WAVING_LEAVES"] = g_settings->getBool("enable_waving_leaves") ? 1 : 0; + constants["ENABLE_WAVING_PLANTS"] = g_settings->getBool("enable_waving_plants") ? 1 : 0; + } +}; + /**************************************************************************** ****************************************************************************/ @@ -837,11 +907,6 @@ Game::Game() : } - -/**************************************************************************** - MinetestApp Public - ****************************************************************************/ - Game::~Game() { delete client; @@ -1289,6 +1354,8 @@ bool Game::createClient(const GameStartData &start_data) return false; } + shader_src->addShaderConstantSetter(new NodeShaderConstantSetter()); + auto *scsf = new GameGlobalShaderUniformSetterFactory(client); shader_src->addShaderUniformSetterFactory(scsf); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index 643c381c0..e2985e66b 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -187,8 +187,7 @@ public: /* - MainShaderConstantSetter: Set some random general constants - NodeShaderConstantSetter: Set constants for node rendering + MainShaderConstantSetter: Sets some random general constants */ class MainShaderConstantSetter : public IShaderConstantSetter @@ -250,96 +249,14 @@ public: }; -class NodeShaderConstantSetter : public IShaderConstantSetter -{ -public: - NodeShaderConstantSetter() = default; - ~NodeShaderConstantSetter() = default; - - void onGenerate(const std::string &name, ShaderConstants &constants) override - { - if (constants.find("DRAWTYPE") == constants.end()) - return; // not a node shader - [[maybe_unused]] const auto drawtype = - static_cast(std::get(constants["DRAWTYPE"])); - [[maybe_unused]] const auto material_type = - static_cast(std::get(constants["MATERIAL_TYPE"])); - -#define PROVIDE(constant) constants[ #constant ] = (int)constant - - PROVIDE(NDT_NORMAL); - PROVIDE(NDT_AIRLIKE); - PROVIDE(NDT_LIQUID); - PROVIDE(NDT_FLOWINGLIQUID); - PROVIDE(NDT_GLASSLIKE); - PROVIDE(NDT_ALLFACES); - PROVIDE(NDT_ALLFACES_OPTIONAL); - PROVIDE(NDT_TORCHLIKE); - PROVIDE(NDT_SIGNLIKE); - PROVIDE(NDT_PLANTLIKE); - PROVIDE(NDT_FENCELIKE); - PROVIDE(NDT_RAILLIKE); - PROVIDE(NDT_NODEBOX); - PROVIDE(NDT_GLASSLIKE_FRAMED); - PROVIDE(NDT_FIRELIKE); - PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL); - PROVIDE(NDT_PLANTLIKE_ROOTED); - - PROVIDE(TILE_MATERIAL_BASIC); - PROVIDE(TILE_MATERIAL_ALPHA); - PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT); - PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE); - PROVIDE(TILE_MATERIAL_WAVING_LEAVES); - PROVIDE(TILE_MATERIAL_WAVING_PLANTS); - PROVIDE(TILE_MATERIAL_OPAQUE); - PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC); - PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT); - PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE); - PROVIDE(TILE_MATERIAL_PLAIN); - PROVIDE(TILE_MATERIAL_PLAIN_ALPHA); - -#undef PROVIDE - - bool enable_waving_water = g_settings->getBool("enable_waving_water"); - constants["ENABLE_WAVING_WATER"] = enable_waving_water ? 1 : 0; - if (enable_waving_water) { - constants["WATER_WAVE_HEIGHT"] = g_settings->getFloat("water_wave_height"); - constants["WATER_WAVE_LENGTH"] = g_settings->getFloat("water_wave_length"); - constants["WATER_WAVE_SPEED"] = g_settings->getFloat("water_wave_speed"); - } - switch (material_type) { - case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: - case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: - case TILE_MATERIAL_WAVING_LIQUID_BASIC: - constants["MATERIAL_WAVING_LIQUID"] = 1; - break; - default: - constants["MATERIAL_WAVING_LIQUID"] = 0; - break; - } - switch (material_type) { - case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT: - case TILE_MATERIAL_WAVING_LIQUID_OPAQUE: - case TILE_MATERIAL_WAVING_LIQUID_BASIC: - case TILE_MATERIAL_LIQUID_TRANSPARENT: - constants["MATERIAL_WATER_REFLECTIONS"] = 1; - break; - default: - constants["MATERIAL_WATER_REFLECTIONS"] = 0; - break; - } - - constants["ENABLE_WAVING_LEAVES"] = g_settings->getBool("enable_waving_leaves") ? 1 : 0; - constants["ENABLE_WAVING_PLANTS"] = g_settings->getBool("enable_waving_plants") ? 1 : 0; - } -}; - /* MainShaderUniformSetter: Set basic uniforms required for almost everything */ class MainShaderUniformSetter : public IShaderUniformSetter { + using SamplerLayer_t = s32; + CachedVertexShaderSetting m_world_view_proj{"mWorldViewProj"}; CachedVertexShaderSetting m_world{"mWorld"}; @@ -348,6 +265,11 @@ class MainShaderUniformSetter : public IShaderUniformSetter // Texture matrix CachedVertexShaderSetting m_texture{"mTexture"}; + CachedPixelShaderSetting m_texture0{"texture0"}; + CachedPixelShaderSetting m_texture1{"texture1"}; + CachedPixelShaderSetting m_texture2{"texture2"}; + CachedPixelShaderSetting m_texture3{"texture3"}; + // commonly used way to pass material color to shader video::SColor m_material_color; CachedPixelShaderSetting m_material_color_setting{"materialColor"}; @@ -385,6 +307,16 @@ public: m_texture.set(texture, services); } + SamplerLayer_t tex_id; + tex_id = 0; + m_texture0.set(&tex_id, services); + tex_id = 1; + m_texture1.set(&tex_id, services); + tex_id = 2; + m_texture2.set(&tex_id, services); + tex_id = 3; + m_texture3.set(&tex_id, services); + video::SColorf colorf(m_material_color); m_material_color_setting.set(colorf, services); } @@ -508,7 +440,6 @@ ShaderSource::ShaderSource() // Add global stuff addShaderConstantSetter(new MainShaderConstantSetter()); - addShaderConstantSetter(new NodeShaderConstantSetter()); addShaderUniformSetterFactory(new MainShaderUniformSetterFactory()); } diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 1297bf175..17260e21d 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -514,6 +514,9 @@ void ShadowRenderer::mixShadowsQuad() * Shaders system with custom IShaderConstantSetCallBack without messing up the * code too much. If anyone knows how to integrate this with the standard MT * shaders, please feel free to change it. + * + * TODO: as of now (2025) it should be possible to hook these up to the normal + * shader system. */ void ShadowRenderer::createShaders() From c0e42c65881cb26dbbcdc8206a736fd407beae51 Mon Sep 17 00:00:00 2001 From: Linn16 Date: Mon, 21 Apr 2025 12:32:58 +0200 Subject: [PATCH 154/284] Use map_compression_level_disk from minetest.conf for --recompress (#16037) --- src/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d62ee1f21..bf01db71b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1280,6 +1280,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting u64 last_update_time = 0; bool &kill = *porting::signal_handler_killstatus(); const u8 serialize_as_ver = SER_FMT_VER_HIGHEST_WRITE; + const s16 map_compression_level = rangelim(g_settings->getS16("map_compression_level_disk"), -1, 9); // This is ok because the server doesn't actually run std::vector blocks; @@ -1307,7 +1308,7 @@ static bool recompress_map_database(const GameParams &game_params, const Setting oss.str(""); oss.clear(); writeU8(oss, serialize_as_ver); - mb.serialize(oss, serialize_as_ver, true, -1); + mb.serialize(oss, serialize_as_ver, true, map_compression_level); } db->saveBlock(*it, oss.str()); From 5c6e4d35b0195e97fcb3dbfdd4b20df6dcbad13d Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Mon, 21 Apr 2025 12:33:19 +0200 Subject: [PATCH 155/284] Client: protect against circular attachments (#16038) The server already includes such check. There must be a desync issue that causes an ID mismatch, resulting in client crashes. Any such crash must be prevented. --- src/client/content_cao.cpp | 26 ++++++++++++++++++++++++++ src/server/unit_sao.cpp | 6 ++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 02e980cab..e0ac5fff0 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -411,6 +411,32 @@ void GenericCAO::setChildrenVisible(bool toset) void GenericCAO::setAttachment(object_t parent_id, const std::string &bone, v3f position, v3f rotation, bool force_visible) { + // Do checks to avoid circular references + // See similar check in `UnitSAO::setAttachment` (but with different types). + { + auto *obj = m_env->getActiveObject(parent_id); + if (obj == this) { + assert(false); + return; + } + bool problem = false; + if (obj) { + // The chain of wanted parent must not refer or contain "this" + for (obj = obj->getParent(); obj; obj = obj->getParent()) { + if (obj == this) { + problem = true; + break; + } + } + } + if (problem) { + warningstream << "Network or mod bug: " + << "Attempted to attach object " << m_id << " to parent " + << parent_id << " but former is an (in)direct parent of latter." << std::endl; + return; + } + } + const auto old_parent = m_attachment_parent_id; m_attachment_parent_id = parent_id; m_attachment_bone = bone; diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index d7fff69bf..35c92063d 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -128,8 +128,9 @@ void UnitSAO::setAttachment(const object_t new_parent, const std::string &bone, }; // Do checks to avoid circular references + // See similar check in `GenericCAO::setAttachment` (but with different types). { - auto *obj = new_parent ? m_env->getActiveObject(new_parent) : nullptr; + auto *obj = m_env->getActiveObject(new_parent); if (obj == this) { assert(false); return; @@ -145,7 +146,8 @@ void UnitSAO::setAttachment(const object_t new_parent, const std::string &bone, } } if (problem) { - warningstream << "Mod bug: Attempted to attach object " << m_id << " to parent " + warningstream << "Mod bug: " + << "Attempted to attach object " << m_id << " to parent " << new_parent << " but former is an (in)direct parent of latter." << std::endl; return; } From 0cf1c47f6c7fe4fd507e97c1dc9462cfe0a81d3c Mon Sep 17 00:00:00 2001 From: grorp Date: Mon, 21 Apr 2025 06:33:41 -0400 Subject: [PATCH 156/284] Fix scrollbar on ContentDB grid by adding an area label (#16042) Co-authored-by: rubenwardy --- builtin/mainmenu/content/dlg_contentdb.lua | 23 ++-- doc/lua_api.md | 15 +++ src/gui/guiFormSpecMenu.cpp | 128 ++++++++++++--------- src/network/networkprotocol.cpp | 2 +- 4 files changed, 108 insertions(+), 60 deletions(-) diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua index 872fab113..7f389135b 100644 --- a/builtin/mainmenu/content/dlg_contentdb.lua +++ b/builtin/mainmenu/content/dlg_contentdb.lua @@ -310,9 +310,17 @@ local function get_formspec(dlgdata) }) local img_w = cell_h * 3 / 2 + -- Use as much of the available space as possible (so no padding on the + -- right/bottom), but don't quite allow the text to touch the border. + local text_w = cell_w - img_w - 0.25 - 0.025 + local text_h = cell_h - 0.25 - 0.025 + local start_idx = (cur_page - 1) * num_per_page + 1 for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do local package = contentdb.packages[i] + local text = core.colorize(mt_color_green, package.title) .. + core.colorize("#BFBFBF", " by " .. package.author) .. "\n" .. + package.short_description table.insert_all(formspec, { "container[", @@ -327,13 +335,14 @@ local function get_formspec(dlgdata) "image[0,0;", img_w, ",", cell_h, ";", core.formspec_escape(get_screenshot(package, package.thumbnail, 2)), "]", - "label[", img_w + 0.25 + 0.05, ",0.5;", - core.formspec_escape( - core.colorize(mt_color_green, package.title) .. - core.colorize("#BFBFBF", " by " .. package.author)), "]", + "label[", img_w + 0.25, ",0.25;", text_w, ",", text_h, ";", + core.formspec_escape(text), "]", - "textarea[", img_w + 0.25, ",0.75;", cell_w - img_w - 0.25, ",", cell_h - 0.75, ";;;", - core.formspec_escape(package.short_description), "]", + -- Add a tooltip in case the label overflows and the short description is cut off. + "tooltip[", img_w + 0.25, ",0.25;", text_w, ",", text_h, ";", + -- Text in tooltips doesn't wrap automatically, so we do it manually to + -- avoid everything being one long line. + core.formspec_escape(core.wrap_text(package.short_description, 80)), "]", "style[view_", i, ";border=false]", "style[view_", i, ":hovered;bgimg=", core.formspec_escape(defaulttexturedir .. "button_hover_semitrans.png"), "]", @@ -349,7 +358,7 @@ local function get_formspec(dlgdata) end table.insert_all(formspec, { - "container[", cell_w - 0.625,",", 0.25, "]", + "container[", cell_w - 0.625,",", 0.125, "]", }) if package.downloading then diff --git a/doc/lua_api.md b/doc/lua_api.md index 31eb35dd2..6943dec16 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -2842,6 +2842,9 @@ Version History * Add field_enter_after_edit[] (experimental) * Formspec version 8 (5.10.0) * scroll_container[]: content padding parameter +* Formspec version 9 (5.12.0) + * Add allow_close[] + * label[]: Add "area label" variant Elements -------- @@ -3154,9 +3157,11 @@ Elements ### `textarea[,;,;; - net.minetest.minetest.desktop + org.luanti.luanti.desktop https://www.luanti.org/media/gallery/1.jpg From dca88be81d23bcbd9e947700fc6c78afe011f1f1 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 18 May 2025 12:13:33 +0200 Subject: [PATCH 253/284] Remove PrefersNonDefaultGPU from desktop file (#16095) --- misc/AppImageBuilder.yml | 2 -- misc/org.luanti.luanti.desktop | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/misc/AppImageBuilder.yml b/misc/AppImageBuilder.yml index 4862e58f1..b69b88a17 100644 --- a/misc/AppImageBuilder.yml +++ b/misc/AppImageBuilder.yml @@ -56,5 +56,3 @@ script: | # Is a backup icon location in case mkdir -p AppDir/usr/share/luanti/misc cp AppDir/usr/share/icons/hicolor/128x128/apps/luanti.png AppDir/usr/share/luanti/misc/luanti-xorg-icon-128.png - # Validation issues - sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/org.luanti.luanti.desktop diff --git a/misc/org.luanti.luanti.desktop b/misc/org.luanti.luanti.desktop index 325bd59d6..eab76aabb 100644 --- a/misc/org.luanti.luanti.desktop +++ b/misc/org.luanti.luanti.desktop @@ -7,7 +7,7 @@ Comment[fr]=Plate-forme de jeu multijoueurs à base de blocs Exec=luanti Icon=luanti Terminal=false -PrefersNonDefaultGPU=true +# Note: don't add PrefersNonDefaultGPU here, see #16095 Type=Application Categories=Game;Simulation; StartupNotify=false From 8c8b7cb251d85baf4a55c08dcd0545bc17d7bdc6 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 18 May 2025 12:13:48 +0200 Subject: [PATCH 254/284] Clean up menus properly on client exit (#16150) --- src/client/game.cpp | 11 +++++------ src/client/game_formspec.cpp | 5 +++-- src/client/game_formspec.h | 3 ++- src/gui/mainmenumanager.h | 2 ++ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/client/game.cpp b/src/client/game.cpp index f340f98e4..2c9c4fb77 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1114,8 +1114,12 @@ void Game::run() void Game::shutdown() { - // Clear text when exiting. + // Delete text and menus first m_game_ui->clearText(); + m_game_formspec.reset(); + while (g_menumgr.menuCount() > 0) { + g_menumgr.deleteFront(); + } if (g_touchcontrols) g_touchcontrols->hide(); @@ -1126,11 +1130,6 @@ void Game::shutdown() sky.reset(); - /* cleanup menus */ - while (g_menumgr.menuCount() > 0) { - g_menumgr.deleteFront(); - } - // only if the shutdown progress bar isn't shown yet if (m_shutdown_progress == 0.0f) showOverlayMessage(N_("Shutting down..."), 0, 0); diff --git a/src/client/game_formspec.cpp b/src/client/game_formspec.cpp index 3d46ddab5..dc51247b0 100644 --- a/src/client/game_formspec.cpp +++ b/src/client/game_formspec.cpp @@ -217,10 +217,11 @@ void GameFormSpec::deleteFormspec() } } -GameFormSpec::~GameFormSpec() { +void GameFormSpec::reset() +{ if (m_formspec) m_formspec->quitMenu(); - this->deleteFormspec(); + deleteFormspec(); } bool GameFormSpec::handleEmptyFormspec(const std::string &formspec, const std::string &formname) diff --git a/src/client/game_formspec.h b/src/client/game_formspec.h index 6dff32e50..980dac47f 100644 --- a/src/client/game_formspec.h +++ b/src/client/game_formspec.h @@ -26,7 +26,7 @@ struct GameFormSpec { void init(Client *client, RenderingEngine *rendering_engine, InputHandler *input); - ~GameFormSpec(); + ~GameFormSpec() { reset(); } void showFormSpec(const std::string &formspec, const std::string &formname); void showCSMFormSpec(const std::string &formspec, const std::string &formname); @@ -43,6 +43,7 @@ struct GameFormSpec void disableDebugView(); bool handleCallbacks(); + void reset(); #ifdef __ANDROID__ // Returns false if no formspec open diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 3bc8f7daa..553d6ffce 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -59,6 +59,8 @@ public: if(!m_stack.empty()) { m_stack.back()->setVisible(true); guienv->setFocus(m_stack.back()); + } else { + guienv->removeFocus(menu); } } From 554dd5ddf4b800a9a891f29efeb522f8c68807c4 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 18 May 2025 12:13:57 +0200 Subject: [PATCH 255/284] Update credits for 5.12.0 (#16142) --- builtin/mainmenu/credits.json | 15 +++++++-------- util/gather_git_credits.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/builtin/mainmenu/credits.json b/builtin/mainmenu/credits.json index bd4f0591f..61e53d3ba 100644 --- a/builtin/mainmenu/credits.json +++ b/builtin/mainmenu/credits.json @@ -47,22 +47,20 @@ ], "#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py", "contributors": [ - "JosiahWI", "Erich Schubert", "wrrrzr", - "1F616EMO", - "red-001 ", - "veprogames", - "paradust7", - "AFCMS", "siliconsniffer", - "Wuzzy", - "Zemtzov7" + "JosiahWI", + "veprogames", + "Miguel P.L", + "AFCMS" ], "previous_contributors": [ "Ælla Chiana Moskopp (erle) [Logo]", "numzero", + "red-001 ", "Giuseppe Bilotta", + "HybridDog", "ClobberXD", "Dániel Juhász (juhdanad) ", "MirceaKitsune ", @@ -75,6 +73,7 @@ "stujones11", "Rogier ", "Gregory Currie (gregorycu)", + "paradust7", "JacobF", "Jeija " ] diff --git a/util/gather_git_credits.py b/util/gather_git_credits.py index b02275644..c415e4263 100755 --- a/util/gather_git_credits.py +++ b/util/gather_git_credits.py @@ -6,7 +6,7 @@ from collections import defaultdict codefiles = r"(\.[ch](pp)?|\.lua|\.md|\.cmake|\.java|\.gradle|Makefile|CMakeLists\.txt)$" # two minor versions back, for "Active Contributors" -REVS_ACTIVE = "5.9.0..HEAD" +REVS_ACTIVE = "5.10.0..HEAD" # all time, for "Previous Contributors" REVS_PREVIOUS = "HEAD" From 56ecf6d332ac3feab562b4f60bf38b0d677f594f Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 18 May 2025 20:41:42 +0200 Subject: [PATCH 256/284] Mainmenu: Fix error after ESC in dialog windows (#16130) The error was caused by fd857374, where 'MenuQuit' was processed after 'try_quit'. This commit fixes the error by moving the special 'MenuQuit' handling to Lua. --- builtin/fstk/ui.lua | 4 ++++ src/gui/guiFormSpecMenu.cpp | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua index 47b9d086c..44d2ab3d8 100644 --- a/builtin/fstk/ui.lua +++ b/builtin/fstk/ui.lua @@ -166,6 +166,10 @@ end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- core.button_handler = function(fields) + if fields["try_quit"] and not fields["key_enter"] then + core.event_handler("MenuQuit") + return + end if fields["btn_reconnect_yes"] then gamedata.reconnect_requested = false gamedata.errormessage = nil diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index cee066131..4b9601430 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -4053,7 +4053,6 @@ void GUIFormSpecMenu::tryClose() quitMenu(); } else { acceptInput(quit_mode_try); - m_text_dst->gotText(L"MenuQuit"); } } From 4700939949fcd4334475a7fd151c4776fe566629 Mon Sep 17 00:00:00 2001 From: Daniel Cristian Date: Sun, 18 May 2025 16:59:57 -0300 Subject: [PATCH 257/284] Fix uninitialized variable warning in generate_srp_verifier_and_salt --- src/util/auth.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/auth.cpp b/src/util/auth.cpp index 040d15bf9..5116e4be7 100644 --- a/src/util/auth.cpp +++ b/src/util/auth.cpp @@ -67,9 +67,9 @@ void generate_srp_verifier_and_salt(const std::string &name, std::string *salt) { char *bytes_v = nullptr; - size_t verifier_len; + size_t verifier_len = 0; char *salt_ptr = nullptr; - size_t salt_len; + size_t salt_len = 0; gen_srp_v(name, password, &salt_ptr, &salt_len, &bytes_v, &verifier_len); *verifier = std::string(bytes_v, verifier_len); *salt = std::string(salt_ptr, salt_len); From 30e33d71cc761ebb77ac5716a75de61677a6627b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathana=C3=ABlle=20Courant?= Date: Mon, 19 May 2025 10:29:37 +0200 Subject: [PATCH 258/284] Main menu: Fix ContentDB aliases for games having the '_game' suffix (#16157) --- builtin/mainmenu/content/contentdb.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua index be148841a..fbd94376d 100644 --- a/builtin/mainmenu/content/contentdb.lua +++ b/builtin/mainmenu/content/contentdb.lua @@ -170,14 +170,16 @@ function contentdb.get_package_by_id(id) end -function contentdb.calculate_package_id(type, author, name) - local id = author:lower() .. "/" +local function strip_game_suffix(type, name) if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then - id = id .. name:sub(1, #name - 5) + return name:sub(1, #name - 5) else - id = id .. name + return name end - return id +end + +function contentdb.calculate_package_id(type, author, name) + return author:lower() .. "/" .. strip_game_suffix(type, name) end @@ -427,7 +429,7 @@ function contentdb.set_packages_from_api(packages) -- We currently don't support name changing local suffix = "/" .. package.name if alias:sub(-#suffix) == suffix then - contentdb.aliases[alias:lower()] = package.id + contentdb.aliases[strip_game_suffix(packages.type, alias:lower())] = package.id end end end From 7ac5502fdf23263ad6b424c2da6c18ffff122df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 20 May 2025 18:37:33 +0200 Subject: [PATCH 259/284] Fix handling of skinned meshes for nodes Second try after the revert in 8a28339 due to an unexpected regression. - Rigidly animated models (e.g. the glTF frog node) were not working correctly, since cloning the mesh ignored the transformation matrices. Note that scaling the mesh needs to occur *after* transforming the vertices. - Visual scale did not apply to skinned models, as resetting the animation overwrote scaled vertex data with static positions & normals. For backwards compatibility, we now apply a 10x scale to static, non-glTF models. We now do scale static meshes, as the bug that caused meshes not to be scaled was limited to skeletally animated meshes, hence we ought not to reproduce it for skinned meshes that do not take advantage of skeletal animations (e.g. current MTG doors). However, glTF models (e.g. Wuzzy's eyeballs) up until recently were always affected due to technical reasons (using skeletal animation for rigid animation). Thus, to preserve behavior, we: 1. Do not apply 10x scale to glTF models. 2. Apply 10x scale to obj models. 3. Apply 10x scale to static x or b3d models, but not to animated ones. See also: #16141 --- doc/lua_api.md | 9 ++-- irr/include/SkinnedMesh.h | 21 ++++++-- irr/src/CB3DMeshFileLoader.cpp | 2 +- irr/src/CGLTFMeshFileLoader.cpp | 3 +- irr/src/CSceneManager.cpp | 2 +- irr/src/CXMeshFileLoader.cpp | 2 +- src/client/content_mapblock.cpp | 2 +- src/client/mesh.cpp | 95 +++++++++++++++++---------------- src/client/mesh.h | 6 +-- src/client/wieldmesh.cpp | 4 +- src/nodedef.cpp | 45 ++++++++++++---- src/nodedef.h | 2 +- 12 files changed, 118 insertions(+), 75 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index f028a14d8..b604b317c 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -10172,9 +10172,12 @@ Used by `core.register_node`. mesh = "", -- File name of mesh when using "mesh" drawtype -- The center of the node is the model origin. - -- For legacy reasons, models in OBJ format use a scale of 1 node = 1 unit; - -- all other model file formats use a scale of 1 node = 10 units, - -- consistent with the scale used for entities. + -- For legacy reasons, this uses a different scale depending on the mesh: + -- 1. For glTF models: 10 units = 1 node (consistent with the scale for entities). + -- 2. For obj models: 1 unit = 1 node. + -- 3. For b3d and x models: 1 unit = 1 node if static, otherwise 10 units = 1 node. + -- Using static glTF or obj models is recommended. + -- You can use the `visual_scale` multiplier to achieve the expected scale. selection_box = { -- see [Node boxes] for possibilities diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h index c9ea99365..a527db76e 100644 --- a/irr/include/SkinnedMesh.h +++ b/irr/include/SkinnedMesh.h @@ -26,12 +26,21 @@ class ISceneManager; class SkinnedMesh : public IAnimatedMesh { public: + + enum class SourceFormat { + B3D, + X, + GLTF, + OTHER, + }; + //! constructor - SkinnedMesh() : + SkinnedMesh(SourceFormat src_format) : EndFrame(0.f), FramesPerSecond(25.f), LastAnimatedFrame(-1), SkinnedLastFrame(false), HasAnimation(false), PreparedForSkinning(false), - AnimateNormals(true), HardwareSkinning(false) + AnimateNormals(true), HardwareSkinning(false), + SrcFormat(src_format) { SkinningBuffers = &LocalBuffers; } @@ -39,6 +48,10 @@ public: //! destructor virtual ~SkinnedMesh(); + //! The source (file) format the mesh was loaded from. + //! Important for legacy reasons pertaining to different mesh loader behavior. + SourceFormat getSourceFormat() const { return SrcFormat; } + //! If the duration is 0, it is a static (=non animated) mesh. f32 getMaxFrameNumber() const override; @@ -382,12 +395,14 @@ protected: bool PreparedForSkinning; bool AnimateNormals; bool HardwareSkinning; + + SourceFormat SrcFormat; }; // Interface for mesh loaders class SkinnedMeshBuilder : public SkinnedMesh { public: - SkinnedMeshBuilder() : SkinnedMesh() {} + SkinnedMeshBuilder(SourceFormat src_format) : SkinnedMesh(src_format) {} //! loaders should call this after populating the mesh // returns *this, so do not try to drop the mesh builder instance diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index e99bd2eed..51342f451 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -48,7 +48,7 @@ IAnimatedMesh *CB3DMeshFileLoader::createMesh(io::IReadFile *file) return 0; B3DFile = file; - AnimatedMesh = new scene::SkinnedMeshBuilder(); + AnimatedMesh = new scene::SkinnedMeshBuilder(SkinnedMesh::SourceFormat::B3D); ShowWarning = true; // If true a warning is issued if too many textures are used VerticesStart = 0; diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index f70f6692b..3f2096f40 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -347,7 +347,8 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file) const char *filename = file->getFileName().c_str(); try { tiniergltf::GlTF model = parseGLTF(file); - irr_ptr mesh(new SkinnedMeshBuilder()); + irr_ptr mesh(new SkinnedMeshBuilder( + SkinnedMesh::SourceFormat::GLTF)); MeshExtractor extractor(std::move(model), mesh.get()); try { extractor.load(); diff --git a/irr/src/CSceneManager.cpp b/irr/src/CSceneManager.cpp index b5a310287..05f8de71a 100644 --- a/irr/src/CSceneManager.cpp +++ b/irr/src/CSceneManager.cpp @@ -762,7 +762,7 @@ ISceneManager *CSceneManager::createNewSceneManager(bool cloneContent) //! Get a skinned mesh, which is not available as header-only code SkinnedMesh *CSceneManager::createSkinnedMesh() { - return new SkinnedMesh(); + return new SkinnedMesh(SkinnedMesh::SourceFormat::OTHER); } // creates a scenemanager diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index ed8c18350..d93502d6b 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -54,7 +54,7 @@ IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file) u32 time = os::Timer::getRealTime(); #endif - AnimatedMesh = new SkinnedMeshBuilder(); + AnimatedMesh = new SkinnedMeshBuilder(SkinnedMesh::SourceFormat::X); SkinnedMesh *res = nullptr; if (load(file)) { diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index f5b528287..3edba95e3 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -1676,7 +1676,7 @@ void MapblockMeshGenerator::drawMeshNode() if (cur_node.f->mesh_ptr) { // clone and rotate mesh - mesh = cloneMesh(cur_node.f->mesh_ptr); + mesh = cloneStaticMesh(cur_node.f->mesh_ptr); bool modified = true; if (facedir) rotateMeshBy6dFacedir(mesh, facedir); diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp index a2c0ae327..808dcdd18 100644 --- a/src/client/mesh.cpp +++ b/src/client/mesh.cpp @@ -3,6 +3,8 @@ // Copyright (C) 2010-2013 celeron55, Perttu Ahola #include "mesh.h" +#include "IMeshBuffer.h" +#include "SSkinMeshBuffer.h" #include "debug.h" #include "log.h" #include @@ -102,6 +104,21 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale) return anim_mesh; } +template +inline static void transformMeshBuffer(scene::IMeshBuffer *buf, + const F &transform_vertex) +{ + const u32 stride = getVertexPitchFromType(buf->getVertexType()); + u32 vertex_count = buf->getVertexCount(); + u8 *vertices = (u8 *)buf->getVertices(); + for (u32 i = 0; i < vertex_count; i++) { + auto *vertex = (video::S3DVertex *)(vertices + i * stride); + transform_vertex(vertex); + } + buf->setDirty(scene::EBT_VERTEX); + buf->recalculateBoundingBox(); +} + void scaleMesh(scene::IMesh *mesh, v3f scale) { if (mesh == NULL) @@ -112,14 +129,9 @@ void scaleMesh(scene::IMesh *mesh, v3f scale) u32 mc = mesh->getMeshBufferCount(); for (u32 j = 0; j < mc; j++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *)(vertices + i * stride))->Pos *= scale; - - buf->setDirty(scene::EBT_VERTEX); - buf->recalculateBoundingBox(); + transformMeshBuffer(buf, [scale](video::S3DVertex *vertex) { + vertex->Pos *= scale; + }); // calculate total bounding box if (j == 0) @@ -140,14 +152,9 @@ void translateMesh(scene::IMesh *mesh, v3f vec) u32 mc = mesh->getMeshBufferCount(); for (u32 j = 0; j < mc; j++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - const u32 stride = getVertexPitchFromType(buf->getVertexType()); - u32 vertex_count = buf->getVertexCount(); - u8 *vertices = (u8 *)buf->getVertices(); - for (u32 i = 0; i < vertex_count; i++) - ((video::S3DVertex *)(vertices + i * stride))->Pos += vec; - - buf->setDirty(scene::EBT_VERTEX); - buf->recalculateBoundingBox(); + transformMeshBuffer(buf, [vec](video::S3DVertex *vertex) { + vertex->Pos += vec; + }); // calculate total bounding box if (j == 0) @@ -330,44 +337,40 @@ bool checkMeshNormals(scene::IMesh *mesh) return true; } +template +static scene::IMeshBuffer *cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) +{ + auto *v = static_cast(mesh_buffer->getVertices()); + u16 *indices = mesh_buffer->getIndices(); + auto *cloned_buffer = new SMeshBufferType(); + cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, + mesh_buffer->getIndexCount()); + // Rigidly animated meshes may have transformation matrices that need to be applied + if (auto *sbuf = dynamic_cast(mesh_buffer)) { + transformMeshBuffer(cloned_buffer, [sbuf](video::S3DVertex *vertex) { + sbuf->Transformation.transformVect(vertex->Pos); + vertex->Normal = sbuf->Transformation.rotateAndScaleVect(vertex->Normal); + vertex->Normal.normalize(); + }); + } + return cloned_buffer; +} + scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer) { switch (mesh_buffer->getVertexType()) { - case video::EVT_STANDARD: { - 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()); - return cloned_buffer; + case video::EVT_STANDARD: + return cloneMeshBuffer(mesh_buffer); + case video::EVT_2TCOORDS: + return cloneMeshBuffer(mesh_buffer); + case video::EVT_TANGENTS: + return cloneMeshBuffer(mesh_buffer); } - case video::EVT_2TCOORDS: { - video::S3DVertex2TCoords *v = - (video::S3DVertex2TCoords *) mesh_buffer->getVertices(); - u16 *indices = mesh_buffer->getIndices(); - scene::SMeshBufferLightMap *cloned_buffer = - new scene::SMeshBufferLightMap(); - cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); - return cloned_buffer; - } - case video::EVT_TANGENTS: { - video::S3DVertexTangents *v = - (video::S3DVertexTangents *) mesh_buffer->getVertices(); - u16 *indices = mesh_buffer->getIndices(); - scene::SMeshBufferTangents *cloned_buffer = - new scene::SMeshBufferTangents(); - cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices, - mesh_buffer->getIndexCount()); - return cloned_buffer; - } - } - // This should not happen. sanity_check(false); return NULL; } -scene::SMesh* cloneMesh(scene::IMesh *src_mesh) +scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh) { scene::SMesh* dst_mesh = new scene::SMesh(); for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) { diff --git a/src/client/mesh.h b/src/client/mesh.h index d8eb6080e..53c54fc51 100644 --- a/src/client/mesh.h +++ b/src/client/mesh.h @@ -93,10 +93,8 @@ void rotateMeshYZby (scene::IMesh *mesh, f64 degrees); */ scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer); -/* - Clone the mesh. -*/ -scene::SMesh* cloneMesh(scene::IMesh *src_mesh); +/// Clone a mesh. For an animated mesh, this will clone the static pose. +scene::SMesh* cloneStaticMesh(scene::IMesh *src_mesh); /* Convert nodeboxes to mesh. Each tile goes into a different buffer. diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index baa72f1d9..bdd24a727 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -255,7 +255,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, dim = core::dimension2d(dim.Width, frame_height); } scene::IMesh *original = g_extrusion_mesh_cache->create(dim); - scene::SMesh *mesh = cloneMesh(original); + scene::SMesh *mesh = cloneStaticMesh(original); original->drop(); //set texture mesh->getMeshBuffer(0)->getMaterial().setTexture(0, @@ -639,7 +639,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, // get mesh core::dimension2d dim = texture->getSize(); scene::IMesh *original = g_extrusion_mesh_cache->create(dim); - scene::SMesh *mesh = cloneMesh(original); + scene::SMesh *mesh = cloneStaticMesh(original); original->drop(); //set texture diff --git a/src/nodedef.cpp b/src/nodedef.cpp index d4dc16a61..04f1959c1 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -4,6 +4,7 @@ #include "nodedef.h" +#include "SAnimatedMesh.h" #include "itemdef.h" #if CHECK_CLIENT_BUILD() #include "client/mesh.h" @@ -13,6 +14,7 @@ #include "client/texturesource.h" #include "client/tile.h" #include +#include #include #endif #include "log.h" @@ -959,23 +961,44 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc palette = tsrc->getPalette(palette_name); if (drawtype == NDT_MESH && !mesh.empty()) { - // Read the mesh and apply scale - mesh_ptr = client->getMesh(mesh); - if (mesh_ptr) { - v3f scale = v3f(BS) * visual_scale; - scaleMesh(mesh_ptr, scale); + // Note: By freshly reading, we get an unencumbered mesh. + if (scene::IMesh *src_mesh = client->getMesh(mesh)) { + bool apply_bs = false; + // For frame-animated meshes, always get the first frame, + // which holds a model for which we can eventually get the static pose. + while (auto *src_meshes = dynamic_cast(src_mesh)) { + src_mesh = src_meshes->getMesh(0.0f); + src_mesh->grab(); + src_meshes->drop(); + } + if (auto *skinned_mesh = dynamic_cast(src_mesh)) { + // Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS. + // See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329 + bool is_gltf = skinned_mesh->getSourceFormat() == + scene::SkinnedMesh::SourceFormat::GLTF; + apply_bs = skinned_mesh->isStatic() && !is_gltf; + // Nodes do not support mesh animation, so we clone the static pose. + // This simplifies working with the mesh: We can just scale the vertices + // as transformations have already been applied. + mesh_ptr = cloneStaticMesh(src_mesh); + src_mesh->drop(); + } else { + auto *static_mesh = dynamic_cast(src_mesh); + assert(static_mesh); + mesh_ptr = static_mesh; + // Compatibility: Apply BS scaling to static meshes (.obj). See #15811. + apply_bs = true; + } + scaleMesh(mesh_ptr, v3f((apply_bs ? BS : 1.0f) * visual_scale)); recalculateBoundingBox(mesh_ptr); if (!checkMeshNormals(mesh_ptr)) { + // TODO this should be done consistently when the mesh is loaded infostream << "ContentFeatures: recalculating normals for mesh " << mesh << std::endl; meshmanip->recalculateNormals(mesh_ptr, true, false); - } else { - // Animation is not supported, but we need to reset it to - // default state if it is animated. - // Note: recalculateNormals() also does this hence the else-block - if (mesh_ptr->getMeshType() == scene::EAMT_SKINNED) - ((scene::SkinnedMesh*) mesh_ptr)->resetAnimation(); } + } else { + mesh_ptr = nullptr; } } } diff --git a/src/nodedef.h b/src/nodedef.h index 71a61896b..967e3fcd4 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -342,7 +342,7 @@ struct ContentFeatures enum NodeDrawType drawtype; std::string mesh; #if CHECK_CLIENT_BUILD() - scene::IMesh *mesh_ptr; // mesh in case of mesh node + scene::SMesh *mesh_ptr; // mesh in case of mesh node video::SColor minimap_color; #endif float visual_scale; // Misc. scale parameter From 95695f1cd2bb6c5bb331c25adce0b381f24039ed Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 14 May 2025 23:49:26 +0200 Subject: [PATCH 260/284] Translated using Weblate (German) Currently translated at 99.8% (1528 of 1531 strings) --- po/de/luanti.po | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/po/de/luanti.po b/po/de/luanti.po index d8cba07fb..2f37c2ec7 100644 --- a/po/de/luanti.po +++ b/po/de/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: German (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-04-25 19:50+0000\n" -"Last-Translator: Wuzzy \n" +"PO-Revision-Date: 2025-05-16 10:31+0000\n" +"Last-Translator: sfan5 \n" "Language-Team: German \n" "Language: de\n" @@ -937,29 +937,27 @@ msgstr "Die Welt „$1“ löschen?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Aufgrund dessen könnte sich Ihre Tastenbelegung geändert haben." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Schauen Sie sich die Einstellungen oder die Dokumentation an:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Schließen" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Tastenbelegung" +msgstr "Geänderte Tastenbelegung" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Einstellungen" +msgstr "Einstellungen öffnen" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "Das Steuerungssystem wurde in Luanti 5.12.0 überarbeitet." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" @@ -4660,7 +4658,7 @@ msgid "" "Instrument global callback functions on registration.\n" "(anything you pass to a core.register_*() function)" msgstr "" -"Globale Rückruffunktionen bei ihrer Registrierung instrumentieren\n" +"Globale Rückruffunktionen bei ihrer Registrierung instrumentieren.\n" "(alles, was man einer Funktion wie core.register_*() übergibt)." #: src/settings_translation_file.cpp From 31e923e51a85c4f4aa1e4400ef2e57c5387d1ba0 Mon Sep 17 00:00:00 2001 From: Francesco Rossi Date: Thu, 15 May 2025 00:01:39 +0200 Subject: [PATCH 261/284] Translated using Weblate (Italian) Currently translated at 76.0% (1164 of 1531 strings) --- po/it/luanti.po | 783 ++++++++++++++++++++++-------------------------- 1 file changed, 366 insertions(+), 417 deletions(-) diff --git a/po/it/luanti.po b/po/it/luanti.po index 6377cca82..cbbbd78cc 100644 --- a/po/it/luanti.po +++ b/po/it/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: Italian (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2024-01-18 07:31+0000\n" -"Last-Translator: Filippo Alfieri \n" +"PO-Revision-Date: 2025-05-23 15:04+0000\n" +"Last-Translator: Francesco Rossi \n" "Language-Team: Italian \n" "Language: it\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.4-dev\n" +"X-Generator: Weblate 5.12-dev\n" #: builtin/client/chatcommands.lua msgid "Clear the out chat queue" @@ -85,16 +85,15 @@ msgstr "Esplora" #: builtin/common/settings/components.lua msgid "Conflicts with \"$1\"" -msgstr "" +msgstr "Va in conflitto con \"$1\"" #: builtin/common/settings/components.lua msgid "Edit" msgstr "Modifica" #: builtin/common/settings/components.lua -#, fuzzy msgid "Remove keybinding" -msgstr "Mappatura dei tasti." +msgstr "Rimuovi questa mappatura." #: builtin/common/settings/components.lua msgid "Select directory" @@ -219,7 +218,7 @@ msgstr "Accessibilità" #: builtin/common/settings/dlg_settings.lua msgid "Auto" -msgstr "" +msgstr "Automatico" #: builtin/common/settings/dlg_settings.lua #: builtin/mainmenu/content/dlg_contentdb.lua @@ -229,7 +228,7 @@ msgstr "Indietro" #: builtin/common/settings/dlg_settings.lua msgid "Buttons with crosshair" -msgstr "" +msgstr "Pulsanti e mirino" #: builtin/common/settings/dlg_settings.lua src/gui/touchscreenlayout.cpp #: src/settings_translation_file.cpp @@ -259,7 +258,7 @@ msgstr "Generale" #: builtin/common/settings/dlg_settings.lua msgid "Long tap" -msgstr "" +msgstr "Tocco prolungato" #: builtin/common/settings/dlg_settings.lua msgid "Movement" @@ -284,7 +283,7 @@ msgstr "Ricerca" #: builtin/common/settings/dlg_settings.lua msgid "Short tap" -msgstr "" +msgstr "Tocco rapido" #: builtin/common/settings/dlg_settings.lua msgid "Show advanced settings" @@ -301,7 +300,7 @@ msgstr "Tab" #: builtin/common/settings/dlg_settings.lua msgid "Tap with crosshair" -msgstr "" +msgstr "Tocco con mirino" #: builtin/common/settings/dlg_settings.lua #, fuzzy @@ -436,7 +435,7 @@ msgstr "Scaricando $1..." #: builtin/mainmenu/content/dlg_contentdb.lua msgid "All" -msgstr "" +msgstr "Tutto" #: builtin/mainmenu/content/dlg_contentdb.lua #, fuzzy @@ -449,7 +448,7 @@ msgstr "Scaricamento..." #: builtin/mainmenu/content/dlg_contentdb.lua msgid "Featured" -msgstr "" +msgstr "In evidenza" #: builtin/mainmenu/content/dlg_contentdb.lua msgid "Games" @@ -522,7 +521,7 @@ msgstr "Dipendenze:" #: builtin/mainmenu/content/dlg_install.lua msgid "Error getting dependencies for package $1" -msgstr "" +msgstr "Errore nell'ottenere le dipendenze per il pacchetto $1" #: builtin/mainmenu/content/dlg_install.lua msgid "Install" @@ -546,7 +545,7 @@ msgstr "Per favore, controlla che il gioco di base sia corretto." #: builtin/mainmenu/content/dlg_install.lua msgid "You need to install a game before you can install a mod" -msgstr "Devi installare un gioco prima di poter installare un mod" +msgstr "Devi installare un gioco prima di poter installare un modulo" #: builtin/mainmenu/content/dlg_overwrite.lua msgid "\"$1\" already exists. Would you like to overwrite it?" @@ -568,20 +567,19 @@ msgstr "Descrizione del Server" #: builtin/mainmenu/content/dlg_package.lua msgid "Donate" -msgstr "" +msgstr "Dona" #: builtin/mainmenu/content/dlg_package.lua msgid "Error loading package information" -msgstr "" +msgstr "Errore nel caricare le informazioni del pacchetto" #: builtin/mainmenu/content/dlg_package.lua -#, fuzzy msgid "Error loading reviews" -msgstr "Errore di creazione del client: %s" +msgstr "Errore nel caricare le recensioni" #: builtin/mainmenu/content/dlg_package.lua msgid "Forum Topic" -msgstr "" +msgstr "Discussione sul forum" #: builtin/mainmenu/content/dlg_package.lua #, fuzzy @@ -595,19 +593,19 @@ msgstr "Installa $1" #: builtin/mainmenu/content/dlg_package.lua msgid "Issue Tracker" -msgstr "" +msgstr "Segnalazioni" #: builtin/mainmenu/content/dlg_package.lua msgid "Reviews" -msgstr "" +msgstr "Recensioni" #: builtin/mainmenu/content/dlg_package.lua msgid "Source" -msgstr "" +msgstr "Sorgente" #: builtin/mainmenu/content/dlg_package.lua msgid "Translate" -msgstr "" +msgstr "Traduci" #: builtin/mainmenu/content/dlg_package.lua builtin/mainmenu/tab_content.lua msgid "Uninstall" @@ -624,7 +622,7 @@ msgstr "Visita il sito Web" #: builtin/mainmenu/content/dlg_package.lua msgid "by $1 — $2 downloads — +$3 / $4 / -$5" -msgstr "" +msgstr "di $1 — $2 scaricamenti — +$3 / $4 / -$5" #: builtin/mainmenu/content/pkgmgr.lua msgid "$1 (Enabled)" @@ -632,7 +630,7 @@ msgstr "$1 (Attivato)" #: builtin/mainmenu/content/pkgmgr.lua msgid "$1 mods" -msgstr "$1 mod" +msgstr "$1 moduli" #: builtin/mainmenu/content/pkgmgr.lua msgid "Failed to install $1 to $2" @@ -646,11 +644,11 @@ msgstr "" #: builtin/mainmenu/content/pkgmgr.lua msgid "Unable to find a valid mod, modpack, or game" -msgstr "Impossibile trovare un mod o un pacchetto mod validi" +msgstr "Impossibile trovare un modulo o un pacchetto moduli validi" #: builtin/mainmenu/content/pkgmgr.lua msgid "Unable to install a $1 as a $2" -msgstr "Impossibile installare una mod come un $1" +msgstr "Impossibile installare $1 come se fosse $2" #: builtin/mainmenu/content/pkgmgr.lua msgid "Unable to install a $1 as a texture pack" @@ -661,6 +659,8 @@ msgid "" "Players connected to\n" "$1" msgstr "" +"Giocanti connessɜ a\n" +"$1" #: builtin/mainmenu/dlg_config_world.lua msgid "(Enabled, has error)" @@ -676,7 +676,7 @@ msgstr "Disattiva tutto" #: builtin/mainmenu/dlg_config_world.lua msgid "Disable modpack" -msgstr "Disattiva pacchetto mod" +msgstr "Disattiva pacchetto moduli" #: builtin/mainmenu/dlg_config_world.lua msgid "Enable all" @@ -684,14 +684,14 @@ msgstr "Attiva tutto" #: builtin/mainmenu/dlg_config_world.lua msgid "Enable modpack" -msgstr "Attiva pacchetto mod" +msgstr "Attiva pacchetto moduli" #: builtin/mainmenu/dlg_config_world.lua msgid "" "Failed to enable mod \"$1\" as it contains disallowed characters. Only " "characters [a-z0-9_] are allowed." msgstr "" -"Impossibile abilitare la mod \"$1\" poiché contiene caratteri non " +"Impossibile abilitare il modulo \"$1\" poiché contiene caratteri non " "consentiti. Sono ammessi solo caratteri [a-z0-9_]." #: builtin/mainmenu/dlg_config_world.lua @@ -716,7 +716,7 @@ msgstr "Nessuna dipendenza necessaria" #: builtin/mainmenu/dlg_config_world.lua msgid "No modpack description provided." -msgstr "Non è stata fornita alcuna descrizione per il pacchetto mod." +msgstr "Non è stata fornita alcuna descrizione per il pacchetto moduli." #: builtin/mainmenu/dlg_config_world.lua msgid "No optional dependencies" @@ -776,7 +776,7 @@ msgstr "Decorazioni" #: builtin/mainmenu/dlg_create_world.lua msgid "Desert temples" -msgstr "" +msgstr "Templi del deserto" #: builtin/mainmenu/dlg_create_world.lua msgid "Development Test is meant for developers." @@ -947,20 +947,19 @@ msgstr "Eliminare il mondo \"$1\"?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Di conseguenza, la mappatura dei tuoi tasti potrebbe essere cambiata." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Dài un occhio alle impostazioni dei tasti o consulta la documentazione:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Chiudi" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Mappatura dei tasti." +msgstr "Mappatura dei tasti cambiata" #: builtin/mainmenu/dlg_rebind_keys.lua #, fuzzy @@ -970,6 +969,7 @@ msgstr "Impostazioni" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." msgstr "" +"Il sistema di gestione degli input è stato rivisitato nella 5.12.0 di Luanti." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" @@ -1038,14 +1038,14 @@ msgstr "Conferma" #: builtin/mainmenu/dlg_rename_modpack.lua msgid "Rename Modpack:" -msgstr "Rinomina il pacchetto mod:" +msgstr "Rinomina il pacchetto moduli:" #: builtin/mainmenu/dlg_rename_modpack.lua msgid "" "This modpack has an explicit name given in its modpack.conf which will " "override any renaming here." msgstr "" -"Questo pacchetto mod esplicita un nome fornito in modpack.conf che " +"Questo pacchetto moduli esplicita un nome fornito in modpack.conf che " "sovrascriverà ogni modifica del nome qui effettuata." #: builtin/mainmenu/dlg_server_list_mods.lua @@ -1055,15 +1055,15 @@ msgstr "Attiva tutto" #: builtin/mainmenu/dlg_server_list_mods.lua msgid "Group by prefix" -msgstr "" +msgstr "Raggruppa per prefisso" #: builtin/mainmenu/dlg_server_list_mods.lua msgid "The $1 server uses a game called $2 and the following mods:" -msgstr "" +msgstr "Il server $1 usa un gioco chiamato $2 e i seguenti moduli:" #: builtin/mainmenu/dlg_server_list_mods.lua msgid "The $1 server uses the following mods:" -msgstr "" +msgstr "Il server $1 usa i seguenti moduli:" #: builtin/mainmenu/dlg_version_info.lua msgid "A new $1 version is available" @@ -1136,7 +1136,7 @@ msgid "" "Opens the directory that contains user-provided worlds, games, mods,\n" "and texture packs in a file manager / explorer." msgstr "" -"Apre la cartella contenente mondi, giochi, mod e pacchetti\n" +"Apre la cartella contenente mondi, giochi, moduli e pacchetti\n" "texture forniti dall'utente in un gestore / visualizzatore di file ." #: builtin/mainmenu/tab_about.lua @@ -1228,16 +1228,16 @@ msgid "Install games from ContentDB" msgstr "Installa giochi da ContentDB" #: builtin/mainmenu/tab_local.lua -#, fuzzy msgid "Luanti doesn't come with a game by default." -msgstr "" -"Il gioco Minetest Game non è più installato per impostazione predefinita" +msgstr "Di base, Luanti non viene fornito con giochi già installati." #: builtin/mainmenu/tab_local.lua msgid "" "Luanti is a game-creation platform that allows you to play many different " "games." msgstr "" +"Luanti è una piattaforma di gioco che permette di giocare a tanti titoli " +"differenti." #: builtin/mainmenu/tab_local.lua msgid "New" @@ -1272,9 +1272,8 @@ msgid "Start Game" msgstr "Gioca" #: builtin/mainmenu/tab_local.lua -#, fuzzy msgid "You need to install a game before you can create a world." -msgstr "Devi installare un gioco prima di poter installare un mod" +msgstr "Devi installare un gioco prima di poter creare un mondo." #: builtin/mainmenu/tab_online.lua #, fuzzy @@ -1316,9 +1315,8 @@ msgid "Login" msgstr "Accedi" #: builtin/mainmenu/tab_online.lua -#, fuzzy msgid "Number of mods: $1" -msgstr "Numero di thread emerge" +msgstr "Numero di moduli: $1" #: builtin/mainmenu/tab_online.lua #, fuzzy @@ -1330,11 +1328,12 @@ msgid "Ping" msgstr "Ping" #: builtin/mainmenu/tab_online.lua -#, fuzzy msgid "" "Players:\n" "$1" -msgstr "Client" +msgstr "" +"Giocatori:\n" +"$1" #: builtin/mainmenu/tab_online.lua msgid "" @@ -1343,6 +1342,10 @@ msgid "" "mod:\n" "player:" msgstr "" +"Possibili filtri\n" +"game:\n" +"mod:\n" +"player:" #: builtin/mainmenu/tab_online.lua msgid "Public Servers" @@ -1475,9 +1478,9 @@ msgid "Camera update enabled" msgstr "Aggiornamento telecamera abilitato" #: src/client/game.cpp -#, fuzzy msgid "Can't show block bounds (disabled by game or mod)" -msgstr "Impossibile mostrare i limiti del blocco (disabilitato da mod o gioco)" +msgstr "" +"Impossibile mostrare i limiti del blocco (disabilitato da modulo o gioco)" #: src/client/game.cpp msgid "Cinematic mode disabled" @@ -1489,11 +1492,11 @@ msgstr "Modalità cinematica attiva" #: src/client/game.cpp msgid "Client disconnected" -msgstr "Client disconnesso" +msgstr "Cliente disconnesso" #: src/client/game.cpp msgid "Client side scripting is disabled" -msgstr "Scripting su lato client disabilitato" +msgstr "Gli script lato cliente sono disabilitati" #: src/client/game.cpp msgid "Connecting to server..." @@ -1514,7 +1517,7 @@ msgstr "Impossibile risolvere l'indirizzo: %s" #: src/client/game.cpp msgid "Creating client..." -msgstr "Creazione del client..." +msgstr "Creazione del cliente..." #: src/client/game.cpp msgid "Creating server..." @@ -1527,7 +1530,7 @@ msgstr "Info debug mostrate" #: src/client/game.cpp #, c-format msgid "Error creating client: %s" -msgstr "Errore di creazione del client: %s" +msgstr "Errore di creazione del cliente: %s" #: src/client/game.cpp msgid "Fast mode disabled" @@ -1562,9 +1565,8 @@ msgid "Fog enabled" msgstr "Nebbia attivata" #: src/client/game.cpp -#, fuzzy msgid "Fog enabled by game or mod" -msgstr "Ingrandimento attualmente disabilitato dal gioco o da un mod" +msgstr "Nebbia abilitata dal gioco o da un modulo" #: src/client/game.cpp msgid "Item definitions..." @@ -1580,7 +1582,7 @@ msgstr "MiB/s" #: src/client/game.cpp msgid "Minimap currently disabled by game or mod" -msgstr "Minimappa attualmente disabilitata dal gioco o da una mod" +msgstr "Minimappa attualmente disabilitata dal gioco o da un modulo" #: src/client/game.cpp msgid "Multiplayer" @@ -1604,15 +1606,15 @@ msgstr "Definizione dei nodi..." #: src/client/game.cpp msgid "Pitch move mode disabled" -msgstr "Modalità inclinazione movimento disabilitata" +msgstr "Beccheggio disabilitato" #: src/client/game.cpp msgid "Pitch move mode enabled" -msgstr "Modalità inclinazione movimento abilitata" +msgstr "Beccheggio abilitato" #: src/client/game.cpp msgid "Profiler graph shown" -msgstr "Grafico profiler visualizzato" +msgstr "Profilatore visualizzato" #: src/client/game.cpp msgid "Resolving address..." @@ -1665,7 +1667,8 @@ msgstr "Raggio visivo illimitato abilitato" #: src/client/game.cpp msgid "Unlimited viewing range enabled, but forbidden by game or mod" -msgstr "Raggio visivo illimitato abilitato, ma vietato dal gioco o dal mod" +msgstr "" +"Raggio visivo illimitato abilitato, ma è bloccato dal gioco o da un modulo" #: src/client/game.cpp #, fuzzy, c-format @@ -1676,8 +1679,8 @@ msgstr "Il raggio visivo è al minimo: %d" #, c-format msgid "Viewing changed to %d (the minimum), but limited to %d by game or mod" msgstr "" -"Raggio visivo modificato a %d (il minimo), ma limitato a %d dal gioco o dal " -"mod" +"Raggio visivo modificato a %d (il minimo), ma limitato a %d dal gioco o da " +"un modulo" #: src/client/game.cpp #, c-format @@ -1694,13 +1697,13 @@ msgstr "Raggio visivo cambiato a %d" msgid "" "Viewing range changed to %d (the maximum), but limited to %d by game or mod" msgstr "" -"Raggio visivo modificato a %d (il massimo), ma limitato a %d dal gioco o dal " -"mod" +"Raggio visivo modificato a %d (il massimo), ma limitato a %d dal gioco o da " +"un modulo" #: src/client/game.cpp -#, fuzzy, c-format +#, c-format msgid "Viewing range changed to %d, but limited to %d by game or mod" -msgstr "Raggio visivo cambiato a %d" +msgstr "Raggio visivo cambiato a %d, ma limitato a %d dal gioco o da un modulo" #: src/client/game.cpp #, c-format @@ -1717,7 +1720,7 @@ msgstr "Struttura a fili visualizzata" #: src/client/game.cpp msgid "Zoom currently disabled by game or mod" -msgstr "Ingrandimento attualmente disabilitato dal gioco o da un mod" +msgstr "Zoom attualmente disabilitato dal gioco o da un modulo" #: src/client/game_formspec.cpp msgid "- Mode: " @@ -1819,7 +1822,7 @@ msgstr "Sei morto" #: src/client/gameui.cpp msgid "Chat currently disabled by game or mod" -msgstr "Chat attualmente disabilitata dal gioco o da un mod" +msgstr "Chat attualmente disabilitata dal gioco o da un modulo" #: src/client/gameui.cpp msgid "Chat hidden" @@ -2170,7 +2173,7 @@ msgstr "%s mancante:" msgid "" "Install and enable the required mods, or disable the mods causing errors." msgstr "" -"Installa e abilita le mod richieste o disabilita le mod che causano errori." +"Installa e abilita i moduli richiesti o disabilita quelli che causano errori." #: src/content/mod_configuration.cpp msgid "" @@ -2178,11 +2181,11 @@ msgid "" "the mods." msgstr "" "Nota: questo potrebbe essere causato da un ciclo di dipendenza, nel qual " -"caso prova ad aggiornare le mod." +"caso prova ad aggiornare i moduli." #: src/content/mod_configuration.cpp msgid "Some mods have unsatisfied dependencies:" -msgstr "Alcune mod hanno dipendenze non soddisfatte:" +msgstr "Alcuni moduli hanno dipendenze non soddisfatte:" #: src/gui/guiButtonKey.h #, fuzzy @@ -2203,11 +2206,11 @@ msgstr "Prosegui" #: src/gui/guiOpenURL.cpp msgid "Open" -msgstr "" +msgstr "Apri" #: src/gui/guiOpenURL.cpp msgid "Open URL?" -msgstr "" +msgstr "Apri URL?" #: src/gui/guiOpenURL.cpp #, fuzzy @@ -2250,13 +2253,12 @@ msgid "Done" msgstr "Fatto!" #: src/gui/touchscreeneditor.cpp -#, fuzzy msgid "Remove" -msgstr "Server remoto" +msgstr "Rimuovi" #: src/gui/touchscreeneditor.cpp msgid "Reset" -msgstr "" +msgstr "Ripristina" #: src/gui/touchscreeneditor.cpp msgid "Start dragging a button to add. Tap outside to cancel." @@ -2264,11 +2266,11 @@ msgstr "" #: src/gui/touchscreeneditor.cpp msgid "Tap a button to select it. Drag a button to move it." -msgstr "" +msgstr "Tocca un pulsante per selezionarlo. Trascinalo per spostarlo." #: src/gui/touchscreeneditor.cpp msgid "Tap outside to deselect." -msgstr "" +msgstr "Tocca fuori per deselezionare." #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Aux1" @@ -2280,7 +2282,7 @@ msgstr "Cambia vista" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Dig/punch/use" -msgstr "" +msgstr "Scava/colpisci/usa" #: src/gui/touchscreenlayout.cpp msgid "Drop" @@ -2301,16 +2303,15 @@ msgstr "ID del joystick" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Jump" -msgstr "Salta" +msgstr "Salto" #: src/gui/touchscreenlayout.cpp msgid "Overflow menu" msgstr "" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp -#, fuzzy msgid "Place/use" -msgstr "Tasto piazza" +msgstr "Piazza/usa" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Range select" @@ -2325,13 +2326,12 @@ msgid "Toggle chat log" msgstr "Log chat sì/no" #: src/gui/touchscreenlayout.cpp -#, fuzzy msgid "Toggle debug" -msgstr "Nebbia sì/no" +msgstr "Debug sì/no" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Toggle fast" -msgstr "Corsa sì/no" +msgstr "Veloce sì/no" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Toggle fly" @@ -2354,14 +2354,16 @@ msgid "" "Another client is connected with this name. If your client closed " "unexpectedly, try again in a minute." msgstr "" +"C'è già un altro cliente connesso con questo nome. Se è stato il tuo a " +"chiudersi di colpo, riprova tra un minuto." #: src/network/clientpackethandler.cpp msgid "Empty passwords are disallowed. Set a password and try again." -msgstr "" +msgstr "Non sono permesse password vuote. Imposta una password e riprova." #: src/network/clientpackethandler.cpp msgid "Internal server error" -msgstr "" +msgstr "Errore interno del server" #: src/network/clientpackethandler.cpp #, fuzzy @@ -2389,7 +2391,7 @@ msgstr "Nome già in uso. Scegli un altro nome, per favore" #: src/network/clientpackethandler.cpp msgid "Player name contains disallowed characters" -msgstr "" +msgstr "Il nome contiene caratteri non permessi" #: src/network/clientpackethandler.cpp #, fuzzy @@ -2404,31 +2406,35 @@ msgstr "Uscita dal gioco..." #: src/network/clientpackethandler.cpp msgid "" "The server has experienced an internal error. You will now be disconnected." -msgstr "" +msgstr "Il server ha avuto un errore interno. Sei statə scollegatə." #: src/network/clientpackethandler.cpp msgid "The server is running in singleplayer mode. You cannot connect." -msgstr "" +msgstr "Il server è in modalità giocatore singolo. Non puoi connetterti." #: src/network/clientpackethandler.cpp msgid "Too many users" -msgstr "" +msgstr "Troppɜ utenti" #: src/network/clientpackethandler.cpp msgid "Unknown disconnect reason." -msgstr "" +msgstr "Motivo della disconnessione: sconosciuto." #: src/network/clientpackethandler.cpp msgid "" "Your client sent something the server didn't expect. Try reconnecting or " "updating your client." msgstr "" +"Il server ha ricevuto qualcosa di inaspettato dal tuo cliente. Prova a " +"riconnetterti o ad aggiornare Luanti." #: src/network/clientpackethandler.cpp msgid "" "Your client's version is not supported.\n" "Please contact the server administrator." msgstr "" +"La versione del tuo cliente non è supportata.\n" +"Contatta un amministratore del server." #: src/server.cpp #, fuzzy, c-format @@ -2601,12 +2607,12 @@ msgstr "" #: src/settings_translation_file.cpp msgid "A message to be displayed to all clients when the server crashes." -msgstr "" -"Un messaggio da mostrare a tutti i client quando il server va in crash." +msgstr "Un messaggio da mostrare a tutti i clienti quando il server crasha." #: src/settings_translation_file.cpp msgid "A message to be displayed to all clients when the server shuts down." -msgstr "Un messaggio da mostrare a tutti i client quando il server chiude." +msgstr "" +"Un messaggio da mostrare a tutti i clienti quando il server viene spento." #: src/settings_translation_file.cpp msgid "ABM interval" @@ -2688,7 +2694,7 @@ msgstr "Usare l'aspetto 3D per le nuvole invece di quello piatto." #: src/settings_translation_file.cpp msgid "Allows liquids to be translucent." -msgstr "" +msgstr "Attiva la traslucenza sui liquidi." #: src/settings_translation_file.cpp msgid "" @@ -2741,11 +2747,11 @@ msgstr "Anti-Scalettatura:" #: src/settings_translation_file.cpp msgid "Anticheat flags" -msgstr "" +msgstr "Segnalini dell'anticheat" #: src/settings_translation_file.cpp msgid "Anticheat movement tolerance" -msgstr "" +msgstr "Tolleranza di movimento dell'anticheat" #: src/settings_translation_file.cpp msgid "Append item name" @@ -2791,7 +2797,6 @@ msgid "Ask to reconnect after crash" msgstr "Chiedi di riconnettersi dopo un crash" #: src/settings_translation_file.cpp -#, fuzzy msgid "" "At this distance the server will aggressively optimize which blocks are sent " "to\n" @@ -2804,17 +2809,14 @@ msgid "" "Stated in MapBlocks (16 nodes)." msgstr "" "A questa distanza il server ottimizzerà aggressivamente quali blocchi sono\n" -"inviati ai client.\n" +"inviati ai clienti.\n" "Potenzialmente valori piccoli migliorano molto le prestazioni, al costo di\n" -"difetti di disegno visibili (alcuni blocchi non saranno disegnati sott'acqua " -"e\n" -"nelle grotte, come a volte sul terreno).\n" +"difetti di renderizzazione (con alcuni blocchi nelle grotte).\n" "Impostarla a un valore maggiore di max_block_send_distance disabilita\n" "questa ottimizzazione.\n" "Fissata in blocchi mappa (16 nodi)." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "At this distance the server will perform a simpler and cheaper occlusion " "check.\n" @@ -2824,14 +2826,10 @@ msgid "" "This is especially useful for very large viewing range (upwards of 500).\n" "Stated in MapBlocks (16 nodes)." msgstr "" -"A questa distanza il server ottimizzerà aggressivamente quali blocchi sono\n" -"inviati ai client.\n" +"A questa distanza il server ottimizzerà aggressivamente l'occlusione.\n" "Potenzialmente valori piccoli migliorano molto le prestazioni, al costo di\n" -"difetti di disegno visibili (alcuni blocchi non saranno disegnati sott'acqua " -"e\n" -"nelle grotte, come a volte sul terreno).\n" -"Impostarla a un valore maggiore di max_block_send_distance disabilita\n" -"questa ottimizzazione.\n" +"difetti di renderizzazione (blocchi mancanti).\n" +"Torna utile soprattutto con un raggio di visione molto alto (sopra i 500).\n" "Fissata in blocchi mappa (16 nodi)." #: src/settings_translation_file.cpp @@ -2844,7 +2842,7 @@ msgstr "Salto automatico" #: src/settings_translation_file.cpp msgid "Automatically jump up single-node obstacles." -msgstr "Salta automaticamente su ostacoli di un nodo singolo." +msgstr "Salta automaticamente su ostacoli alti un nodo." #: src/settings_translation_file.cpp msgid "Automatically report to the serverlist." @@ -2852,7 +2850,7 @@ msgstr "Fa rapporto automatico all'elenco dei server." #: src/settings_translation_file.cpp msgid "Autoscaling mode" -msgstr "Modalità scalamento automatico" +msgstr "Scalatura automatica" #: src/settings_translation_file.cpp msgid "Aux1 key for climbing/descending" @@ -2953,7 +2951,7 @@ msgstr "Fluidità della telecamera" #: src/settings_translation_file.cpp msgid "Camera smoothing in cinematic mode" -msgstr "Fluidità della telecamera in modalità cinematic" +msgstr "Fluidità della telecamera in modalità cinematica" #: src/settings_translation_file.cpp msgid "Cave noise" @@ -3059,7 +3057,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Client" -msgstr "Client" +msgstr "Cliente" #: src/settings_translation_file.cpp #, fuzzy @@ -3068,29 +3066,27 @@ msgstr "Debugging" #: src/settings_translation_file.cpp msgid "Client Mesh Chunksize" -msgstr "Grandezza Client della Mesh del Chunk" +msgstr "Grandezza dei pezzi di mappa del cliente" #: src/settings_translation_file.cpp msgid "Client and Server" -msgstr "Client e server" +msgstr "Cliente e server" #: src/settings_translation_file.cpp msgid "Client modding" -msgstr "Modifica del client" +msgstr "Moddaggio del cliente" #: src/settings_translation_file.cpp msgid "Client side modding restrictions" -msgstr "Restrizioni delle modifiche del client" +msgstr "Restrizioni del moddaggio del cliente" #: src/settings_translation_file.cpp -#, fuzzy msgid "Client-side Modding" -msgstr "Mod lato client" +msgstr "Mod lato cliente" #: src/settings_translation_file.cpp -#, fuzzy msgid "Client-side node lookup range restriction" -msgstr "Restrizione dell'area di ricerca dei nodi su lato client" +msgstr "Restrizione dell'area di ricerca dei nodi lato cliente" #: src/settings_translation_file.cpp msgid "Climbing speed" @@ -3153,17 +3149,19 @@ msgid "" "Comma-separated list of mods that are allowed to access HTTP APIs, which\n" "allow them to upload and download data to/from the internet." msgstr "" -"Elenco separato da virgole di mod a cui è permesso l'accesso alle API HTTP,\n" -"che gli permettono di caricare e scaricare dati su/da internet." +"Elenco, separato da virgole, di moduli a cui è permesso l'accesso alle API " +"HTTP,\n" +"le quali permettono a tali moduli di caricare e scaricare dati su/da " +"internet." #: src/settings_translation_file.cpp msgid "" "Comma-separated list of trusted mods that are allowed to access insecure\n" "functions even when mod security is on (via request_insecure_environment())." msgstr "" -"Elenco separato da virgole delle mod fidate ai quali è permesso l'accesso a " +"Elenco separato da virgole dei moduli fidati ai quali è permesso l'accesso a " "funzioni non sicure\n" -"anche quando la sicurezza mod è attiva (tramite " +"anche quando la sicurezza moduli è attiva (tramite " "request_insecure_environment())." #: src/settings_translation_file.cpp @@ -3191,7 +3189,7 @@ msgid "" "9 - best compression, slowest" msgstr "" "Livello di compressione da utilizzare per l'invio dei blocchi-mappa al " -"client.\n" +"cliente.\n" "-1 - utilizza il livello di compressione predefinito\n" "0 - compressione minima, più veloce\n" "9 - compressione migliore, più lento" @@ -3323,9 +3321,8 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Decrease view range" -msgstr "Diminuisci raggio" +msgstr "Diminuisci raggio visivo" #: src/settings_translation_file.cpp #, fuzzy @@ -3376,7 +3373,6 @@ msgstr "" "ma utilizza più risorse." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "Define the oldest clients allowed to connect.\n" "Older clients are compatible in the sense that they will not crash when " @@ -3388,11 +3384,11 @@ msgid "" "Luanti still enforces its own internal minimum, and enabling\n" "strict_protocol_version_checking will effectively override this." msgstr "" -"Abilitare per impedire ai client obsoleti di connettersi.\n" -"I client più vecchi sono compatibili nel senso che non andranno in crash " -"alla connessione\n" +"Abilitare per impedire ai clienti obsoleti di connettersi.\n" +"I clienti più vecchi sono compatibili nel senso che non crasheranno al " +"connettersi\n" "ai nuovi server, ma potrebbero non supportare tutte le nuove caratteristiche " -"che ti aspetti." +"che ci si aspetta." #: src/settings_translation_file.cpp msgid "Defines areas where trees have apples." @@ -3466,9 +3462,9 @@ msgid "" "Delay between mesh updates on the client in ms. Increasing this will slow\n" "down the rate of mesh updates, thus reducing jitter on slower clients." msgstr "" -"Ritardo in ms tra gli aggiornamenti delle mesh sul client. Aumentandolo si\n" +"Ritardo in ms degli aggiornamenti delle mesh lato cliente. Aumentandolo si\n" "ritarderà il ritmo di aggiornamento delle mesh, riducendo così lo sfarfallio " -"sui client più lenti." +"sui clienti più lenti." #: src/settings_translation_file.cpp msgid "Delay in sending blocks after building" @@ -3539,12 +3535,11 @@ msgstr "Nome di dominio del server, da mostrarsi nell'elenco dei server." #: src/settings_translation_file.cpp msgid "Double tap jump for fly" -msgstr "Doppio \"salta\" per volare" +msgstr "Premi due volte salto per volare" #: src/settings_translation_file.cpp msgid "Double-tapping the jump key toggles fly mode." -msgstr "" -"Premendo due volte il tasto di salto si (dis)attiva la modalità di volo." +msgstr "Premendo due volte il tasto salto si (dis)attiva la modalità volo." #: src/settings_translation_file.cpp msgid "" @@ -3555,9 +3550,8 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Drop item" -msgstr "Tasto butta oggetto" +msgstr "Getta oggetto" #: src/settings_translation_file.cpp msgid "Dump the mapgen debug information." @@ -3602,7 +3596,7 @@ msgid "" "Enable IPv6 support (for both client and server).\n" "Required for IPv6 connections to work at all." msgstr "" -"Abilitare il supporto IPv6 (sia per il client che per il server).\n" +"Abilitare il supporto IPv6 (sia per il cliente che per il server).\n" "Necessario per il funzionamento delle connessioni IPv6." #: src/settings_translation_file.cpp @@ -3611,13 +3605,17 @@ msgid "" "Note that clients will be able to connect with both IPv4 and IPv6.\n" "Ignored if bind_address is set." msgstr "" +"Abilita il supporto per IPv6 sul server.\n" +"Notare che i clienti saranno in grado di connettersi sia tramite IPv4 che " +"IPv6.\n" +"Ignorato se bind_address è definito." #: src/settings_translation_file.cpp msgid "" "Enable Lua modding support on client.\n" "This support is experimental and API can change." msgstr "" -"Abilita il supporto per le modifiche tramite Lua sul client.\n" +"Abilita il supporto per il moddaggio tramite Lua sul cliente.\n" "Questo supporto è sperimentale e l'API potrebbe cambiare." #: src/settings_translation_file.cpp @@ -3674,24 +3672,23 @@ msgstr "Abilita i joystick. Richiede un riavvio del gioco per avere effetto" #: src/settings_translation_file.cpp msgid "Enable mod channels support." -msgstr "Abilita il supporto canali mod." +msgstr "Abilita il supporto canali per i moduli." #: src/settings_translation_file.cpp msgid "Enable mod security" -msgstr "Abilita la sicurezza mod" +msgstr "Abilita la sicurezza moduli" #: src/settings_translation_file.cpp msgid "Enable mouse wheel (scroll) for item selection in hotbar." msgstr "" "Abilita lo scorrimento della rotellina del mouse per la selezione degli " -"oggetti nella hotbar." +"oggetti nella barra delle azioni." #: src/settings_translation_file.cpp -#, fuzzy msgid "Enable random mod loading (mainly used for testing)." msgstr "" -"Abilita l'ingresso di dati casuali da parte dell'utente (utilizzato solo per " -"i test)." +"Abilita il caricamento casuale di moduli (usando principalmente per fare dei " +"test)." #: src/settings_translation_file.cpp msgid "Enable random user input (only used for testing)." @@ -3718,11 +3715,11 @@ msgid "" "to new servers, but they may not support all new features that you are " "expecting." msgstr "" -"Abilitare per impedire ai client obsoleti di connettersi.\n" -"I client più vecchi sono compatibili nel senso che non andranno in crash " -"alla connessione\n" +"Abilitare per impedire ai clienti obsoleti di connettersi.\n" +"I clienti più vecchi sono compatibili nel senso che non crasheranno al " +"connettersi\n" "ai nuovi server, ma potrebbero non supportare tutte le nuove caratteristiche " -"che ti aspetti." +"che ci si aspetta." #: src/settings_translation_file.cpp msgid "Enable updates available indicator on content tab" @@ -3852,11 +3849,11 @@ msgstr "Percorso del carattere di ripiego" #: src/settings_translation_file.cpp msgid "Fast mode acceleration" -msgstr "Accelerazione della modalità veloce" +msgstr "Accelerazione in modalità veloce" #: src/settings_translation_file.cpp msgid "Fast mode speed" -msgstr "Velocità della modalità veloce" +msgstr "Velocità in modalità veloce" #: src/settings_translation_file.cpp msgid "Field of view" @@ -3872,7 +3869,7 @@ msgid "" "the\n" "Multiplayer Tab." msgstr "" -"File in client/serverlist/ contenente i vostri server preferiti mostrati " +"File in client/serverlist/ contenente i tuoi server preferiti mostrati " "nella\n" "scheda di gioco in rete." @@ -4018,9 +4015,9 @@ msgstr "" "divisibili per questo\n" "valore, in pixel, in caso di caratteri in stile pixel che non vengono " "ridimensionati correttamente.\n" -"Per esempio, un carattere pixelato alto 16 pixels richiederebbe che questo " +"Per esempio, un carattere pixelato alto 16 pixel richiederebbe che questo " "sia 16, così sarà sempre\n" -"solo grande 16, 32, 48, etc., e una mod che richiede una grandezza di 25 " +"solo grande 16, 32, 48, etc., e un modulo che richiede una grandezza di 25 " "otterrà 32." #: src/settings_translation_file.cpp @@ -4074,15 +4071,15 @@ msgid "" "From how far blocks are generated for clients, stated in mapblocks (16 " "nodes)." msgstr "" -"Da che distanza vengono generati i blocchi per i client, fissata in blocchi " +"Da che distanza vengono generati i blocchi per i clienti, fissata in blocchi " "mappa (16 nodi)." #: src/settings_translation_file.cpp msgid "" "From how far blocks are sent to clients, stated in mapblocks (16 nodes)." msgstr "" -"Da che distanza i blocchi sono inviati ai client, fissata in blocchi mappa " -"(16 nodi)." +"Da che distanza i blocchi sono inviati ai clienti, fissata in blocchi mappa (" +"16 nodi)." #: src/settings_translation_file.cpp msgid "" @@ -4092,7 +4089,7 @@ msgid "" "to maintain active objects up to this distance in the direction the\n" "player is looking. (This can avoid mobs suddenly disappearing from view)" msgstr "" -"Da che distanza il client sa degli oggetti, fissata in blocchi mappa (16 " +"Da che distanza il cliente rileva gli oggetti, fissata in blocchi mappa (16 " "nodi).\n" "\n" "Impostarla maggiore di active_block_range provocherà il mantenimento\n" @@ -4106,7 +4103,7 @@ msgstr "Schermo intero" #: src/settings_translation_file.cpp msgid "Fullscreen mode." -msgstr "Modalità a schermo intero." +msgstr "Schermo intero." #: src/settings_translation_file.cpp #, fuzzy @@ -4179,7 +4176,7 @@ msgstr "Rumore del terreno" #: src/settings_translation_file.cpp msgid "HTTP mods" -msgstr "Mod HTTP" +msgstr "Moduli HTTP" #: src/settings_translation_file.cpp msgid "HUD" @@ -4198,10 +4195,10 @@ msgid "" msgstr "" "Gestione delle chiamate API Lua deprecate:\n" "- none (nessuno): non registra le chiamate obsolete\n" -"- log (registro): imita e registra il backtrace di una chiamata obsoleta " -"(impostazione predefinita).\n" -"- error (errore): interrompe l'utilizzo della chiamata deprecata " -"(consigliata per gli sviluppatori di mod)." +"- log (registro): imita e registra il backtrace di una chiamata obsoleta (" +"impostazione predefinita).\n" +"- error (errore): interrompe l'utilizzo della chiamata deprecata (" +"consigliata per chi sviluppa moduli)." #: src/settings_translation_file.cpp msgid "" @@ -4211,7 +4208,7 @@ msgid "" "call).\n" "* Instrument the sampler being used to update the statistics." msgstr "" -"Fare in modo che il generatore di profili si predisponga da sé:\n" +"Fare in modo che il profilatore si predisponga da sé:\n" "* Predisporre una funzione vuota.\n" "Ciò stima il sovraccarico che la predisposizione aggiunge (+1 chiamata di " "funzione).\n" @@ -4226,11 +4223,8 @@ msgid "Heat noise" msgstr "Rumore del calore" #: src/settings_translation_file.cpp -#, fuzzy msgid "Height component of the initial window size." -msgstr "" -"Componente altezza della dimensione iniziale della finestra. Ignorata in " -"modalità a schermo intero." +msgstr "Componente altezza della dimensione iniziale della finestra." #: src/settings_translation_file.cpp msgid "Height noise" @@ -4293,196 +4287,160 @@ msgstr "" "in nodi al secondo per secondo." #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 1" -msgstr "Tasto riquadro 1 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 1" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 10" -msgstr "Tasto riquadro 10 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 10" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 11" -msgstr "Tasto riquadro 11 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 11" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 12" -msgstr "Tasto riquadro 12 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 12" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 13" -msgstr "Tasto riquadro 13 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 13" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 14" -msgstr "Tasto riquadro 14 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 14" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 15" -msgstr "Tasto riquadro 15 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 15" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 16" -msgstr "Tasto riquadro 16 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 16" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 17" -msgstr "Tasto riquadro 17 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 17" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 18" -msgstr "Tasto riquadro 18 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 18" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 19" -msgstr "Tasto riquadro 19 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 19" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 2" -msgstr "Tasto riquadro 2 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 2" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 20" -msgstr "Tasto riquadro 20 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 20" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 21" -msgstr "Tasto riquadro 21 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 21" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 22" -msgstr "Tasto riquadro 22 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 22" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 23" -msgstr "Tasto riquadro 23 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 23" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 24" -msgstr "Tasto riquadro 24 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 24" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 25" -msgstr "Tasto riquadro 25 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 25" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 26" -msgstr "Tasto riquadro 26 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 26" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 27" -msgstr "Tasto riquadro 27 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 27" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 28" -msgstr "Tasto riquadro 28 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 28" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 29" -msgstr "Tasto riquadro 29 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 29" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 3" -msgstr "Tasto riquadro 3 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 3" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 30" -msgstr "Tasto riquadro 30 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 30" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 31" -msgstr "Tasto riquadro 31 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 31" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 32" -msgstr "Tasto riquadro 32 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 32" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 4" -msgstr "Tasto riquadro 4 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 4" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 5" -msgstr "Tasto riquadro 5 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 5" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 6" -msgstr "Tasto riquadro 6 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 6" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 7" -msgstr "Tasto riquadro 7 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 7" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 8" -msgstr "Tasto riquadro 8 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 8" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 9" -msgstr "Tasto riquadro 9 della barra di scelta rapida" +msgstr "Barra delle azioni, casella 9" #: src/settings_translation_file.cpp msgid "Hotbar: Enable mouse wheel for selection" -msgstr "Hotbar: attiva la rotellina del mouse per la selezione" +msgstr "Barra delle azioni: attiva la rotellina del mouse per la selezione" #: src/settings_translation_file.cpp msgid "Hotbar: Invert mouse wheel direction" -msgstr "Hotbar: inverti la direzione della rotellina del mouse" +msgstr "Barra delle azioni: inverti la direzione della rotellina del mouse" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar: select next item" -msgstr "Tasto successivo della barra di scelta rapida" +msgstr "Barra delle azioni: seleziona prossimo oggetto" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar: select previous item" -msgstr "Tasto precedente della barra di scelta rapida" +msgstr "Barra delle azioni: seleziona oggetto precedente" #: src/settings_translation_file.cpp msgid "How deep to make rivers." msgstr "Quanto fare profondi i fiumi." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "How fast liquid waves will move. Higher = faster.\n" "If negative, liquid waves will move backwards." msgstr "" "A quale velocità si muovono le onde dei liquidi. Maggiore = più veloce.\n" -"Se negativa, le onde dei liquidi si sposteranno all'indietro.\n" -"Richiede l'abilitazione dei liquidi ondeggianti." +"Se negativa, le onde dei liquidi si sposteranno all'indietro." #: src/settings_translation_file.cpp msgid "" @@ -4500,7 +4458,7 @@ msgid "" "How much you are slowed down when moving inside a liquid.\n" "Decrease this to increase liquid resistance to movement." msgstr "" -"Quanto sei rallentato mentre ti muovi dentro un liquido.\n" +"Quanto sei rallentatə mentre ti muovi dentro un liquido.\n" "Riducila per aumentare la resistenza del liquido al movimento." #: src/settings_translation_file.cpp @@ -4550,6 +4508,10 @@ msgid "" "ContentDB to\n" "check for package updates when opening the mainmenu." msgstr "" +"Se abilitato e si hanno pacchetti provenienti da ContentDB installati, " +"Luanti potrebbe contattare\n" +"ContentDB per controllare eventuali aggiornamenti quando viene aperto il " +"menù principale." #: src/settings_translation_file.cpp msgid "" @@ -4600,16 +4562,17 @@ msgstr "" #: src/settings_translation_file.cpp msgid "If enabled, the \"Aux1\" key will toggle when pressed." -msgstr "" +msgstr "Se abilitato, il tasto \"Aux1\" si azionerà quando premuto." #: src/settings_translation_file.cpp msgid "" "If enabled, the \"Sneak\" key will toggle when pressed.\n" "This functionality is ignored when fly is enabled." msgstr "" +"Se abilitato, il tasto \"Furtivo\" si azionerà quando premuto.\n" +"Questa funzionalità è ignorata quando la modalità volo è abilitata." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "If enabled, the server will perform map block occlusion culling based on\n" "on the eye position of the player. This can reduce the number of blocks\n" @@ -4620,7 +4583,7 @@ msgstr "" "basandosi\n" "sulla posizione degli occhi del giocatore. Questo può ridurre del 50-80% il " "numero dei blocchi\n" -"inviati al client. Il client non riceverà più la maggior parte degli " +"inviati al cliente. Il cliente non riceverà più la maggior parte degli " "invisibili\n" "cosicché l'utilità della modalità incorporea è ridotta." @@ -4661,7 +4624,7 @@ msgid "" "deleting an older debug.txt.1 if it exists.\n" "debug.txt is only moved if this setting is positive." msgstr "" -"Se la dimensione del file debug.txt supera il numero di Mb indicati in\n" +"Se la dimensione del file debug.txt supera il numero di MB indicati in\n" "questa impostazione quando viene aperto, il file viene rinominato in " "debug.txt.1,\n" "eliminando un eventuale debug.txt.1 più vecchio.\n" @@ -4690,9 +4653,8 @@ msgid "In-game chat console height, between 0.1 (10%) and 1.0 (100%)." msgstr "Altezza della console di chat nel gioco, tra 0.1 (10%) e 1.0 (100%)." #: src/settings_translation_file.cpp -#, fuzzy msgid "Increase view range" -msgstr "Aumenta raggio" +msgstr "Aumenta raggio visivo" #: src/settings_translation_file.cpp #, fuzzy @@ -4759,13 +4721,13 @@ msgstr "Animazioni degli oggetti dell'inventario" #: src/settings_translation_file.cpp msgid "Invert mouse" -msgstr "Invertire il mouse" +msgstr "Mouse invertito" #: src/settings_translation_file.cpp msgid "Invert mouse wheel (scroll) direction for item selection in hotbar." msgstr "" -"Inverte la direzione di scorrimento della rotellina del mouse per la " -"selezione degli oggetti nella hotbar." +"Inverte lo scorrimento della rotellina del mouse per la selezione degli " +"oggetti nella barra delle azioni." #: src/settings_translation_file.cpp msgid "Invert vertical mouse movement." @@ -4892,15 +4854,15 @@ msgstr "Velocità di salto" #: src/settings_translation_file.cpp msgid "Key for decreasing the viewing range." -msgstr "" +msgstr "Tasto per diminuire il raggio visivo." #: src/settings_translation_file.cpp msgid "Key for decreasing the volume." -msgstr "" +msgstr "Tasto per abbassare il volume." #: src/settings_translation_file.cpp msgid "Key for decrementing the selected value in Quicktune." -msgstr "" +msgstr "Tasto per diminuire il valore di Quicktune." #: src/settings_translation_file.cpp msgid "" @@ -4910,27 +4872,27 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for dropping the currently selected item." -msgstr "" +msgstr "Tasto per gettare l'oggetto che si ha in mano." #: src/settings_translation_file.cpp msgid "Key for increasing the viewing range." -msgstr "" +msgstr "Tasto per aumentare il raggio visivo." #: src/settings_translation_file.cpp msgid "Key for increasing the volume." -msgstr "" +msgstr "Tasto per alzare il volume." #: src/settings_translation_file.cpp msgid "Key for incrementing the selected value in Quicktune." -msgstr "" +msgstr "Tasto per aumentare il valore di Quicktune." #: src/settings_translation_file.cpp msgid "Key for jumping." -msgstr "" +msgstr "Tasto per saltare." #: src/settings_translation_file.cpp msgid "Key for moving fast in fast mode." -msgstr "" +msgstr "Tasto per muoversi rapidamente in modalità veloce." #: src/settings_translation_file.cpp #, fuzzy @@ -4945,35 +4907,36 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for moving the player forward." -msgstr "" +msgstr "Tasto per muovere il giocatore in avanti." #: src/settings_translation_file.cpp msgid "Key for moving the player left." -msgstr "" +msgstr "Tasto per muovere il giocatore a sinistra." #: src/settings_translation_file.cpp msgid "Key for moving the player right." -msgstr "" +msgstr "Tasto per muovere il giocatore a destra." #: src/settings_translation_file.cpp msgid "Key for muting the game." -msgstr "" +msgstr "Tasto per mutare il gioco." #: src/settings_translation_file.cpp msgid "Key for opening the chat window to type commands." -msgstr "" +msgstr "Tasto per aprire la finestra della chat, pronta per scrivere comandi." #: src/settings_translation_file.cpp msgid "Key for opening the chat window to type local commands." msgstr "" +"Tasto per aprire la finestra della chat, pronta per scrivere comandi locali." #: src/settings_translation_file.cpp msgid "Key for opening the chat window." -msgstr "" +msgstr "Tasto per aprire la finestra della chat." #: src/settings_translation_file.cpp msgid "Key for opening the inventory." -msgstr "" +msgstr "Tasto per aprire l'inventario." #: src/settings_translation_file.cpp msgid "" @@ -4983,139 +4946,139 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for selecting the 11th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare l'11° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 12th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 12° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 13th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 13° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 14th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 14° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 15th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 15° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 16th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 16° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 17th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 17° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 18th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 18° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 19th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 19° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 20th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 20° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 21st hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 21° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 22nd hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 22° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 23rd hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 23° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 24th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 24° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 25th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 25° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 26th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 26° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 27th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 27° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 28th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 28° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 29th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 29° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 30th hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 30° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 31st hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 31° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the 32nd hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 32° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the eighth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare l'8° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the fifth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 5° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the first hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 1° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the fourth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 4° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the next item in the hotbar." -msgstr "" +msgstr "Tasto per selezionare il prossimo oggetto nella barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the ninth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 9° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the previous item in the hotbar." -msgstr "" +msgstr "Tasto per selezionare l'oggetto precedente nella barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the second hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 2° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the seventh hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 7° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the sixth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 6° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the tenth hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 10° casella della barra delle azioni." #: src/settings_translation_file.cpp msgid "Key for selecting the third hotbar slot." -msgstr "" +msgstr "Tasto per selezionare la 3° casella della barra delle azioni." #: src/settings_translation_file.cpp #, fuzzy @@ -5132,7 +5095,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for switching between first- and third-person camera." -msgstr "" +msgstr "Tasto per ruotare tra prima e terza persona." #: src/settings_translation_file.cpp msgid "Key for switching to the next entry in Quicktune." @@ -5149,37 +5112,35 @@ msgstr "Formato degli screenshot." #: src/settings_translation_file.cpp msgid "Key for toggling autoforward." -msgstr "" +msgstr "Tasto per (dis)attivare l'avanzamento automatico." #: src/settings_translation_file.cpp -#, fuzzy msgid "Key for toggling cinematic mode." -msgstr "Fluidità della telecamera in modalità cinematic" +msgstr "Tasto per attivare e disattivare la modalità cinematica." #: src/settings_translation_file.cpp msgid "Key for toggling display of minimap." -msgstr "" +msgstr "Tasto per (dis)attivare la minimappa." #: src/settings_translation_file.cpp msgid "Key for toggling fast mode." -msgstr "" +msgstr "Tasto per attivare e disattivare la modalità veloce." #: src/settings_translation_file.cpp msgid "Key for toggling flying." -msgstr "" +msgstr "Tasto per (dis)attivare il volo." #: src/settings_translation_file.cpp -#, fuzzy msgid "Key for toggling fullscreen mode." -msgstr "Modalità a schermo intero." +msgstr "Tasto per attivare e disattivare la modalità a schermo intero." #: src/settings_translation_file.cpp msgid "Key for toggling noclip mode." -msgstr "" +msgstr "Tasto per attivare e disattivare la modalità incorporea." #: src/settings_translation_file.cpp msgid "Key for toggling pitch move mode." -msgstr "" +msgstr "Tasto per attivare e disattivare il beccheggio." #: src/settings_translation_file.cpp msgid "Key for toggling the camera update. Only usable with 'debug' privilege." @@ -5212,20 +5173,20 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Key for toggling the display of the profiler. Used for development." msgstr "" +"Tasto per alternare la visualizzazione del profilatore. Usato per fini di " +"sviluppo." #: src/settings_translation_file.cpp -#, fuzzy msgid "Key for toggling unlimited view range." -msgstr "Raggio visivo illimitato disabilitato" +msgstr "Tasto per (dis)attivare il raggio visivo illimitato." #: src/settings_translation_file.cpp msgid "Key to use view zoom when possible." msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Keybindings" -msgstr "Mappatura dei tasti." +msgstr "Mappatura dei tasti" #: src/settings_translation_file.cpp msgid "Keyboard and Mouse" @@ -5265,9 +5226,8 @@ msgid "Large cave proportion flooded" msgstr "Proporzione inondata della grotta grande" #: src/settings_translation_file.cpp -#, fuzzy msgid "Large chat console" -msgstr "Tasto console grande di chat" +msgstr "Console" #: src/settings_translation_file.cpp msgid "Leaves style" @@ -5401,7 +5361,8 @@ msgstr "" "- Recupero dei file multimediali se il server usa l'impostazione " "remote_media.\n" "- Scaricamento dell'elenco dei server e annuncio del server.\n" -"- Scaricamenti effettuati dal menu principale (per es. il gestore mod.).\n" +"- Scaricamenti effettuati dal menu principale (per es. il gestore " +"moduli.).\n" "Ha effetto solo se compilato con cURL." #: src/settings_translation_file.cpp @@ -5439,7 +5400,7 @@ msgstr "Scatto di aggiornamento del liquido" #: src/settings_translation_file.cpp msgid "Load the game profiler" -msgstr "Caricare il generatore di profili del gioco" +msgstr "Caricare il profilatore del gioco" #: src/settings_translation_file.cpp msgid "" @@ -5447,10 +5408,9 @@ msgid "" "Provides a /profiler command to access the compiled profile.\n" "Useful for mod developers and server operators." msgstr "" -"Carica il generatore di profili per raccogliere i dati di profilazione del " -"gioco.\n" +"Carica il profilatore per raccogliere i dati di profilazione del gioco.\n" "Fornisce un comando /profiler per accedere al profilo compilato.\n" -"Utile per sviluppatori di moduli e operatori di server." +"Utile per chi sviluppa moduli e chi opera dei server." #: src/settings_translation_file.cpp msgid "Loading Block Modifiers" @@ -5699,7 +5659,7 @@ msgstr "Numero massimo di blocchi caricati a forza" #: src/settings_translation_file.cpp msgid "Maximum hotbar width" -msgstr "Larghezza massima della barra di scelta rapida" +msgstr "Larghezza massima della barra delle azioni" #: src/settings_translation_file.cpp msgid "Maximum limit of random number of large caves per mapchunk." @@ -5724,9 +5684,9 @@ msgid "" "The maximum total count is calculated dynamically:\n" "max_total = ceil((#clients + max_users) * per_client / 4)" msgstr "" -"Numero massimo di blocchi inviati simultaneamente per client.\n" +"Numero massimo di blocchi inviati simultaneamente per clientecliente.\n" "Il conto totale massimo è calcolato dinamicamente:\n" -"tot_max = arrotonda((N°client + max_utenti) * per_client / 4)" +"tot_max = arrotonda((N°clienti + max_utenti) * per_cliente / 4)" #: src/settings_translation_file.cpp msgid "Maximum number of blocks that can be queued for loading." @@ -5797,13 +5757,13 @@ msgid "" "Maximum proportion of current window to be used for hotbar.\n" "Useful if there's something to be displayed right or left of hotbar." msgstr "" -"Porzione massima della finestra attuale da usarsi per la barra di scelta " -"rapida.\n" +"Porzione massima della finestra attuale da usarsi per la barra delle azioni." +"\n" "Utile se c'è qualcosa da mostrare a destra o sinistra della barra." #: src/settings_translation_file.cpp msgid "Maximum simultaneous block sends per client" -msgstr "Invii simultanei massimi di blocchi per client" +msgstr "Invii simultanei massimi di blocchi per cliente" #: src/settings_translation_file.cpp #, fuzzy @@ -5825,8 +5785,8 @@ msgid "" "Maximum time a file download (e.g. a mod download) may take, stated in " "milliseconds." msgstr "" -"Tempo massimo in millisecondi che può richiedere lo scaricamento di un file " -"(es. un mod)." +"Tempo massimo in millisecondi che può richiedere lo scaricamento di un file (" +"es. un modulo)." #: src/settings_translation_file.cpp msgid "" @@ -5896,7 +5856,7 @@ msgstr "Sicurezza Mod" #: src/settings_translation_file.cpp msgid "Mod channels" -msgstr "Canali mod" +msgstr "Canali per i moduli" #: src/settings_translation_file.cpp msgid "Modifies the size of the HUD elements." @@ -5939,28 +5899,24 @@ msgid "Mouse sensitivity multiplier." msgstr "Moltiplicatore della sensibilità del mouse." #: src/settings_translation_file.cpp -#, fuzzy msgid "Move backward" -msgstr "Indietreggia" +msgstr "Indietro" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move forward" -msgstr "Avanzam. autom." +msgstr "Avanti" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move left" -msgstr "Movimento" +msgstr "Sinistra" #: src/settings_translation_file.cpp msgid "Move right" -msgstr "" +msgstr "Destra" #: src/settings_translation_file.cpp -#, fuzzy msgid "Movement threshold" -msgstr "Soglia della caverna" +msgstr "Soglia di movimento" #: src/settings_translation_file.cpp msgid "Mud noise" @@ -5987,14 +5943,13 @@ msgstr "" "- 'floatlands' in v7 (disabilitato per impostazione predefinita)." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "Name of the player.\n" "When running a server, a client connecting with this name is admin.\n" "When starting from the main menu, this is overridden." msgstr "" "Nome del giocatore.\n" -"Quando si esegue un server, i client che si connettono con questo nome sono " +"Quando si esegue un server, i clienti che si connettono con questo nome sono " "amministratori.\n" "Quando si avvia dal menu principale, questo viene scavalcato." @@ -6118,7 +6073,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Open chat" -msgstr "" +msgstr "Apri chat" #: src/settings_translation_file.cpp #, fuzzy @@ -6149,9 +6104,8 @@ msgid "Optional override for chat weblink color." msgstr "Sovrascrittura opzionale per i colori dei link web in chat." #: src/settings_translation_file.cpp -#, fuzzy msgid "Other Effects" -msgstr "Effetti grafici" +msgstr "Altri effetti" #: src/settings_translation_file.cpp msgid "" @@ -6247,7 +6201,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Prevent mods from doing insecure things like running shell commands." msgstr "" -"Impedisce che i mod facciano cose non sicure come eseguire comandi della " +"Impedisce che i moduli facciano cose non sicure come eseguire comandi della " "shell." #: src/settings_translation_file.cpp @@ -6265,7 +6219,7 @@ msgstr "Privilegi che i giocatori con basic_privs possono concedere" #: src/settings_translation_file.cpp msgid "Profiler" -msgstr "Generatore di profili" +msgstr "Profilatore" #: src/settings_translation_file.cpp msgid "Prometheus listener address" @@ -6339,7 +6293,7 @@ msgstr "Dati in ingresso casuali" #: src/settings_translation_file.cpp msgid "Random mod load order" -msgstr "" +msgstr "Ordine casuale di caricamento moduli" #: src/settings_translation_file.cpp msgid "Recent Chat Messages" @@ -6363,7 +6317,7 @@ msgid "" "Remove color codes from incoming chat messages\n" "Use this to stop players from being able to use color in their messages" msgstr "" -"Leva i codici di colore dai messaggi di chat in arrivo\n" +"Rimuove i codici di colore dai messaggi di chat in arrivo\n" "Usalo per impedire ai giocatori di usare i colori nei loro messaggi" #: src/settings_translation_file.cpp @@ -6387,18 +6341,17 @@ msgid "" "csm_restriction_noderange)\n" "READ_PLAYERINFO: 32 (disable get_player_names call client-side)" msgstr "" -"Restringe l'accesso di certe funzioni lato-client sui server.\n" +"Restringe l'accesso di certe funzioni lato cliente sui server.\n" "Combina i valori byte sottostanti per restringere le caratteristiche\n" -"lato-client, o imposta a 0 per nessuna restrizione:\n" -"LOAD_CLIENT_MODS: 1 (disabilita il caricamento di mod forniti dal client)\n" -"CHAT_MESSAGES: 2 (disabilita la chiamata di send_chat_message su lato-" -"client)\n" -"READ_ITEMDEFS: 4 (disabilita la chiamata di get_item_def su lato-client)\n" -"READ_NODEDEFS: 8 (disabilita la chiamata di get_node_def su lato-client)\n" -"LOOKUP_NODES_LIMIT: 16 (limita la chiamata get_node su lato-client a\n" +"lato cliente, o imposta a 0 per nessuna restrizione:\n" +"LOAD_CLIENT_MODS: 1 (disabilita il caricamento di moduli forniti dal cliente)" +"\n" +"CHAT_MESSAGES: 2 (disabilita la chiamata di send_chat_message lato cliente)\n" +"READ_ITEMDEFS: 4 (disabilita la chiamata di get_item_def lato cliente)\n" +"READ_NODEDEFS: 8 (disabilita la chiamata di get_node_def lato cliente)\n" +"LOOKUP_NODES_LIMIT: 16 (limita la chiamata get_node lato cliente a\n" "csm_restriction_noderange)\n" -"READ_PLAYERINFO: 32 (disabilita la chiamata di get_player_names su lato-" -"client)" +"READ_PLAYERINFO: 32 (disabilita la chiamata di get_player_names lato cliente)" #: src/settings_translation_file.cpp msgid "Ridge mountain spread noise" @@ -6463,7 +6416,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Save the map received by the client on disk." -msgstr "Salvare su disco la mappa ricevuta dal client." +msgstr "Salvare su disco la mappa ricevuta dal cliente." #: src/settings_translation_file.cpp msgid "" @@ -6765,7 +6718,7 @@ msgid "" "Set the maximum length of a chat message (in characters) sent by clients." msgstr "" "Imposta la lunghezza massima (in caratteri) di un messaggio di chat inviato " -"dai client." +"dai clienti." #: src/settings_translation_file.cpp msgid "" @@ -6919,7 +6872,7 @@ msgid "" "draw calls, benefiting especially high-end GPUs.\n" "Systems with a low-end GPU (or no GPU) would benefit from smaller values." msgstr "" -"Lunghezza del lato di un cubo di blocchi mappa che il client considererà " +"Lunghezza del lato di un cubo di blocchi mappa che il cliente considererà " "assieme\n" "durante la generazione delle mesh.\n" "Valori maggiori aumentano l'utilizzo della GPU riducendo il numero di\n" @@ -7040,7 +6993,7 @@ msgid "" "(obviously, remote_media should end with a slash).\n" "Files that are not present will be fetched the usual way." msgstr "" -"Specifica l'URL da cui il client recupera i file multimediali invece di " +"Specifica l'URL da cui il cliente recupera i file multimediali invece di " "usare UDP.\n" "$filename dovrebbe essere accessibile da $remote_media$filename tramite\n" "cURL (ovviamente, remote_media dovrebbe finire con una barra).\n" @@ -7053,7 +7006,7 @@ msgid "" "items." msgstr "" "Fissa la dimensione predefinita della pila di nodi, oggetti e strumenti.\n" -"Si noti che mod o giochi possono impostare esplicitamente una pila per " +"Si noti che moduli o giochi possono impostare esplicitamente una pila per " "alcuni (o tutti) gli oggetti." #: src/settings_translation_file.cpp @@ -7271,6 +7224,16 @@ msgid "" "Known from the classic Luanti mobile controls.\n" "Combat is more or less impossible." msgstr "" +"L'azione per prendere a pugni giocanti/entità.\n" +"Può essere sovrascritto da giochi e moduli.\n" +"\n" +"* Tocco rapido\n" +"Facile da usare e diffuso su altri giochi che non possono essere nominati.\n" +"\n" +"* Tocco prolungato\n" +"Conosciuto dalla comunità di Luanti in quanto è stato il controllo " +"predefinito da telefono.\n" +"Combattere è pressoché impossibile." #: src/settings_translation_file.cpp msgid "The identifier of the joystick to use" @@ -7294,13 +7257,11 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "" "The length in pixels after which a touch interaction is considered movement." -msgstr "La distanza in pixel richiesta per avviare l'interazione touch screen." +msgstr "La distanza in pixel dopo la quale un tocco è considerato movimento." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "The maximum height of the surface of waving liquids.\n" "4.0 = Wave height is two nodes.\n" @@ -7310,8 +7271,7 @@ msgstr "" "L'altezza massima della superficie dei liquidi ondulanti.\n" "4.0 = L'altezza dell'onda è di due nodi.\n" "0.0 = L'onda non si muove affatto.\n" -"Il valore predefinito è 1.0 (1/2 nodo).\n" -"Richiede l'abilitazione dei liquidi ondulanti." +"Il valore predefinito è 1.0 (1/2 nodo)." #: src/settings_translation_file.cpp #, fuzzy @@ -7334,7 +7294,7 @@ msgid "" msgstr "" "I privilegi ricevuti automaticamente dai nuovi utenti.\n" "Si veda /privs in gioco per un elenco completo sul vostro server e la " -"configurazione dei mod." +"configurazione dei moduli." #: src/settings_translation_file.cpp msgid "" @@ -7458,7 +7418,7 @@ msgid "" "Setting it to -1 disables the feature." msgstr "" "Tempo di vita in secondi per le entità oggetto (oggetti buttati).\n" -"Impostandola a -1 disabilita la caratteristica." +"Imposta a -1 per disabilitare la funzionalità." #: src/settings_translation_file.cpp msgid "Time of day when a new world is started, in millihours (0-23999)." @@ -7473,8 +7433,8 @@ msgstr "Velocità del tempo" #: src/settings_translation_file.cpp msgid "Timeout for client to remove unused map data from memory, in seconds." msgstr "" -"Scadenza per il client per rimuovere dalla memoria dati mappa inutilizzati, " -"in secondi." +"Scadenza per il cliente per rimuovere dalla memoria i dati della mappa " +"inutilizzati, in secondi." #: src/settings_translation_file.cpp msgid "" @@ -7489,72 +7449,64 @@ msgstr "" "rimosso un nodo." #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle Aux1 key" -msgstr "Tasto Speciale" +msgstr "Taso Aux1 sì/no" #: src/settings_translation_file.cpp msgid "Toggle HUD" msgstr "HUD sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle Sneak key" -msgstr "Tasto di (dis)attivazione della modalità telecamera" +msgstr "Furtivo sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle automatic forward" -msgstr "Tasto di avanzamento automatico" +msgstr "Avanzamento automatico sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle block bounds" -msgstr "Limiti del blocco nascosto" +msgstr "Limiti del blocco sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle camera mode" -msgstr "Tasto di (dis)attivazione della modalità telecamera" +msgstr "Cambia inquadratura" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle camera update" -msgstr "Tasto di (dis)attivazione della modalità telecamera" +msgstr "Aggiornamento telecamera sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle cinematic mode" -msgstr "Scegli cinematica" +msgstr "Cinematica sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle debug info" -msgstr "Nebbia sì/no" +msgstr "Mostra informazioni di debug" #: src/settings_translation_file.cpp msgid "Toggle fog" msgstr "Nebbia sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle fullscreen" -msgstr "Volo sì/no" +msgstr "Schermo intero sì/no" #: src/settings_translation_file.cpp msgid "Toggle pitchmove" msgstr "Beccheggio sì/no" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle profiler" -msgstr "Volo sì/no" +msgstr "Profilatore sì/no" #: src/settings_translation_file.cpp msgid "" "Tolerance of movement cheat detector.\n" "Increase the value if players experience stuttery movement." msgstr "" +"Tolleranza del rilevatore di cheat di movimento.\n" +"Aumentane il valore se i giocatori vivono un'esperienza a scatti." #: src/settings_translation_file.cpp msgid "Tooltip delay" @@ -7622,7 +7574,7 @@ msgstr "" #: src/settings_translation_file.cpp msgid "Trusted mods" -msgstr "Mod fidate" +msgstr "Moduli fidati" #: src/settings_translation_file.cpp msgid "" @@ -8010,14 +7962,14 @@ msgid "" msgstr "" "Se lo sfondo dell'etichetta del nome debba essere mostrato per impostazione " "predefinita.\n" -"Le mod possono comunque impostare uno sfondo." +"I moduli possono comunque impostare uno sfondo." #: src/settings_translation_file.cpp msgid "" "Whether players are shown to clients without any range limit.\n" "Deprecated, use the setting player_transfer_distance instead." msgstr "" -"Se i giocatori vengono mostrati ai client senza alcun limite di raggio.\n" +"Se i giocatori vengono mostrati ai clienti senza alcun limite di raggio.\n" "Deprecata, usa invece l'impostazione player_transfer_distance." #: src/settings_translation_file.cpp @@ -8029,7 +7981,7 @@ msgid "" "Whether to ask clients to reconnect after a (Lua) crash.\n" "Set this to true if your server is set up to restart automatically." msgstr "" -"Se chiedere ai client di riconnettersi dopo un crash (Lua).\n" +"Se chiedere ai clienti di riconnettersi dopo un crash (Lua).\n" "Impostatela su Vero se il vostro server è configurato per riavviarsi " "automaticamente." @@ -8038,23 +7990,21 @@ msgid "Whether to fog out the end of the visible area." msgstr "Se annebbiare o meno la fine dell'area visibile." #: src/settings_translation_file.cpp -#, fuzzy msgid "" "Whether to mute sounds. You can unmute sounds at any time.\n" "In-game, you can toggle the mute state with the mute key or by using the\n" "pause menu." msgstr "" "Se silenziare i suoni. È possibile de-silenziare i suoni in qualsiasi " -"momento, a meno che\n" -"il sistema audio non sia disabilitato (enable_sound=false).\n" -"Nel gioco, puoi alternare lo stato silenziato col tasto muta o usando\n" +"momento.\n" +"In partita puoi mutare e smutare il gioco col tasto muta o usando\n" "il menu di pausa." #: src/settings_translation_file.cpp msgid "" "Whether to show the client debug info (has the same effect as hitting F5)." msgstr "" -"Se mostrare le informazioni di debug del client (ha lo stesso effetto di " +"Se mostrare le informazioni di debug del cliente (ha lo stesso effetto di " "premere F5)." #: src/settings_translation_file.cpp @@ -8096,7 +8046,6 @@ msgid "World start time" msgstr "Ora di avvio del mondo" #: src/settings_translation_file.cpp -#, fuzzy msgid "" "World-aligned textures may be scaled to span several nodes. However,\n" "the server may not send the scale you want, especially if you use\n" @@ -8110,7 +8059,7 @@ msgstr "" "Tuttavia, il server potrebbe non inviare le dimensioni desiderate, " "specialmente se è in uso un\n" "pacchetto texture progettato in modo specifico; con questa opzione, il " -"client prova a stabilire\n" +"cliente prova a stabilire\n" "automaticamente le dimensioni basandosi sulla grandezza della texture.\n" "Si veda anche texture_min_size.\n" "Avviso: questa opzione è SPERIMENTALE!" From 1b9a5074a2b85860106ede70d554003c02b30be3 Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 15 May 2025 04:13:36 +0200 Subject: [PATCH 262/284] Translated using Weblate (Indonesian) Currently translated at 100.0% (1531 of 1531 strings) --- po/id/luanti.po | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/po/id/luanti.po b/po/id/luanti.po index 2b29b9cc1..801bdf42b 100644 --- a/po/id/luanti.po +++ b/po/id/luanti.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: Indonesian (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-04-25 10:52+0000\n" +"PO-Revision-Date: 2025-05-15 23:01+0000\n" "Last-Translator: Linerly \n" "Language-Team: Indonesian \n" @@ -12,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.11.1-dev\n" +"X-Generator: Weblate 5.12-dev\n" #: builtin/client/chatcommands.lua msgid "Clear the out chat queue" @@ -930,29 +930,27 @@ msgstr "Hapus dunia \"$1\"?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Oleh sebab itu, pengikatan tombol kamu dapat berubah." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Periksa pengaturan tombol atau rujuk pada dokumentasi:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Tutup" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Pengikatan tombol" +msgstr "Pengikatan tombol berubah" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Pengaturan" +msgstr "Buka pengaturan" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "Sistem penanganan masukan telah dikerjakan ulang dalam Luanti 5.12.0." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" From 813f67021b824de152c1d2bd120d3cb2edc390fb Mon Sep 17 00:00:00 2001 From: y5nw Date: Thu, 15 May 2025 00:24:35 +0200 Subject: [PATCH 263/284] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 88.7% (1358 of 1531 strings) --- po/zh_CN/luanti.po | 194 ++++++++++++++++----------------------------- 1 file changed, 69 insertions(+), 125 deletions(-) diff --git a/po/zh_CN/luanti.po b/po/zh_CN/luanti.po index 9d85accc8..201169c0a 100644 --- a/po/zh_CN/luanti.po +++ b/po/zh_CN/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: Chinese (Simplified) (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-02-24 16:36+0000\n" -"Last-Translator: BX Zhang \n" +"PO-Revision-Date: 2025-05-15 23:02+0000\n" +"Last-Translator: y5nw \n" "Language-Team: Chinese (Simplified Han script) \n" "Language: zh_CN\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.10.1-dev\n" +"X-Generator: Weblate 5.12-dev\n" #: builtin/client/chatcommands.lua msgid "Clear the out chat queue" @@ -82,16 +82,15 @@ msgstr "浏览" #: builtin/common/settings/components.lua msgid "Conflicts with \"$1\"" -msgstr "" +msgstr "与“$1”冲突" #: builtin/common/settings/components.lua msgid "Edit" msgstr "编辑" #: builtin/common/settings/components.lua -#, fuzzy msgid "Remove keybinding" -msgstr "按键绑定。" +msgstr "移除按键绑定" #: builtin/common/settings/components.lua msgid "Select directory" @@ -253,7 +252,7 @@ msgstr "通用" #: builtin/common/settings/dlg_settings.lua msgid "Long tap" -msgstr "" +msgstr "长按" #: builtin/common/settings/dlg_settings.lua msgid "Movement" @@ -558,12 +557,11 @@ msgstr "捐赠" #: builtin/mainmenu/content/dlg_package.lua msgid "Error loading package information" -msgstr "" +msgstr "无法获取软件包信息" #: builtin/mainmenu/content/dlg_package.lua -#, fuzzy msgid "Error loading reviews" -msgstr "创建客户端出错:%s" +msgstr "无法加载评价" #: builtin/mainmenu/content/dlg_package.lua msgid "Forum Topic" @@ -583,7 +581,7 @@ msgstr "问题跟踪器" #: builtin/mainmenu/content/dlg_package.lua msgid "Reviews" -msgstr "" +msgstr "评价" #: builtin/mainmenu/content/dlg_package.lua msgid "Source" @@ -638,10 +636,11 @@ msgid "Unable to install a $1 as a texture pack" msgstr "无法将$1安装为材质包" #: builtin/mainmenu/dlg_clients_list.lua +#, fuzzy msgid "" "Players connected to\n" "$1" -msgstr "" +msgstr "连接到$1的玩家" #: builtin/mainmenu/dlg_config_world.lua msgid "(Enabled, has error)" @@ -924,25 +923,23 @@ msgstr "删除世界“$1”?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "您的一些按键绑定有可能已被更改。" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "请查看按键绑定设置或参考文档:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "关闭" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "按键绑定。" +msgstr "按键绑定已被更改" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "设置" +msgstr "打开设置" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." @@ -1292,12 +1289,11 @@ msgid "Ping" msgstr "ping值" #: builtin/mainmenu/tab_online.lua -#, fuzzy msgid "" "Players:\n" "$1" msgstr "" -"客户端:\n" +"玩家::\n" "$1" #: builtin/mainmenu/tab_online.lua @@ -2247,7 +2243,7 @@ msgstr "溢出菜单" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp #, fuzzy msgid "Place/use" -msgstr "放置键" +msgstr "放置或使用" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Range select" @@ -2262,9 +2258,8 @@ msgid "Toggle chat log" msgstr "启用/禁用聊天记录" #: src/gui/touchscreenlayout.cpp -#, fuzzy msgid "Toggle debug" -msgstr "启用/禁用雾" +msgstr "显示/隐藏调试信息" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Toggle fast" @@ -2301,9 +2296,8 @@ msgid "Internal server error" msgstr "内部服务器错误" #: src/network/clientpackethandler.cpp -#, fuzzy msgid "Invalid password" -msgstr "旧密码" +msgstr "密码错误" #. ~ DO NOT TRANSLATE THIS LITERALLY! #. This is a special string which needs to contain the translation's @@ -2977,9 +2971,8 @@ msgid "Client" msgstr "客户端" #: src/settings_translation_file.cpp -#, fuzzy msgid "Client Debugging" -msgstr "调试" +msgstr "客户端调试" #: src/settings_translation_file.cpp msgid "Client Mesh Chunksize" @@ -3233,7 +3226,6 @@ msgid "Decrease view range" msgstr "减少可视范围" #: src/settings_translation_file.cpp -#, fuzzy msgid "Decrease volume" msgstr "减小音量" @@ -3446,9 +3438,8 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Drop item" -msgstr "丢弃物品键" +msgstr "丢弃物品" #: src/settings_translation_file.cpp msgid "Dump the mapgen debug information." @@ -4133,164 +4124,132 @@ msgstr "" "单位为方块每二次方秒。" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 1" -msgstr "快捷栏1键" +msgstr "快捷栏第1项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 10" -msgstr "快捷栏10键" +msgstr "快捷栏第10项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 11" -msgstr "快捷栏11键" +msgstr "快捷栏第11项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 12" -msgstr "快捷栏12键" +msgstr "快捷栏第12项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 13" -msgstr "快捷栏13键" +msgstr "快捷栏第13项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 14" -msgstr "快捷栏14键" +msgstr "快捷栏第14项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 15" -msgstr "快捷栏15键" +msgstr "快捷栏第15项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 16" -msgstr "快捷栏16键" +msgstr "快捷栏第16项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 17" -msgstr "快捷栏17键" +msgstr "快捷栏第17项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 18" -msgstr "快捷栏18键" +msgstr "快捷栏第18项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 19" -msgstr "快捷栏19键" +msgstr "快捷栏第19项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 2" -msgstr "快捷栏2键" +msgstr "快捷栏第2项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 20" -msgstr "快捷栏20键" +msgstr "快捷栏第20项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 21" -msgstr "快捷栏21键" +msgstr "快捷栏第21项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 22" -msgstr "快捷栏22键" +msgstr "快捷栏第22项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 23" -msgstr "快捷栏23键" +msgstr "快捷栏第23项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 24" -msgstr "快捷栏24键" +msgstr "快捷栏第24项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 25" -msgstr "快捷栏25键" +msgstr "快捷栏第25项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 26" -msgstr "快捷栏26键" +msgstr "快捷栏第26项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 27" -msgstr "快捷栏27键" +msgstr "快捷栏第27项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 28" -msgstr "快捷栏28键" +msgstr "快捷栏第28项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 29" -msgstr "快捷栏29键" +msgstr "快捷栏第29项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 3" -msgstr "快捷栏3键" +msgstr "快捷栏第3项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 30" -msgstr "快捷栏30键" +msgstr "快捷栏第30项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 31" -msgstr "快捷栏31键" +msgstr "快捷栏第31项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 32" -msgstr "快捷栏32键" +msgstr "快捷栏第32项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 4" -msgstr "快捷栏4键" +msgstr "快捷栏第4项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 5" -msgstr "快捷栏5键" +msgstr "快捷栏第5项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 6" -msgstr "快捷栏6键" +msgstr "快捷栏第6项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 7" -msgstr "快捷栏7键" +msgstr "快捷栏第7项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 8" -msgstr "快捷栏8键" +msgstr "快捷栏第8项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar slot 9" -msgstr "快捷栏9键" +msgstr "快捷栏第9项" #: src/settings_translation_file.cpp msgid "Hotbar: Enable mouse wheel for selection" @@ -4301,14 +4260,12 @@ msgid "Hotbar: Invert mouse wheel direction" msgstr "快捷栏:反转鼠标滚轮方向" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar: select next item" -msgstr "快捷栏下一个键" +msgstr "选择快捷栏下一项" #: src/settings_translation_file.cpp -#, fuzzy msgid "Hotbar: select previous item" -msgstr "快捷栏上一个键" +msgstr "选择快捷栏上一项" #: src/settings_translation_file.cpp msgid "How deep to make rivers." @@ -4513,7 +4470,6 @@ msgid "Increase view range" msgstr "增加可视范围" #: src/settings_translation_file.cpp -#, fuzzy msgid "Increase volume" msgstr "增大音量" @@ -5031,9 +4987,8 @@ msgid "Key to use view zoom when possible." msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Keybindings" -msgstr "按键绑定。" +msgstr "按键绑定" #: src/settings_translation_file.cpp msgid "Keyboard and Mouse" @@ -5072,9 +5027,8 @@ msgid "Large cave proportion flooded" msgstr "大型洞穴淹没比" #: src/settings_translation_file.cpp -#, fuzzy msgid "Large chat console" -msgstr "大型聊天控制台键" +msgstr "大型聊天控制台" #: src/settings_translation_file.cpp msgid "Leaves style" @@ -5714,23 +5668,20 @@ msgid "Mouse sensitivity multiplier." msgstr "鼠标灵敏度倍数。" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move backward" -msgstr "向后" +msgstr "后退" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move forward" -msgstr "自动向前" +msgstr "前进" #: src/settings_translation_file.cpp -#, fuzzy msgid "Move left" -msgstr "移动" +msgstr "往左移动" #: src/settings_translation_file.cpp msgid "Move right" -msgstr "" +msgstr "往右移动" #: src/settings_translation_file.cpp #, fuzzy @@ -5879,14 +5830,12 @@ msgid "" msgstr "默认字体后阴影的透明度(alpha),取值范围0~255。" #: src/settings_translation_file.cpp -#, fuzzy msgid "Open chat" -msgstr "打开" +msgstr "打开聊天室" #: src/settings_translation_file.cpp -#, fuzzy msgid "Open inventory" -msgstr "物品栏" +msgstr "打开物品栏" #: src/settings_translation_file.cpp msgid "" @@ -7174,14 +7123,12 @@ msgid "Toggle Sneak key" msgstr "启用/禁用拍照模式键" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle automatic forward" -msgstr "自动前进键" +msgstr "启用/禁用自动前进" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle block bounds" -msgstr "地图块边界" +msgstr "显示/隐藏地图块边界" #: src/settings_translation_file.cpp #, fuzzy @@ -7199,18 +7146,16 @@ msgid "Toggle cinematic mode" msgstr "切换电影模式" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle debug info" -msgstr "启用/禁用雾" +msgstr "显示/隐藏调试信息" #: src/settings_translation_file.cpp msgid "Toggle fog" msgstr "启用/禁用雾" #: src/settings_translation_file.cpp -#, fuzzy msgid "Toggle fullscreen" -msgstr "启用/禁用飞行模式" +msgstr "切换全屏模式" #: src/settings_translation_file.cpp msgid "Toggle pitchmove" @@ -7238,9 +7183,8 @@ msgid "Touchscreen" msgstr "触摸屏" #: src/settings_translation_file.cpp -#, fuzzy msgid "Touchscreen controls" -msgstr "触屏阈值" +msgstr "触屏控制" #: src/settings_translation_file.cpp msgid "Touchscreen sensitivity" From f6c933d891e0bc8f4e794c7d4d4898513b4f91d5 Mon Sep 17 00:00:00 2001 From: BlackImpostor Date: Fri, 16 May 2025 13:20:49 +0200 Subject: [PATCH 264/284] Translated using Weblate (Russian) Currently translated at 100.0% (1531 of 1531 strings) --- po/ru/luanti.po | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/po/ru/luanti.po b/po/ru/luanti.po index a9109e95d..c31366bc0 100644 --- a/po/ru/luanti.po +++ b/po/ru/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: Russian (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-05-03 05:01+0000\n" -"Last-Translator: Nana_M \n" +"PO-Revision-Date: 2025-05-16 16:16+0000\n" +"Last-Translator: BlackImpostor \n" "Language-Team: Russian \n" "Language: ru\n" @@ -934,29 +934,27 @@ msgstr "Удалить мир «$1»?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "В результате ваши привязки клавиш, возможно, были изменены." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Ознакомьтесь с настройками клавиш или обратитесь к документации:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Закрыть" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Привязки клавиш" +msgstr "Изменены привязки клавиш" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Настройки" +msgstr "Открыть настройки" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "Система обработки входных данных была переработана в Luanti 5.12.0." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" @@ -2250,7 +2248,7 @@ msgstr "Бросить" #: src/gui/touchscreenlayout.cpp msgid "Exit" -msgstr "Закрыть" +msgstr "Выйти" #: src/gui/touchscreenlayout.cpp msgid "Inventory" From db561ff094fb3577de4dc9c0ea9137b364cf5509 Mon Sep 17 00:00:00 2001 From: Ian Pedras Date: Sun, 18 May 2025 11:55:00 +0200 Subject: [PATCH 265/284] Translated using Weblate (Portuguese) Currently translated at 80.6% (1235 of 1531 strings) --- po/pt/luanti.po | 86 ++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/po/pt/luanti.po b/po/pt/luanti.po index 86a3f1344..974f76346 100644 --- a/po/pt/luanti.po +++ b/po/pt/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: Portuguese (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-05-12 16:41+0000\n" -"Last-Translator: Felipe Amaral \n" +"PO-Revision-Date: 2025-05-18 15:01+0000\n" +"Last-Translator: Ian Pedras \n" "Language-Team: Portuguese \n" "Language: pt\n" @@ -934,30 +934,29 @@ msgid "Delete World \"$1\"?" msgstr "Eliminar mundo \"$1\"?" #: builtin/mainmenu/dlg_rebind_keys.lua +#, fuzzy msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Como resultado, os seus comandos foram mudados." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Veja as configurações ou refera a decumentação:" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Fechar" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Combinações de teclas." +msgstr "Combinações de teclas mudadas" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Definições" +msgstr "Abrir Definições" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "O sistema de combinações de teclas foi remodelado no Luanti 5.12.0." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" @@ -2058,7 +2057,7 @@ msgstr "Scroll Lock" #. ~ Key name #: src/client/keycode.cpp msgid "Select" -msgstr "Seleccionar" +msgstr "Selecionar" #: src/client/keycode.cpp msgid "Shift Key" @@ -2115,14 +2114,14 @@ msgid "Minimap in texture mode" msgstr "Minimapa em modo de textura" #: src/client/shader.cpp -#, fuzzy, c-format +#, c-format msgid "Failed to compile the \"%s\" shader." -msgstr "Falha ao abrir página da web" +msgstr "Falha ao compilar o \"%s\" shader." #: src/client/shader.cpp #, fuzzy msgid "GLSL is not supported by the driver" -msgstr "Som do sistema não é suportado nesta versão" +msgstr "GLSL não é suportado pelo seu sistema" #. ~ Error when a mod is missing dependencies. Ex: "Mod Title is missing: mod1, mod2, mod3" #: src/content/mod_configuration.cpp @@ -2168,11 +2167,11 @@ msgstr "Continuar" #: src/gui/guiOpenURL.cpp msgid "Open" -msgstr "" +msgstr "Abrir" #: src/gui/guiOpenURL.cpp msgid "Open URL?" -msgstr "" +msgstr "Abrir URL?" #: src/gui/guiOpenURL.cpp #, fuzzy @@ -2207,33 +2206,34 @@ msgstr "Volume do som: %d%%" #: src/gui/touchscreeneditor.cpp #, fuzzy msgid "Add button" -msgstr "Roda do Rato" +msgstr "Adicionar botão" #: src/gui/touchscreeneditor.cpp -#, fuzzy msgid "Done" -msgstr "Feito!" +msgstr "Feito" #: src/gui/touchscreeneditor.cpp #, fuzzy msgid "Remove" -msgstr "Servidor remoto" +msgstr "Remover" #: src/gui/touchscreeneditor.cpp +#, fuzzy msgid "Reset" -msgstr "" +msgstr "Refazer" #: src/gui/touchscreeneditor.cpp +#, fuzzy msgid "Start dragging a button to add. Tap outside to cancel." -msgstr "" +msgstr "Começe a arrastar um botão para adicionar. Clique fora para cancelar." #: src/gui/touchscreeneditor.cpp msgid "Tap a button to select it. Drag a button to move it." -msgstr "" +msgstr "Clique num botão para selecioná-lo. Arraste o botão para movê-lo." #: src/gui/touchscreeneditor.cpp msgid "Tap outside to deselect." -msgstr "" +msgstr "Click fora para desselecionar." #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Aux1" @@ -2245,7 +2245,7 @@ msgstr "Mudar camera" #: src/gui/touchscreenlayout.cpp src/settings_translation_file.cpp msgid "Dig/punch/use" -msgstr "" +msgstr "Escavar/Attacar/Usar" #: src/gui/touchscreenlayout.cpp msgid "Drop" @@ -2370,27 +2370,32 @@ msgstr "" #: src/network/clientpackethandler.cpp msgid "The server is running in singleplayer mode. You cannot connect." -msgstr "" +msgstr "Este servidor está a correr no modo uni jogador. Não se pode connectar." #: src/network/clientpackethandler.cpp msgid "Too many users" -msgstr "" +msgstr "Demasiados utilizadores" #: src/network/clientpackethandler.cpp msgid "Unknown disconnect reason." -msgstr "" +msgstr "Razão de desconecto desconhecido." #: src/network/clientpackethandler.cpp +#, fuzzy msgid "" "Your client sent something the server didn't expect. Try reconnecting or " "updating your client." msgstr "" +"O seu cliente mandou algo que o servidor não esperava. Tente reconectar ou " +"atualizar o seu cliente." #: src/network/clientpackethandler.cpp msgid "" "Your client's version is not supported.\n" "Please contact the server administrator." msgstr "" +"A versão do seu cliente não é supportado.\n" +"Por favor contacte o administrador do servidor." #: src/server.cpp #, c-format @@ -2465,7 +2470,7 @@ msgstr "Ruído 2D que localiza os vales e canais dos rios." #: src/settings_translation_file.cpp msgid "3D" -msgstr "" +msgstr "3D" #: src/settings_translation_file.cpp msgid "3D clouds" @@ -2638,13 +2643,13 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp -#, fuzzy msgid "Allow clouds to look 3D instead of flat." msgstr "Usar nuvens 3D em vez de planas." #: src/settings_translation_file.cpp +#, fuzzy msgid "Allows liquids to be translucent." -msgstr "" +msgstr "Permitir liquidos semi transparentes." #: src/settings_translation_file.cpp msgid "" @@ -2697,8 +2702,9 @@ msgid "Anticheat flags" msgstr "" #: src/settings_translation_file.cpp +#, fuzzy msgid "Anticheat movement tolerance" -msgstr "" +msgstr "Tolerância de movimentos anti-batota" #: src/settings_translation_file.cpp msgid "Append item name" @@ -2724,8 +2730,9 @@ msgid "" msgstr "" #: src/settings_translation_file.cpp +#, fuzzy msgid "Apply specular shading to nodes." -msgstr "" +msgstr "Aplicar shading especular aos nodes." #: src/settings_translation_file.cpp msgid "Arm inertia" @@ -2824,9 +2831,8 @@ msgid "Base terrain height." msgstr "Altura base do terreno." #: src/settings_translation_file.cpp -#, fuzzy msgid "Base texture size" -msgstr "Tamanho mínimo da textura" +msgstr "Tamanho base da textura" #: src/settings_translation_file.cpp msgid "Basic privileges" @@ -2849,9 +2855,8 @@ msgid "Bind address" msgstr "Endereço de bind" #: src/settings_translation_file.cpp -#, fuzzy msgid "Biome API" -msgstr "Biomas" +msgstr "API de Biomas" #: src/settings_translation_file.cpp msgid "Biome noise" @@ -3016,13 +3021,12 @@ msgid "Client" msgstr "Cliente" #: src/settings_translation_file.cpp -#, fuzzy msgid "Client Debugging" -msgstr "Debugging" +msgstr "Debugging de cliente" #: src/settings_translation_file.cpp msgid "Client Mesh Chunksize" -msgstr "" +msgstr "Tamhãnho do Client Mesh Chunksize" #: src/settings_translation_file.cpp msgid "Client and Server" @@ -3063,7 +3067,7 @@ msgstr "Nuvens no menu" #: src/settings_translation_file.cpp msgid "Color depth for post-processing texture" -msgstr "" +msgstr "Profundidade da cor para a textura post-processing" #: src/settings_translation_file.cpp msgid "Colored fog" From fc1d57b6664ba65c21362db160fce3e9ed07e172 Mon Sep 17 00:00:00 2001 From: waxtatect Date: Sun, 18 May 2025 19:19:47 +0200 Subject: [PATCH 266/284] Translated using Weblate (French) Currently translated at 100.0% (1531 of 1531 strings) --- po/fr/luanti.po | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/po/fr/luanti.po b/po/fr/luanti.po index fa6fa8839..089ba13c3 100644 --- a/po/fr/luanti.po +++ b/po/fr/luanti.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: French (Minetest)\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2025-04-28 18:58+0000\n" +"PO-Revision-Date: 2025-05-18 17:31+0000\n" "Last-Translator: waxtatect \n" "Language-Team: French \n" @@ -938,29 +938,27 @@ msgstr "Supprimer le monde « $1 » ?" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "As a result, your keybindings may have been changed." -msgstr "" +msgstr "Ainsi, il est possible que certaines touches aient été modifiées." #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Check out the key settings or refer to the documentation:" -msgstr "" +msgstr "Vérifier les paramètres des touches ou consulter la documentation :" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "Close" -msgstr "" +msgstr "Fermer" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Keybindings changed" -msgstr "Raccourcis clavier" +msgstr "Modification des raccourcis clavier" #: builtin/mainmenu/dlg_rebind_keys.lua -#, fuzzy msgid "Open settings" -msgstr "Paramètres" +msgstr "Ouvrir les paramètres" #: builtin/mainmenu/dlg_rebind_keys.lua msgid "The input handling system was reworked in Luanti 5.12.0." -msgstr "" +msgstr "Le système d'affectation des touches a été remanié dans Luanti 5.12.0." #: builtin/mainmenu/dlg_register.lua src/gui/guiPasswordChange.cpp msgid "Confirm Password" From b459d6ee6352a7194c5d2f01cfe2ef0dfd1d1f96 Mon Sep 17 00:00:00 2001 From: Josu Igoa Date: Wed, 21 May 2025 11:19:48 +0200 Subject: [PATCH 267/284] Translated using Weblate (Basque) Currently translated at 21.0% (323 of 1531 strings) --- po/eu/luanti.po | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/po/eu/luanti.po b/po/eu/luanti.po index 39e391e21..5e8dc52e7 100644 --- a/po/eu/luanti.po +++ b/po/eu/luanti.po @@ -3,8 +3,8 @@ msgstr "" "Project-Id-Version: minetest\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-14 23:02+0200\n" -"PO-Revision-Date: 2022-04-29 20:12+0000\n" -"Last-Translator: JonAnder Oier \n" +"PO-Revision-Date: 2025-05-21 10:48+0000\n" +"Last-Translator: Josu Igoa \n" "Language-Team: Basque \n" "Language: eu\n" @@ -12,7 +12,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.12.1\n" +"X-Generator: Weblate 5.12-dev\n" #: builtin/client/chatcommands.lua msgid "Clear the out chat queue" @@ -23,9 +23,8 @@ msgid "Empty command." msgstr "Agindu hutsa." #: builtin/client/chatcommands.lua -#, fuzzy msgid "Exit to main menu" -msgstr "Itzuli menu nagusira" +msgstr "Irten menu nagusira" #: builtin/client/chatcommands.lua msgid "Invalid command: " @@ -64,9 +63,8 @@ msgid "Command not available: " msgstr "Komandoa ez dago eskuragarri: " #: builtin/common/chatcommands.lua -#, fuzzy msgid "Get help for commands (-t: output in chat)" -msgstr "Eskuratu laguntza komandoetarako" +msgstr "Eskuratu laguntza komandoetarako (-t: irteera txat-ean)" #: builtin/common/chatcommands.lua msgid "" @@ -76,9 +74,8 @@ msgstr "" "zerrendatzeko." #: builtin/common/chatcommands.lua -#, fuzzy msgid "[all | ] [-t]" -msgstr "[guztia | ]" +msgstr "[all | ] [-t]" #: builtin/common/settings/components.lua msgid "Browse" From 9b2aeb2ca27fe67ae261596a4fcd2dd7d4dbcd14 Mon Sep 17 00:00:00 2001 From: "updatepo.sh" Date: Fri, 23 May 2025 17:09:44 +0200 Subject: [PATCH 268/284] Update minetest.conf.example --- minetest.conf.example | 240 +++++++++++++++++++++--------------------- 1 file changed, 118 insertions(+), 122 deletions(-) diff --git a/minetest.conf.example b/minetest.conf.example index d9b7a58ff..29cbb207b 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -95,366 +95,366 @@ ### Keybindings # Key for moving the player forward. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_forward = KEY_KEY_W +# keymap_forward = SYSTEM_SCANCODE_26 # Key for moving the player backward. # Will also disable autoforward, when active. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_backward = KEY_KEY_S +# keymap_backward = SYSTEM_SCANCODE_22 # Key for moving the player left. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_left = KEY_KEY_A +# keymap_left = SYSTEM_SCANCODE_4 # Key for moving the player right. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_right = KEY_KEY_D +# keymap_right = SYSTEM_SCANCODE_7 # Key for jumping. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_jump = KEY_SPACE +# keymap_jump = SYSTEM_SCANCODE_44 # Key for sneaking. # Also used for climbing down and descending in water if aux1_descends is disabled. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_sneak = KEY_LSHIFT +# keymap_sneak = SYSTEM_SCANCODE_225 # Key for digging, punching or using something. # (Note: The actual meaning might vary on a per-game basis.) -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_dig = KEY_LBUTTON # Key for placing an item/block or for using something. # (Note: The actual meaning might vary on a per-game basis.) -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_place = KEY_RBUTTON # Key for opening the inventory. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_inventory = KEY_KEY_I +# keymap_inventory = SYSTEM_SCANCODE_12 # Key for moving fast in fast mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_aux1 = KEY_KEY_E +# keymap_aux1 = SYSTEM_SCANCODE_8 # Key for opening the chat window. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_chat = KEY_KEY_T +# keymap_chat = SYSTEM_SCANCODE_23 # Key for opening the chat window to type commands. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_cmd = / +# keymap_cmd = SYSTEM_SCANCODE_56 # Key for opening the chat window to type local commands. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_cmd_local = . +# keymap_cmd_local = SYSTEM_SCANCODE_55 # Key for toggling unlimited view range. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_rangeselect = # Key for toggling flying. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_freemove = KEY_KEY_K +# keymap_freemove = SYSTEM_SCANCODE_14 # Key for toggling pitch move mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_pitchmove = # Key for toggling fast mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_fastmove = KEY_KEY_J +# keymap_fastmove = SYSTEM_SCANCODE_13 # Key for toggling noclip mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_noclip = KEY_KEY_H +# keymap_noclip = SYSTEM_SCANCODE_11 # Key for selecting the next item in the hotbar. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_hotbar_next = KEY_KEY_N +# keymap_hotbar_next = SYSTEM_SCANCODE_17 # Key for selecting the previous item in the hotbar. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_hotbar_previous = KEY_KEY_B +# keymap_hotbar_previous = SYSTEM_SCANCODE_5 # Key for muting the game. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_mute = KEY_KEY_M +# keymap_mute = SYSTEM_SCANCODE_16 # Key for increasing the volume. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_increase_volume = # Key for decreasing the volume. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_decrease_volume = # Key for toggling autoforward. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_autoforward = # Key for toggling cinematic mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_cinematic = # Key for toggling display of minimap. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_minimap = KEY_KEY_V +# keymap_minimap = SYSTEM_SCANCODE_25 # Key for taking screenshots. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_screenshot = KEY_F12 +# keymap_screenshot = SYSTEM_SCANCODE_69 # Key for toggling fullscreen mode. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_fullscreen = KEY_F11 +# keymap_fullscreen = SYSTEM_SCANCODE_68 # Key for dropping the currently selected item. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_drop = KEY_KEY_Q +# keymap_drop = SYSTEM_SCANCODE_20 # Key to use view zoom when possible. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_zoom = KEY_KEY_Z +# keymap_zoom = SYSTEM_SCANCODE_29 # Key for toggling the display of the HUD. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_hud = KEY_F1 +# keymap_toggle_hud = SYSTEM_SCANCODE_58 # Key for toggling the display of chat. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_chat = KEY_F2 +# keymap_toggle_chat = SYSTEM_SCANCODE_59 # Key for toggling the display of the large chat console. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_console = KEY_F10 +# keymap_console = SYSTEM_SCANCODE_67 # Key for toggling the display of fog. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_fog = KEY_F3 +# keymap_toggle_fog = SYSTEM_SCANCODE_60 # Key for toggling the display of debug info. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_debug = KEY_F5 +# keymap_toggle_debug = SYSTEM_SCANCODE_62 # Key for toggling the display of the profiler. Used for development. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_toggle_profiler = KEY_F6 +# keymap_toggle_profiler = SYSTEM_SCANCODE_63 # Key for toggling the display of mapblock boundaries. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_toggle_block_bounds = # Key for switching between first- and third-person camera. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_camera_mode = KEY_KEY_C +# keymap_camera_mode = SYSTEM_SCANCODE_6 # Key for increasing the viewing range. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_increase_viewing_range_min = + +# keymap_increase_viewing_range_min = SYSTEM_SCANCODE_46 # Key for decreasing the viewing range. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_decrease_viewing_range_min = - +# keymap_decrease_viewing_range_min = SYSTEM_SCANCODE_45 # Key for selecting the first hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot1 = KEY_KEY_1 +# keymap_slot1 = SYSTEM_SCANCODE_30 # Key for selecting the second hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot2 = KEY_KEY_2 +# keymap_slot2 = SYSTEM_SCANCODE_31 # Key for selecting the third hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot3 = KEY_KEY_3 +# keymap_slot3 = SYSTEM_SCANCODE_32 # Key for selecting the fourth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot4 = KEY_KEY_4 +# keymap_slot4 = SYSTEM_SCANCODE_33 # Key for selecting the fifth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot5 = KEY_KEY_5 +# keymap_slot5 = SYSTEM_SCANCODE_34 # Key for selecting the sixth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot6 = KEY_KEY_6 +# keymap_slot6 = SYSTEM_SCANCODE_35 # Key for selecting the seventh hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot7 = KEY_KEY_7 +# keymap_slot7 = SYSTEM_SCANCODE_36 # Key for selecting the eighth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot8 = KEY_KEY_8 +# keymap_slot8 = SYSTEM_SCANCODE_37 # Key for selecting the ninth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot9 = KEY_KEY_9 +# keymap_slot9 = SYSTEM_SCANCODE_38 # Key for selecting the tenth hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key -# keymap_slot10 = KEY_KEY_0 +# keymap_slot10 = SYSTEM_SCANCODE_39 # Key for selecting the 11th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot11 = # Key for selecting the 12th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot12 = # Key for selecting the 13th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot13 = # Key for selecting the 14th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot14 = # Key for selecting the 15th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot15 = # Key for selecting the 16th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot16 = # Key for selecting the 17th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot17 = # Key for selecting the 18th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot18 = # Key for selecting the 19th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot19 = # Key for selecting the 20th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot20 = # Key for selecting the 21st hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot21 = # Key for selecting the 22nd hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot22 = # Key for selecting the 23rd hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot23 = # Key for selecting the 24th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot24 = # Key for selecting the 25th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot25 = # Key for selecting the 26th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot26 = # Key for selecting the 27th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot27 = # Key for selecting the 28th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot28 = # Key for selecting the 29th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot29 = # Key for selecting the 30th hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot30 = # Key for selecting the 31st hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot31 = # Key for selecting the 32nd hotbar slot. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_slot32 = @@ -925,10 +925,6 @@ # type: bool # enable_translucent_foliage = false -# Apply specular shading to nodes. -# type: bool -# enable_node_specular = false - # When enabled, liquid reflections are simulated. # type: bool # enable_water_reflections = false @@ -3554,27 +3550,27 @@ ### Client Debugging # Key for toggling the camera update. Only usable with 'debug' privilege. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_toggle_update_camera = # Key for switching to the previous entry in Quicktune. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_quicktune_prev = # Key for switching to the next entry in Quicktune. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_quicktune_next = # Key for decrementing the selected value in Quicktune. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_quicktune_dec = # Key for incrementing the selected value in Quicktune. -# See https://github.com/luanti-org/luanti/blob/master/irr/include/Keycodes.h +# See https://docs.luanti.org/for-players/controls/ # type: key # keymap_quicktune_inc = From 8f0838506a83c108ff85f18050ced4f00a51950b Mon Sep 17 00:00:00 2001 From: sfan5 Date: Fri, 23 May 2025 17:43:08 +0200 Subject: [PATCH 269/284] Bump version to 5.12.0 --- CMakeLists.txt | 2 +- misc/org.luanti.luanti.metainfo.xml | 2 +- util/bump_version.sh | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bf2effd1..8977af474 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ set(VERSION_PATCH 0) set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") # Change to false for releases -set(DEVELOPMENT_BUILD TRUE) +set(DEVELOPMENT_BUILD FALSE) set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") if(VERSION_EXTRA) diff --git a/misc/org.luanti.luanti.metainfo.xml b/misc/org.luanti.luanti.metainfo.xml index 0c5b1a717..3e99aa36b 100644 --- a/misc/org.luanti.luanti.metainfo.xml +++ b/misc/org.luanti.luanti.metainfo.xml @@ -174,6 +174,6 @@ celeron55@gmail.com - + diff --git a/util/bump_version.sh b/util/bump_version.sh index 77b4e603b..0fd4f3ae1 100755 --- a/util/bump_version.sh +++ b/util/bump_version.sh @@ -124,10 +124,10 @@ perform_release() { local release_version=$1 RELEASE_DATE=$(date +%Y-%m-%d) - sed -i '/\ Date: Fri, 23 May 2025 17:43:09 +0200 Subject: [PATCH 270/284] Continue with 5.13.0-dev --- CMakeLists.txt | 4 ++-- android/build.gradle | 2 +- doc/client_lua_api.md | 2 +- doc/menu_lua_api.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8977af474..70a027f57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,12 +14,12 @@ set(CLANG_MINIMUM_VERSION "7.0.1") # You should not need to edit these manually, use util/bump_version.sh set(VERSION_MAJOR 5) -set(VERSION_MINOR 12) +set(VERSION_MINOR 13) set(VERSION_PATCH 0) set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") # Change to false for releases -set(DEVELOPMENT_BUILD FALSE) +set(DEVELOPMENT_BUILD TRUE) set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") if(VERSION_EXTRA) diff --git a/android/build.gradle b/android/build.gradle index 61637c2ec..d2a3b3b2f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. project.ext.set("versionMajor", 5) // Version Major -project.ext.set("versionMinor", 12) // Version Minor +project.ext.set("versionMinor", 13) // Version Minor project.ext.set("versionPatch", 0) // Version Patch // ^ keep in sync with cmake diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index aedf0a4ff..017f8b89b 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -1,4 +1,4 @@ -Luanti Lua Client Modding API Reference 5.12.0 +Luanti Lua Client Modding API Reference 5.13.0 ============================================== **WARNING**: if you're looking for the `minetest` namespace (e.g. `minetest.something`), diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md index c0dcc9068..0a067764e 100644 --- a/doc/menu_lua_api.md +++ b/doc/menu_lua_api.md @@ -1,4 +1,4 @@ -Luanti Lua Mainmenu API Reference 5.12.0 +Luanti Lua Mainmenu API Reference 5.13.0 ======================================== Introduction From 2f1171e2a7dd09775a0c7c6898da9f18e32ba6ab Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sat, 24 May 2025 15:58:04 +0200 Subject: [PATCH 271/284] Formspec: Fix broken 9-slice image button with gui_scaling_filter (#16146) The setting 'gui_scaling_filter = true' previously broke 9-slice images. With this change, custom button background images now scale the same as backgrounds created using 'background9[...]' (9-slice images). --- src/gui/guiButton.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index 9592ba922..9975549fe 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -718,19 +718,24 @@ void GUIButton::setFromStyle(const StyleSpec& style) setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true)); setOverrideFont(style.getFont()); + BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle); + if (style.isNotDefault(StyleSpec::BGIMG)) { video::ITexture *texture = style.getTexture(StyleSpec::BGIMG, getTextureSource()); - setImage(guiScalingImageButton( - Environment->getVideoDriver(), texture, - AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); + if (BgMiddle.getArea() == 0) { + setImage(guiScalingImageButton( + Environment->getVideoDriver(), texture, + AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); + } else { + // Scaling happens in `draw2DImage9Slice` + setImage(texture); + } setScaleImage(true); } else { setImage(nullptr); } - BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle); - // Child padding and offset Padding = style.getRect(StyleSpec::PADDING, core::rect()); Padding = core::rect( From d17f22f536cf5a94dda88d4f36b1faf0bb44958e Mon Sep 17 00:00:00 2001 From: cx384 Date: Sat, 24 May 2025 15:59:32 +0200 Subject: [PATCH 272/284] Fix texture coordinates of cuboid drawtypes (#16091) Fixes issues related to combining animated and world-aligned textures. Changes texture coordinates of cuboid drawtypes to stay in the [0,1] range, instead of carrying the mapblock alignment and becoming negative after transformations. --- src/client/content_mapblock.cpp | 111 +++++++++++++++++++++---------- src/client/content_mapblock.h | 2 +- src/client/meshgen/collector.cpp | 12 +--- src/client/meshgen/collector.h | 2 +- 4 files changed, 80 insertions(+), 47 deletions(-) diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 3edba95e3..c07a0a3b1 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -137,11 +137,20 @@ void MapblockMeshGenerator::drawQuad(const TileSpec &tile, v3f *coords, const v3 } static std::array setupCuboidVertices(const aabb3f &box, - const f32 *txc, const TileSpec *tiles, int tilecount) + const f32 *txc, const TileSpec *tiles, int tilecount, v3s16 alignment) { v3f min = box.MinEdge; v3f max = box.MaxEdge; + // Texture coords are [0,1] if not specified otherwise + f32 uniform_txc[24]; + if (!txc) { + for (int i = 0; i != 24; ++i) { + uniform_txc[i] = (i % 4 < 2) ? 0.0f : 1.0f; + } + txc = uniform_txc; + } + std::array vertices = {{ // top video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, {}, txc[0], txc[1]), @@ -185,15 +194,47 @@ static std::array setupCuboidVertices(const aabb3f &box, case TileRotation::None: break; case TileRotation::R90: - tcoords.set(-tcoords.Y, tcoords.X); + tcoords.set(1 - tcoords.Y, tcoords.X); break; case TileRotation::R180: - tcoords.set(-tcoords.X, -tcoords.Y); + tcoords.set(1 - tcoords.X, 1 - tcoords.Y); break; case TileRotation::R270: - tcoords.set(tcoords.Y, -tcoords.X); + tcoords.set(tcoords.Y, 1 - tcoords.X); break; } + + if (tile.world_aligned) { + // Maps uv dimension of every face to world dimension xyz + constexpr int coord_dim[12] = { + 0, 2, // up + 0, 2, // down + 2, 1, // right + 2, 1, // left + 0, 1, // back + 0, 1, // front + }; + + auto scale = tile.layers[0].scale; + f32 scale_factor = 1.0f / scale; + + float x = alignment[coord_dim[face*2]] % scale; + float y = alignment[coord_dim[face*2 + 1]] % scale; + + // Faces grow in different directions + if (face != 1) { + y = tcoords.Y + ((scale-1)-y); + } else { + y = tcoords.Y + y; + } + if (face == 3 || face == 4) { + x = tcoords.X + ((scale-1)-x); + } else { + x = tcoords.X + x; + } + + tcoords.set(x * scale_factor, y * scale_factor); + } } } @@ -212,6 +253,7 @@ enum class QuadDiagonal { // for the opposite corners of each face - therefore, there // 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 +// if nullptr use standard [0,1] coords // (compatible with ContentFeatures). // mask - a bit mask that suppresses drawing of tiles. // tile i will not be drawn if mask & (1 << i) is 1 @@ -224,7 +266,7 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, { assert(tilecount >= 1 && tilecount <= 6); // pre-condition - auto vertices = setupCuboidVertices(box, txc, tiles, tilecount); + auto vertices = setupCuboidVertices(box, txc, tiles, tilecount, cur_node.p); for (int k = 0; k < 6; ++k) { if (mask & (1 << k)) @@ -301,12 +343,13 @@ video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos, void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords) { - f32 tx1 = (box.MinEdge.X / BS) + 0.5; - f32 ty1 = (box.MinEdge.Y / BS) + 0.5; - f32 tz1 = (box.MinEdge.Z / BS) + 0.5; - f32 tx2 = (box.MaxEdge.X / BS) + 0.5; - f32 ty2 = (box.MaxEdge.Y / BS) + 0.5; - f32 tz2 = (box.MaxEdge.Z / BS) + 0.5; + // Generate texture coords which are aligned to coords of a solid nodes + f32 tx1 = (box.MinEdge.X / BS) + 0.5f; + f32 ty1 = (box.MinEdge.Y / BS) + 0.5f; + f32 tz1 = (box.MinEdge.Z / BS) + 0.5f; + f32 tx2 = (box.MaxEdge.X / BS) + 0.5f; + f32 ty2 = (box.MaxEdge.Y / BS) + 0.5f; + f32 tz2 = (box.MaxEdge.Z / BS) + 0.5f; f32 txc[24] = { tx1, 1 - tz2, tx2, 1 - tz1, // up tx1, tz1, tx2, tz2, // down @@ -334,7 +377,6 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const TileSpec *tiles, int tile_count, const f32 *txc, u8 mask) { bool scale = std::fabs(cur_node.f->visual_scale - 1.0f) > 1e-3f; - f32 texture_coord_buf[24]; f32 dx1 = box.MinEdge.X; f32 dy1 = box.MinEdge.Y; f32 dz1 = box.MinEdge.Z; @@ -342,19 +384,11 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, f32 dy2 = box.MaxEdge.Y; f32 dz2 = box.MaxEdge.Z; if (scale) { - if (!txc) { // generate texture coords before scaling - generateCuboidTextureCoords(box, texture_coord_buf); - txc = texture_coord_buf; - } box.MinEdge *= cur_node.f->visual_scale; box.MaxEdge *= cur_node.f->visual_scale; } box.MinEdge += cur_node.origin; box.MaxEdge += cur_node.origin; - if (!txc) { - generateCuboidTextureCoords(box, texture_coord_buf); - txc = texture_coord_buf; - } if (data->m_smooth_lighting) { LightInfo lights[8]; for (int j = 0; j < 8; ++j) { @@ -442,10 +476,8 @@ void MapblockMeshGenerator::drawSolidNode() u8 mask = faces ^ 0b0011'1111; // k-th bit is set if k-th face is to be *omitted*, as expected by cuboid drawing functions. cur_node.origin = intToFloat(cur_node.p, BS); auto box = aabb3f(v3f(-0.5 * BS), v3f(0.5 * BS)); - f32 texture_coord_buf[24]; box.MinEdge += cur_node.origin; box.MaxEdge += cur_node.origin; - generateCuboidTextureCoords(box, texture_coord_buf); if (data->m_smooth_lighting) { LightPair lights[6][4]; for (int face = 0; face < 6; ++face) { @@ -458,7 +490,7 @@ void MapblockMeshGenerator::drawSolidNode() } } - drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) { + drawCuboid(box, tiles, 6, nullptr, mask, [&] (int face, video::S3DVertex vertices[4]) { auto final_lights = lights[face]; for (int j = 0; j < 4; j++) { video::S3DVertex &vertex = vertices[j]; @@ -471,7 +503,7 @@ void MapblockMeshGenerator::drawSolidNode() return QuadDiagonal::Diag02; }); } else { - drawCuboid(box, tiles, 6, texture_coord_buf, mask, [&] (int face, video::S3DVertex vertices[4]) { + drawCuboid(box, tiles, 6, nullptr, mask, [&] (int face, video::S3DVertex vertices[4]) { video::SColor color = encode_light(lights[face], cur_node.f->light_source); if (!cur_node.f->light_source) applyFacesShading(color, vertices[0].Normal); @@ -952,7 +984,10 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]]; if (edge_invisible) continue; - drawAutoLightedCuboid(frame_edges[edge], tiles[1]); + + f32 txc[24]; + generateCuboidTextureCoords(frame_edges[edge], txc); + drawAutoLightedCuboid(frame_edges[edge], tiles[1], txc); } for (int face = 0; face < 6; face++) { @@ -996,16 +1031,17 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() float vlev = (param2 / 63.0f) * 2.0f - 1.0f; TileSpec tile; 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) - ), - tile); + aabb3f box( + -(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) + ); + f32 txc[24]; + generateCuboidTextureCoords(box, txc); + drawAutoLightedCuboid(box, tile, txc); } } @@ -1649,7 +1685,10 @@ void MapblockMeshGenerator::drawNodeboxNode() for (auto &box : boxes) { u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors); - drawAutoLightedCuboid(box, tiles, 6, nullptr, mask); + + f32 txc[24]; + generateCuboidTextureCoords(box, txc); + drawAutoLightedCuboid(box, tiles, 6, txc, mask); } } diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index a4ed6a0fc..9d51ba2bc 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -86,7 +86,7 @@ private: template void drawCuboid(const aabb3f &box, const TileSpec *tiles, int tilecount, const f32 *txc, u8 mask, Fn &&face_lighter); - void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); + static void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); void drawAutoLightedCuboid(aabb3f box, const TileSpec &tile, f32 const *txc = nullptr, u8 mask = 0); void drawAutoLightedCuboid(aabb3f box, const TileSpec *tiles, int tile_count, f32 const *txc = nullptr, u8 mask = 0); u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const; diff --git a/src/client/meshgen/collector.cpp b/src/client/meshgen/collector.cpp index c8b726cde..6e3002624 100644 --- a/src/client/meshgen/collector.cpp +++ b/src/client/meshgen/collector.cpp @@ -14,25 +14,19 @@ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertice const TileLayer *layer = &tile.layers[layernum]; if (layer->empty()) continue; - append(*layer, vertices, numVertices, indices, numIndices, layernum, - tile.world_aligned); + append(*layer, vertices, numVertices, indices, numIndices, layernum); } } void MeshCollector::append(const TileLayer &layer, const video::S3DVertex *vertices, - u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum, - bool use_scale) + u32 numVertices, const u16 *indices, u32 numIndices, u8 layernum) { PreMeshBuffer &p = findBuffer(layer, layernum, numVertices); - f32 scale = 1.0f; - if (use_scale) - scale = 1.0f / layer.scale; - u32 vertex_count = p.vertices.size(); for (u32 i = 0; i < numVertices; i++) { p.vertices.emplace_back(vertices[i].Pos + offset, vertices[i].Normal, - vertices[i].Color, scale * vertices[i].TCoords); + vertices[i].Color, vertices[i].TCoords); m_bounding_radius_sq = std::max(m_bounding_radius_sq, (vertices[i].Pos - m_center_pos).getLengthSQ()); } diff --git a/src/client/meshgen/collector.h b/src/client/meshgen/collector.h index f1f8a1481..876338baa 100644 --- a/src/client/meshgen/collector.h +++ b/src/client/meshgen/collector.h @@ -55,7 +55,7 @@ private: void append(const TileLayer &material, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices, - u8 layernum, bool use_scale = false); + u8 layernum); PreMeshBuffer &findBuffer(const TileLayer &layer, u8 layernum, u32 numVertices); }; From 452160cd000ab77dd35c80ae185966cf7dc28757 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 13 May 2025 17:43:30 +0200 Subject: [PATCH 273/284] Clean up read_tiledef and related parts a bit --- src/client/tile.h | 2 - src/script/common/c_content.cpp | 85 ++++++++++++++++++++------------- src/script/cpp_api/s_node.h | 1 - 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/client/tile.h b/src/client/tile.h index ffbe78bac..3293b4dd1 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -191,8 +191,6 @@ struct TileSpec bool world_aligned = false; //! Tile rotation. TileRotation rotation = TileRotation::None; - //! This much light does the tile emit. - u8 emissive_light = 0; //! The first is base texture, the second is overlay. TileLayer layers[MAX_TILE_LAYERS]; }; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index e4d94b3fc..91aa5b405 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -56,9 +56,16 @@ void read_item_definition(lua_State* L, int index, if (index < 0) index = lua_gettop(L) + 1 + index; - def.type = (ItemType)getenumfield(L, index, "type", - es_ItemType, ITEM_NONE); + def.name.clear(); getstringfield(L, index, "name", def.name); + + { + auto str = getstringfield_default(L, index, "type", ""); + if (!string_to_enum(es_ItemType, def.type, str)) + warningstream << "Item " << def.name + << " has unknown type \"" << str << '"' << std::endl; + } + getstringfield(L, index, "description", def.description); getstringfield(L, index, "short_description", def.short_description); getstringfield(L, index, "inventory_image", def.inventory_image); @@ -605,9 +612,6 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) case NDT_PLANTLIKE: case NDT_FIRELIKE: default_tiling = false; - // "break" is omitted here intentionaly, as PLANTLIKE - // FIRELIKE drawtype both should default to having - // backface_culling to false. [[fallthrough]]; case NDT_MESH: case NDT_LIQUID: @@ -621,7 +625,6 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) break; } - // key at index -2 and value at index if(lua_isstring(L, index)){ // "default_lava.png" tiledef.name = lua_tostring(L, index); @@ -634,7 +637,10 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) // name="default_lava.png" tiledef.name.clear(); getstringfield(L, index, "name", tiledef.name); - getstringfield(L, index, "image", tiledef.name); // MaterialSpec compat. + warn_if_field_exists(L, index, "image", "TileDef", + "Deprecated: new name is \"name\"."); + getstringfield(L, index, "image", tiledef.name); + tiledef.backface_culling = getboolfield_default( L, index, "backface_culling", default_culling); tiledef.tileable_horizontal = getboolfield_default( @@ -659,6 +665,9 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) lua_getfield(L, index, "animation"); tiledef.animation = read_animation_definition(L, -1); lua_pop(L, 1); + } else if (!lua_isnil(L, index)) { + // TODO: should be an error + errorstream << "TileDef: Invalid type! (expected string or table)" << std::endl; } return tiledef; @@ -672,13 +681,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) /* Cache existence of some callbacks */ lua_getfield(L, index, "on_construct"); - if(!lua_isnil(L, -1)) f.has_on_construct = true; + f.has_on_construct = !lua_isnil(L, -1); lua_pop(L, 1); lua_getfield(L, index, "on_destruct"); - if(!lua_isnil(L, -1)) f.has_on_destruct = true; + f.has_on_destruct = !lua_isnil(L, -1); lua_pop(L, 1); lua_getfield(L, index, "after_destruct"); - if(!lua_isnil(L, -1)) f.has_after_destruct = true; + f.has_after_destruct = !lua_isnil(L, -1); lua_pop(L, 1); lua_getfield(L, index, "on_rightclick"); @@ -695,8 +704,13 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) /* Visual definition */ - f.drawtype = (NodeDrawType)getenumfield(L, index, "drawtype", - ScriptApiNode::es_DrawType,NDT_NORMAL); + { + auto str = getstringfield_default(L, index, "drawtype", ""); + if (!string_to_enum(ScriptApiNode::es_DrawType, f.drawtype, str)) + warningstream << "Node " << f.name + << " has unknown drawtype \"" << str << '"' << std::endl; + } + getfloatfield(L, index, "visual_scale", f.visual_scale); /* Meshnode model filename */ @@ -796,10 +810,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) if (lua_toboolean(L, -1)) f.alpha = (f.drawtype == NDT_NORMAL) ? ALPHAMODE_CLIP : ALPHAMODE_BLEND; } else if (check_field_or_nil(L, -1, LUA_TSTRING, "use_texture_alpha")) { - int result = f.alpha; - string_to_enum(ScriptApiNode::es_TextureAlphaMode, result, - std::string(lua_tostring(L, -1))); - f.alpha = static_cast(result); + string_to_enum(ScriptApiNode::es_TextureAlphaMode, f.alpha, lua_tostring(L, -1)); } lua_pop(L, 1); @@ -817,10 +828,18 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) getboolfield(L, index, "post_effect_color_shaded", f.post_effect_color_shaded); - f.param_type = (ContentParamType)getenumfield(L, index, "paramtype", - ScriptApiNode::es_ContentParamType, CPT_NONE); - f.param_type_2 = (ContentParamType2)getenumfield(L, index, "paramtype2", - ScriptApiNode::es_ContentParamType2, CPT2_NONE); + { + auto str = getstringfield_default(L, index, "paramtype", ""); + if (!string_to_enum(ScriptApiNode::es_ContentParamType, f.param_type, str)) + warningstream << "Node " << f.name + << " has unknown paramtype \"" << str << '"' << std::endl; + } + { + auto str = getstringfield_default(L, index, "paramtype2", ""); + if (!string_to_enum(ScriptApiNode::es_ContentParamType2, f.param_type_2, str)) + warningstream << "Node " << f.name + << " has unknown paramtype2 \"" << str << '"' << std::endl; + } if (!f.palette_name.empty() && !(f.param_type_2 == CPT2_COLOR || @@ -855,8 +874,12 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) // Liquids flow into and replace node getboolfield(L, index, "floodable", f.floodable); // Whether the node is non-liquid, source liquid or flowing liquid - f.liquid_type = (LiquidType)getenumfield(L, index, "liquidtype", - ScriptApiNode::es_LiquidType, LIQUID_NONE); + { + auto str = getstringfield_default(L, index, "liquidtype", ""); + if (!string_to_enum(ScriptApiNode::es_LiquidType, f.liquid_type, str)) + warningstream << "Node " << f.name + << " has unknown liquidtype \"" << str << '"' << std::endl; + } // If the content is liquid, this is the flowing version of the liquid. getstringfield(L, index, "liquid_alternative_flowing", f.liquid_alternative_flowing); @@ -915,7 +938,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) lua_pushnil(L); while (lua_next(L, table) != 0) { // Value at -1 - std::string side(lua_tostring(L, -1)); + std::string_view side(lua_tostring(L, -1)); // Note faces are flipped to make checking easier if (side == "top") f.connect_sides |= 2; @@ -986,6 +1009,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) } else if(lua_isnil(L, -1)) { f.liquid_move_physics = f.liquid_type != LIQUID_NONE; } else { + // TODO: should be an error errorstream << "Field \"liquid_move_physics\": Invalid type!" << std::endl; } lua_pop(L, 1); @@ -1805,10 +1829,8 @@ WearBarParams read_wear_bar_params( auto blend = WearBarParams::BLEND_MODE_CONSTANT; lua_getfield(L, stack_idx, "blend"); if (check_field_or_nil(L, -1, LUA_TSTRING, "blend")) { - int blendInt; - if (!string_to_enum(WearBarParams::es_BlendMode, blendInt, std::string(lua_tostring(L, -1)))) + if (!string_to_enum(WearBarParams::es_BlendMode, blend, lua_tostring(L, -1))) throw LuaError("Invalid wear bar color blend mode"); - blend = static_cast(blendInt); } lua_pop(L, 1); @@ -2395,14 +2417,9 @@ void push_hud_element(lua_State *L, HudElement *elem) bool read_hud_change(lua_State *L, HudElementStat &stat, HudElement *elem, void **value) { std::string statstr = lua_tostring(L, 3); - { - int statint; - if (!string_to_enum(es_HudElementStat, statint, statstr)) { - script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream); - return false; - } - - stat = static_cast(statint); + if (!string_to_enum(es_HudElementStat, stat, statstr)) { + script_log_unique(L, "Unknown HUD stat type: " + statstr, warningstream); + return false; } switch (stat) { diff --git a/src/script/cpp_api/s_node.h b/src/script/cpp_api/s_node.h index bf4dc6af5..2c7133df1 100644 --- a/src/script/cpp_api/s_node.h +++ b/src/script/cpp_api/s_node.h @@ -38,7 +38,6 @@ public: static struct EnumString es_ContentParamType[]; static struct EnumString es_ContentParamType2[]; static struct EnumString es_LiquidType[]; - static struct EnumString es_LiquidMoveType[]; static struct EnumString es_NodeBoxType[]; static struct EnumString es_TextureAlphaMode[]; }; From 1214a1d4a6d3023e45feb5be034a83ca30b3c732 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 24 May 2025 22:49:29 +0200 Subject: [PATCH 274/284] Refactor ITextureSource use in main menu (#16135) --- irr/include/IVideoDriver.h | 4 ++-- src/client/clientlauncher.cpp | 19 +++++++++++++++---- src/client/shadows/dynamicshadowsrender.cpp | 2 +- src/gui/guiEngine.cpp | 19 +++++-------------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h index f392eb636..02c9a2d4f 100644 --- a/irr/include/IVideoDriver.h +++ b/irr/include/IVideoDriver.h @@ -172,7 +172,7 @@ public: \return Pointer to the texture, or 0 if the texture could not be loaded. This pointer should not be dropped. See IReferenceCounted::drop() for more information. */ - virtual ITexture *getTexture(const io::path &filename) = 0; + [[deprecated]] virtual ITexture *getTexture(const io::path &filename) = 0; //! Get access to a named texture. /** Loads the texture from disk if it is not @@ -184,7 +184,7 @@ public: \return Pointer to the texture, or 0 if the texture could not be loaded. This pointer should not be dropped. See IReferenceCounted::drop() for more information. */ - virtual ITexture *getTexture(io::IReadFile *file) = 0; + [[deprecated]] virtual ITexture *getTexture(io::IReadFile *file) = 0; //! Returns amount of textures currently loaded /** \return Amount of textures currently loaded */ diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index a770dbcae..64f2e8f51 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -326,6 +326,18 @@ void ClientLauncher::setting_changed_callback(const std::string &name, void *dat static_cast(data)->config_guienv(); } +static video::ITexture *loadTexture(video::IVideoDriver *driver, const char *path) +{ + // FIXME?: it would be cleaner to do this through a ITextureSource, but we don't have one + video::ITexture *texture = nullptr; + verbosestream << "Loading texture " << path << std::endl; + if (auto *image = driver->createImageFromFile(path); image) { + texture = driver->addTexture(fs::GetFilenameFromPath(path), image); + image->drop(); + } + return texture; +} + void ClientLauncher::config_guienv() { gui::IGUISkin *skin = guienv->getSkin(); @@ -364,10 +376,9 @@ void ClientLauncher::config_guienv() if (cached_id != sprite_ids.end()) { skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, cached_id->second); } else { - gui::IGUISpriteBank *sprites = skin->getSpriteBank(); - video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); - video::ITexture *texture = driver->getTexture(path.c_str()); - s32 id = sprites->addTextureAsSprite(texture); + auto *driver = m_rendering_engine->get_video_driver(); + auto *texture = loadTexture(driver, path.c_str()); + s32 id = skin->getSpriteBank()->addTextureAsSprite(texture); if (id != -1) { skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, id); sprite_ids.emplace(path, id); diff --git a/src/client/shadows/dynamicshadowsrender.cpp b/src/client/shadows/dynamicshadowsrender.cpp index 17260e21d..dbabf6dd7 100644 --- a/src/client/shadows/dynamicshadowsrender.cpp +++ b/src/client/shadows/dynamicshadowsrender.cpp @@ -410,7 +410,7 @@ video::ITexture *ShadowRenderer::getSMTexture(const std::string &shadow_map_name shadow_map_name.c_str(), texture_format); } - return m_driver->getTexture(shadow_map_name.c_str()); + return m_driver->findTexture(shadow_map_name.c_str()); } void ShadowRenderer::renderShadowMap(video::ITexture *target, diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 8cc9954fc..aef76aec7 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -74,6 +74,7 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) if (retval) return retval; + verbosestream << "MenuTextureSource: loading " << name << std::endl; video::IImage *image = m_driver->createImageFromFile(name.c_str()); if (!image) return NULL; @@ -597,26 +598,16 @@ void GUIEngine::drawFooter(video::IVideoDriver *driver) bool GUIEngine::setTexture(texture_layer layer, const std::string &texturepath, bool tile_image, unsigned int minsize) { - video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); + m_textures[layer].texture = nullptr; - if (m_textures[layer].texture) { - driver->removeTexture(m_textures[layer].texture); - m_textures[layer].texture = NULL; - } - - if (texturepath.empty() || !fs::PathExists(texturepath)) { + if (texturepath.empty() || !fs::PathExists(texturepath)) return false; - } - m_textures[layer].texture = driver->getTexture(texturepath.c_str()); + m_textures[layer].texture = m_texture_source->getTexture(texturepath); m_textures[layer].tile = tile_image; m_textures[layer].minsize = minsize; - if (!m_textures[layer].texture) { - return false; - } - - return true; + return m_textures[layer].texture != nullptr; } /******************************************************************************/ From e9b32843a5989026b6ee49b525c965577d0ab17d Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 22 May 2025 17:02:22 -0500 Subject: [PATCH 275/284] Make MTP server shutdown flag atomic I noticed this potential data race while reading the code. I have not detected it with TSan in practice. --- src/network/mtp/impl.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/mtp/impl.h b/src/network/mtp/impl.h index 3f81fe27b..b8059f202 100644 --- a/src/network/mtp/impl.h +++ b/src/network/mtp/impl.h @@ -12,6 +12,7 @@ #include "util/numeric.h" #include "porting.h" #include "network/networkprotocol.h" +#include #include #include #include @@ -301,7 +302,7 @@ private: // Backwards compatibility PeerHandler *m_bc_peerhandler; - bool m_shutting_down = false; + std::atomic m_shutting_down = false; }; } // namespace From fa0c09d202537acbd267a38958f16c883359baca Mon Sep 17 00:00:00 2001 From: Josiah VanderZee Date: Thu, 22 May 2025 17:03:11 -0500 Subject: [PATCH 276/284] Do not modify peer timeout on shutdown Shortening the peer timeout was supposedly necessary at some point to work around an unknown bug. I was not able to reproduce the bug running a headless Luanti server on WSL Tumbleweed and connecting with a client on the Windows host. That is not enough to say the issue no longer exists. This commit may cause a regression. The access to change the peer timeout was unsynchronized and done by a different thread than the sending thread, so it was detected by TSan to be a data race. Since this patch deletes the code performing the write, the data race is no longer a concern and no synchronization must be added. --- src/network/mtp/impl.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp index 07bdfc735..d54c116a2 100644 --- a/src/network/mtp/impl.cpp +++ b/src/network/mtp/impl.cpp @@ -1314,11 +1314,6 @@ Connection::~Connection() m_sendThread->stop(); m_receiveThread->stop(); - //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); - // wait for threads to finish m_sendThread->wait(); m_receiveThread->wait(); From da7897a822e37a7b373e49f135318369c5c3ba17 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 27 May 2025 13:56:18 +0200 Subject: [PATCH 277/284] Fix texture double-free in main menu bug introduced in 1214a1d4a6d3023e45feb5be034a83ca30b3c732 --- src/gui/guiEngine.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index aef76aec7..b31f12ef9 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -405,12 +405,6 @@ GUIEngine::~GUIEngine() m_sound_manager.reset(); m_irr_toplefttext->remove(); - - // delete textures - for (image_definition &texture : m_textures) { - if (texture.texture) - m_rendering_engine->get_video_driver()->removeTexture(texture.texture); - } } /******************************************************************************/ From 94a9b94baf5c6626b1f917ef2007e760786401e8 Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Tue, 27 May 2025 18:47:41 +0200 Subject: [PATCH 278/284] Formspec: Fix incorrect cell size when using non-default fonts (#16178) --- src/gui/guiFormSpecMenu.cpp | 10 +++++----- src/gui/guiTable.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 4b9601430..f1e91bd0b 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -1235,10 +1235,14 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item)))); } - //now really show table GUITable *e = new GUITable(Environment, data->current_parent, spec.fid, rect, m_tsrc); + // Apply styling before calculating the cell sizes + auto style = getDefaultStyleForElement("table", name); + e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + e->setOverrideFont(style.getFont()); + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -1252,10 +1256,6 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) if (!str_initial_selection.empty() && str_initial_selection != "0") e->setSelected(stoi(str_initial_selection)); - 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); } diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h index 7dbf17a51..2ade0ee3e 100644 --- a/src/gui/guiTable.h +++ b/src/gui/guiTable.h @@ -85,7 +85,7 @@ public: void setTextList(const std::vector &content, bool transparent); - /* Set generic table options, columns and content */ + /** Set generic table options, columns and content, calculate cell sizes */ // Adds empty strings to end of content if there is an incomplete row void setTable(const TableOptions &options, const TableColumns &columns, From 986cd32f289b4d315dcb645180db8abfc0d2ae86 Mon Sep 17 00:00:00 2001 From: grorp Date: Wed, 28 May 2025 07:29:03 -0400 Subject: [PATCH 279/284] Minor lua_api.md improvements (#16169) --- doc/client_lua_api.md | 5 +- doc/lua_api.md | 174 ++++++++++++++++++++++-------------------- 2 files changed, 96 insertions(+), 83 deletions(-) diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index 017f8b89b..ff4af5ccc 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -5,8 +5,11 @@ Luanti Lua Client Modding API Reference 5.13.0 it's now called `core` due to the renaming of Luanti (formerly Minetest). `minetest` will keep existing as an alias, so that old code won't break. +Note that `core` has already existed since version 0.4.10, so you can use it +safely without breaking backwards compatibility. + * More information at -* Developer Wiki: +* Additional documentation: Introduction ------------ diff --git a/doc/lua_api.md b/doc/lua_api.md index b604b317c..747c523a4 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5,8 +5,11 @@ Luanti Lua Modding API Reference it's now called `core` due to the renaming of Luanti (formerly Minetest). `minetest` will keep existing as an alias, so that old code won't break. +Note that `core` has already existed since version 0.4.10, so you can use it +safely without breaking backwards compatibility. + * More information at -* Developer Wiki: +* Additional documentation: * (Unofficial) Minetest Modding Book by rubenwardy: * Modding tools: @@ -4581,11 +4584,13 @@ and offset the noise variation. The final fractal value noise variation is created as follows: +``` noise = offset + scale * (octave1 + octave2 * persistence + octave3 * persistence ^ 2 + octave4 * persistence ^ 3 + ...) +``` Noise Parameters ---------------- @@ -4699,11 +4704,13 @@ with restraint. The absolute value of each octave's noise variation is used when combining the octaves. The final value noise variation is created as follows: +``` noise = offset + scale * (abs(octave1) + abs(octave2) * persistence + abs(octave3) * persistence ^ 2 + abs(octave4) * persistence ^ 3 + ...) +``` ### Format example @@ -6562,13 +6569,10 @@ Environment access * The actual seed used is the noiseparams seed plus the world seed. * `core.get_value_noise(seeddiff, octaves, persistence, spread)` * Deprecated: use `core.get_value_noise(noiseparams)` instead. - * Return world-specific value noise * `core.get_perlin(noiseparams)` - * Deprecated: use `core.get_value_noise(noiseparams)` instead. - * Return world-specific value noise (was not Perlin noise) + * Deprecated: renamed to `core.get_value_noise` in version 5.12.0. * `core.get_perlin(seeddiff, octaves, persistence, spread)` - * Deprecated: use `core.get_value_noise(noiseparams)` instead. - * Return world-specific value noise (was not Perlin noise) + * Deprecated: renamed to `core.get_value_noise` in version 5.12.0. * `core.get_voxel_manip([pos1, pos2])` * Return voxel manipulator object. * Loads the manipulator from the map if positions are passed. @@ -9061,78 +9065,6 @@ offering very strong randomness. * `get_state()`: return generator state encoded in string * `set_state(state_string)`: restore generator state from encoded string -`ValueNoise` -------------- - -A value noise generator. -It can be created via `ValueNoise()` or `core.get_value_noise()`. -For legacy reasons, it can also be created via `PerlinNoise()` or `core.get_perlin()`, -but the implemented noise is not Perlin noise. -For `core.get_value_noise()`, the actual seed used is the noiseparams seed -plus the world seed, to create world-specific noise. - -* `ValueNoise(noiseparams) -* `ValueNoise(seed, octaves, persistence, spread)` (Deprecated) -* `PerlinNoise(noiseparams)` (Deprecated) -* `PerlinNoise(seed, octaves, persistence, spread)` (Deprecated) - -* `core.get_value_noise(noiseparams)` -* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (Deprecated) -* `core.get_perlin(noiseparams)` (Deprecated) -* `core.get_perlin(seeddiff, octaves, persistence, spread)` (Deprecated) - -### Methods - -* `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}` -* `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}` - -`ValueNoiseMap` ----------------- - -A fast, bulk noise generator. - -It can be created via `ValueNoiseMap(noiseparams, size)` or -`core.get_value_noise_map(noiseparams, size)`. -For legacy reasons, it can also be created via `PerlinNoiseMap(noiseparams, size)` -or `core.get_perlin_map(noiseparams, size)`, but it is not Perlin noise. -For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed -plus the world seed, to create world-specific noise. - -Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted -for 2D noise, and it must be larger than 1 for 3D noise (otherwise -`nil` is returned). - -For each of the functions with an optional `buffer` parameter: If `buffer` is -not nil, this table will be used to store the result instead of creating a new -table. - -### Methods - -* `get_2d_map(pos)`: returns a `` times `` 2D array of 2D noise - with values starting at `pos={x=,y=}` -* `get_3d_map(pos)`: returns a `` times `` times `` - 3D array of 3D noise with values starting at `pos={x=,y=,z=}`. -* `get_2d_map_flat(pos, buffer)`: returns a flat `` element - array of 2D noise with values starting at `pos={x=,y=}` -* `get_3d_map_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise -* `calc_2d_map(pos)`: Calculates the 2d noise map starting at `pos`. The result - is stored internally. -* `calc_3d_map(pos)`: Calculates the 3d noise map starting at `pos`. The result - is stored internally. -* `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array, - returns a slice of the most recently computed noise results. The result slice - begins at coordinates `slice_offset` and takes a chunk of `slice_size`. - E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer - offset y = 20: - `noisevals = noise:get_map_slice({y=20}, {y=2})` - It is important to note that `slice_offset` offset coordinates begin at 1, - and are relative to the starting position of the most recently calculated - noise. - To grab a single vertical column of noise starting at map coordinates - x = 1023, y=1000, z = 1000: - `noise:calc_3d_map({x=1000, y=1000, z=1000})` - `noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1})` - `PlayerMetaRef` --------------- @@ -9184,14 +9116,17 @@ end The map is loaded as the ray advances. If the map is modified after the `Raycast` is created, the changes may or may not have an effect on the object. -It can be created via `Raycast(pos1, pos2, objects, liquids)` or -`core.raycast(pos1, pos2, objects, liquids)` where: +It can be created via `Raycast(pos1, pos2, objects, liquids, pointabilities)` +or `core.raycast(pos1, pos2, objects, liquids, pointabilities)` where: * `pos1`: start of the ray * `pos2`: end of the ray -* `objects`: if false, only nodes will be returned. Default is true. +* `objects`: if false, only nodes will be returned. Default is `true`. * `liquids`: if false, liquid nodes (`liquidtype ~= "none"`) won't be - returned. Default is false. + returned. Default is `false`. +* `pointabilities`: Allows overriding the `pointable` property of + nodes and objects. Uses the same format as the `pointabilities` property + of item definitions. Default is `nil`. ### Limitations @@ -9307,6 +9242,81 @@ to restrictions of JSON. * All methods in MetaDataRef +`ValueNoise` +------------- + +A value noise generator. +It can be created via `ValueNoise()` or `core.get_value_noise()`. +For `core.get_value_noise()`, the actual seed used is the noiseparams seed +plus the world seed, to create world-specific noise. + +* `ValueNoise(noiseparams)` +* `ValueNoise(seed, octaves, persistence, spread)` (deprecated) +* `core.get_value_noise(noiseparams)` +* `core.get_value_noise(seeddiff, octaves, persistence, spread)` (deprecated) + +These were previously called `PerlinNoise()` and `core.get_perlin()`, but the +implemented noise was not Perlin noise. They were renamed in 5.12.0. The old +names still exist as aliases. + +### Methods + +* `get_2d(pos)`: returns 2D noise value at `pos={x=,y=}` +* `get_3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}` + +`ValueNoiseMap` +---------------- + +A fast, bulk noise generator. + +It can be created via `ValueNoiseMap(noiseparams, size)` or +`core.get_value_noise_map(noiseparams, size)`. +For `core.get_value_noise_map()`, the actual seed used is the noiseparams seed +plus the world seed, to create world-specific noise. + +These were previously called `PerlinNoiseMap()` and `core.get_perlin_map()`, +but the implemented noise was not Perlin noise. They were renamed in 5.12.0. +The old names still exist as aliases. + +Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` component is omitted +for 2D noise, and it must be larger than 1 for 3D noise (otherwise +`nil` is returned). + +For each of the functions with an optional `buffer` parameter: If `buffer` is +not nil, this table will be used to store the result instead of creating a new +table. + +### Methods + +* `get_2d_map(pos)`: returns a `` times `` 2D array of 2D noise + with values starting at `pos={x=,y=}` +* `get_3d_map(pos)`: returns a `` times `` times `` + 3D array of 3D noise with values starting at `pos={x=,y=,z=}`. +* `get_2d_map_flat(pos, buffer)`: returns a flat `` element + array of 2D noise with values starting at `pos={x=,y=}` +* `get_3d_map_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise +* `calc_2d_map(pos)`: Calculates the 2d noise map starting at `pos`. The result + is stored internally. +* `calc_3d_map(pos)`: Calculates the 3d noise map starting at `pos`. The result + is stored internally. +* `get_map_slice(slice_offset, slice_size, buffer)`: In the form of an array, + returns a slice of the most recently computed noise results. The result slice + begins at coordinates `slice_offset` and takes a chunk of `slice_size`. + E.g., to grab a 2-slice high horizontal 2d plane of noise starting at buffer + offset `y = 20`: + ```lua + noisevals = noise:get_map_slice({y=20}, {y=2}) + ``` + It is important to note that `slice_offset` offset coordinates begin at 1, + and are relative to the starting position of the most recently calculated + noise. + To grab a single vertical column of noise starting at map coordinates + `x = 1023, y=1000, z = 1000`: + ```lua + noise:calc_3d_map({x=1000, y=1000, z=1000}) + noisevals = noise:get_map_slice({x=24, z=1}, {x=1, z=1}) + ``` + From ae35f37bc30ccddb0b9ae69341e5d7b4b876099a Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 28 May 2025 13:29:20 +0200 Subject: [PATCH 280/284] Move one CI run to be on 64-bit ARM --- .github/workflows/linux.yml | 14 +++++++------- util/ci/common.sh | 13 ++++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e5c98a901..14d63f4fb 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -132,16 +132,16 @@ jobs: run: | ./util/test_multiplayer.sh - # Build with prometheus-cpp (server-only) - clang_11_prometheus: - name: "clang_11 (PROMETHEUS=1)" - runs-on: ubuntu-22.04 + # Build with prometheus-cpp (server-only), also runs on ARM64 + clang_prometheus_arm: + name: "clang (with Prometheus, ARM64)" + runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 - name: Install deps run: | source ./util/ci/common.sh - install_linux_deps clang-11 + install_linux_deps --headless clang libluajit-5.1-dev - name: Build prometheus-cpp run: ./util/ci/build_prometheus_cpp.sh @@ -150,8 +150,8 @@ jobs: run: | ./util/ci/build.sh env: - CC: clang-11 - CXX: clang++-11 + CC: clang + CXX: clang++ CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0 -DENABLE_CURSES=0" - name: Test diff --git a/util/ci/common.sh b/util/ci/common.sh index 374aabf5f..4ee68c8b9 100644 --- a/util/ci/common.sh +++ b/util/ci/common.sh @@ -2,12 +2,19 @@ # Linux build only install_linux_deps() { + local graphics=1 + if [[ "$1" == "--headless" ]]; then + graphics= + shift + fi local pkgs=( cmake gettext postgresql + libsqlite3-dev libhiredis-dev libogg-dev libgmp-dev libpq-dev + libleveldb-dev libcurl4-openssl-dev libzstd-dev libssl-dev + ) + [ -n "$graphics" ] && pkgs+=( libpng-dev libjpeg-dev libgl1-mesa-dev libsdl2-dev libfreetype-dev - libsqlite3-dev libhiredis-dev libogg-dev libgmp-dev libvorbis-dev - libopenal-dev libpq-dev libleveldb-dev libcurl4-openssl-dev libzstd-dev - libssl-dev + libogg-dev libvorbis-dev libopenal-dev ) sudo apt-get update From a5263dc7ed4108b7ea57c08f82555bc7b2b4053c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sun, 18 May 2025 22:44:20 +0200 Subject: [PATCH 281/284] Do not allow vector components to be nil --- doc/breakages.md | 1 + src/script/common/c_converter.cpp | 69 ++++++++++++++----------------- src/script/common/c_converter.h | 13 +++++- src/script/lua_api/l_env.cpp | 23 +++++++---- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/doc/breakages.md b/doc/breakages.md index 412cf2e41..6c9acbd95 100644 --- a/doc/breakages.md +++ b/doc/breakages.md @@ -25,3 +25,4 @@ This list is largely advisory and items may be reevaluated once the time comes. * remove built-in knockback and related functions entirely * remove `safe` parameter from `core.serialize`, always enforce `safe = true`. possibly error when `loadstring` calls are encountered in `core.deserialize`. +* introduce strict type checking for all instances of `v3s16` / `v3f` read from Lua diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 138063d54..5b7331e47 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -18,6 +18,8 @@ extern "C" { #include #include "common/c_types.h" +static v3d read_v3d(lua_State *L, int index); +static v3d check_v3d(lua_State *L, int index); #define CHECK_TYPE(index, name, type) do { \ int t = lua_type(L, (index)); \ @@ -28,6 +30,13 @@ extern "C" { } \ } while(0) +#define CHECK_NOT_NIL(index, name) do { \ + if (lua_isnoneornil(L, (index))) { \ + throw LuaError(std::string("Invalid ") + (name) + \ + " (value is nil)."); \ + } \ + } while(0) + #define CHECK_FLOAT(value, name) do {\ if (std::isnan(value) || std::isinf(value)) { \ throw LuaError("Invalid float value for '" name \ @@ -35,7 +44,13 @@ extern "C" { } \ } while (0) +// strictly check type of coordinate +// (this won't permit string-to-int conversion, so maybe not the best idea?) #define CHECK_POS_COORD(index, name) CHECK_TYPE(index, "vector coordinate " name, LUA_TNUMBER) +// loosely check type of coordinate +#define CHECK_POS_COORD2(index, name) CHECK_NOT_NIL(index, "vector coordinate " name) + +// Note: not needed when using read_v3_aux #define CHECK_POS_TAB(index) CHECK_TYPE(index, "vector", LUA_TTABLE) @@ -44,6 +59,7 @@ extern "C" { */ static void read_v3_aux(lua_State *L, int index) { + // TODO: someone find out if it's faster to have the type check in Lua too CHECK_POS_TAB(index); lua_pushvalue(L, index); lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_VECTOR); @@ -87,24 +103,12 @@ void push_v2f(lua_State *L, v2f p) v2s16 read_v2s16(lua_State *L, int index) { - v2s16 p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - p.X = lua_tonumber(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - p.Y = lua_tonumber(L, -1); - lua_pop(L, 1); - return p; + return v2s16::from(read_v2f(L, index)); } void push_v2s16(lua_State *L, v2s16 p) { - lua_createtable(L, 0, 2); - lua_pushinteger(L, p.X); - lua_setfield(L, -2, "x"); - lua_pushinteger(L, p.Y); - lua_setfield(L, -2, "y"); + push_v2s32(L, v2s32::from(p)); } void push_v2s32(lua_State *L, v2s32 p) @@ -127,15 +131,7 @@ void push_v2u32(lua_State *L, v2u32 p) v2s32 read_v2s32(lua_State *L, int index) { - v2s32 p; - CHECK_POS_TAB(index); - lua_getfield(L, index, "x"); - p.X = lua_tonumber(L, -1); - lua_pop(L, 1); - lua_getfield(L, index, "y"); - p.Y = lua_tonumber(L, -1); - lua_pop(L, 1); - return p; + return v2s32::from(read_v2f(L, index)); } v2f read_v2f(lua_State *L, int index) @@ -143,9 +139,11 @@ v2f read_v2f(lua_State *L, int index) v2f p; CHECK_POS_TAB(index); lua_getfield(L, index, "x"); + CHECK_POS_COORD2(-1, "x"); p.X = lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, index, "y"); + CHECK_POS_COORD2(-1, "y"); p.Y = lua_tonumber(L, -1); lua_pop(L, 1); return p; @@ -170,30 +168,20 @@ v2f check_v2f(lua_State *L, int index) v3f read_v3f(lua_State *L, int index) { - read_v3_aux(L, index); - float x = lua_tonumber(L, -3); - float y = lua_tonumber(L, -2); - float z = lua_tonumber(L, -1); - lua_pop(L, 3); - return v3f(x, y, z); + return v3f::from(read_v3d(L, index)); } v3f check_v3f(lua_State *L, int index) { - read_v3_aux(L, index); - CHECK_POS_COORD(-3, "x"); - CHECK_POS_COORD(-2, "y"); - CHECK_POS_COORD(-1, "z"); - float x = lua_tonumber(L, -3); - float y = lua_tonumber(L, -2); - float z = lua_tonumber(L, -1); - lua_pop(L, 3); - return v3f(x, y, z); + return v3f::from(check_v3d(L, index)); } v3d read_v3d(lua_State *L, int index) { read_v3_aux(L, index); + CHECK_POS_COORD2(-3, "x"); + CHECK_POS_COORD2(-2, "y"); + CHECK_POS_COORD2(-1, "z"); double x = lua_tonumber(L, -3); double y = lua_tonumber(L, -2); double z = lua_tonumber(L, -1); @@ -286,18 +274,23 @@ video::SColor read_ARGB8(lua_State *L, int index) return std::fmax(0.0, std::fmin(255.0, c)); }; + // FIXME: maybe we should have strict type checks here. compare to is_color_table() + video::SColor color(0); CHECK_TYPE(index, "ARGB color", LUA_TTABLE); lua_getfield(L, index, "a"); color.setAlpha(lua_isnumber(L, -1) ? clamp_col(lua_tonumber(L, -1)) : 0xFF); lua_pop(L, 1); lua_getfield(L, index, "r"); + CHECK_NOT_NIL(-1, "color component R"); color.setRed(clamp_col(lua_tonumber(L, -1))); lua_pop(L, 1); lua_getfield(L, index, "g"); + CHECK_NOT_NIL(-1, "color component G"); color.setGreen(clamp_col(lua_tonumber(L, -1))); lua_pop(L, 1); lua_getfield(L, index, "b"); + CHECK_NOT_NIL(-1, "color component B"); color.setBlue(clamp_col(lua_tonumber(L, -1))); lua_pop(L, 1); return color; diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index b55f1c9c9..2744fa0b5 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -74,13 +74,23 @@ v2f check_v2f(lua_State *L, int index); v3f check_v3f(lua_State *L, int index); v3s16 check_v3s16(lua_State *L, int index); +// TODO: some day we should figure out the type-checking situation so it's done +// everywhere. (right now {x=true, y=false} as v2f is {0,0} with no warning) + +/// @warning relaxed type-checking, prefer `check_v3f`. v3f read_v3f(lua_State *L, int index); +/// @warning relaxed type-checking, prefer `check_v2f`. v2f read_v2f(lua_State *L, int index); +/// @warning relaxed type-checking v2s16 read_v2s16(lua_State *L, int index); +/// @warning relaxed type-checking v2s32 read_v2s32(lua_State *L, int index); +/// @warning relaxed type-checking, prefer `check_v3s16`. +v3s16 read_v3s16(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); +bool is_color_table(lua_State *L, int index); /** * Read a floating-point axis-aligned box from Lua. @@ -95,7 +105,6 @@ 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); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index aafde7540..423479040 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -266,15 +266,22 @@ int ModApiEnv::l_bulk_swap_node(lua_State *L) // get_node_raw(x, y, z) -> content, param1, param2, pos_ok int ModApiEnv::l_get_node_raw(lua_State *L) { - GET_ENV_PTR; + GET_PLAIN_ENV_PTR; - // pos - // mirrors implementation of read_v3s16 (with the exact same rounding) - double x = lua_tonumber(L, 1); - double y = lua_tonumber(L, 2); - double z = lua_tonumber(L, 3); - v3s16 pos = doubleToInt(v3d(x, y, z), 1.0); - // Do it + v3s16 pos; + // mirrors the implementation of read_v3s16 (with the exact same rounding) + { + if (lua_isnoneornil(L, 1)) + throw LuaError("X position is nil"); + if (lua_isnoneornil(L, 2)) + throw LuaError("Y position is nil"); + if (lua_isnoneornil(L, 3)) + throw LuaError("Z position is nil"); + double x = lua_tonumber(L, 1); + double y = lua_tonumber(L, 2); + double z = lua_tonumber(L, 3); + pos = doubleToInt(v3d(x, y, z), 1.0); + } bool pos_ok; MapNode n = env->getMap().getNode(pos, &pos_ok); // Return node and pos_ok From ec16fb33d0fb224220d323120986711b99d2e19e Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 May 2025 17:41:13 +0200 Subject: [PATCH 282/284] Add unit tests for Lua vector reading --- builtin/common/strict.lua | 17 ++- games/devtest/mods/unittests/misc.lua | 12 -- src/unittest/CMakeLists.txt | 1 + src/unittest/test.h | 4 +- src/unittest/test_scriptapi.cpp | 183 ++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 20 deletions(-) create mode 100644 src/unittest/test_scriptapi.cpp diff --git a/builtin/common/strict.lua b/builtin/common/strict.lua index 9bfa8d7a2..b3c4ccce4 100644 --- a/builtin/common/strict.lua +++ b/builtin/common/strict.lua @@ -19,12 +19,14 @@ function meta:__newindex(name, value) return end local info = getinfo(2, "Sl") - local desc = ("%s:%d"):format(info.short_src, info.currentline) - local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) - if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then - core.log("warning", ("Assignment to undeclared global %q inside a function at %s.") - :format(name, desc)) - warned[warn_key] = true + if info ~= nil then + local desc = ("%s:%d"):format(info.short_src, info.currentline) + local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) + if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then + core.log("warning", ("Assignment to undeclared global %q inside a function at %s.") + :format(name, desc)) + warned[warn_key] = true + end end declared[name] = true end @@ -35,6 +37,9 @@ function meta:__index(name) return end local info = getinfo(2, "Sl") + if info == nil then + return + end local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) if not warned[warn_key] and info.what ~= "C" then core.log("warning", ("Undeclared global variable %q accessed at %s:%s") diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 65dc3259e..28cc2c1eb 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -67,18 +67,6 @@ local function test_dynamic_media(cb, player) end unittests.register("test_dynamic_media", test_dynamic_media, {async=true, player=true}) -local function test_v3f_metatable(player) - assert(vector.check(player:get_pos())) -end -unittests.register("test_v3f_metatable", test_v3f_metatable, {player=true}) - -local function test_v3s16_metatable(player, pos) - local node = core.get_node(pos) - local found_pos = core.find_node_near(pos, 0, node.name, true) - assert(vector.check(found_pos)) -end -unittests.register("test_v3s16_metatable", test_v3s16_metatable, {map=true}) - local function test_clear_meta(_, pos) local ref = core.get_meta(pos) diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 9ac275d7f..2a38af68d 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -36,6 +36,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_sao.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_scriptapi.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_serveractiveobjectmgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_server_shutdown_state.cpp diff --git a/src/unittest/test.h b/src/unittest/test.h index dcecb9fb4..7e861a24a 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -41,7 +41,7 @@ public: #define UTEST(x, fmt, ...) \ if (!(x)) { \ char utest_buf[1024]; \ - snprintf(utest_buf, sizeof(utest_buf), fmt, __VA_ARGS__); \ + porting::mt_snprintf(utest_buf, sizeof(utest_buf), fmt, __VA_ARGS__); \ throw TestFailedException(utest_buf, __FILE__, __LINE__); \ } @@ -68,7 +68,7 @@ public: } catch (EType &e) { \ exception_thrown = true; \ } \ - UASSERT(exception_thrown); \ + UTEST(exception_thrown, "Exception %s not thrown", #EType); \ } class IGameDef; diff --git a/src/unittest/test_scriptapi.cpp b/src/unittest/test_scriptapi.cpp new file mode 100644 index 000000000..4fc03e6c5 --- /dev/null +++ b/src/unittest/test_scriptapi.cpp @@ -0,0 +1,183 @@ +// Luanti +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (C) 2022 Minetest core developers & community + +#include "test.h" + +#include +#include "script/cpp_api/s_base.h" +#include "script/lua_api/l_util.h" +#include "script/lua_api/l_settings.h" +#include "script/common/c_converter.h" +#include "irrlicht_changes/printing.h" +#include "server.h" + +namespace { + class MyScriptApi : virtual public ScriptApiBase { + public: + MyScriptApi() : ScriptApiBase(ScriptingType::Async) {}; + void init(); + using ScriptApiBase::getStack; + }; +} + +class TestScriptApi : public TestBase +{ +public: + TestScriptApi() { TestManager::registerTestModule(this); } + const char *getName() { return "TestScriptApi"; } + + void runTests(IGameDef *gamedef); + + void testVectorMetatable(MyScriptApi *script); + void testVectorRead(MyScriptApi *script); + void testVectorReadErr(MyScriptApi *script); + void testVectorReadMix(MyScriptApi *script); +}; + +static TestScriptApi g_test_instance; + +void MyScriptApi::init() +{ + lua_State *L = getStack(); + + lua_getglobal(L, "core"); + int top = lua_gettop(L); + + // By creating an environment of 'async' type we have the fewest amount + // of external classes needed. + lua_pushstring(L, "async"); + lua_setglobal(L, "INIT"); + + LuaSettings::Register(L); + ModApiUtil::InitializeAsync(L, top); + + lua_pop(L, 1); + + loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", BUILTIN_MOD_NAME); + checkSetByBuiltin(); +} + +void TestScriptApi::runTests(IGameDef *gamedef) +{ + MyScriptApi script; + try { + script.init(); + } catch (ModError &e) { + rawstream << e.what() << std::endl; + num_tests_failed = 1; + return; + } + + TEST(testVectorMetatable, &script); + TEST(testVectorRead, &script); + TEST(testVectorReadErr, &script); + TEST(testVectorReadMix, &script); +} + +// Runs Lua code and leaves `nresults` return values on the stack +static void run(lua_State *L, const char *code, int nresults) +{ + if (luaL_loadstring(L, code) != 0) { + rawstream << lua_tostring(L, -1) << std::endl; + UASSERT(false); + } + if (lua_pcall(L, 0, nresults, 0) != 0) { + throw LuaError(lua_tostring(L, -1)); + } +} + +void TestScriptApi::testVectorMetatable(MyScriptApi *script) +{ + lua_State *L = script->getStack(); + StackUnroller unroller(L); + + const auto &call_vector_check = [&] () -> bool { + lua_setglobal(L, "tmp"); + run(L, "return vector.check(tmp)", 1); + return lua_toboolean(L, -1); + }; + + push_v3s16(L, {1, 2, 3}); + UASSERT(call_vector_check()); + + push_v3f(L, {1, 2, 3}); + UASSERT(call_vector_check()); + + // 2-component vectors must not have this metatable + push_v2s32(L, {0, 0}); + UASSERT(!call_vector_check()); + + push_v2f(L, {0, 0}); + UASSERT(!call_vector_check()); +} + +void TestScriptApi::testVectorRead(MyScriptApi *script) +{ + lua_State *L = script->getStack(); + StackUnroller unroller(L); + + // both methods should parse these + const std::pair pairs1[] = { + {"return {x=1, y=-2, z=3}", {1, -2, 3}}, + {"return {x=1.1, y=0, z=0}", {1, 0, 0}}, + {"return {x=1.5, y=0, z=0}", {2, 0, 0}}, + {"return {x=-1.1, y=0, z=0}", {-1, 0, 0}}, + {"return {x=-1.5, y=0, z=0}", {-2, 0, 0}}, + {"return vector.new(5, 6, 7)", {5, 6, 7}}, + {"return vector.new(32767, 0, -32768)", {S16_MAX, 0, S16_MIN}}, + }; + for (auto &it : pairs1) { + run(L, it.first, 1); + v3s16 v = read_v3s16(L, -1); + UASSERTEQ(auto, v, it.second); + v = check_v3s16(L, -1); + UASSERTEQ(auto, v, it.second); + lua_pop(L, 1); + } +} + +void TestScriptApi::testVectorReadErr(MyScriptApi *script) +{ + lua_State *L = script->getStack(); + StackUnroller unroller(L); + + // both methods should reject these + const char *errs1[] = { + "return {y=1, z=3}", + "return {x=1, z=3}", + "return {x=1, y=3}", + "return {}", + "return 'bamboo'", + "return function() end", + "return nil", + }; + for (auto &it : errs1) { + infostream << it << std::endl; + run(L, it, 1); + EXCEPTION_CHECK(LuaError, read_v3s16(L, -1)); + EXCEPTION_CHECK(LuaError, check_v3s16(L, -1)); + } +} + +void TestScriptApi::testVectorReadMix(MyScriptApi *script) +{ + lua_State *L = script->getStack(); + StackUnroller unroller(L); + + // read_v3s16 should allow these, but check_v3s16 should not + const std::pair pairs2[] = { + {"return {x='3', y='2.9', z=3}", {3, 3, 3}}, + {"return {x=false, y=0, z=0}", {0, 0, 0}}, + {"return {x='?', y=0, z=0}", {0, 0, 0}}, + {"return {x={'baguette'}, y=0, z=0}", {0, 0, 0}}, + }; + for (auto &it : pairs2) { + infostream << it.first << std::endl; + run(L, it.first, 1); + v3s16 v = read_v3s16(L, -1); + UASSERTEQ(auto, v, it.second); + EXCEPTION_CHECK(LuaError, check_v3s16(L, -1)); + lua_pop(L, 1); + } +} From 6ca9d75f0bacfe71a232be4f590cc4f5d15ed068 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 19 May 2025 17:44:59 +0200 Subject: [PATCH 283/284] Reject NaN and Inf in check_v3d() too check_v2f() was already doing this --- src/script/common/c_converter.cpp | 3 +++ src/unittest/test_scriptapi.cpp | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 5b7331e47..a8c9ace3a 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -199,6 +199,9 @@ v3d check_v3d(lua_State *L, int index) double y = lua_tonumber(L, -2); double z = lua_tonumber(L, -1); lua_pop(L, 3); + CHECK_FLOAT(x, "x"); + CHECK_FLOAT(y, "y"); + CHECK_FLOAT(z, "z"); return v3d(x, y, z); } diff --git a/src/unittest/test_scriptapi.cpp b/src/unittest/test_scriptapi.cpp index 4fc03e6c5..0e407daf5 100644 --- a/src/unittest/test_scriptapi.cpp +++ b/src/unittest/test_scriptapi.cpp @@ -180,4 +180,17 @@ void TestScriptApi::testVectorReadMix(MyScriptApi *script) EXCEPTION_CHECK(LuaError, check_v3s16(L, -1)); lua_pop(L, 1); } + + // same but even the result is undefined + const char *errs2[] = { + "return {x=0, y=0, z=0/0}", // nan + "return {x=0, y=0, z=math.huge}", // inf + }; + for (auto &it : errs2) { + infostream << it << std::endl; + run(L, it, 1); + (void)read_v3s16(L, -1); + EXCEPTION_CHECK(LuaError, check_v3s16(L, -1)); + lua_pop(L, 1); + } } From 5c2599315cb6295196a5dc8f96debc930f28b8ad Mon Sep 17 00:00:00 2001 From: sfan5 Date: Mon, 26 May 2025 21:42:21 +0200 Subject: [PATCH 284/284] Change nil-component error to deprecation warning --- src/script/common/c_converter.cpp | 7 +++++-- src/script/lua_api/l_env.cpp | 6 +++--- src/unittest/test_scriptapi.cpp | 8 ++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index a8c9ace3a..2b711bfda 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -30,10 +30,13 @@ static v3d check_v3d(lua_State *L, int index); } \ } while(0) +// TODO: this should be turned into an error in 2026. +// Just revert the commit that added this line. #define CHECK_NOT_NIL(index, name) do { \ if (lua_isnoneornil(L, (index))) { \ - throw LuaError(std::string("Invalid ") + (name) + \ - " (value is nil)."); \ + auto msg = std::string("Invalid ") + (name) + \ + " (value is nil)."; \ + log_deprecated(L, msg, 1, true); \ } \ } while(0) diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 423479040..e21a954ac 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -272,11 +272,11 @@ int ModApiEnv::l_get_node_raw(lua_State *L) // mirrors the implementation of read_v3s16 (with the exact same rounding) { if (lua_isnoneornil(L, 1)) - throw LuaError("X position is nil"); + log_deprecated(L, "X position is nil", 1, true); if (lua_isnoneornil(L, 2)) - throw LuaError("Y position is nil"); + log_deprecated(L, "Y position is nil", 1, true); if (lua_isnoneornil(L, 3)) - throw LuaError("Z position is nil"); + log_deprecated(L, "Z position is nil", 1, true); double x = lua_tonumber(L, 1); double y = lua_tonumber(L, 2); double z = lua_tonumber(L, 3); diff --git a/src/unittest/test_scriptapi.cpp b/src/unittest/test_scriptapi.cpp index 0e407daf5..03c3713b2 100644 --- a/src/unittest/test_scriptapi.cpp +++ b/src/unittest/test_scriptapi.cpp @@ -144,10 +144,6 @@ void TestScriptApi::testVectorReadErr(MyScriptApi *script) // both methods should reject these const char *errs1[] = { - "return {y=1, z=3}", - "return {x=1, z=3}", - "return {x=1, y=3}", - "return {}", "return 'bamboo'", "return function() end", "return nil", @@ -167,6 +163,10 @@ void TestScriptApi::testVectorReadMix(MyScriptApi *script) // read_v3s16 should allow these, but check_v3s16 should not const std::pair pairs2[] = { + {"return {}", {0, 0, 0}}, + {"return {y=1, z=3}", {0, 1, 3}}, + {"return {x=1, z=3}", {1, 0, 3}}, + {"return {x=1, y=3}", {1, 3, 0}}, {"return {x='3', y='2.9', z=3}", {3, 3, 3}}, {"return {x=false, y=0, z=0}", {0, 0, 0}}, {"return {x='?', y=0, z=0}", {0, 0, 0}},