diff --git a/.gitignore b/.gitignore index 34d5687..e54445b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.pch *.sbr build/ +build_debug/ Build/ bin/ Debug/ diff --git a/assets/lang/en_US.json b/assets/lang/en_US.json index bd45502..b0e9e0c 100644 --- a/assets/lang/en_US.json +++ b/assets/lang/en_US.json @@ -4,6 +4,7 @@ "menu.main.join_game": "Join Game", "menu.main.credits": "Credits", "menu.main.quit": "Quit", + "menu.screenshot": "Screenshots", "menu.main.player_name": "Player: {name}", "menu.main.cubed_version": "Cubed: {version}", "menu.pause.pause_menu": "Pause Menu", @@ -46,5 +47,10 @@ "item.snowy_grass_block.name": "snowy grass block", "item.stone.name": "stone", "item.water.name": "water", - "item.pig_spawn_egg.name": "pig spawn egg" + "item.pig_spawn_egg.name": "pig spawn egg", + "screenshot.no_screenshot": "No Screenshot", + "button.show_in_file_manager": "Show In File Manager", + "file.sort_type": "File Sort Type: {type}", + "file.newest_first": "Newest First", + "file.oldest_first": "Oldest First" } \ No newline at end of file diff --git a/assets/lang/zh_CN.json b/assets/lang/zh_CN.json index 362fb53..0b13bda 100644 --- a/assets/lang/zh_CN.json +++ b/assets/lang/zh_CN.json @@ -4,6 +4,7 @@ "menu.main.join_game": "加入游戏", "menu.main.credits": "制作人员", "menu.main.quit": "退出", + "menu.screenshot": "屏幕截图", "menu.main.player_name": "玩家:{name}", "menu.main.cubed_version": "Cubed:{version}", "menu.pause.pause_menu": "暂停菜单", @@ -46,5 +47,10 @@ "item.snowy_grass_block.name": "覆雪草方块", "item.stone.name": "石头", "item.water.name": "水", - "item.pig_spawn_egg.name": "猪猪刷怪蛋" + "item.pig_spawn_egg.name": "猪猪刷怪蛋", + "screenshot.no_screenshot": "没有截图", + "button.show_in_file_manager": "在文件管理器中显示", + "file.sort_type": "文件排序方式:{type}", + "file.newest_first": "最新在前", + "file.oldest_first": "最旧在前" } \ No newline at end of file diff --git a/include/Cubed/audio/audio_engine.hpp b/include/Cubed/audio/audio_engine.hpp index 09b5a1c..1d77270 100644 --- a/include/Cubed/audio/audio_engine.hpp +++ b/include/Cubed/audio/audio_engine.hpp @@ -62,7 +62,7 @@ private: OpusDecoder* m_decoder{nullptr}; AudioRecording m_recording; std::weak_ptr m_client; - glm::vec3 listener_pos; + glm::vec3 listener_pos{0.0f}; std::unique_ptr m_bgm; FadeMap m_fade_map; SoundManager m_sounds; diff --git a/include/Cubed/audio/audio_source.hpp b/include/Cubed/audio/audio_source.hpp index b4a09de..99996bf 100644 --- a/include/Cubed/audio/audio_source.hpp +++ b/include/Cubed/audio/audio_source.hpp @@ -49,6 +49,6 @@ private: ALuint m_source = 0; float m_target_volume = 1.0f; float m_duration = 0.0f; - bool m_using; + bool m_using = false; }; } // namespace Cubed diff --git a/include/Cubed/camera.hpp b/include/Cubed/camera.hpp index 5fe9769..796671a 100644 --- a/include/Cubed/camera.hpp +++ b/include/Cubed/camera.hpp @@ -18,12 +18,12 @@ private: THIRD_PERSON_FRONT, }; - LocalPlayer* m_player; - float m_last_mouse_x, m_last_mouse_y; - glm::vec3 m_camera_pos; + LocalPlayer* m_player = nullptr; + float m_last_mouse_x = 0.0f, m_last_mouse_y = 0.0f; + glm::vec3 m_camera_pos{0.0f}; bool m_under_water = false; Perspective m_perspective = Perspective::FIRST_PERSON; - glm::vec3 m_front; + glm::vec3 m_front{0.0f}; glm::vec3 camera_collision(glm::vec3 start, glm::vec3 end, float radius = 0.2f); diff --git a/include/Cubed/dev_panel.hpp b/include/Cubed/dev_panel.hpp index 28a6935..84b95f9 100644 --- a/include/Cubed/dev_panel.hpp +++ b/include/Cubed/dev_panel.hpp @@ -42,7 +42,7 @@ private: WorldScene& m_world_scene; Config& m_config; ConfigView m_config_view; - LocalPlayer* m_player; + LocalPlayer* m_player = nullptr; PlayerProfile m_player_profile; bool m_need_save_config = false; bool m_gen_thread_running = true; diff --git a/include/Cubed/gameplay/block.hpp b/include/Cubed/gameplay/block.hpp index 8a8056f..4411097 100644 --- a/include/Cubed/gameplay/block.hpp +++ b/include/Cubed/gameplay/block.hpp @@ -22,7 +22,7 @@ struct Block : public BlockTexture {}; struct BlockRenderData { std::vector draw_face; - unsigned block_id; + unsigned block_id = 0; BlockRenderData() = default; BlockRenderData(const BlockRenderData&) = default; BlockRenderData& operator=(const BlockRenderData&) = default; diff --git a/include/Cubed/gameplay/client_chunk.hpp b/include/Cubed/gameplay/client_chunk.hpp index 52a2952..5854c83 100644 --- a/include/Cubed/gameplay/client_chunk.hpp +++ b/include/Cubed/gameplay/client_chunk.hpp @@ -14,18 +14,18 @@ namespace Cubed { class ClientWorld; struct ChunkRenderSnapshot { - GLuint normal_vao; - size_t normal_vertices_count; - GLuint cross_vao; - size_t cross_vertices_count; - GLuint normal_discard_vao; - size_t normal_discard_vertices_count; - GLuint normal_blend_vao; - size_t normal_blend_vertices_count; - GLuint water_vao; - size_t water_vertices_count; - glm::vec3 center; - glm::vec3 half_extents; + GLuint normal_vao = 0; + size_t normal_vertices_count = 0; + GLuint cross_vao = 0; + size_t cross_vertices_count = 0; + GLuint normal_discard_vao = 0; + size_t normal_discard_vertices_count = 0; + GLuint normal_blend_vao = 0; + size_t normal_blend_vertices_count = 0; + GLuint water_vao = 0; + size_t water_vertices_count = 0; + glm::vec3 center{0.0f}; + glm::vec3 half_extents{0.0f}; }; class ClientChunk : public Chunk { public: @@ -98,7 +98,7 @@ private: std::atomic m_is_on_gen_vertex_data{false}; std::atomic m_biome = BiomeType::PLAIN; std::mutex m_vertexs_data_mutex; - ChunkPos m_chunk_pos; + ChunkPos m_chunk_pos{0, 0}; ClientWorld& m_world; // the index is a array of block id std::vector m_blocks; @@ -111,7 +111,7 @@ private: */ std::vector m_vertex_data; - ChunkRenderSnapshot m_render_snapshot; + ChunkRenderSnapshot m_render_snapshot{}; unsigned m_seed = 0; void clear_dirty(); diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index 3040b22..bfa288c 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -107,7 +107,7 @@ public: private: struct VoiceMessage { std::string data; - glm::vec3 pos; + glm::vec3 pos{0.0f}; }; std::atomic m_is_pending_delete_queue_free{false}; @@ -127,7 +127,7 @@ private: struct PendingSound { std::string sound; - glm::vec3 sound_pos; + glm::vec3 sound_pos{0.0f}; }; static constexpr int WORLD_EXIT_TIMEOUT = 200; diff --git a/include/Cubed/gameplay/ecs/client_entity.hpp b/include/Cubed/gameplay/ecs/client_entity.hpp index 98df8af..8f7f899 100644 --- a/include/Cubed/gameplay/ecs/client_entity.hpp +++ b/include/Cubed/gameplay/ecs/client_entity.hpp @@ -7,15 +7,15 @@ namespace Cubed { struct BaseClientCreature { - Transform transform; + Transform transform{}; - WalkPose pose; + WalkPose pose{}; - ModelID model; + ModelID model = 0; }; struct SoundTime { - float next_call_time; + float next_call_time = 0.0f; }; struct ClientEntitySnapshot { diff --git a/include/Cubed/gameplay/server_chunk.hpp b/include/Cubed/gameplay/server_chunk.hpp index 9eb9a9a..e8964b3 100644 --- a/include/Cubed/gameplay/server_chunk.hpp +++ b/include/Cubed/gameplay/server_chunk.hpp @@ -67,7 +67,7 @@ private: ChunkPos m_chunk_pos; ServerWorld& m_world; - HeightMapArray m_heightmap; + HeightMapArray m_heightmap{}; // the index is a array of block id std::vector m_blocks; OptionalBlockVectorArray m_neightbor_blocks; diff --git a/include/Cubed/gameplay/server_entity_manager.hpp b/include/Cubed/gameplay/server_entity_manager.hpp index e5af786..f01e31b 100644 --- a/include/Cubed/gameplay/server_entity_manager.hpp +++ b/include/Cubed/gameplay/server_entity_manager.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Cubed { class ServerWorld; class Session; diff --git a/include/Cubed/gameplay/server_player.hpp b/include/Cubed/gameplay/server_player.hpp index 9c28b79..c78e3a9 100644 --- a/include/Cubed/gameplay/server_player.hpp +++ b/include/Cubed/gameplay/server_player.hpp @@ -59,7 +59,7 @@ private: std::atomic m_chunk_task_id{0}; std::atomic m_yaw{0.0f}; std::atomic m_pitch{0.0f}; - std::atomic m_gait; + std::atomic m_gait{Gait::STOP}; mutable std::shared_mutex m_chunk_pos_mutex; ChunkPosSet m_player_chunk_pos_set; }; diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index e1e6930..8e4613d 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -115,7 +115,7 @@ public: private: enum class ChunkState { NONE, GENERATING, READY, PENDING_DELETE }; struct ChunkEntity { - ChunkState state; + ChunkState state = ChunkState::NONE; std::shared_ptr chunk; std::atomic ref_count = 0; ChunkEntity() = default; diff --git a/include/Cubed/render/player_renderer.hpp b/include/Cubed/render/player_renderer.hpp index d35e80e..40308e2 100644 --- a/include/Cubed/render/player_renderer.hpp +++ b/include/Cubed/render/player_renderer.hpp @@ -24,8 +24,8 @@ private: float tx = 0.0f, ty = 0.0f, tz = 0.0f; }; Renderer& m_renderer; - std::array m_vao; - std::array m_vbo; + std::array m_vao{}; + std::array m_vbo{}; bool m_inited{false}; std::array, BODY_PART_NUM> m_vertices; }; diff --git a/include/Cubed/render/renderer.hpp b/include/Cubed/render/renderer.hpp index b2deb3f..be03121 100644 --- a/include/Cubed/render/renderer.hpp +++ b/include/Cubed/render/renderer.hpp @@ -25,7 +25,7 @@ class DevPanel; class Renderer { public: constexpr static int NUM_VAO = 7; - + constexpr static const char* SCREENSHOT_PATH = "./screenshot"; Renderer(TextureManager& texture_manager, Config& config); ~Renderer(); void reload_config(); @@ -83,6 +83,8 @@ public: ModelRender& model_renderer(); + void save_screenshot(); + private: TextureManager& m_texture_manager; bool m_init = false; @@ -98,7 +100,9 @@ private: float m_window_width = 0.0f; float m_window_height = 0.0f; - glm::mat4 m_world_proj_matrix; + bool m_save_screenshot = false; + + glm::mat4 m_world_proj_matrix{0.0f}; std::unique_ptr m_sky_vbo; std::unique_ptr m_outline_indices_vbo; @@ -107,7 +111,7 @@ private: std::unique_ptr m_player_vbo; std::unique_ptr m_quad_vbo; - glm::mat4 m_ui_proj_matrix; + glm::mat4 m_ui_proj_matrix{0.0f}; ShaderManager m_shaders; /* @@ -129,6 +133,7 @@ private: void updata_framebuffer(int width, int height); void init_quad(); void init_text(); + void handle_screenshot(); }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/render/world_renderer.hpp b/include/Cubed/render/world_renderer.hpp index 51a2c1f..6842643 100644 --- a/include/Cubed/render/world_renderer.hpp +++ b/include/Cubed/render/world_renderer.hpp @@ -89,8 +89,8 @@ private: std::unique_ptr m_depth_map_fbo; std::unique_ptr m_depth_map_texture; - glm::vec3 m_blend_from_lightdir; - glm::vec3 m_blend_to_lightdir; + glm::vec3 m_blend_from_lightdir{0.0f}; + glm::vec3 m_blend_to_lightdir{0.0f}; float m_blend_t = 1.0f; bool m_blend_initialized = false; static constexpr float BLEND_DURATION = 0.15f; @@ -125,7 +125,7 @@ private: ParallelLight m_parallel_light; SkyUniform m_sky_uniform; - glm::mat4 view_matrix; + glm::mat4 view_matrix{0.0f}; const TextureManager& m_texture_manager; void day_night_calculation(ClientWorld& world); diff --git a/include/Cubed/scene/scene.hpp b/include/Cubed/scene/scene.hpp index b11f8c2..ce94a9b 100644 --- a/include/Cubed/scene/scene.hpp +++ b/include/Cubed/scene/scene.hpp @@ -9,7 +9,8 @@ enum class SceneType { CREDITS, SETTINGS, HOST_GAME, - JOIN_GAME + JOIN_GAME, + SCREENSHOT }; class Scene { diff --git a/include/Cubed/scene/screenshot_scene.hpp b/include/Cubed/scene/screenshot_scene.hpp new file mode 100644 index 0000000..49c0cef --- /dev/null +++ b/include/Cubed/scene/screenshot_scene.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "Cubed/scene/scene.hpp" +#include "Cubed/ui/screenshot_ui.hpp" +namespace Cubed { +class SceneManager; +class ScreenshotScene : public Scene { +public: + ScreenshotScene(SceneManager& scene_manager); + ~ScreenshotScene(); + + ScreenshotScene(const ScreenshotScene&) = delete; + ScreenshotScene(ScreenshotScene&&) = delete; + ScreenshotScene& operator=(const ScreenshotScene&) = delete; + ScreenshotScene& operator=(ScreenshotScene&&) = delete; + + void update(float dt) override; + void render(Renderer& renderer) override; + bool handle_event(const Event& e) override; + void on_enter() override; + void on_leave() override; + void on_re_enter() override; + + SceneManager& scene_manager(); + +private: + SceneManager& m_scene_manager; + ScreenshotUI m_ui; +}; +} // namespace Cubed diff --git a/include/Cubed/scene/world_scene.hpp b/include/Cubed/scene/world_scene.hpp index 448142e..32f7016 100644 --- a/include/Cubed/scene/world_scene.hpp +++ b/include/Cubed/scene/world_scene.hpp @@ -62,7 +62,7 @@ private: WorldUIManager m_hud_ui; ErrorUI m_error_ui; const Argument& m_argument; - VoiceInputType m_input_type; + VoiceInputType m_input_type = VoiceInputType::OFF; RunMode m_runmode = RunMode::HYBRID; bool handle_mouse_move_event(const MouseMoveEvent& e) override; bool handle_mouse_button_event(const MouseButtonEvent& e) override; diff --git a/include/Cubed/texture_manager.hpp b/include/Cubed/texture_manager.hpp index b078106..c765bd9 100644 --- a/include/Cubed/texture_manager.hpp +++ b/include/Cubed/texture_manager.hpp @@ -20,7 +20,9 @@ public: const Texture* get_block_status_array() const; const Texture* get_texture_array() const; const Texture* get_cross_plane_array() const; - const Texture* get_image_texture(const std::string& path); + const Texture* get_image_texture(const std::string& path, + bool full_path = false, + bool check_exist = true); const Texture* get_pbr_texture() const; const ItemTextureMap& get_item_textures() const; const Texture* get_skin() const; @@ -49,7 +51,9 @@ private: void load_block_texture(unsigned block_id); void init_item_texture(); void load_cross_plane_texture(unsigned id); - const Texture* load_image_texture(const std::string& path); + const Texture* load_image_texture(const std::string& path, + bool full_path = false, + bool check_exist = true); void load_pbr_texture(unsigned id); void init_item(); void init_block(); diff --git a/include/Cubed/tools/file_utils.hpp b/include/Cubed/tools/file_utils.hpp new file mode 100644 index 0000000..9f095bb --- /dev/null +++ b/include/Cubed/tools/file_utils.hpp @@ -0,0 +1,66 @@ +#pragma once +#include "Cubed/tools/log.hpp" + +#include +#include + +#ifdef _WIN32 +// clang-format off +#include +#include +// clang-format on +#elif __linux__ +#include +#endif + +namespace Cubed::Tools { + +inline void open_file_manager(const std::filesystem::path& path) { +#ifdef _WIN32 + auto abs_path = std::filesystem::absolute(path); + if (!std::filesystem::exists(abs_path)) { + Logger::warn("Path does not exist: {}", abs_path.string()); + return; + } + HINSTANCE result = + ShellExecuteW(nullptr, L"open", abs_path.wstring().c_str(), nullptr, + nullptr, SW_SHOWNORMAL); + if ((INT_PTR)result <= 32) { + Logger::warn("ShellExecuteW failed for path: {}, error code: {}", + path.string(), (int)(INT_PTR)result); + } + +#elif __linux__ + + constexpr const char* FILE_MANAGERS[] = {"nautilus", "dolphin", "thunar", + "nemo", "pcmanfm"}; + + auto is_available = [](const char* exe) { + std::string cmd = std::string("command -v ") + exe + " >/dev/null 2>&1"; + return std::system(cmd.c_str()) == 0; + }; + + const std::string QUOTED = "\"" + path.string() + "\""; + for (const char* fm : FILE_MANAGERS) { + if (!is_available(fm)) { + continue; + } + std::string cmd = std::string(fm) + " " + QUOTED + " >/dev/null 2>&1 &"; + if (std::system(cmd.c_str()) == 0) { + return; + } + } + + // Fallback: let the system choose a handler. + if (is_available("xdg-open")) { + std::string cmd = "xdg-open " + QUOTED + " >/dev/null 2>&1 &"; + if (std::system(cmd.c_str()) == 0) { + return; + } + } + + Logger::warn("Failed to open file manager for {}", path.string()); +#endif +} + +} // namespace Cubed::Tools \ No newline at end of file diff --git a/include/Cubed/tools/sensitive_filter.hpp b/include/Cubed/tools/sensitive_filter.hpp index bf93e9f..a0144c5 100644 --- a/include/Cubed/tools/sensitive_filter.hpp +++ b/include/Cubed/tools/sensitive_filter.hpp @@ -1,8 +1,10 @@ #pragma once - #include +#include +#include #include #include + namespace Cubed { class SensitiveFilter { public: diff --git a/include/Cubed/tools/time_tools.hpp b/include/Cubed/tools/time_tools.hpp index 3c22df6..aa1d767 100644 --- a/include/Cubed/tools/time_tools.hpp +++ b/include/Cubed/tools/time_tools.hpp @@ -1,11 +1,23 @@ #pragma once #include +#include #include +#include +#include namespace Cubed { namespace Tools { // return ms inline uint64_t get_time_ticks() { return SDL_GetTicks(); } + +inline std::string get_time_date_str() { + static auto* zone = std::chrono::current_zone(); + auto now = std::chrono::system_clock::now(); + auto local = + std::chrono::time_point_cast(zone->to_local(now)); + return std::format("{:%Y-%m-%d_%H-%M-%S}", local); +} + } // namespace Tools } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/button.hpp b/include/Cubed/ui/button.hpp index 1d8b306..a1f40f3 100644 --- a/include/Cubed/ui/button.hpp +++ b/include/Cubed/ui/button.hpp @@ -28,6 +28,7 @@ public: Button& set_background_image(const std::string& path, TextureManager& texture_manager); Button& set_default_image(TextureManager& texture_manager); + Button& set_texture(const Texture* texture, bool change_button_size); virtual Button& set_text(const std::string& text); Button& set_auto_scale(bool auto_scale); diff --git a/include/Cubed/ui/credits_ui.hpp b/include/Cubed/ui/credits_ui.hpp index ed9b7dc..9872c94 100644 --- a/include/Cubed/ui/credits_ui.hpp +++ b/include/Cubed/ui/credits_ui.hpp @@ -9,9 +9,11 @@ class CreditsUI : public UIManager { public: CreditsUI(CreditsScene& m_scene); void init() override; + void update_layout(int width, int height); private: bool handle_key_event(const KeyEvent& e) override; + bool handle_window_resize_event(const WindowResizeEvent& e) override; CreditsScene& m_scene; }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/host_game_ui.hpp b/include/Cubed/ui/host_game_ui.hpp index cdcd1b4..1a50b68 100644 --- a/include/Cubed/ui/host_game_ui.hpp +++ b/include/Cubed/ui/host_game_ui.hpp @@ -13,7 +13,7 @@ public: private: HostGameScene& m_scene; - Label* m_error_label; + Label* m_error_label = nullptr; void set_error(std::string_view error); void clear_error(); }; diff --git a/include/Cubed/ui/item_slot.hpp b/include/Cubed/ui/item_slot.hpp index 36bf015..df93f4c 100644 --- a/include/Cubed/ui/item_slot.hpp +++ b/include/Cubed/ui/item_slot.hpp @@ -26,7 +26,7 @@ private: void on_update(float dt) override; std::unique_ptr m_background; std::unique_ptr m_foreground; - ItemID m_id; + ItemID m_id = 0; float m_scale = 1.0f; bool m_hovered = false; }; diff --git a/include/Cubed/ui/join_game_ui.hpp b/include/Cubed/ui/join_game_ui.hpp index 1d15ab9..4cfa7ed 100644 --- a/include/Cubed/ui/join_game_ui.hpp +++ b/include/Cubed/ui/join_game_ui.hpp @@ -18,7 +18,7 @@ public: private: JoinGameScene& m_scene; - Label* m_error_label; + Label* m_error_label = nullptr; void set_error(std::string_view error); void clear_error(); }; diff --git a/include/Cubed/ui/screenshot_ui.hpp b/include/Cubed/ui/screenshot_ui.hpp new file mode 100644 index 0000000..28603ae --- /dev/null +++ b/include/Cubed/ui/screenshot_ui.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "Cubed/ui/image.hpp" +#include "Cubed/ui/ui_manager.hpp" + +#include +namespace Cubed { +class Texture; +class ScreenshotScene; +class ScreenshotUI : public UIManager { +public: + ScreenshotUI(ScreenshotScene& m_scene); + ~ScreenshotUI(); + + ScreenshotUI(const ScreenshotUI&) = delete; + ScreenshotUI(ScreenshotUI&&) = delete; + ScreenshotUI& operator=(const ScreenshotUI&) = delete; + ScreenshotUI& operator=(ScreenshotUI&&) = delete; + + void init() override; + + void update_layout(int width, int height); + + void open_lightbox(const Texture* image); + void update(float dt) override; + +private: + struct ImageInfo { + const Texture* texture = nullptr; + int width = 0; + int height = 0; + std::chrono::nanoseconds time{0}; + }; + + enum class SortType { NEWEST_FIRST, OLDEST_FIRST }; + + ScreenshotScene& m_scene; + std::vector m_image_infos; + SortType m_sort_type = SortType::NEWEST_FIRST; + bool m_need_rebuild = false; + Widget* m_lightbox = nullptr; + Image* m_lightbox_image = nullptr; + bool handle_window_resize_event(const WindowResizeEvent& e) override; + bool handle_key_event(const KeyEvent& e) override; + bool handle_mouse_button_event(const MouseButtonEvent& e) override; + bool handle_mouse_move_event(const MouseMoveEvent& e) override; + bool handle_mouse_wheel_event(const MouseWheelEvent& e) override; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/scroll_view.hpp b/include/Cubed/ui/scroll_view.hpp new file mode 100644 index 0000000..7d99b83 --- /dev/null +++ b/include/Cubed/ui/scroll_view.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "Cubed/ui/widget.hpp" +namespace Cubed { +class ColumnLayout; +class ScrollView : public Widget { +public: + ScrollView(const ScrollView&) = delete; + ScrollView(ScrollView&&) = delete; + ScrollView& operator=(const ScrollView&) = delete; + ScrollView& operator=(ScrollView&&) = delete; + + ScrollView(Widget* parent); + ~ScrollView(); + + void render(Renderer& renderer) override; + + float width() const override; + // visable height, you need notice offset + float height() const override; + + ScrollView& set_child(std::unique_ptr children); + + bool handle_mouse_wheel_event(const MouseWheelEvent& e) override; + bool handle_mouse_move_event(const MouseMoveEvent& e) override; + Widget& set_fill_parent(bool fill) override; + Widget& set_fill_width(bool fill) override; + Widget& set_fill_height(bool fill) override; + +private: + ColumnLayout* m_child = nullptr; + bool m_hovered = false; + float m_scroll_speed = 50.0f; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/ui/widget.hpp b/include/Cubed/ui/widget.hpp index 9b7928b..7b77b32 100644 --- a/include/Cubed/ui/widget.hpp +++ b/include/Cubed/ui/widget.hpp @@ -28,12 +28,19 @@ public: Widget& operator=(Widget&&) = delete; Widget(Widget* parent); + + static void set_window_size(int width, int height); + static int get_window_height(); + static int get_window_width(); + virtual ~Widget() = default; virtual void update(float dt); virtual void render(Renderer& renderer); virtual Widget& set_anchor(Anchor anchor); - virtual Widget& set_offset(glm::ivec2 offset); - static void set_window_size(int width, int height); + virtual Widget& set_offset(glm::vec2 offset); + virtual Widget& add_offset(glm::vec2 offset); + virtual glm::vec2 get_offset() const; + virtual Widget& set_visible(bool visible); // Returns the final display size @@ -56,6 +63,8 @@ public: virtual bool handle_window_resize_event(const WindowResizeEvent& e); virtual bool handle_mouse_move_event(const MouseMoveEvent& e); virtual bool handle_text_input_event(const TextInputEvent& e); + + virtual bool is_visible() const; void set_order(TraversalOrder order); template T& add_child(Args&&... args) { @@ -78,7 +87,7 @@ protected: bool m_fill_width = false; bool m_show_border = false; int m_border_size = 5; - glm::ivec2 m_offset{0, 0}; + glm::vec2 m_offset{0, 0}; std::vector>& children(); @@ -101,7 +110,7 @@ private: */ std::array, 4> m_border; TraversalOrder m_order = TraversalOrder::BACK_TO_FRONT; - glm::vec2 m_mouse_pos; + glm::vec2 m_mouse_pos{0.0f}; float m_width = 0.0f; float m_height = 0.0f; }; diff --git a/include/Cubed/window.hpp b/include/Cubed/window.hpp index 8be876a..380283b 100644 --- a/include/Cubed/window.hpp +++ b/include/Cubed/window.hpp @@ -50,8 +50,8 @@ private: bool m_imgui_init = false; bool m_game_running = false; bool m_enable_exclusive = false; - SDL_Window* m_window; - SDL_GLContext m_context; + SDL_Window* m_window = nullptr; + SDL_GLContext m_context = nullptr; int m_window_width = 0; int m_window_height = 0; SDL_DisplayID m_windowed_display = 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2665999..b1fdc73 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,4 +106,7 @@ target_sources(${PROJECT_NAME} tools/json_utils.cpp gameplay/item_manager.cpp gameplay/block_manager.cpp + ui/screenshot_ui.cpp + scene/screenshot_scene.cpp + ui/scroll_view.cpp ) \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp index eb5422f..78b787b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -187,7 +187,7 @@ void App::handle_argument(int argc, char** argv) { }}, {"--enable-filelog", [&](ArgParser&) { m_argument.enable_filelog = true; }}, - {"--enale-consolelog", + {"--enable-consolelog", [&](ArgParser&) { m_argument.enable_consolelog = true; }}, {"--direct-enter", [&](ArgParser&) { m_argument.direct_enter = true; }} diff --git a/src/audio/audio_engine.cpp b/src/audio/audio_engine.cpp index 1ea2e4b..02b43b9 100644 --- a/src/audio/audio_engine.cpp +++ b/src/audio/audio_engine.cpp @@ -119,6 +119,10 @@ void AudioEngine::init() { Logger::info("Audio Engine Init Success"); + Logger::info("OpenAL vendor: {}", alGetString(AL_VENDOR)); + Logger::info("OpenAL version: {}", alGetString(AL_VERSION)); + Logger::info("OpenAL renderer: {}", alGetString(AL_RENDERER)); + m_init = true; } @@ -148,11 +152,12 @@ void AudioEngine::play_3d(const std::string& sound, const glm::vec3& pos, return; } auto* source = m_pool->acquire(); - source->set_volume(m_sfx_volume); + if (!source) { Logger::error("Source is Full"); + return; } - + source->set_volume(m_sfx_volume); try { auto& buffer = m_sounds.get_buffer(sound); if (m_efx_supported && m_underwater) { @@ -176,6 +181,7 @@ void AudioEngine::play_2d(const std::string& sound, bool check) { auto* source = m_pool->acquire(); if (!source) { Logger::error("Source is Full"); + return; } source->set_volume(m_sfx_volume); @@ -301,8 +307,9 @@ void AudioEngine::receive_voice(std::span opus, return; } m_voice_source->set_pos(pos); - m_voice_source->push_pcm(std::span(pcm.data(), len), - AudioRecording::SAMPLE_RATE); + m_voice_source->push_pcm( + std::span(pcm.data(), static_cast(len)), + AudioRecording::SAMPLE_RATE); } AudioRecording& AudioEngine::audio_recording() { return m_recording; } diff --git a/src/audio/audio_source.cpp b/src/audio/audio_source.cpp index 9bb83b8..42ab83a 100644 --- a/src/audio/audio_source.cpp +++ b/src/audio/audio_source.cpp @@ -80,11 +80,9 @@ void AudioSource::set_pitch(float pitch) { } void AudioSource::play() { - - if (state() != AudioState::INITIAL) { - stop(); - } alSourcePlay(m_source); + + check_al_error(); } void AudioSource::play_2d(const AudioBuffer& buffer) { diff --git a/src/audio/source_pool.cpp b/src/audio/source_pool.cpp index 08f2707..aeffa57 100644 --- a/src/audio/source_pool.cpp +++ b/src/audio/source_pool.cpp @@ -1,5 +1,7 @@ #include "Cubed/audio/source_pool.hpp" +#include "Cubed/tools/log.hpp" + namespace Cubed { SourcePool::SourcePool(size_t size) { m_sources.reserve(size); @@ -10,8 +12,13 @@ SourcePool::SourcePool(size_t size) { SourcePool::~SourcePool() { m_sources.clear(); } void SourcePool::update() { for (auto& source_ptr : m_sources) { - if (source_ptr->in_use() && - source_ptr->state() == AudioState::STOPPED) { + if (!source_ptr->in_use()) { + continue; + } + + auto state = source_ptr->state(); + + if (state == AudioState::STOPPED || state == AudioState::INITIAL) { source_ptr->reset(); } } diff --git a/src/gameplay/chunk_generator.cpp b/src/gameplay/chunk_generator.cpp index aa09c40..ee7220c 100644 --- a/src/gameplay/chunk_generator.cpp +++ b/src/gameplay/chunk_generator.cpp @@ -109,6 +109,7 @@ ChunkGenerator::ChunkGenerator(ServerChunk& chunk) : m_chunk(chunk) { unsigned seed = HASH::chunk_seed_hash(pos.x, pos.z, m_generator_seed); m_random.init(seed); m_chunk_seed = seed; + m_neighbor_biome.fill(NONE); } void ChunkGenerator::init() { diff --git a/src/gameplay/client_entity_manager.cpp b/src/gameplay/client_entity_manager.cpp index 1af7e37..9f7f2fd 100644 --- a/src/gameplay/client_entity_manager.cpp +++ b/src/gameplay/client_entity_manager.cpp @@ -110,7 +110,8 @@ void ClientEntityManager::handle_entity_create(EntityID id, ASSERT(m_factories.contains(name)); m_factories[name](id); acc a; - ASSERT(m_entities.find(a, id)); + bool found = m_entities.find(a, id); + ASSERT(found); auto* c = m_registry.try_get(a->second); ASSERT(c); c->transform.position.value = pos; diff --git a/src/render/model_manager.cpp b/src/render/model_manager.cpp index f11931a..9a81b26 100644 --- a/src/render/model_manager.cpp +++ b/src/render/model_manager.cpp @@ -88,7 +88,7 @@ ModelManager::Handle ModelManager::load_model(std::string_view model_name) { auto model = m_loader.load(path); fs::path anim_path = path; anim_path = anim_path.parent_path() / "animation.json"; - load_anim_config(model, anim_path); + load_anim_config(model, anim_path.string()); ModelMap::accessor acc; if (m_models.insert(acc, m_next++)) { acc->second = std::move(model); diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index b6d974c..1e02858 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -9,10 +9,13 @@ #include "Cubed/tools/font.hpp" #include "Cubed/tools/log.hpp" #include "Cubed/tools/shader_tools.hpp" +#include "Cubed/tools/time_tools.hpp" +#include #include #include - +#include +namespace fs = std::filesystem; namespace Cubed { Renderer::Renderer(TextureManager& texture_manager, Config& config) @@ -139,7 +142,7 @@ void Renderer::begin_frame() { glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } -void Renderer::end_frame() {} +void Renderer::end_frame() { handle_screenshot(); } void Renderer::begin_render_ui() { glEnable(GL_BLEND); @@ -288,6 +291,34 @@ bool Renderer::handle_frame_buffer_resize_event( return false; } +void Renderer::handle_screenshot() { + if (!m_save_screenshot) { + return; + } + m_save_screenshot = false; + std::array screen_stats; + glGetIntegerv(GL_VIEWPORT, screen_stats.data()); + int width = screen_stats[2]; + int height = screen_stats[3]; + std::vector pixels(width * height * 4); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + std::vector flipped_pixels(width * height * 4); + for (int y = 0; y < height; ++y) { + std::memcpy(&flipped_pixels[y * width * 4], + &pixels[(height - 1 - y) * width * 4], width * 4); + } + fs::path path = SCREENSHOT_PATH; + fs::create_directories(path); + fs::path image = + path / std::format("screenshot {}.png", Tools::get_time_date_str()); + if (!stbi_write_png(image.string().c_str(), width, height, 4, + flipped_pixels.data(), width * 4)) { + Logger::error("Failed to write {} image", image.string()); + } else { + Logger::info("save {} success!", image.string()); + } +} + float& Renderer::ambient_strength() { return m_world_renderer.ambient_strength(); } @@ -341,4 +372,5 @@ float Renderer::frame_width() const { return m_frame_width; } const glm::mat4& Renderer::p_mat() const { return m_world_proj_matrix; } const std::vector& Renderer::vao() const { return m_vao; } ModelRender& Renderer::model_renderer() { return m_model_renderer; } +void Renderer::save_screenshot() { m_save_screenshot = true; } } // namespace Cubed \ No newline at end of file diff --git a/src/scene/scene_manager.cpp b/src/scene/scene_manager.cpp index 6c58b16..2ac27f2 100644 --- a/src/scene/scene_manager.cpp +++ b/src/scene/scene_manager.cpp @@ -4,6 +4,7 @@ #include "Cubed/scene/host_game_scene.hpp" #include "Cubed/scene/join_game_scene.hpp" #include "Cubed/scene/main_menu_scene.hpp" +#include "Cubed/scene/screenshot_scene.hpp" #include "Cubed/scene/settings_scene.hpp" #include "Cubed/scene/world_scene.hpp" @@ -33,15 +34,24 @@ bool SceneManager::handle_event(const Event& e) { } void SceneManager::request_change(SceneType type) { - ASSERT(!m_operation.has_value()); + if (m_operation.has_value()) { + Logger::error("Scene operation has_value"); + return; + } m_operation = {OperationType::CHANGE, type}; } void SceneManager::request_push(SceneType type) { - ASSERT(!m_operation.has_value()); + if (m_operation.has_value()) { + Logger::error("Scene operation has_value"); + return; + } m_operation = {OperationType::PUSH, type}; } void SceneManager::request_pop() { - ASSERT(!m_operation.has_value()); + if (m_operation.has_value()) { + Logger::error("Scene operation has_value"); + return; + } m_operation = {OperationType::POP, std::nullopt}; } @@ -103,6 +113,8 @@ std::unique_ptr SceneManager::create_scene(SceneType type) { return std::make_unique(*this); case SceneType::JOIN_GAME: return std::make_unique(*this); + case SceneType::SCREENSHOT: + return std::make_unique(*this); } std::string err = std::format("Unknown Scene"); diff --git a/src/scene/screenshot_scene.cpp b/src/scene/screenshot_scene.cpp new file mode 100644 index 0000000..1e04ff9 --- /dev/null +++ b/src/scene/screenshot_scene.cpp @@ -0,0 +1,20 @@ +#include "Cubed/scene/screenshot_scene.hpp" + +namespace Cubed { +ScreenshotScene::ScreenshotScene(SceneManager& scene_manager) + : m_scene_manager(scene_manager), m_ui(*this) {} + +ScreenshotScene::~ScreenshotScene() {} + +void ScreenshotScene::update(float dt) { m_ui.update(dt); } +void ScreenshotScene::render(Renderer& renderer) { m_ui.render(renderer); } +bool ScreenshotScene::handle_event(const Event& e) { + return m_ui.handle_event(e); +} +void ScreenshotScene::on_enter() { m_ui.init(); } +void ScreenshotScene::on_leave() {} +void ScreenshotScene::on_re_enter() {} + +SceneManager& ScreenshotScene::scene_manager() { return m_scene_manager; } + +} // namespace Cubed \ No newline at end of file diff --git a/src/scene/world_scene.cpp b/src/scene/world_scene.cpp index 6e5b0e1..d4c6a05 100644 --- a/src/scene/world_scene.cpp +++ b/src/scene/world_scene.cpp @@ -3,6 +3,7 @@ #include "Cubed/app.hpp" #include "Cubed/render/renderer.hpp" #include "Cubed/scene/scene_manager.hpp" +#include "Cubed/tools/time_tools.hpp" namespace Cubed { WorldScene::WorldScene(SceneManager& scene_manager) : m_scene_manager(scene_manager), m_dev_panel(*this), @@ -380,7 +381,16 @@ bool WorldScene::handle_key_event(const KeyEvent& e) { } } } - + if (e.key == Key::F2 && e.action == KeyAction::PRESS) { + m_scene_manager.app().renderer().save_screenshot(); + ChatMessage msg; + msg.system_msg = true; + msg.color = Color::WHITE; + msg.text = "Saved Screenshot!"; + msg.time = Tools::get_time_ticks(); + m_hud_ui.add_chat_message(msg); + return true; + } if (m_paused) { switch (m_paused_ui) { case PauseUI::INVENTORY: diff --git a/src/texture_manager.cpp b/src/texture_manager.cpp index c49d7a8..4698542 100644 --- a/src/texture_manager.cpp +++ b/src/texture_manager.cpp @@ -57,12 +57,14 @@ const Texture* TextureManager::get_texture_array() const { const Texture* TextureManager::get_cross_plane_array() const { return m_cross_plane_array.get(); } -const Texture* TextureManager::get_image_texture(const std::string& path) { +const Texture* TextureManager::get_image_texture(const std::string& path, + bool full_path, + bool check_exist) { auto it = m_ui_map.find(path); if (it != m_ui_map.end()) { return it->second.get(); } - return load_image_texture(path); + return load_image_texture(path, full_path, check_exist); } const Texture* TextureManager::get_pbr_texture() const { @@ -151,9 +153,15 @@ void TextureManager::load_cross_plane_texture(unsigned id) { CROSS_PLANE_SIZE, CROSS_PLANE_SIZE); } -const Texture* TextureManager::load_image_texture(const std::string& path) { +const Texture* TextureManager::load_image_texture(const std::string& path, + bool full_path, + bool check_exist) { - auto image_data = (Tools::load_image_data(path)); + auto image_data = (Tools::load_image_data(path, check_exist, full_path)); + if (!image_data.data) { + Logger::error("Failed to load image texture: {}", path); + return nullptr; + } std::unique_ptr image = std::make_unique(TextureType::TEXTURE_2D); image->tex_image_2d(RGBA, RGBA, GL_UNSIGNED_BYTE, image_data.data, diff --git a/src/tools/shader_tools.cpp b/src/tools/shader_tools.cpp index e24f1aa..38b9589 100644 --- a/src/tools/shader_tools.cpp +++ b/src/tools/shader_tools.cpp @@ -184,7 +184,7 @@ ImageData load_image_data(const std::string& tex_image_path, bool check_exist, path = ASSETS_PATH + tex_image_path; } if (check_exist) { - ASSERT_MSG(fs::is_regular_file(path), path.c_str()); + ASSERT_MSG(fs::is_regular_file(path), path.string().c_str()); } unsigned char* data = nullptr; int width, height, channels; diff --git a/src/ui/button.cpp b/src/ui/button.cpp index 6b8091a..72aa13e 100644 --- a/src/ui/button.cpp +++ b/src/ui/button.cpp @@ -103,7 +103,18 @@ Button& Button::set_background_image(const std::string& path, Button& Button::set_default_image(TextureManager& texture_manager) { return set_background_image(DEFAULT_BUTTON_IMAGE, texture_manager); } - +Button& Button::set_texture(const Texture* texture, bool change_button_size) { + if (!texture) { + Logger::error("Button Background Image is nullptr"); + return *this; + } + m_background->set_texture(texture, false); + if (change_button_size) { + set_width(texture->width()); + set_height(texture->height()); + } + return *this; +} Button& Button::set_text(const std::string& text) { m_foreground->set_text(text); update_text_scale(); diff --git a/src/ui/credits_ui.cpp b/src/ui/credits_ui.cpp index 003c8cc..d2bdfb8 100644 --- a/src/ui/credits_ui.cpp +++ b/src/ui/credits_ui.cpp @@ -8,12 +8,19 @@ #include "Cubed/ui/column_layout.hpp" #include "Cubed/ui/image.hpp" #include "Cubed/ui/rect.hpp" +#include "Cubed/ui/scroll_view.hpp" +#include namespace Cubed { CreditsUI::CreditsUI(CreditsScene& scene) : m_scene(scene) {} void CreditsUI::init() { + auto& renderer = m_scene.scene_manager().app().renderer(); + update_layout(renderer.window_width(), renderer.window_height()); +} +void CreditsUI::update_layout(int, int height) { + m_root_widget.reset(); auto image = std::make_unique(nullptr); image ->set_image("texture/ui/background.png", @@ -23,25 +30,38 @@ void CreditsUI::init() { auto& rect = image->add_child(); rect.set_color(Color::BLACK).set_alpha(0.7f).set_fill_parent(true); + auto& title = rect.add_child