From df47842c5e0039b299513520b3bc9de7bc313a5a Mon Sep 17 00:00:00 2001 From: iProgramInCpp Date: Wed, 9 Aug 2023 22:13:04 +0300 Subject: [PATCH] * Add PatchManager - Allows you to patch terrain.png and items.png on the fly. This can be used for modding, since you don't need to share copyrighted Mojang assets alongside your texture patches. --- .gitignore | 3 + game/assets/patches/patch_data.txt | 28 +++++ platforms/windows/AppPlatform_windows.cpp | 19 ++- platforms/windows/AppPlatform_windows.hpp | 3 + source/AppPlatform.cpp | 5 + source/AppPlatform.hpp | 2 + source/Minecraft.cpp | 11 ++ source/client/renderer/PatchManager.cpp | 136 ++++++++++++++++++++++ source/client/renderer/PatchManager.hpp | 80 +++++++++++++ source/world/tile/GrassTile.cpp | 7 +- source/world/tile/LeafTile.cpp | 7 +- source/world/tile/MetalTile.cpp | 10 +- windows_vs/minecraftcpp.vcxproj | 2 + windows_vs/minecraftcpp.vcxproj.filters | 6 + 14 files changed, 311 insertions(+), 8 deletions(-) create mode 100644 game/assets/patches/patch_data.txt create mode 100644 source/client/renderer/PatchManager.cpp create mode 100644 source/client/renderer/PatchManager.hpp diff --git a/.gitignore b/.gitignore index 700a365..dbff122 100644 --- a/.gitignore +++ b/.gitignore @@ -401,6 +401,9 @@ FodyWeavers.xsd /game/assets/particles.png /game/assets/terrain.png +# Avoid including Minecraft Classic assets from Mojang, for now. +/game/assets/patches/*.png + # Ignore keep directory - where you can keep files for later use /keep diff --git a/game/assets/patches/patch_data.txt b/game/assets/patches/patch_data.txt new file mode 100644 index 0000000..bf514f4 --- /dev/null +++ b/game/assets/patches/patch_data.txt @@ -0,0 +1,28 @@ +#Texture patch data for Minecraft PE. The # at the start denotes a comment, removing it makes it a command. +# Commands you can use: +# - terrain|x|y|filename -- Patches terrain.png +# - items|x|y|filename -- Patches gui/items.png +# - vegetation_tint|bool -- Enables default vegetation tint. (grass and leaves) +# - metal_block_sides|int -- Makes metal blocks have separate side textures, and allows specification of their Y offset. -1 to disable. + +# * The filename parameter will be a PNG file that can be found relative to this directory. For example, '../quiver.png' will load it from assets/, 'chain.png' will load it from assets/patches/. +# * The X and Y destination coordinates will be multiplied by 16. +# * The texture doesn't have to be 16x16, all of it will be patched on to terrain.png. + + +# Below is an example of what you can do: + +#terrain|0|0|c_grass_top.png +#terrain|4|3|c_leaves_tra.png +#terrain|5|3|c_leaves_opa.png +#terrain|7|0|c_bricks.png +#terrain|12|0|c_rose.png +#terrain|6|11|c_iron.png +#terrain|7|11|c_gold.png +#terrain|8|11|c_emerald.png +#items|7|0|c_i_coal.png +#items|7|1|c_i_iron.png +#items|7|2|c_i_gold.png +#items|7|3|c_i_emerald.png +#vegetation_tint|false +#metal_block_sides|10 diff --git a/platforms/windows/AppPlatform_windows.cpp b/platforms/windows/AppPlatform_windows.cpp index cda1e6e..63b1276 100644 --- a/platforms/windows/AppPlatform_windows.cpp +++ b/platforms/windows/AppPlatform_windows.cpp @@ -183,8 +183,13 @@ Texture AppPlatform_windows::loadTexture(const std::string& str, bool b) goto _error; } + uint32_t* img2 = new uint32_t[width * height]; + memcpy(img2, img, width * height * sizeof(uint32_t)); + stbi_image_free(img); + img = nullptr; + fclose(f); - return Texture(width, height, (uint32_t*)img, 1, 0); + return Texture(width, height, img2, 1, 0); } std::vector AppPlatform_windows::getOptionStrings() @@ -233,6 +238,18 @@ void AppPlatform_windows::setOptionStrings(const std::vector& str) os << str[i] << ':' << str[i + 1] << '\n'; } +std::string AppPlatform_windows::getPatchData() +{ + std::ifstream ifs("assets/patches/patch_data.txt"); + if (!ifs.is_open()) + return ""; + + std::stringstream ss; + ss << ifs.rdbuf(); + + return ss.str(); +} + void AppPlatform_windows::setScreenSize(int width, int height) { m_ScreenWidth = width; diff --git a/platforms/windows/AppPlatform_windows.hpp b/platforms/windows/AppPlatform_windows.hpp index 1af536d..d1c2d36 100644 --- a/platforms/windows/AppPlatform_windows.hpp +++ b/platforms/windows/AppPlatform_windows.hpp @@ -52,6 +52,9 @@ public: // Also add these to allow saving options. void setOptionStrings(const std::vector & str) override; + // Also add this to allow dynamic texture patching. + std::string getPatchData() override; + void setScreenSize(int width, int height); private: diff --git a/source/AppPlatform.cpp b/source/AppPlatform.cpp index d7eaa81..65ce3ad 100644 --- a/source/AppPlatform.cpp +++ b/source/AppPlatform.cpp @@ -126,3 +126,8 @@ bool AppPlatform::shiftPressed() void AppPlatform::setOptionStrings(const std::vector& vec) { } + +std::string AppPlatform::getPatchData() +{ + return ""; +} diff --git a/source/AppPlatform.hpp b/source/AppPlatform.hpp index d836b3a..cdc27ce 100644 --- a/source/AppPlatform.hpp +++ b/source/AppPlatform.hpp @@ -52,6 +52,8 @@ public: virtual bool shiftPressed(); // Also add this to allow option saving. virtual void setOptionStrings(const std::vector& vec); + // Also add this to allow dynamic patching. + virtual std::string getPatchData(); #endif diff --git a/source/Minecraft.cpp b/source/Minecraft.cpp index b9c8d19..183ebab 100644 --- a/source/Minecraft.cpp +++ b/source/Minecraft.cpp @@ -23,6 +23,9 @@ #include "client/player/input/ControllerTurnInput.hpp" #endif +// custom: +#include "client/renderer/PatchManager.hpp" + // note: Nothing changes these, so it'll think we're always running at 854x480 even if not int Minecraft::width = C_DEFAULT_SCREEN_WIDTH; int Minecraft::height = C_DEFAULT_SCREEN_HEIGHT; @@ -684,6 +687,14 @@ void Minecraft::init() reloadOptions(); m_pFont = new Font(&m_options, "font/default.png", m_pTextures); + + // Patch Manager + GetPatchManager()->LoadPatchData(platform()->getPatchData()); + + m_pTextures->loadAndBindTexture(C_TERRAIN_NAME); + GetPatchManager()->PatchTextures(platform(), TYPE_TERRAIN); + m_pTextures->loadAndBindTexture(C_ITEMS_NAME); + GetPatchManager()->PatchTextures(platform(), TYPE_ITEMS); } Minecraft::~Minecraft() diff --git a/source/client/renderer/PatchManager.cpp b/source/client/renderer/PatchManager.cpp new file mode 100644 index 0000000..05c835b --- /dev/null +++ b/source/client/renderer/PatchManager.cpp @@ -0,0 +1,136 @@ +#include "PatchManager.hpp" +#include "AppPlatform.hpp" +#include "client/common/Utils.hpp" +#include "compat/GL.hpp" + +#define PM_SEPARATOR ('|') + +PatchManager* g_pPatchManager; +PatchManager* GetPatchManager() +{ + if (!g_pPatchManager) + g_pPatchManager = new PatchManager; + + return g_pPatchManager; +} + +PatchManager::PatchManager() +{ + m_bGrassTinted = true; + m_nMetalSideYOffset = -1; +} + +void PatchManager::LoadPatchData(const std::string& patchData) +{ + std::stringstream patchDataStream(patchData); + std::string currLine; + + while (std::getline(patchDataStream, currLine)) + { + if (currLine.empty()) continue; + if (currLine[0] == '#') continue; + + std::string command; + std::stringstream lineStream(currLine); + // read command type + if (!std::getline(lineStream, command, PM_SEPARATOR)) + continue; + + if (command == "terrain" || command == "items") + { + bool bIsItems = command == "items"; + std::string xStr, yStr, fileName; + + if (!std::getline(lineStream, xStr, PM_SEPARATOR)) continue; + if (!std::getline(lineStream, yStr, PM_SEPARATOR)) continue; + if (!std::getline(lineStream, fileName, PM_SEPARATOR)) continue; + + // turn the xStr and yStr into ints. + int x, y; + if (!sscanf(xStr.c_str(), "%d", &x)) continue; + if (!sscanf(yStr.c_str(), "%d", &y)) continue; + + m_patchData.push_back(PatchData(bIsItems ? TYPE_ITEMS : TYPE_TERRAIN, x, y, fileName)); + + continue; + } + + // features -- TODO un-hardcode this + if (command == "vegetation_tint") + { + ReadBool(lineStream, m_bGrassTinted); + continue; + } + + // features -- TODO un-hardcode this + if (command == "metal_block_sides") + { + ReadInt(lineStream, m_nMetalSideYOffset); + continue; + } + + LogMsg("Unknown command %s from patch data.", command.c_str()); + } +} + +void PatchManager::PatchTextures(AppPlatform* pAppPlatform, ePatchType patchType) +{ + // Use glTexSubImage2D to patch the terrain.png texture on the fly. + for (int i = 0; i < int(m_patchData.size()); i++) + { + PatchData& pd = m_patchData[i]; + if (pd.m_type != patchType) + continue; + + Texture texture = pAppPlatform->loadTexture("patches/" + pd.m_filename, true); + if (texture.m_width == 0) + { + LogMsg("Image %s has width 0, not found?! Skipping", pd.m_filename.c_str()); + continue; + } + + glTexSubImage2D( + GL_TEXTURE_2D, + 0, + pd.m_destX, + pd.m_destY, + texture.m_width, + texture.m_height, + GL_RGBA, + GL_UNSIGNED_BYTE, + texture.m_pixels + ); + + SAFE_DELETE_ARRAY(texture.m_pixels); + } +} + +bool PatchManager::IsGrassTinted() +{ + return m_bGrassTinted; +} + +int PatchManager::GetMetalSideYOffset() +{ + return m_nMetalSideYOffset; +} + +void PatchManager::ReadBool(std::istream& is, bool& b) +{ + std::string flag; + if (!std::getline(is, flag, PM_SEPARATOR)) + return; + + b = (flag == "true" || flag == "1" || flag == "TRUE"); +} + +void PatchManager::ReadInt(std::istream& is, int& i) +{ + std::string flag; + if (!std::getline(is, flag, PM_SEPARATOR)) + return; + + int x = -1; + if (sscanf(flag.c_str(), "%d", &x)) + i = x; +} diff --git a/source/client/renderer/PatchManager.hpp b/source/client/renderer/PatchManager.hpp new file mode 100644 index 0000000..5a2c135 --- /dev/null +++ b/source/client/renderer/PatchManager.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +class AppPlatform; + +enum ePatchType +{ + TYPE_NONE, + TYPE_TERRAIN, + TYPE_ITEMS, + TYPE_FEATURE, +}; + +enum ePatchOption +{ + PO_NONE, + PO_GRASS_TINT, +}; + +struct PatchData +{ + ePatchType m_type; + ePatchOption m_option; + int m_destX, m_destY; + std::string m_filename; + bool m_bEnable; + + PatchData(ePatchType type, int x, int y, const std::string& fn) + { + _init(); + m_type = type; + m_destX = x * 16; + m_destY = y * 16; + m_filename = fn; + } + + PatchData(ePatchType type, ePatchOption opt, bool enable) + { + _init(); + m_type = type; + m_option = opt; + m_bEnable = enable; + } + + void _init() + { + m_type = TYPE_NONE; + m_option = PO_NONE; + m_destX = m_destY = 0; + m_bEnable = false; + } +}; + +class PatchManager +{ +public: + PatchManager(); + + void LoadPatchData(const std::string& patchData); + + void PatchTextures(AppPlatform*, ePatchType); + + // Features + bool IsGrassTinted(); + int GetMetalSideYOffset(); + +private: + void ReadBool(std::istream& is, bool& b); + void ReadInt(std::istream& is, int& b); + +private: + bool m_bGrassTinted; + int m_nMetalSideYOffset; + std::vector m_patchData; +}; + +PatchManager* GetPatchManager(); diff --git a/source/world/tile/GrassTile.cpp b/source/world/tile/GrassTile.cpp index 8f49e9b..dfa41ba 100644 --- a/source/world/tile/GrassTile.cpp +++ b/source/world/tile/GrassTile.cpp @@ -8,6 +8,7 @@ #include "Tile.hpp" #include "world/level/Level.hpp" +#include "client/renderer/PatchManager.hpp" GrassTile::GrassTile(int id, Material* c) : Tile(id, c) { @@ -17,10 +18,10 @@ GrassTile::GrassTile(int id, Material* c) : Tile(id, c) int GrassTile::getColor(LevelSource*, int x, int y, int z) { -#ifdef MOD_DONT_COLOR_GRASS + if (GetPatchManager()->IsGrassTinted()) + return 0x339933; + return 0xffffff; -#endif - return 0x339933; } int GrassTile::getResource(int i, Random* random) diff --git a/source/world/tile/LeafTile.cpp b/source/world/tile/LeafTile.cpp index a536199..f41cf08 100644 --- a/source/world/tile/LeafTile.cpp +++ b/source/world/tile/LeafTile.cpp @@ -8,6 +8,7 @@ #include "Tile.hpp" #include "world/level/Level.hpp" +#include "client/renderer/PatchManager.hpp" LeafTile::LeafTile(int id) : TransparentTile(id, TEXTURE_LEAVES_TRANSPARENT, Material::leaves, false) { @@ -33,10 +34,10 @@ void LeafTile::die(Level* level, int x, int y, int z) int LeafTile::getColor(LevelSource* level, int x, int y, int z) { -#ifdef MOD_DONT_COLOR_GRASS + if (GetPatchManager()->IsGrassTinted()) + return 0x339933; + return 0xffffff; -#endif - return 0x339933; } int LeafTile::getTexture(int dir, int data) diff --git a/source/world/tile/MetalTile.cpp b/source/world/tile/MetalTile.cpp index d625565..8b4c49e 100644 --- a/source/world/tile/MetalTile.cpp +++ b/source/world/tile/MetalTile.cpp @@ -8,6 +8,7 @@ #include "Tile.hpp" #include "world/level/Level.hpp" +#include "client/renderer/PatchManager.hpp" MetalTile::MetalTile(int ID, int texture, Material* pMtl) : Tile(ID, pMtl) { @@ -19,5 +20,12 @@ MetalTile::MetalTile(int ID, int texture, Material* pMtl) : Tile(ID, pMtl) // textures for these tiles. :) int MetalTile::getTexture(int dir) { - return m_TextureFrame; + int yoff = GetPatchManager()->GetMetalSideYOffset(); + if (yoff < 0) + return m_TextureFrame; + + if (dir == DIR_YPOS) return m_TextureFrame; + if (dir == DIR_YNEG) return m_TextureFrame + 16 * (yoff + 1); + + return m_TextureFrame + 16 * (yoff + 0); } diff --git a/windows_vs/minecraftcpp.vcxproj b/windows_vs/minecraftcpp.vcxproj index 1c221d3..807845e 100644 --- a/windows_vs/minecraftcpp.vcxproj +++ b/windows_vs/minecraftcpp.vcxproj @@ -113,6 +113,7 @@ + @@ -426,6 +427,7 @@ + diff --git a/windows_vs/minecraftcpp.vcxproj.filters b/windows_vs/minecraftcpp.vcxproj.filters index d52d165..835a1d6 100644 --- a/windows_vs/minecraftcpp.vcxproj.filters +++ b/windows_vs/minecraftcpp.vcxproj.filters @@ -996,6 +996,9 @@ source\client + + source\client\renderer + @@ -1967,6 +1970,9 @@ source\client\renderer + + source\client\renderer +