diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..f8956990 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ['https://www.donationalerts.com/r/mihailris'] diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 00000000..330c55bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,31 @@ +--- +name: Найден баг +about: Создайте отчет, чтобы помочь нам исправить этот баг +title: BUG +labels: bug, wontfix +assignees: '' + +--- + +**Опишите ошибку** +Четкое и краткое описание ошибки. + +**Для воспроизведения** +Шаги для воспроизведения поведения: +1. Перейдите в раздел «...» +2. Нажмите «....» +3. Прокрутите вниз до «....» +4. См. ошибку + +**Ожидаемое поведение** +Четкое и краткое описание того, что вы ожидаете. + +**Скриншоты** +Если применимо, добавьте снимки экрана, которые помогут объяснить вашу проблему. + +**Техническая информация (заполните следующую информацию):** + - ОС: [например. Windows 10] + - Версия [например. 0.17] + +**Дополнительный контекст** +Добавьте сюда любой другой контекст проблемы. diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 00000000..f4b4b213 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,20 @@ +--- +name: Новая фича +about: Новая идея для этого проекта +title: 'ENHANCEMENT ' +labels: enhancement +assignees: '' + +--- + +**Связан ли ваш запрос на добавление функции с проблемой? Пожалуйста, опишите.** +Четкое и краткое описание проблемы. Бывший. Я всегда расстраиваюсь, когда [...] + +**Опишите желаемое решение** +Четкое и краткое описание того, чего вы хотите. + +**Опишите альтернативы, которые вы рассматривали** +Четкое и краткое описание любых альтернативных решений или функций, которые вы рассматривали. + +**Дополнительный контекст** +Добавьте сюда любой другой контекст или снимки экрана о запросе функции. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 00000000..118f464b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,12 @@ +--- +name: Вопрос +about: Вопрос другого характера +title: QUESTION +labels: question +assignees: '' + +--- + +**Опишите свой вопрос** +Кратко и понятно опишите ваш вопрос. +Перед созданием этого запроса убедитесь, что ранее не были созданы подобные. diff --git a/.gitignore b/.gitignore index 76b2d370..2487d29e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,11 +25,16 @@ Debug/voxel_engine .settings .cproject .project +.fleet .git /Default/ AppDir appimage-build/ +# for vcpkg +/vcpkg/ +.gitmodules + # macOS folder attributes *.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f9a4bed..7ec5691b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,8 @@ +option(VOXELENGINE_BUILD_WINDOWS_VCPKG OFF) +if(VOXELENGINE_BUILD_WINDOWS_VCPKG AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake") + set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") +endif() + cmake_minimum_required(VERSION 3.15) project(VoxelEngine) @@ -28,8 +33,10 @@ if(VOXELENGINE_BUILD_APPDIR) install(TARGETS VoxelEngine DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/AppDir/usr/bin) endif() - if(MSVC) + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) + endif() if((CMAKE_BUILD_TYPE EQUAL "Release") OR (CMAKE_BUILD_TYPE EQUAL "RelWithDebInfo")) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Release>") target_compile_options(${PROJECT_NAME} PRIVATE /W4 /MT /O2) @@ -45,16 +52,52 @@ else() -Wwrite-strings -Wno-unused-parameter) endif() +if(VOXELENGINE_BUILD_WINDOWS_VCPKG AND WIN32) + if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/.git") + find_package(Git QUIET) + if(GIT_FOUND) + message(STATUS "Adding vcpkg as a git submodule...") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule add https://github.com/microsoft/vcpkg.git vcpkg WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + else() + message(FATAL_ERROR "Git not found, cannot add vcpkg submodule.") + endif() + endif() + + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/.git") + message(STATUS "Initializing and updating vcpkg submodule...") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + execute_process(COMMAND ${CMAKE_COMMAND} -E chdir vcpkg ./bootstrap-vcpkg.bat WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + + foreach(CONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${CONFIG_TYPE} CONFIG_TYPE_UPPER) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/res ${CMAKE_BINARY_DIR}/${CONFIG_TYPE_UPPER}/res) + endforeach() +endif() + find_package(OpenGL REQUIRED) find_package(GLEW REQUIRED) find_package(OpenAL REQUIRED) -# luajit has no CMakeLists.txt to use it as subdirectory, so install it -find_package(Lua REQUIRED) +find_package(ZLIB REQUIRED) if (WIN32) - set(PNGLIB spng) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs/glfw) + if(VOXELENGINE_BUILD_WINDOWS_VCPKG) + set(LUA_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/packages/luajit_x64-windows/lib/lua51.lib") + set(LUA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/packages/luajit_x64-windows/include/luajit") + find_package(glfw3 REQUIRED) + find_package(spng REQUIRED) + find_package(glm REQUIRED) + set(PNGLIB spng::spng) + else() + find_package(Lua REQUIRED) + set(PNGLIB spng) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs/glfw) + endif() else() + # luajit has no CMakeLists.txt to use it as subdirectory, so install it manually + find_package(Lua REQUIRED) find_package(PNG REQUIRED) set(PNGLIB PNG::PNG) endif() @@ -75,7 +118,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() include_directories(${LUA_INCLUDE_DIR}) -target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ${PNGLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS}) +target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ZLIB::ZLIB ${PNGLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/res DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..3785e02c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# Build docker container: docker build -t voxel-engine . +# Build project: docker run --rm -it -v$(pwd):/project voxel-engine bash -c "cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && cmake --build build" +# Run project in docker: docker run --rm -it -v$(pwd):/project -v/tmp/.X11-unix:/tmp/.X11-unix -v${XAUTHORITY}:/home/user/.Xauthority:ro -eDISPLAY --network=host voxel-engine ./build/VoxelEngine + +FROM debian:bullseye-slim +LABEL Description="Docker container for building VoxelEngine for Linux" + +# Install dependencies +RUN apt-get update && apt-get install --no-install-recommends -y \ + git \ + g++ \ + make \ + cmake \ + xauth \ + gdb \ + gdbserver \ + libglfw3-dev \ + libglfw3 \ + libglew-dev \ + libglm-dev \ + libpng-dev \ + libopenal-dev \ + libluajit-5.1-dev \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# CMake missing LUA_INCLUDE_DIR and LUA_LIBRARIES fix: +RUN ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a \ + && ln -s /usr/include/luajit-2.1 /usr/include/lua + +# Install LuaJIT: +RUN git clone https://luajit.org/git/luajit.git \ + && cd luajit \ + && make && make install INSTALL_INC=/usr/include/lua \ + && cd .. && rm -rf luajit + +# Create default user, due to: +# - Build and test artifacts have user permissions and not root permissions +# - Don't give root privileges from host to containers (security) +ARG USER=user +ARG UID=1000 +ARG GID=1000 +RUN useradd -m ${USER} --uid=${UID} +USER ${UID}:${GID} + +# Project workspace +WORKDIR /project diff --git a/README.md b/README.md index 2c84ca96..51d6b407 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - **Tab** - open inventory - **W** **A** **S** **D** - movement - **Space** - jump -- **LMB** - remove block +- **LMB** - remove block - **RMB** - place block - **F** - toggle flight mode - **N** - noclip mode @@ -76,3 +76,44 @@ brew install glfw3 glew glm libpng lua luajit openal-soft ``` If homebrew for some reason could not install the necessary packages: ```lua luajit openal-soft```, then download, install and compile them manually (Lua, LuaJIT and OpenAL). + +## Build using Docker + +### Step 0. Install docker on your system + +See https://docs.docker.com/engine/install + +### Step 1. Build docker container + +``` +docker build -t voxel-engine . +``` + +### Step 2. Build project using the docker container + +``` +docker run --rm -it -v$(pwd):/project voxel-engine bash -c "cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && cmake --build build" +``` + +### Step 3. Run project using the docker container + +``` +docker run --rm -it -v$(pwd):/project -v/tmp/.X11-unix:/tmp/.X11-unix -v${XAUTHORITY}:/home/user/.Xauthority:ro -eDISPLAY --network=host voxel-engine ./build/VoxelEngine +``` + +## Build with CMake and vcpkg for Windows + +```sh +git clone --recursive https://github.com/MihailRis/VoxelEngine-Cpp.git +cd VoxelEngine-Cpp +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON .. +del CMakeCache.txt +rmdir /s /q CMakeFiles +cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON .. +cmake --build . --config Release +``` +note: you can use ```rm CMakeCache.txt``` and ```rm -rf CMakeFiles``` while using Git Bash + +If you have issues during the vcpkg integration, try navigate to ```vcpkg\downloads``` and extract PowerShell-[version]-win-x86 to ```vcpkg\downloads\tools``` as powershell-core-[version]-windows. Then rerun ```cmake -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_WINDOWS_VCPKG=ON ..``` \ No newline at end of file diff --git a/res/shaders/main.glslf b/res/shaders/main.glslf index ab561063..97e49cfb 100644 --- a/res/shaders/main.glslf +++ b/res/shaders/main.glslf @@ -16,7 +16,7 @@ void main(){ float depth = (a_distance/256.0); float alpha = a_color.a * tex_color.a; // anyway it's any alpha-test alternative required - if (alpha < 0.1f) + if (alpha < 0.3f) discard; f_color = mix(a_color * tex_color, vec4(fogColor,1.0), min(1.0, pow(depth*u_fogFactor, u_fogCurve))); f_color.a = alpha; diff --git a/res/texts/be_BY.txt b/res/texts/be_BY.txt index cef3570f..b2261a46 100644 --- a/res/texts/be_BY.txt +++ b/res/texts/be_BY.txt @@ -24,6 +24,7 @@ world.Name=Назва menu.Create World=Стварыць Свет world.convert-request=Ёсць змены ў індэксах! Канвертаваць свет? +world.delete-confirm=Выдаліць свет незваротна? # Настройки settings.Load Distance=Дыстанцыя Загрузкі diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 71db2ddc..f26803bd 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -2,6 +2,7 @@ menu.missing-content=Missing Content! world.convert-request=Content indices have changed! Convert world files? error.pack-not-found=Could not to find pack +world.delete-confirm=Do you want to delete world forever? # Bindings movement.forward=Forward diff --git a/res/texts/fi_FI.txt b/res/texts/fi_FI.txt index dbc85649..4da41a1c 100644 --- a/res/texts/fi_FI.txt +++ b/res/texts/fi_FI.txt @@ -12,8 +12,9 @@ error.pack-not-found=Pakettia ei löytynyt! menu.New World = Uusi Maailma menu.Quit=Poistu menu.Continue=Jatka -menu.Save and Quit to Menu=Tallenna ja Takaisin valikoon +menu.Save and Quit to Menu=Tallenna ja poistu valikkoon menu.missing-content=Puuttuu jotkut lisäosat! +menu.Content Error=Sisältövirhe! menu.Controls=Ohjaus menu.Back to Main Menu=Takaisin Valikoon menu.Settings=Asetukset @@ -23,12 +24,13 @@ menu.Name=Nimi menu.Create World=Luo Maailma world.convert-request=Indeksit vaihdettu! Lataa maailma uudeleen? +world.delete-confirm=Poistetaanko maailma ikuisesti? # Настройки -settings.Load Distance=Lataus Alue -settings.Load Speed=Lataus Nopeus -settings.Fog Curve=Sumun valaistus -settings.Backlight=Valaistus +settings.Load Distance=Latausalue +settings.Load Speed=Latausnopeus +settings.Fog Curve=Sumun tiheys +settings.Backlight=Taustavalo settings.V-Sync=Pystytahdistus settings.FOV=Näkökenttä @@ -41,8 +43,8 @@ movement.back=Taaksepäin movement.left=Vaseemalle movement.right=Oikealle movement.jump=Hyppy -movement.sprint=Kiihtyvyys -movement.crouch=Istu alas +movement.sprint=Juoksu +movement.crouch=Hiipiä movement.cheat=Huijata hud.inventory=Varasto player.pick=Ottaa lohko @@ -50,4 +52,4 @@ player.attack=Lyödä / Rikkoa player.build=Aseta lohko player.flight=Lento camera.zoom=Lähentäminen -camera.mode=Vaihda Kameratila \ No newline at end of file +camera.mode=Vaihda Kameratila diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index c3b4868a..61a33233 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -24,6 +24,7 @@ world.Name=Название menu.Create World=Создать Мир world.convert-request=Есть изменения в индексах! Конвертировать мир? +world.delete-confirm=Удалить мир безвозвратно? # Настройки settings.Load Distance=Дистанция Загрузки diff --git a/res/textures/gui/delete_icon.png b/res/textures/gui/delete_icon.png new file mode 100644 index 00000000..f388f8d8 Binary files /dev/null and b/res/textures/gui/delete_icon.png differ diff --git a/res/textures/menubg.png b/res/textures/gui/menubg.png similarity index 100% rename from res/textures/menubg.png rename to res/textures/gui/menubg.png diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 49328351..e5ab0d79 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -58,7 +58,8 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, bool allAssets) { loader.add(ASSET_SHADER, SHADERS_FOLDER"/ui3d", "ui3d"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/background", "background"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/skybox_gen", "skybox_gen"); - loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/menubg.png", "menubg"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/menubg.png", "gui/menubg"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/delete_icon.png", "gui/delete_icon"); loader.add(ASSET_FONT, FONTS_FOLDER"/font", "normal"); } loader.add(ASSET_ATLAS, TEXTURES_FOLDER"/blocks", "blocks"); diff --git a/src/coders/binary_json.cpp b/src/coders/binary_json.cpp index c65012d3..c296dc16 100644 --- a/src/coders/binary_json.cpp +++ b/src/coders/binary_json.cpp @@ -2,6 +2,7 @@ #include +#include "gzip.h" #include "byte_utils.h" using namespace json; @@ -148,12 +149,21 @@ static Map* object_from_binary(ByteReader& reader) { } std::unique_ptr json::from_binary(const ubyte* src, size_t size) { - ByteReader reader(src, size); - std::unique_ptr value (value_from_binary(reader)); - if (value->type != valtype::map) { - throw std::runtime_error("root value is not an object"); + if (size < 2) { + throw std::runtime_error("bytes length is less than 2"); + } + if (src[0] == gzip::MAGIC[0] && src[1] == gzip::MAGIC[1]) { + // reading compressed document + auto data = gzip::decompress(src, size); + return from_binary(data.data(), data.size()); + } else { + ByteReader reader(src, size); + std::unique_ptr value (value_from_binary(reader)); + if (value->type != valtype::map) { + throw std::runtime_error("root value is not an object"); + } + std::unique_ptr obj (value->value.map); + value->value.map = nullptr; + return obj; } - std::unique_ptr obj (value->value.map); - value->value.map = nullptr; - return obj; } diff --git a/src/coders/gzip.h b/src/coders/gzip.h index 2bf6cba7..f83cdfe6 100644 --- a/src/coders/gzip.h +++ b/src/coders/gzip.h @@ -5,6 +5,7 @@ #include "../typedefs.h" namespace gzip { + const unsigned char MAGIC[] = "\x1F\x8B"; std::vector compress(const ubyte* src, size_t size); std::vector decompress(const ubyte* src, size_t size); } diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index d0cd7e90..5fccd3a6 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -172,6 +172,7 @@ void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { root->flag("sky-light-passing", def->skyLightPassing); root->num("draw-group", def->drawGroup); root->str("picking-item", def->pickingItem); + root->str("script-name", def->scriptName); } void ContentLoader::loadCustomBlockModel(Block* def, dynamic::Map* primitives) { @@ -233,6 +234,8 @@ void ContentLoader::loadItem(ItemDef* def, std::string name, fs::path file) { } root->str("icon", def->icon); root->str("placing-block", def->placingBlock); + root->str("script-name", def->scriptName); + root->num("stack-size", def->stackSize); // item light emission [r, g, b] where r,g,b in range [0..15] auto emissionarr = root->list("emission"); @@ -247,7 +250,7 @@ void ContentLoader::loadBlock(Block* def, std::string full, std::string name) { auto folder = pack->folder; fs::path configFile = folder/fs::path("blocks/"+name+".json"); - fs::path scriptfile = folder/fs::path("scripts/"+name+".lua"); + fs::path scriptfile = folder/fs::path("scripts/"+def->scriptName+".lua"); loadBlock(def, full, configFile); if (fs::is_regular_file(scriptfile)) { scripting::load_block_script(full, scriptfile, &def->rt.funcsset); @@ -258,7 +261,7 @@ void ContentLoader::loadItem(ItemDef* def, std::string full, std::string name) { auto folder = pack->folder; fs::path configFile = folder/fs::path("items/"+name+".json"); - fs::path scriptfile = folder/fs::path("scripts/"+name+".lua"); + fs::path scriptfile = folder/fs::path("scripts/"+def->scriptName+".lua"); loadItem(def, full, configFile); if (fs::is_regular_file(scriptfile)) { scripting::load_item_script(full, scriptfile, &def->rt.funcsset); diff --git a/src/engine.cpp b/src/engine.cpp index ee3b4da1..2204945e 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -46,7 +46,7 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths) } auto resdir = paths->getResources(); - scripting::initialize(paths); + scripting::initialize(this); std::cout << "-- loading assets" << std::endl; std::vector roots {resdir}; diff --git a/src/frontend/InventoryView.cpp b/src/frontend/InventoryView.cpp index 2b898c32..723af338 100644 --- a/src/frontend/InventoryView.cpp +++ b/src/frontend/InventoryView.cpp @@ -1,146 +1,334 @@ #include "InventoryView.h" +#include #include #include "BlocksPreview.h" #include "LevelFrontend.h" #include "../window/Events.h" +#include "../window/input.h" #include "../assets/Assets.h" #include "../graphics/Atlas.h" #include "../graphics/Shader.h" #include "../graphics/Batch2D.h" #include "../graphics/GfxContext.h" +#include "../graphics/Font.h" #include "../content/Content.h" #include "../items/ItemDef.h" +#include "../items/Inventory.h" #include "../maths/voxmaths.h" #include "../objects/Player.h" #include "../voxels/Block.h" +#include "../frontend/gui/controls.h" +#include "../util/stringutil.h" + +InventoryLayout::InventoryLayout(glm::vec2 size) : size(size) {} + +void InventoryLayout::add(SlotLayout slot) { + slots.push_back(slot); +} + +void InventoryLayout::setSize(glm::vec2 size) { + this->size = size; +} + +void InventoryLayout::setOrigin(glm::vec2 origin) { + this->origin = origin; +} + +glm::vec2 InventoryLayout::getSize() const { + return size; +} + +glm::vec2 InventoryLayout::getOrigin() const { + return origin; +} + +std::vector& InventoryLayout::getSlots() { + return slots; +} + +SlotLayout::SlotLayout( + glm::vec2 position, + bool background, + bool itemSource, + itemsharefunc shareFunc, + slotcallback rightClick +) + : position(position), + background(background), + itemSource(itemSource), + shareFunc(shareFunc), + rightClick(rightClick) {} + +InventoryPanel::InventoryPanel( + glm::vec2 position, + glm::vec2 size, + glm::vec4 color) + : position(position), size(size), color(color) {} + +InventoryBuilder::InventoryBuilder() + : layout(std::make_unique(glm::vec2())) +{} + +void InventoryBuilder::addGrid( + int cols, int rows, + glm::vec2 coord, + int padding, + SlotLayout slotLayout) +{ + const int slotSize = InventoryView::SLOT_SIZE; + const int interval = InventoryView::SLOT_INTERVAL; + + uint width = cols * (slotSize + interval) - interval + padding*2; + uint height = rows * (slotSize + interval) - interval + padding*2; + + auto lsize = layout->getSize(); + if (coord.x + width > lsize.x) { + lsize.x = coord.x + width; + } + if (coord.y + height > lsize.y) { + lsize.y = coord.y + height; + } + layout->setSize(lsize); + + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + glm::vec2 position ( + col * (slotSize + interval) + padding, + row * (slotSize + interval) + padding + ); + auto builtSlot = slotLayout; + builtSlot.position = position; + layout->add(builtSlot); + } + } +} + +std::unique_ptr InventoryBuilder::build() { + return std::unique_ptr(layout.release()); +} + +SlotView::SlotView( + ItemStack& stack, + LevelFrontend* frontend, + InventoryInteraction* interaction, + const Content* content, + SlotLayout layout) + : UINode(glm::vec2(), glm::vec2(InventoryView::SLOT_SIZE)), + frontend(frontend), + interaction(interaction), + content(content), + stack(stack), + layout(layout) { + color(glm::vec4(0, 0, 0, 0.2f)); +} + +// performance disaster +void SlotView::draw(Batch2D* batch, Assets* assets) { + glm::vec2 coord = calcCoord(); + + int slotSize = InventoryView::SLOT_SIZE; + + glm::vec4 tint(1.0f); + glm::vec4 color = color_; + if (hover_ || highlighted) { + tint *= 1.333f; + color = glm::vec4(1, 1, 1, 0.2f); + } + + batch->color = color; + if (color.a > 0.0) { + batch->texture(nullptr); + if (highlighted) { + batch->rect(coord.x-4, coord.y-4, slotSize+8, slotSize+8); + } else { + batch->rect(coord.x, coord.y, slotSize, slotSize); + } + } + + batch->color = glm::vec4(1.0f); + + Shader* uiShader = assets->getShader("ui"); + Viewport viewport(Window::width, Window::height); + GfxContext ctx(nullptr, viewport, batch); + + auto preview = frontend->getBlocksPreview(); + auto indices = content->getIndices(); + + ItemDef* item = indices->getItemDef(stack.getItemId()); + switch (item->iconType) { + case item_icon_type::none: + break; + case item_icon_type::block: + batch->render(); + { + GfxContext subctx = ctx.sub(); + subctx.depthTest(true); + subctx.cullFace(true); + + Block* cblock = content->requireBlock(item->icon); + preview->begin(&subctx.getViewport()); + preview->draw(cblock, coord.x, coord.y, slotSize, tint); + } + uiShader->use(); + batch->begin(); + break; + case item_icon_type::sprite: { + size_t index = item->icon.find(':'); + std::string name = item->icon.substr(index+1); + UVRegion region(0.0f, 0.0, 1.0f, 1.0f); + if (index == std::string::npos) { + batch->texture(assets->getTexture(name)); + } else { + std::string atlasname = item->icon.substr(0, index); + Atlas* atlas = assets->getAtlas(atlasname); + if (atlas && atlas->has(name)) { + region = atlas->get(name); + batch->texture(atlas->getTexture()); + } + } + batch->rect( + coord.x, coord.y, slotSize, slotSize, + 0, 0, 0, region, false, true, tint); + break; + } + } + + if (stack.getCount() > 1) { + auto font = assets->getFont("normal"); + std::wstring text = std::to_wstring(stack.getCount()); + + int x = coord.x+slotSize-text.length()*8; + int y = coord.y+slotSize-16; + + batch->color = glm::vec4(0, 0, 0, 1.0f); + font->draw(batch, text, x+1, y+1); + batch->color = glm::vec4(1.0f); + font->draw(batch, text, x, y); + } +} + +void SlotView::setHighlighted(bool flag) { + highlighted = flag; +} + +bool SlotView::isHighlighted() const { + return highlighted; +} + +void SlotView::clicked(gui::GUI* gui, int button) { + ItemStack& grabbed = interaction->getGrabbedItem(); + if (button == mousecode::BUTTON_1) { + if (Events::pressed(keycode::LEFT_SHIFT)) { + if (layout.shareFunc) { + layout.shareFunc(stack); + } + return; + } + if (!layout.itemSource && stack.accepts(grabbed)) { + stack.move(grabbed, content->getIndices()); + } else { + if (layout.itemSource) { + if (grabbed.isEmpty()) { + grabbed.set(stack); + } else { + grabbed.clear(); + } + } else { + std::swap(grabbed, stack); + } + } + } else if (button == mousecode::BUTTON_2) { + if (layout.rightClick) { + layout.rightClick(stack, grabbed); + return; + } + if (layout.itemSource) + return; + if (grabbed.isEmpty()) { + if (!stack.isEmpty()) { + grabbed.set(stack); + int halfremain = stack.getCount() / 2; + grabbed.setCount(stack.getCount() - halfremain); + stack.setCount(halfremain); + } + } else { + if (stack.isEmpty()) { + stack.set(grabbed); + stack.setCount(1); + } else { + stack.setCount(stack.getCount()+1); + } + grabbed.setCount(grabbed.getCount()-1); + } + } +} InventoryView::InventoryView( - int columns, const Content* content, LevelFrontend* frontend, - std::vector items) - : content(content), + InventoryInteraction* interaction, + std::shared_ptr inventory, + std::unique_ptr layout) + : Container(glm::vec2(), glm::vec2()), + content(content), indices(content->getIndices()), - items(items), + inventory(inventory), + layout(std::move(layout)), frontend(frontend), - columns(columns) { + interaction(interaction) { + size(this->layout->getSize()); + color(glm::vec4(0, 0, 0, 0.5f)); } -InventoryView::~InventoryView() { -} +InventoryView::~InventoryView() {} -void InventoryView::setPosition(int x, int y) { - position.x = x; - position.y = y; -} +void InventoryView::build() { + int index = 0; + for (auto& slot : layout->getSlots()) { + ItemStack& item = inventory->getSlot(index); -int InventoryView::getWidth() const { - return columns * iconSize + (columns-1) * interval + padding.x * 2; -} - -int InventoryView::getHeight() const { - uint inv_rows = ceildiv(items.size(), columns); - return inv_rows * iconSize + (inv_rows-1) * interval + padding.y * 2; -} - -void InventoryView::setSlotConsumer(slotconsumer consumer) { - this->consumer = consumer; -} - -void InventoryView::setItems(std::vector items) { - this->items = items; -} - -void InventoryView::actAndDraw(const GfxContext* ctx) { - Assets* assets = frontend->getAssets(); - Shader* uiShader = assets->getShader("ui"); - - auto viewport = ctx->getViewport(); - uint inv_w = getWidth(); - uint inv_h = getHeight(); - int xs = position.x + padding.x; - int ys = position.y + padding.y; - - glm::vec4 tint (1.0f); - int mx = Events::cursor.x; - int my = Events::cursor.y; - - // background - auto batch = ctx->getBatch2D(); - batch->texture(nullptr); - batch->color = glm::vec4(0.0f, 0.0f, 0.0f, 0.5f); - batch->rect(position.x, position.y, inv_w, inv_h); - batch->render(); - - // blocks & items - if (Events::scroll) { - scroll -= Events::scroll * (iconSize+interval); + auto view = std::make_shared( + item, frontend, interaction, content, slot + ); + if (!slot.background) { + view->color(glm::vec4()); + } + slots.push_back(view.get()); + add(view, slot.position); + index++; } - scroll = std::min(scroll, int(inv_h-viewport.getHeight())); - scroll = std::max(scroll, 0); - - auto blocksPreview = frontend->getBlocksPreview(); - // todo: optimize - { - Window::clearDepth(); - GfxContext subctx = ctx->sub(); - subctx.depthTest(true); - subctx.cullFace(true); - uint index = 0; - for (uint i = 0; i < items.size(); i++) { - ItemDef* item = indices->getItemDef(items[i]); - int x = xs + (iconSize+interval) * (index % columns); - int y = ys + (iconSize+interval) * (index / columns) - scroll; - if (y < -int(iconSize+interval) || y >= int(viewport.getHeight())) { - index++; - continue; - } - if (mx > x && mx < x + (int)iconSize && my > y && my < y + (int)iconSize) { - tint.r *= 1.2f; - tint.g *= 1.2f; - tint.b *= 1.2f; - if (Events::jclicked(mousecode::BUTTON_1)) { - if (consumer) { - consumer(items[i]); - } - } - } else { - tint = glm::vec4(1.0f); - } - switch (item->iconType) { - case item_icon_type::none: - break; - case item_icon_type::block: { - Block* cblock = content->requireBlock(item->icon); - blocksPreview->begin(&ctx->getViewport()); - blocksPreview->draw(cblock, x, y, iconSize, tint); - break; - } - case item_icon_type::sprite: { - batch->begin(); - uiShader->use(); - size_t index = item->icon.find(':'); - std::string name = item->icon.substr(index+1); - UVRegion region(0.0f, 0.0, 1.0f, 1.0f); - if (index == std::string::npos) { - batch->texture(assets->getTexture(name)); - } else { - std::string atlasname = item->icon.substr(0, index); - Atlas* atlas = assets->getAtlas(atlasname); - if (atlas && atlas->has(name)) { - region = atlas->get(name); - batch->texture(atlas->getTexture()); - } - } - batch->rect(x, y, 48, 48, 0, 0, 0, region, false, true, glm::vec4(1.0f)); - batch->render(); - break; - } - } - index++; - } - } - uiShader->use(); +} + +void InventoryView::setSelected(int index) { + for (int i = 0; i < int(slots.size()); i++) { + auto slot = slots[i]; + slot->setHighlighted(i == index); + } +} + +void InventoryView::setCoord(glm::vec2 coord) { + Container::setCoord(coord - layout->getOrigin()); +} + +void InventoryView::setInventory(std::shared_ptr inventory) { + this->inventory = inventory; +} + +InventoryLayout* InventoryView::getLayout() const { + return layout.get(); +} + +// performance disaster x2 +void InventoryView::draw(Batch2D* batch, Assets* assets) { + Container::draw(batch, assets); + Window::clearDepth(); +} + +void InventoryView::drawBackground(Batch2D* batch, Assets* assets) { + glm::vec2 coord = calcCoord(); + batch->texture(nullptr); + batch->color = color_; + batch->rect(coord.x-1, coord.y-1, size_.x+2, size_.y+2); } diff --git a/src/frontend/InventoryView.h b/src/frontend/InventoryView.h index af7f7990..1115428e 100644 --- a/src/frontend/InventoryView.h +++ b/src/frontend/InventoryView.h @@ -5,46 +5,150 @@ #include #include +#include "../frontend/gui/UINode.h" +#include "../frontend/gui/panels.h" +#include "../frontend/gui/controls.h" +#include "../items/ItemStack.h" #include "../typedefs.h" +class Batch2D; class Assets; class GfxContext; class Content; class ContentIndices; class LevelFrontend; +class Inventory; -typedef std::function slotconsumer; +typedef std::function itemsharefunc; +typedef std::function slotcallback; -class InventoryView { +class InventoryInteraction; + +struct SlotLayout { + glm::vec2 position; + bool background; + bool itemSource; + itemsharefunc shareFunc; + slotcallback rightClick; + + SlotLayout(glm::vec2 position, + bool background, + bool itemSource, + itemsharefunc shareFunc, + slotcallback rightClick); +}; + +// temporary unused +struct InventoryPanel { + glm::vec2 position; + glm::vec2 size; + glm::vec4 color; + + InventoryPanel(glm::vec2 position, + glm::vec2 size, + glm::vec4 color); +}; + +class InventoryLayout { + glm::vec2 size; + glm::vec2 origin; + std::vector slots; +public: + InventoryLayout(glm::vec2 size); + + void add(SlotLayout slot); + void setSize(glm::vec2 size); + void setOrigin(glm::vec2 origin); + + glm::vec2 getSize() const; + glm::vec2 getOrigin() const; + + std::vector& getSlots(); +}; + +class InventoryBuilder { + std::unique_ptr layout; +public: + InventoryBuilder(); + + void addGrid( + int cols, int rows, + glm::vec2 coord, + int padding, + SlotLayout slotLayout); + std::unique_ptr build(); +}; + +class SlotView : public gui::UINode { + LevelFrontend* frontend; + InventoryInteraction* interaction; + const Content* const content; + ItemStack& stack; + bool highlighted = false; + + SlotLayout layout; +public: + SlotView(ItemStack& stack, + LevelFrontend* frontend, + InventoryInteraction* interaction, + const Content* content, + SlotLayout layout); + + virtual void draw(Batch2D* batch, Assets* assets) override; + + void setHighlighted(bool flag); + bool isHighlighted() const; + + virtual void clicked(gui::GUI*, int) override; +}; + +class InventoryView : public gui::Container { const Content* content; const ContentIndices* indices; - std::vector items; - slotconsumer consumer = nullptr; + + std::shared_ptr inventory; + std::unique_ptr layout; LevelFrontend* frontend; + InventoryInteraction* interaction; + + std::vector slots; int scroll = 0; - int columns; - uint iconSize = 48; - uint interval = 4; - glm::ivec2 padding {interval, interval}; - glm::ivec2 position {0, 0}; public: InventoryView( - int columns, const Content* content, LevelFrontend* frontend, - std::vector items); + InventoryInteraction* interaction, + std::shared_ptr inventory, + std::unique_ptr layout); virtual ~InventoryView(); - virtual void actAndDraw(const GfxContext* ctx); + void build(); - void setItems(std::vector items); + virtual void draw(Batch2D* batch, Assets* assets) override; + virtual void drawBackground(Batch2D* batch, Assets* assets) override; - void setPosition(int x, int y); - int getWidth() const; - int getHeight() const; - void setSlotConsumer(slotconsumer consumer); + void setInventory(std::shared_ptr inventory); + + virtual void setCoord(glm::vec2 coord) override; + + InventoryLayout* getLayout() const; + + void setSelected(int index); + + static const int SLOT_INTERVAL = 4; + static const int SLOT_SIZE = 48; +}; + +class InventoryInteraction { + ItemStack grabbedItem; +public: + InventoryInteraction() = default; + + ItemStack& getGrabbedItem() { + return grabbedItem; + } }; #endif // FRONTEND_INVENTORY_VIEW_H_ diff --git a/src/frontend/WorldRenderer.cpp b/src/frontend/WorldRenderer.cpp index 0e517951..8ae83df0 100644 --- a/src/frontend/WorldRenderer.cpp +++ b/src/frontend/WorldRenderer.cpp @@ -28,6 +28,8 @@ #include "../settings.h" #include "../engine.h" #include "../items/ItemDef.h" +#include "../items/ItemStack.h" +#include "../items/Inventory.h" #include "LevelFrontend.h" #include "graphics/Skybox.h" @@ -169,8 +171,10 @@ void WorldRenderer::draw(const GfxContext& pctx, Camera* camera, bool hudVisible shader->uniform3f("u_cameraPos", camera->position); shader->uniform1i("u_cubemap", 1); { - itemid_t id = level->player->getChosenItem(); - ItemDef* item = indices->getItemDef(id); + auto player = level->player; + auto inventory = player->getInventory(); + ItemStack& stack = inventory->getSlot(player->getChosenSlot()); + ItemDef* item = indices->getItemDef(stack.getItemId()); assert(item != nullptr); float multiplier = 0.5f; shader->uniform3f("u_torchlightColor", diff --git a/src/frontend/gui/GUI.cpp b/src/frontend/gui/GUI.cpp index 30161f24..2173eb45 100644 --- a/src/frontend/gui/GUI.cpp +++ b/src/frontend/gui/GUI.cpp @@ -40,7 +40,6 @@ PagesControl* GUI::getMenu() { } void GUI::actMouse(float delta) { - auto hover = container->getAt(Events::cursor, nullptr); if (this->hover && this->hover != hover) { this->hover->hover(false); @@ -53,7 +52,7 @@ void GUI::actMouse(float delta) { } this->hover = hover; - if (Events::jclicked(0)) { + if (Events::jclicked(mousecode::BUTTON_1)) { if (pressed == nullptr && this->hover) { pressed = hover; pressed->click(this, Events::cursor.x, Events::cursor.y); @@ -73,6 +72,14 @@ void GUI::actMouse(float delta) { pressed->mouseRelease(this, Events::cursor.x, Events::cursor.y); pressed = nullptr; } + + if (hover) { + for (int i = mousecode::BUTTON_1; i < mousecode::BUTTON_1+12; i++) { + if (Events::jclicked(i)) { + hover->clicked(this, i); + } + } + } } void GUI::act(float delta) { @@ -100,13 +107,6 @@ void GUI::act(float delta) { if (Events::clicked(mousecode::BUTTON_1)) { focus->mouseMove(this, Events::cursor.x, Events::cursor.y); } - if (prevfocus == focus){ - for (int i = mousecode::BUTTON_1; i < mousecode::BUTTON_1+12; i++) { - if (Events::jclicked(i)) { - focus->clicked(this, i); - } - } - } } } } diff --git a/src/frontend/gui/UINode.cpp b/src/frontend/gui/UINode.cpp index 3a77b146..239da047 100644 --- a/src/frontend/gui/UINode.cpp +++ b/src/frontend/gui/UINode.cpp @@ -10,8 +10,6 @@ using gui::Align; using glm::vec2; using glm::vec4; -#include - UINode::UINode(vec2 coord, vec2 size) : coord(coord), size_(size) { } @@ -78,9 +76,20 @@ bool UINode::isInside(glm::vec2 pos) { } shared_ptr UINode::getAt(vec2 pos, shared_ptr self) { + if (!interactive) { + return nullptr; + } return isInside(pos) ? self : nullptr; } +bool UINode::isInteractive() const { + return interactive && visible(); +} + +void UINode::setInteractive(bool flag) { + interactive = flag; +} + vec2 UINode::calcCoord() const { if (parent) { return coord + parent->calcCoord() + parent->contentOffset(); diff --git a/src/frontend/gui/UINode.h b/src/frontend/gui/UINode.h index 25ba270f..668e4d97 100644 --- a/src/frontend/gui/UINode.h +++ b/src/frontend/gui/UINode.h @@ -30,6 +30,7 @@ namespace gui { bool hover_ = false; bool pressed_ = false; bool focused_ = false; + bool interactive = true; Align align_ = Align::left; UINode* parent = nullptr; UINode(glm::vec2 coord, glm::vec2 size); @@ -74,9 +75,12 @@ namespace gui { virtual bool isInside(glm::vec2 pos); virtual std::shared_ptr getAt(glm::vec2 pos, std::shared_ptr self); + virtual bool isInteractive() const; + virtual void setInteractive(bool flag); + virtual glm::vec2 contentOffset() {return glm::vec2(0.0f);}; glm::vec2 calcCoord() const; - void setCoord(glm::vec2 coord); + virtual void setCoord(glm::vec2 coord); glm::vec2 size() const; virtual void size(glm::vec2 size); void _size(glm::vec2 size); diff --git a/src/frontend/gui/controls.cpp b/src/frontend/gui/controls.cpp index 3315912d..626c47de 100644 --- a/src/frontend/gui/controls.cpp +++ b/src/frontend/gui/controls.cpp @@ -62,8 +62,21 @@ void Label::size(vec2 sizenew) { UINode::size(vec2(UINode::size().x, sizenew.y)); } +// ================================= Image ==================================== +Image::Image(string texture, vec2 size) : UINode(vec2(), size), texture(texture) { +} + +void Image::draw(Batch2D* batch, Assets* assets) { + vec2 coord = calcCoord(); + batch->texture(assets->getTexture(texture)); + batch->color = color_; + batch->rect(coord.x, coord.y, size_.x, size_.y, 0, 0, 0, UVRegion(), false, true, color_); +} + // ================================= Button =================================== -Button::Button(shared_ptr content, glm::vec4 padding) : Panel(vec2(32,32), padding, 0) { +Button::Button(shared_ptr content, glm::vec4 padding) + : Panel(content->size()+vec2(padding[0]+padding[2]+content->margin()[0]+content->margin()[2], + padding[1]+padding[3]+content->margin()[1]+content->margin()[3]), padding, 0) { add(content); scrollable(false); } @@ -100,6 +113,11 @@ Button* Button::textSupplier(wstringsupplier supplier) { } return this; } + +void Button::setHoverColor(glm::vec4 color) { + hoverColor = color; +} + void Button::drawBackground(Batch2D* batch, Assets* assets) { vec2 coord = calcCoord(); batch->texture(nullptr); @@ -133,6 +151,35 @@ void Button::textAlign(Align align) { } } +// ============================== RichButton ================================== +RichButton::RichButton(vec2 size) : Container(vec2(), size) { +} + +void RichButton::mouseRelease(GUI* gui, int x, int y) { + UINode::mouseRelease(gui, x, y); + if (isInside(vec2(x, y))) { + for (auto callback : actions) { + callback(gui); + } + } +} + +RichButton* RichButton::listenAction(onaction action) { + actions.push_back(action); + return this; +} + +void RichButton::setHoverColor(glm::vec4 color) { + hoverColor = color; +} + +void RichButton::drawBackground(Batch2D* batch, Assets* assets) { + vec2 coord = calcCoord(); + batch->texture(nullptr); + batch->color = (ispressed() ? pressedColor : (hover_ ? hoverColor : color_)); + batch->rect(coord.x, coord.y, size_.x, size_.y); +} + // ================================ TextBox =================================== TextBox::TextBox(wstring placeholder, vec4 padding) : Panel(vec2(200,32), padding, 0, false), diff --git a/src/frontend/gui/controls.h b/src/frontend/gui/controls.h index a78bd587..60587248 100644 --- a/src/frontend/gui/controls.h +++ b/src/frontend/gui/controls.h @@ -43,6 +43,15 @@ namespace gui { virtual void size(glm::vec2 size) override; }; + class Image : public UINode { + protected: + std::string texture; + public: + Image(std::string texture, glm::vec2 size); + + virtual void draw(Batch2D* batch, Assets* assets) override; + }; + class Button : public Panel { protected: glm::vec4 hoverColor {0.05f, 0.1f, 0.15f, 0.75f}; @@ -55,7 +64,7 @@ namespace gui { glm::vec4 padding=glm::vec4(2.0f), glm::vec4 margin=glm::vec4(1.0f)); - virtual void drawBackground(Batch2D* batch, Assets* assets); + virtual void drawBackground(Batch2D* batch, Assets* assets) override; virtual std::shared_ptr getAt(glm::vec2 pos, std::shared_ptr self) override; @@ -68,6 +77,24 @@ namespace gui { virtual std::wstring text() const; virtual Button* textSupplier(wstringsupplier supplier); + + virtual void setHoverColor(glm::vec4 color); + }; + + class RichButton : public Container { + protected: + glm::vec4 hoverColor {0.05f, 0.1f, 0.15f, 0.75f}; + glm::vec4 pressedColor {0.0f, 0.0f, 0.0f, 0.95f}; + std::vector actions; + public: + RichButton(glm::vec2 size); + + virtual void drawBackground(Batch2D* batch, Assets* assets) override; + + virtual void mouseRelease(GUI*, int x, int y) override; + virtual RichButton* listenAction(onaction action); + + virtual void setHoverColor(glm::vec4 color); }; class TextBox : public Panel { diff --git a/src/frontend/gui/panels.cpp b/src/frontend/gui/panels.cpp index 338b7b6d..2c838aa7 100644 --- a/src/frontend/gui/panels.cpp +++ b/src/frontend/gui/panels.cpp @@ -18,6 +18,9 @@ Container::Container(vec2 coord, vec2 size) : UINode(coord, size) { } shared_ptr Container::getAt(vec2 pos, shared_ptr self) { + if (!interactive) { + return nullptr; + } if (!isInside(pos)) return nullptr; for (auto node : nodes) { if (!node->visible()) @@ -58,7 +61,7 @@ void Container::act(float delta) { void Container::scrolled(int value) { int diff = (actualLength-size().y); if (diff > 0 && scrollable_) { - scroll += value * 20; + scroll += value * 40; if (scroll > 0) scroll = 0; if (-scroll > diff) { @@ -98,6 +101,11 @@ void Container::add(UINode* node) { add(shared_ptr(node)); } +void Container::add(shared_ptr node, glm::vec2 coord) { + node->setCoord(coord); + add(node); +} + void Container::remove(shared_ptr selected) { selected->setParent(nullptr); nodes.erase(std::remove_if(nodes.begin(), nodes.end(), @@ -190,15 +198,12 @@ void Panel::refresh() { node->refresh(); maxh = fmax(maxh, y+margin.y+node->size().y+margin.w+padding.w); } - bool increased = maxh > size.y; if (resizing_) { if (maxLength_) this->size(vec2(glm::min(maxLength_, (int)(x+padding.z)), size.y)); else this->size(vec2(x+padding.z, size.y)); } - if (increased) - refresh(); actualLength = size.y; } } @@ -273,4 +278,4 @@ void PagesControl::reset() { Container::remove(current_.panel); current_ = Page{nullptr}; } -} \ No newline at end of file +} diff --git a/src/frontend/gui/panels.h b/src/frontend/gui/panels.h index ab81bbe0..a93773ea 100644 --- a/src/frontend/gui/panels.h +++ b/src/frontend/gui/panels.h @@ -39,6 +39,7 @@ namespace gui { virtual std::shared_ptr getAt(glm::vec2 pos, std::shared_ptr self) override; virtual void add(std::shared_ptr node); virtual void add(UINode* node); + virtual void add(std::shared_ptr node, glm::vec2 coord); virtual void remove(std::shared_ptr node); virtual void scrolled(int value) override; virtual void scrollable(bool flag); diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index aa7fd378..8a4c3a0c 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -44,6 +44,7 @@ #include "../engine.h" #include "../core_defs.h" #include "../items/ItemDef.h" +#include "../items/Inventory.h" using glm::vec2; using glm::vec3; @@ -163,26 +164,142 @@ void HudRenderer::createDebugPanel(Engine* engine) { panel->refresh(); } +std::shared_ptr HudRenderer::createContentAccess() { + auto level = frontend->getLevel(); + auto content = level->content; + auto indices = content->getIndices(); + auto player = level->player; + auto inventory = player->getInventory(); + + int itemsCount = indices->countItemDefs(); + auto accessInventory = std::make_shared(itemsCount); + for (int id = 1; id < itemsCount; id++) { + accessInventory->getSlot(id-1).set(ItemStack(id, 1)); + } + + const int slotSize = InventoryView::SLOT_SIZE; + const int interval = InventoryView::SLOT_INTERVAL; + int padding = 8; + + int columns = 8; + int rows = ceildiv(itemsCount-1, columns); + uint cawidth = columns * (slotSize + interval) - interval + padding; + uint caheight = rows * (slotSize + interval) - interval + padding*2; + auto layout = std::make_unique(glm::vec2(cawidth, caheight)); + for (int i = 0; i < itemsCount-1; i++) { + int row = i / columns; + int col = i % columns; + glm::vec2 position ( + col * slotSize + (col-1) * interval + padding, + row * slotSize + (row-1) * interval + padding + ); + layout->add(SlotLayout(position, false, true, + [=](ItemStack& item) { + auto copy = ItemStack(item); + inventory->move(copy, indices); + }, + [=](ItemStack& item, ItemStack& grabbed) { + inventory->getSlot(player->getChosenSlot()).set(item); + })); + } + auto contentAccess = std::make_shared( + content, + frontend, + interaction.get(), + accessInventory, + std::move(layout) + ); + contentAccess->build(); + return contentAccess; +} + +std::shared_ptr HudRenderer::createHotbar() { + auto level = frontend->getLevel(); + auto player = level->player; + auto inventory = player->getInventory(); + auto content = level->content; + + const int slotSize = InventoryView::SLOT_SIZE; + const int interval = InventoryView::SLOT_INTERVAL; + + int padding = 4; + uint width = 10 * (slotSize + interval) - interval + padding*2; + uint height = slotSize + padding * 2; + auto layout = std::make_unique(glm::vec2(width, height)); + for (int i = 0; i < 10; i++) { + glm::vec2 position (i * (slotSize + interval) + padding, padding); + layout->add(SlotLayout(position, false, false, nullptr, nullptr)); + } + layout->setOrigin(glm::vec2(width / 2, 0)); + auto view = std::make_shared( + content, + frontend, + interaction.get(), + inventory, + std::move(layout) + ); + view->build(); + view->setInteractive(false); + return view; +} + +std::shared_ptr HudRenderer::createInventory() { + auto level = frontend->getLevel(); + auto player = level->player; + auto inventory = player->getInventory(); + auto content = level->content; + + SlotLayout slotLayout(glm::vec2(), true, false, [=](ItemStack& stack) { + stack.clear(); + }, nullptr); + + int columns = 10; + int rows = ceildiv(inventory->size(), columns); + int padding = 4; + + InventoryBuilder builder; + builder.addGrid(columns, rows, glm::vec2(), padding, slotLayout); + auto layout = builder.build(); + + auto view = std::make_shared( + content, + frontend, + interaction.get(), + inventory, + std::move(layout) + ); + view->build(); + return view; +} + HudRenderer::HudRenderer(Engine* engine, LevelFrontend* frontend) : assets(engine->getAssets()), gui(engine->getGUI()), - frontend(frontend) { - - auto level = frontend->getLevel(); + frontend(frontend) +{ auto menu = gui->getMenu(); - auto content = level->content; - auto indices = content->getIndices(); - std::vector items; - for (itemid_t id = 1; id < indices->countItemDefs(); id++) { - items.push_back(id); - } - contentAccess.reset(new InventoryView(8, content, frontend, items)); - contentAccess->setSlotConsumer([=](blockid_t id) { - level->player->setChosenItem(id); - }); + interaction = std::make_unique(); + grabbedItemView = std::make_shared( + interaction->getGrabbedItem(), + frontend, + interaction.get(), + frontend->getLevel()->content, + SlotLayout(glm::vec2(), false, false, nullptr, nullptr) + ); + grabbedItemView->color(glm::vec4()); + grabbedItemView->setInteractive(false); - hotbarView.reset(new InventoryView(1, content, frontend, std::vector {0})); + contentAccess = createContentAccess(); + contentAccessPanel = std::make_shared( + contentAccess->size(), vec4(0.0f), 0.0f + ); + contentAccessPanel->color(glm::vec4()); + contentAccessPanel->add(contentAccess); + contentAccessPanel->scrollable(true); + + hotbarView = createHotbar(); + inventoryView = createInventory(); uicamera = new Camera(vec3(), 1); uicamera->perspective = false; @@ -191,10 +308,18 @@ HudRenderer::HudRenderer(Engine* engine, LevelFrontend* frontend) createDebugPanel(engine); menu->reset(); - gui->add(this->debugPanel); + gui->add(debugPanel); + gui->add(contentAccessPanel); + gui->add(hotbarView); + gui->add(inventoryView); + gui->add(grabbedItemView); } HudRenderer::~HudRenderer() { + gui->remove(grabbedItemView); + gui->remove(inventoryView); + gui->remove(hotbarView); + gui->remove(contentAccessPanel); gui->remove(debugPanel); delete uicamera; } @@ -206,7 +331,12 @@ void HudRenderer::drawDebug(int fps){ } void HudRenderer::update(bool visible) { + auto level = frontend->getLevel(); + auto player = level->player; auto menu = gui->getMenu(); + + menu->visible(pause); + if (!visible && inventoryOpen) { inventoryOpen = false; } @@ -232,13 +362,35 @@ void HudRenderer::update(bool visible) { if ((pause || inventoryOpen) == Events::_cursor_locked) { Events::toggleCursor(); } + + glm::vec2 invSize = contentAccessPanel->size(); + inventoryView->visible(inventoryOpen); + contentAccessPanel->visible(inventoryOpen); + contentAccessPanel->size(glm::vec2(invSize.x, Window::height)); + + for (int i = keycode::NUM_1; i <= keycode::NUM_9; i++) { + if (Events::jpressed(i)) { + player->setChosenSlot(i - keycode::NUM_1); + } + } + if (Events::jpressed(keycode::NUM_0)) { + player->setChosenSlot(9); + } + if (!inventoryOpen && Events::scroll) { + int slot = player->getChosenSlot(); + slot = (slot + Events::scroll) % 10; + if (slot < 0) { + slot += 10; + } + player->setChosenSlot(slot); + } } void HudRenderer::drawOverlay(const GfxContext& ctx) { if (pause) { Shader* uishader = assets->getShader("ui"); uishader->use(); - uishader->uniformMatrix("u_projview", uicamera->getProjection()*uicamera->getView()); + uishader->uniformMatrix("u_projview", uicamera->getProjView()); const Viewport& viewport = ctx.getViewport(); const uint width = viewport.getWidth(); @@ -271,12 +423,11 @@ void HudRenderer::draw(const GfxContext& ctx){ Shader* uishader = assets->getShader("ui"); uishader->use(); - uishader->uniformMatrix("u_projview", uicamera->getProjection()*uicamera->getView()); + uishader->uniformMatrix("u_projview", uicamera->getProjView()); // Draw selected item preview - hotbarView->setPosition(width-60, height-60); - hotbarView->setItems({player->getChosenItem()}); - hotbarView->actAndDraw(&ctx); + hotbarView->setCoord(glm::vec2(width/2, height-65)); + hotbarView->setSelected(player->getChosenSlot()); // Crosshair batch->begin(); @@ -289,10 +440,20 @@ void HudRenderer::draw(const GfxContext& ctx){ } if (inventoryOpen) { - // draw content access panel (all available items) - contentAccess->setPosition(viewport.getWidth()-contentAccess->getWidth(), 0); - contentAccess->actAndDraw(&ctx); + auto caLayout = contentAccess->getLayout(); + auto invLayout = inventoryView->getLayout(); + float caWidth = caLayout->getSize().x; + glm::vec2 invSize = invLayout->getSize(); + + float width = viewport.getWidth(); + + inventoryView->setCoord(glm::vec2( + glm::min(width/2-invSize.x/2, width-caWidth-10-invSize.x), + height/2-invSize.y/2 + )); + contentAccessPanel->setCoord(glm::vec2(width-caWidth, 0)); } + grabbedItemView->setCoord(glm::vec2(Events::cursor)); batch->render(); } diff --git a/src/frontend/hud.h b/src/frontend/hud.h index 2cf2cdfe..05515e0c 100644 --- a/src/frontend/hud.h +++ b/src/frontend/hud.h @@ -15,12 +15,15 @@ class Assets; class Player; class Level; class Engine; +class SlotView; class InventoryView; class LevelFrontend; +class InventoryInteraction; namespace gui { class GUI; class UINode; + class Panel; } class HudRenderer { @@ -34,13 +37,21 @@ class HudRenderer { bool inventoryOpen = false; bool pause = false; - std::unique_ptr contentAccess; - std::unique_ptr hotbarView; + std::shared_ptr contentAccessPanel; + std::shared_ptr contentAccess; + std::shared_ptr hotbarView; + std::shared_ptr inventoryView; std::shared_ptr debugPanel; + std::unique_ptr interaction; + std::shared_ptr grabbedItemView; gui::GUI* gui; LevelFrontend* frontend; void createDebugPanel(Engine* engine); + + std::shared_ptr createContentAccess(); + std::shared_ptr createHotbar(); + std::shared_ptr createInventory(); public: HudRenderer(Engine* engine, LevelFrontend* frontend); ~HudRenderer(); diff --git a/src/frontend/menu.cpp b/src/frontend/menu.cpp index 4f707ac6..f0d2f2bd 100644 --- a/src/frontend/menu.cpp +++ b/src/frontend/menu.cpp @@ -195,13 +195,40 @@ Panel* create_worlds_panel(Engine* engine) { if (!entry.is_directory()) { continue; } - auto name = entry.path().filename().u8string(); - auto btn = new Button(util::str2wstr_utf8(name), - vec4(10.0f, 8.0f, 10.0f, 8.0f)); + auto folder = entry.path(); + auto name = folder.filename().u8string(); + auto namews = util::str2wstr_utf8(name); + + auto btn = std::make_shared(vec2(390, 46)); btn->color(vec4(1.0f, 1.0f, 1.0f, 0.1f)); + btn->setHoverColor(vec4(1.0f, 1.0f, 1.0f, 0.17f)); + + auto label = std::make_shared