diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fb3032..fa7d657 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,11 @@ include(cmake/CPM.cmake) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) - +set(CMAKE_POLICY_VERSION_MINIMUM 3.10) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_FLAGS "-Wno-c++11-narrowing -Wno-narrowing -Wno-invalid-source-encoding -Wno-reserved-user-defined-literal") endif() - +find_package(Threads REQUIRED) CPMAddPackage("gh:madler/zlib@1.3.2") CPMAddPackage( NAME "libpng" @@ -44,8 +44,191 @@ CPMAddPackage( "GLFW_BUILD_DOCS OFF" "BUILD_SHARED_LIBS ON" ) +# TODO: Clear this paths with * +file(GLOB SERVER_SOURCES + "src/NinecraftApp.cpp" + "src/Performance.cpp" + "src/SharedConstants.cpp" -file(GLOB SOURCES + "src/client/IConfigListener.cpp" + "src/client/Minecraft.cpp" + "src/client/OptionStrings.cpp" + "src/client/Options.cpp" + "src/client/OptionsFile.cpp" + + "src/client/gamemode/CreativeMode.cpp" + "src/client/gamemode/GameMode.cpp" + "src/client/gamemode/SurvivalMode.cpp" + + "src/client/player/LocalPlayer.cpp" + "src/client/player/RemotePlayer.cpp" + "src/client/player/input/KeyboardInput.cpp" + + "src/locale/I18n.cpp" + + "src/main.cpp" + "src/main_dedicated.cpp" + + "src/nbt/Tag.cpp" + + "src/network/ClientSideNetworkHandler.cpp" + "src/network/NetEventCallback.cpp" + "src/network/Packet.cpp" + "src/network/RakNetInstance.cpp" + "src/network/ServerSideNetworkHandler.cpp" + "src/network/command/CommandServer.cpp" + + "src/platform/CThread.cpp" + "src/platform/HttpClient.cpp" + "src/platform/PngLoader.cpp" + "src/platform/time.cpp" + + "src/platform/input/Controller.cpp" + "src/platform/input/Keyboard.cpp" + "src/platform/input/Mouse.cpp" + "src/platform/input/Multitouch.cpp" + + "src/server/ArgumentsSettings.cpp" + "src/server/ServerLevel.cpp" + "src/server/ServerPlayer.cpp" + + "src/util/DataIO.cpp" + "src/util/Mth.cpp" + "src/util/PerfTimer.cpp" + "src/util/StringUtils.cpp" + + "src/world/Direction.cpp" + + "src/world/entity/AgableMob.cpp" + "src/world/entity/Entity.cpp" + "src/world/entity/EntityFactory.cpp" + "src/world/entity/FlyingMob.cpp" + "src/world/entity/HangingEntity.cpp" + "src/world/entity/Mob.cpp" + "src/world/entity/MobCategory.cpp" + "src/world/entity/Motive.cpp" + "src/world/entity/Painting.cpp" + "src/world/entity/PathfinderMob.cpp" + "src/world/entity/SynchedEntityData.cpp" + + "src/world/entity/ai/control/MoveControl.cpp" + + "src/world/entity/animal/Animal.cpp" + "src/world/entity/animal/Chicken.cpp" + "src/world/entity/animal/Cow.cpp" + "src/world/entity/animal/Pig.cpp" + "src/world/entity/animal/Sheep.cpp" + "src/world/entity/animal/WaterAnimal.cpp" + + "src/world/entity/item/FallingTile.cpp" + "src/world/entity/item/ItemEntity.cpp" + "src/world/entity/item/PrimedTnt.cpp" + "src/world/entity/item/TripodCamera.cpp" + + "src/world/entity/monster/Creeper.cpp" + "src/world/entity/monster/Monster.cpp" + "src/world/entity/monster/PigZombie.cpp" + "src/world/entity/monster/Skeleton.cpp" + "src/world/entity/monster/Spider.cpp" + "src/world/entity/monster/Zombie.cpp" + + "src/world/entity/player/Inventory.cpp" + "src/world/entity/player/Player.cpp" + + "src/world/entity/projectile/Arrow.cpp" + "src/world/entity/projectile/Throwable.cpp" + + "src/world/food/SimpleFoodData.cpp" + + "src/world/inventory/BaseContainerMenu.cpp" + "src/world/inventory/ContainerMenu.cpp" + "src/world/inventory/FillingContainer.cpp" + "src/world/inventory/FurnaceMenu.cpp" + + "src/world/item/ArmorItem.cpp" + "src/world/item/BedItem.cpp" + "src/world/item/DyePowderItem.cpp" + "src/world/item/HangingEntityItem.cpp" + "src/world/item/HatchetItem.cpp" + "src/world/item/HoeItem.cpp" + "src/world/item/Item.cpp" + "src/world/item/ItemInstance.cpp" + "src/world/item/PickaxeItem.cpp" + "src/world/item/ShovelItem.cpp" + + "src/world/item/crafting/ArmorRecipes.cpp" + "src/world/item/crafting/FurnaceRecipes.cpp" + "src/world/item/crafting/OreRecipes.cpp" + "src/world/item/crafting/Recipe.cpp" + "src/world/item/crafting/Recipes.cpp" + "src/world/item/crafting/StructureRecipes.cpp" + "src/world/item/crafting/ToolRecipes.cpp" + "src/world/item/crafting/WeaponRecipes.cpp" + + "src/world/level/Explosion.cpp" + "src/world/level/Level.cpp" + "src/world/level/LightLayer.cpp" + "src/world/level/LightUpdate.cpp" + "src/world/level/MobSpawner.cpp" + "src/world/level/Region.cpp" + "src/world/level/TickNextTickData.cpp" + + "src/world/level/biome/Biome.cpp" + "src/world/level/biome/BiomeSource.cpp" + + "src/world/level/chunk/LevelChunk.cpp" + + "src/world/level/dimension/Dimension.cpp" + + "src/world/level/levelgen/CanyonFeature.cpp" + "src/world/level/levelgen/DungeonFeature.cpp" + "src/world/level/levelgen/LargeCaveFeature.cpp" + "src/world/level/levelgen/LargeFeature.cpp" + "src/world/level/levelgen/RandomLevelSource.cpp" + "src/world/level/levelgen/feature/Feature.cpp" + "src/world/level/levelgen/synth/ImprovedNoise.cpp" + "src/world/level/levelgen/synth/PerlinNoise.cpp" + "src/world/level/levelgen/synth/Synth.cpp" + + "src/world/level/material/Material.cpp" + + "src/world/level/pathfinder/Path.cpp" + + "src/world/level/storage/ExternalFileLevelStorage.cpp" + "src/world/level/storage/ExternalFileLevelStorageSource.cpp" + "src/world/level/storage/FolderMethods.cpp" + "src/world/level/storage/LevelData.cpp" + "src/world/level/storage/LevelStorageSource.cpp" + "src/world/level/storage/RegionFile.cpp" + + "src/world/level/tile/BedTile.cpp" + "src/world/level/tile/ChestTile.cpp" + "src/world/level/tile/CropTile.cpp" + "src/world/level/tile/DoorTile.cpp" + "src/world/level/tile/EntityTile.cpp" + "src/world/level/tile/FurnaceTile.cpp" + "src/world/level/tile/GrassTile.cpp" + "src/world/level/tile/HeavyTile.cpp" + "src/world/level/tile/LightGemTile.cpp" + "src/world/level/tile/MelonTile.cpp" + "src/world/level/tile/Mushroom.cpp" + "src/world/level/tile/NetherReactor.cpp" + "src/world/level/tile/NetherReactorPattern.cpp" + "src/world/level/tile/StairTile.cpp" + "src/world/level/tile/StemTile.cpp" + "src/world/level/tile/StoneSlabTile.cpp" + "src/world/level/tile/TallGrass.cpp" + "src/world/level/tile/Tile.cpp" + "src/world/level/tile/TrapDoorTile.cpp" + "src/world/level/tile/entity/ChestTileEntity.cpp" + "src/world/level/tile/entity/FurnaceTileEntity.cpp" + "src/world/level/tile/entity/NetherReactorTileEntity.cpp" + "src/world/level/tile/entity/SignTileEntity.cpp" + "src/world/level/tile/entity/TileEntity.cpp" + + "src/world/phys/HitResult.cpp" +) +file(GLOB CLIENT_SOURCES "src/client/*.cpp" "src/client/gamemode/*.cpp" @@ -129,25 +312,29 @@ endif() if(PLATFORM STREQUAL "PLATFORM_WIN32") - list(APPEND SOURCES "src/AppPlatform_win32.cpp") + list(APPEND CLIENT_SOURCES "src/AppPlatform_win32.cpp") endif() if(PLATFORM STREQUAL "PLATFORM_GLFW") - list(APPEND SOURCES "src/AppPlatform_glfw.cpp") + list(APPEND CLIENT_SOURCES "src/AppPlatform_glfw.cpp") endif() # Explicitly list files added after the initial glob so they are always included -list(APPEND SOURCES +list(APPEND CLIENT_SOURCES "src/client/gui/screens/ConsoleScreen.cpp" "src/client/gui/screens/UsernameScreen.cpp" "src/client/gui/screens/CreditsScreen.cpp" ) add_executable(${PROJECT_NAME} - ${SOURCES} + ${CLIENT_SOURCES} "glad/src/glad.c" ) +#add_executable("${PROJECT_NAME}-server" +# ${SERVER_SOURCES} +#) + if(WIN32) set(EXTRA_LIBS "ws2_32") target_compile_definitions(${PROJECT_NAME} PUBLIC "_CRT_SECURE_NO_WARNINGS") @@ -157,6 +344,7 @@ if(PLATFORM STREQUAL "PLATFORM_WIN32" OR PLATFORM STREQUAL "PLATFORM_GLFW") target_compile_definitions(${PROJECT_NAME} PUBLIC "PLATFORM_DESKTOP") endif() + target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_SOURCE_DIR}/glad/include/" "${CMAKE_SOURCE_DIR}/src" @@ -165,6 +353,16 @@ target_include_directories(${PROJECT_NAME} PUBLIC "lib/include" ) +# Server +#target_link_libraries("${PROJECT_NAME}-server" PRIVATE +# raknet ${CMAKE_THREAD_LIBS_INIT}) +#target_compile_definitions("${PROJECT_NAME}-server" PUBLIC "STANDALONE_SERVER") + +#target_include_directories("${PROJECT_NAME}-server" PUBLIC +# "${CMAKE_SOURCE_DIR}/src/" +#) + +# Client target_compile_definitions(${PROJECT_NAME} PUBLIC "OPENGL_ES" "NO_EGL" ${PLATFORM}) target_link_libraries(${PROJECT_NAME} zlib png_shared alsoft.common OpenAL::OpenAL glfw ${EXTRA_LIBS}) diff --git a/README.md b/README.md index 07baf38..bebbeaa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MinecraftPE > [!Important] -> We have a discord server, where you can report bugs or send feedback https://discord.gg/ryZ884DWJf +> We have a discord server, where you can report bugs or send feedback https://discord.gg/c58YesBxve Source code for **Minecraft Pocket Edition 0.6.1 alpha** with various fixes and improvements. @@ -32,6 +32,12 @@ mkdir build && cd build cmake .. -B . make -j4 ``` +or +``` +mkdir build && cd build +cmake --build . --config Release -j 10 +``` + ## Visual Studio 1. Open the repository folder in **Visual Studio**. @@ -40,17 +46,28 @@ make -j4 4. Press **Run** (or F5) to build and launch the game. ## Android -Download [r14b Android NDK](http://dl.google.com/android/repository/android-ndk-r14b-windows-x86_64.zip) and run `build.ps1`: -``` + +1. Download **Android NDK r14b**: + http://dl.google.com/android/repository/android-ndk-r14b-windows-x86_64.zip + +2. Extract it to the root of your `C:` drive so the path becomes: + + ``` + C:\android-ndk-r14b + ``` + +3. Run the build script: + +```powershell # Full build (NDK + Java + APK + install) .\build.ps1 -# Skip NDK recompile (Java/assets changed only) -.\build.ps1 -NoJava - -# Skip Java recompile (C++ changed only) +# Skip C++ compilation (Java/assets changed only) .\build.ps1 -NoCpp -# Only repackage + install (no recompile at all) +# Skip Java compilation (C++ changed only) +.\build.ps1 -NoJava + +# Only repackage + install (no compilation) .\build.ps1 -NoBuild ``` \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index f95b0b0..e554cfc 100644 --- a/build.ps1 +++ b/build.ps1 @@ -220,14 +220,15 @@ if (-not $NoCpp -and -not $NoBuild) { Write-Step "NDK build (arm64-v8a)" # NDK r14b on Windows hits the 32K CreateProcess limit with long paths. # Work around it by building through a short junction C:\m -> repo root. - $junctionBase = "C:\m" - if (-not (Test-Path $junctionBase)) { - & cmd.exe /c "mklink /J `"$junctionBase`" `"$repo`"" | Out-Null + # Use forward slashes in the build paths to prevent the NDK toolchain from stripping backslashes. + $junctionBase = "C:/m" + if (-not (Test-Path "C:\m")) { + & cmd.exe /c "mklink /J `"C:\m`" `"$repo`"" | Out-Null } - Push-Location "$junctionBase\project\android\jni" - $env:NDK_MODULE_PATH = "$junctionBase\project\lib_projects" + Push-Location "$junctionBase/project/android/jni" + $env:NDK_MODULE_PATH = "$junctionBase/project/lib_projects" # run ndk-build and capture everything; let user see full output for debugging - $ndkOutput = & "$ndk\ndk-build.cmd" NDK_PROJECT_PATH="$junctionBase\project\android" APP_BUILD_SCRIPT="$junctionBase\project\android\jni\Android.mk" 2>&1 | Tee-Object -Variable ndkOutput + $ndkOutput = & "$ndk\ndk-build.cmd" NDK_PROJECT_PATH="$junctionBase/project/android" APP_BUILD_SCRIPT="$junctionBase/project/android/jni/Android.mk" 2>&1 | Tee-Object -Variable ndkOutput # dump entire output for diagnosis Write-Host "---- NDK BUILD OUTPUT BEGIN ----" $ndkOutput | ForEach-Object { Write-Host $_ } diff --git a/data/images/mob/char.png b/data/images/mob/char.png index 7cfa08a..d507d75 100755 Binary files a/data/images/mob/char.png and b/data/images/mob/char.png differ diff --git a/data/lang/en_US.lang b/data/lang/en_US.lang index bdc0b35..a7d9b8f 100755 --- a/data/lang/en_US.lang +++ b/data/lang/en_US.lang @@ -149,6 +149,10 @@ options.group.general=General options.group.game=Game options.group.controls=Controls options.group.graphics=Graphics +options.group.tweaks=Tweaks +options.sprint=Sprint +options.barontop=HUD above inventory +options.autojump=Auto Jump options.thirdperson=Third Person options.servervisible=Server Visible options.sensitivity=Sensitivity diff --git a/project/android/jni/Android.mk b/project/android/jni/Android.mk index 866e870..3deb435 100755 --- a/project/android/jni/Android.mk +++ b/project/android/jni/Android.mk @@ -1,4 +1,6 @@ LOCAL_PATH := $(call my-dir) +# Convert Windows backslashes to forward slashes so NDK toolchain doesn’t treat them as escapes. +LOCAL_PATH := $(subst \,/,$(LOCAL_PATH)) include $(CLEAR_VARS) @@ -12,6 +14,7 @@ LOCAL_SRC_FILES := ../../../src/main.cpp \ ../../../src/platform/input/Multitouch.cpp \ ../../../src/platform/time.cpp \ ../../../src/platform/CThread.cpp \ + ../../../src/platform/HttpClient.cpp \ ../../../src/NinecraftApp.cpp \ ../../../src/Performance.cpp \ ../../../src/SharedConstants.cpp \ @@ -68,6 +71,7 @@ LOCAL_SRC_FILES := ../../../src/main.cpp \ ../../../src/client/gui/screens/SelectWorldScreen.cpp \ ../../../src/client/gui/screens/StartMenuScreen.cpp \ ../../../src/client/gui/screens/TextEditScreen.cpp \ +../../../src/client/gui/screens/JoinByIPScreen.cpp \ ../../../src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.cpp \ ../../../src/client/gui/screens/touch/TouchJoinGameScreen.cpp \ ../../../src/client/gui/screens/touch/TouchSelectWorldScreen.cpp \ diff --git a/project/android/jni/Application.mk b/project/android/jni/Application.mk index bea0653..9853b52 100755 --- a/project/android/jni/Application.mk +++ b/project/android/jni/Application.mk @@ -2,4 +2,5 @@ APP_PLATFORM := android-21 APP_STL := gnustl_static APP_OPTIM := release APP_ABI := arm64-v8a +APP_SHORT_COMMANDS := true #APP_ABI := armeabi-v7a x86 diff --git a/project/android/src/com/mojang/minecraftpe/MainActivity.java b/project/android/src/com/mojang/minecraftpe/MainActivity.java index 95f33f7..7b1e3ae 100755 --- a/project/android/src/com/mojang/minecraftpe/MainActivity.java +++ b/project/android/src/com/mojang/minecraftpe/MainActivity.java @@ -468,17 +468,16 @@ public class MainActivity extends NativeActivity { _userInputStatus = 1; InputMethodManager inputManager = (InputMethodManager)getSystemService("input_method"); - boolean result = inputManager.showSoftInput(this.getCurrentFocus(), InputMethodManager.SHOW_IMPLICIT); - } - - protected void onStart() { - //System.out.println("onStart"); - super.onStart(); - } - - protected void onResume() { - //System.out.println("onResume"); - super.onResume(); + View focused = this.getCurrentFocus(); + if (focused != null) { + boolean result = inputManager.showSoftInput(focused, InputMethodManager.SHOW_IMPLICIT); + } else { + // fallback: try to show using decor view token + View decor = getWindow().getDecorView(); + if (decor != null) { + inputManager.showSoftInput(decor, InputMethodManager.SHOW_IMPLICIT); + } + } } protected void onPause() { diff --git a/project/android_java/src/com/mojang/minecraftpe/MainActivity.java b/project/android_java/src/com/mojang/minecraftpe/MainActivity.java index e1a7d78..365fbe5 100755 --- a/project/android_java/src/com/mojang/minecraftpe/MainActivity.java +++ b/project/android_java/src/com/mojang/minecraftpe/MainActivity.java @@ -583,6 +583,15 @@ public class MainActivity extends Activity { return kcm.get(keyCode, metaState); } + public void openURL(String url) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url)); + startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + } + } + public String getPlatformStringVar(int id) { if (id == 0) return android.os.Build.MODEL; return null; diff --git a/project/dedicated_server/CMakeLists.txt b/project/dedicated_server/CMakeLists.txt index fd4c363..96ea1a7 100755 --- a/project/dedicated_server/CMakeLists.txt +++ b/project/dedicated_server/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.7) +cmake_minimum_required(VERSION 3.5.0) find_package (Threads) include_directories("${PROJECT_SOURCE_DIR}/../lib_projects/raknet/jni/RaknetSources") add_subdirectory("${PROJECT_SOURCE_DIR}/../lib_projects/raknet/jni" "${CMAKE_CURRENT_BINARY_DIR}/raknet") @@ -151,6 +151,9 @@ set(CompileFiles ../../src/main.cpp ../../src/world/level/tile/entity/TileEntity.cpp ../../src/world/level/tile/entity/FurnaceTileEntity.cpp ../../src/world/phys/HitResult.cpp) -message(${CMAKE_LIBRARY_ARCHITECTURE}) + add_executable(mcpe_server ${CompileFiles}) -target_link_libraries(mcpe_server raknet ${CMAKE_THREAD_LIBS_INIT}) \ No newline at end of file +target_link_libraries(mcpe_server raknet ${CMAKE_THREAD_LIBS_INIT}) +target_include_directories(mcpe_server PUBLIC + "../../src/" +) diff --git a/project/lib_projects/raknet/jni/RaknetSources/RakPeer.cpp b/project/lib_projects/raknet/jni/RaknetSources/RakPeer.cpp index 698374e..a24a2b5 100755 --- a/project/lib_projects/raknet/jni/RaknetSources/RakPeer.cpp +++ b/project/lib_projects/raknet/jni/RaknetSources/RakPeer.cpp @@ -124,7 +124,7 @@ static const unsigned int MAX_OFFLINE_DATA_LENGTH=400; // I set this because I l #pragma warning(disable:4309) // 'initializing' : truncation of constant value #endif // Make sure highest bit is 0, so isValid in DatagramHeaderFormat is false -static const char OFFLINE_MESSAGE_DATA_ID[16]={0x00,0xFF,0xFF,0x00,0xFE,0xFE,0xFE,0xFE,0xFD,0xFD,0xFD,0xFD,0x12,0x34,0x56,0x78}; +static const unsigned char OFFLINE_MESSAGE_DATA_ID[16]={0x00,0xFF,0xFF,0x00,0xFE,0xFE,0xFE,0xFE,0xFD,0xFD,0xFD,0xFD,0x12,0x34,0x56,0x78}; struct PacketFollowedByData { diff --git a/src/AppPlatform.h b/src/AppPlatform.h index 14a4448..84163b1 100755 --- a/src/AppPlatform.h +++ b/src/AppPlatform.h @@ -71,6 +71,7 @@ public: virtual void saveScreenshot(const std::string& filename, int glWidth, int glHeight) {} virtual TextureData loadTexture(const std::string& filename_, bool textureFolder) { return TextureData(); } + virtual TextureData loadTextureFromMemory(const unsigned char* data, size_t size) { return TextureData(); } virtual void playSound(const std::string& fn, float volume, float pitch) {} diff --git a/src/AppPlatform_android.h b/src/AppPlatform_android.h index fb2e10e..6ae271c 100755 --- a/src/AppPlatform_android.h +++ b/src/AppPlatform_android.h @@ -151,6 +151,8 @@ public: _methodGetPixelsPerMillimeter = env->GetMethodID( _activityClass, "getPixelsPerMillimeter", "()F"); _methodGetPlatformStringVar = env->GetMethodID( _activityClass, "getPlatformStringVar", "(I)Ljava/lang/String;"); + // custom helper to launch external URLs + _methodOpenURL = env->GetMethodID(_activityClass, "openURL", "(Ljava/lang/String;)V"); _classWindow = (jclass)env->NewGlobalRef(env->FindClass("android/view/Window")); _classContext = (jclass)env->NewGlobalRef(env->FindClass("android/content/Context")); @@ -465,7 +467,15 @@ public: env->ReleaseStringUTFChars(stringVar, str); return out; } - + // Opens a webpage using an Android intent. Called from native code. + virtual void openURL(const std::string& url) { + if (!_isInited || !_methodOpenURL) return; + JVMAttacher ta(_vm); + JNIEnv* env = ta.getEnv(); + jstring jurl = env->NewStringUTF(url.c_str()); + env->CallVoidMethod(instance, _methodOpenURL, jurl); + env->DeleteLocalRef(jurl); + } virtual void finish() { if (!_isInited) return; if (!_methodFinish) return; @@ -616,6 +626,7 @@ private: jmethodID _methodIsNetworkEnabled; jmethodID _methodGetPlatformStringVar; + jmethodID _methodOpenURL; // new JNI method for launching browser jclass _classWindow; jclass _classContext; diff --git a/src/AppPlatform_glfw.h b/src/AppPlatform_glfw.h index ecfb490..265af68 100755 --- a/src/AppPlatform_glfw.h +++ b/src/AppPlatform_glfw.h @@ -3,6 +3,8 @@ #include "AppPlatform.h" #include "platform/log.h" +#include "platform/HttpClient.h" +#include "platform/PngLoader.h" #include "client/renderer/gles.h" #include "world/level/storage/FolderMethods.h" #include @@ -11,6 +13,7 @@ #include #include #include +#include "util/StringUtils.h" #ifdef _WIN32 #include @@ -55,10 +58,19 @@ public: TextureData loadTexture(const std::string& filename_, bool textureFolder) { + // Support fetching PNG textures via HTTP/HTTPS (for skins, etc) + if (Util::startsWith(filename_, "http://") || Util::startsWith(filename_, "https://")) { + std::vector body; + if (HttpClient::download(filename_, body) && !body.empty()) { + return loadTextureFromMemory(body.data(), body.size()); + } + return TextureData(); + } + TextureData out; std::string filename = textureFolder? "data/images/" + filename_ - : filename_; + : filename_; std::ifstream source(filename.c_str(), std::ios::binary); if (source) { @@ -107,7 +119,11 @@ public: } } - std::string getDateString(int s) { + TextureData loadTextureFromMemory(const unsigned char* data, size_t size) override { + return loadPngFromMemory(data, size); + } + + virtual std::string getDateString(int s) override { time_t tm = s; char mbstr[100]; diff --git a/src/AppPlatform_win32.h b/src/AppPlatform_win32.h index 61c3ab2..c3623b3 100755 --- a/src/AppPlatform_win32.h +++ b/src/AppPlatform_win32.h @@ -3,8 +3,11 @@ #include "AppPlatform.h" #include "platform/log.h" +#include "platform/HttpClient.h" +#include "platform/PngLoader.h" #include "client/renderer/gles.h" #include "world/level/storage/FolderMethods.h" +#include "util/StringUtils.h" #include #include #include @@ -50,10 +53,19 @@ public: TextureData loadTexture(const std::string& filename_, bool textureFolder) { + // Support fetching PNG textures via HTTP/HTTPS (for skins, etc). + if (Util::startsWith(filename_, "http://") || Util::startsWith(filename_, "https://")) { + std::vector body; + if (HttpClient::download(filename_, body) && !body.empty()) { + return loadTextureFromMemory(body.data(), body.size()); + } + return TextureData(); + } + TextureData out; std::string filename = textureFolder? "data/images/" + filename_ - : filename_; + : filename_; std::ifstream source(filename.c_str(), std::ios::binary); if (source) { @@ -102,7 +114,9 @@ public: } } - std::string getDateString(int s) { + TextureData loadTextureFromMemory(const unsigned char* data, size_t size) override { + return loadPngFromMemory(data, size); + } time_t tm = s; char mbstr[100]; diff --git a/src/client/Minecraft.cpp b/src/client/Minecraft.cpp index 1c9ba00..46820f7 100755 --- a/src/client/Minecraft.cpp +++ b/src/client/Minecraft.cpp @@ -1,5 +1,10 @@ #include "Minecraft.h" #include "client/player/input/IBuildInput.h" +#include "platform/input/Keyboard.h" +#include "world/item/Item.h" +#include "world/item/ItemInstance.h" +#include +#include #if defined(APPLE_DEMO_PROMOTION) #define NO_NETWORK @@ -8,7 +13,6 @@ #if defined(RPI) #define CREATORMODE #endif - #include "../network/RakNetInstance.h" #include "../network/ClientSideNetworkHandler.h" #include "../network/ServerSideNetworkHandler.h" @@ -56,6 +60,7 @@ #endif +#include "renderer/Chunk.h" #include "player/input/MouseTurnInput.h" #include "../world/entity/MobFactory.h" #include "../world/level/MobSpawner.h" @@ -112,7 +117,7 @@ static void checkGlError(const char* tag) { } #endif /*GLDEBUG*/ } - +#include /*static*/ const char* Minecraft::progressMessages[] = { "Locating server", @@ -450,20 +455,21 @@ void Minecraft::update() { // } //} - if (pause && level != NULL) { - float lastA = timer.a; - timer.advanceTime(); - timer.a = lastA; - } else { + // If we're paused (local world / invisible server), freeze gameplay and + // networking and only keep UI responsive. + bool freezeGame = pause; + + if (!freezeGame) { timer.advanceTime(); } - if (raknetInstance) { + if (raknetInstance && !freezeGame) { raknetInstance->runEvents(netCallback); } TIMER_PUSH("tick"); - int toTick = timer.ticks; + int toTick = freezeGame ? 1 : timer.ticks; + if (!freezeGame) timer.ticks = 0; for (int i = 0; i < toTick; ++i, ++ticks) tick(i, toTick-1); @@ -588,7 +594,9 @@ void Minecraft::tick(int nTick, int maxTick) { #endif } TIMER_POP_PUSH("particles"); - particleEngine->tick(); + if (!pause) { + particleEngine->tick(); + } if (screen) { screenMutex = true; screen->tick(); @@ -722,16 +730,29 @@ void Minecraft::tickInput() { } #endif #if defined(PLATFORM_DESKTOP) + if (key == Keyboard::KEY_LEFT_CTRL) { + player->setSprinting(true); + } + if (key == Keyboard::KEY_E) { screenChooser.setScreen(SCREEN_BLOCKSELECTION); } + if (!screen && key == Keyboard::KEY_T && level) { setScreen(new ConsoleScreen()); } + if (!screen && key == Keyboard::KEY_O || key == 250) { releaseMouse(); } + if (key == Keyboard::KEY_F) + options.viewDistance = (options.viewDistance + 1) % 4; + + if (key == Keyboard::KEY_F3) { + options.renderDebug = !options.renderDebug; + } + if (key == Keyboard::KEY_F5) { options.toggle(OPTIONS_THIRD_PERSON_VIEW); /* @@ -740,12 +761,6 @@ void Minecraft::tickInput() { printf("%d\t%f\n", i, noise.grad2(i, 3, 8)); */ } - #endif - #if defined(WIN32) - if (key == Keyboard::KEY_F) { - options.isFlying = !options.isFlying; - player->noPhysics = options.isFlying; - } if (key == Keyboard::KEY_L) @@ -819,9 +834,6 @@ void Minecraft::tickInput() { if (player->inventory->getItem(i)) player->inventory->dropSlot(i, false); } - if (key == Keyboard::KEY_F3) { - options.renderDebug = !options.renderDebug; - } if (key == Keyboard::KEY_M) { options.difficulty = (options.difficulty == Difficulty::PEACEFUL)? Difficulty::NORMAL : Difficulty::PEACEFUL; @@ -1017,6 +1029,17 @@ bool Minecraft::isOnline() } void Minecraft::pauseGame(bool isBackPaused) { + // Only freeze gameplay when running a local server and it is not accepting + // incoming connections (invisible server), which includes typical single- + // player/lobby mode. If the server is visible, the game should keep ticking. + bool canFreeze = false; + if (raknetInstance && raknetInstance->isServer() && netCallback) { + ServerSideNetworkHandler* ss = (ServerSideNetworkHandler*) netCallback; + if (!ss->allowsIncomingConnections()) + canFreeze = true; + } + pause = canFreeze; + #ifndef STANDALONE_SERVER if (screen != NULL) return; screenChooser.setScreen(isBackPaused? SCREEN_PAUSEPREV : SCREEN_PAUSE); @@ -1069,6 +1092,8 @@ void Minecraft::setScreen( Screen* screen ) //noRender = false; } else { + // Closing a screen and returning to the game should unpause. + pause = false; grabMouse(); } #endif @@ -1288,6 +1313,31 @@ bool Minecraft::joinMultiplayer( const PingedCompatibleServer& server ) return false; } +bool Minecraft::joinMultiplayerFromString( const std::string& server ) +{ + std::string ip = ""; + std::string port = "19132"; + + size_t pos = server.find(":"); + + if (pos != std::string::npos) { + ip = server.substr(0, pos); + port = server.substr(pos + 1); + } else { + ip = server; + } + + printf("%s \n", port.c_str()); + + if (isLookingForMultiplayer && netCallback) { + isLookingForMultiplayer = false; + printf("test"); + int portNum = atoi(port.c_str()); + return raknetInstance->connect(ip.c_str(), portNum); + } + return false; +} + void Minecraft::hostMultiplayer(int port) { // Tear down last instance raknetInstance->disconnect(); @@ -1455,6 +1505,12 @@ LevelStorageSource* Minecraft::getLevelSource() return storageSource; } +// int Minecraft::getLicenseId() { +// if (!LicenseCodes::isReady(_licenseId)) +// _licenseId = platform()->checkLicense(); +// return _licenseId; +// } + void Minecraft::audioEngineOn() { #ifndef STANDALONE_SERVER soundEngine->enable(true); diff --git a/src/client/Minecraft.h b/src/client/Minecraft.h index 1f9b3d7..35f3a22 100755 --- a/src/client/Minecraft.h +++ b/src/client/Minecraft.h @@ -80,6 +80,7 @@ public: void locateMultiplayer(); void cancelLocateMultiplayer(); bool joinMultiplayer(const PingedCompatibleServer& server); + bool joinMultiplayerFromString(const std::string& server); void hostMultiplayer(int port=19132); Player* respawnPlayer(int playerId); void respawnPlayer(); diff --git a/src/client/OptionStrings.cpp b/src/client/OptionStrings.cpp index 2a68643..b856d17 100755 --- a/src/client/OptionStrings.cpp +++ b/src/client/OptionStrings.cpp @@ -6,14 +6,19 @@ const char* OptionStrings::Multiplayer_ServerVisible = "mp_server_visible_defa const char* OptionStrings::Graphics_Fancy = "gfx_fancygraphics"; const char* OptionStrings::Graphics_LowQuality = "gfx_lowquality"; const char* OptionStrings::Graphics_Vsync = "gfx_vsync"; -const char* OptionStrings::Graphics_GUIScale = "gfx_guiscale"; +const char* OptionStrings::Graphics_GUIScale = "gfx_guiscale"; +const char* OptionStrings::Graphics_SmoothLightning = "gfx_smoothlightning"; +const char* OptionStrings::Graphics_Anaglyph = "gfx_anaglyph"; +const char* OptionStrings::Graphics_ViewBobbing = "gfx_viewbobbing"; const char* OptionStrings::Controls_Sensitivity = "ctrl_sensitivity"; const char* OptionStrings::Controls_InvertMouse = "ctrl_invertmouse"; const char* OptionStrings::Controls_UseTouchScreen = "ctrl_usetouchscreen"; const char* OptionStrings::Controls_UseTouchJoypad = "ctrl_usetouchjoypad"; const char* OptionStrings::Controls_IsLefthanded = "ctrl_islefthanded"; +// why it isnt ctrl_feedback_vibration? i dont want touch it because compatibility with older versions const char* OptionStrings::Controls_FeedbackVibration = "feedback_vibration"; +const char* OptionStrings::Controls_AutoJump = "ctrl_autojump"; const char* OptionStrings::Game_DifficultyLevel = "game_difficulty"; diff --git a/src/client/OptionStrings.h b/src/client/OptionStrings.h index 0065dc6..3f4e839 100755 --- a/src/client/OptionStrings.h +++ b/src/client/OptionStrings.h @@ -10,14 +10,27 @@ public: static const char* Graphics_LowQuality; static const char* Graphics_GUIScale; static const char* Graphics_Vsync; + static const char* Graphics_SmoothLightning; + static const char* Graphics_Anaglyph; + static const char* Graphics_ViewBobbing; + static const char* Controls_Sensitivity; static const char* Controls_InvertMouse; static const char* Controls_UseTouchScreen; static const char* Controls_UseTouchJoypad; static const char* Controls_IsLefthanded; static const char* Controls_FeedbackVibration; + static const char* Controls_AutoJump; + + static const char* Audio_Music; + static const char* Audio_Sound; + static const char* Game_DifficultyLevel; + + static const char* Tweaks_Sprint; + static const char* Tweaks_BarOnTop; + }; #endif /*NET_MINECRAFT_CLIENT__OptionsStrings_H__*/ diff --git a/src/client/gamemode/CreativeMode.cpp b/src/client/gamemode/CreativeMode.cpp index 43a1b16..8be511a 100755 --- a/src/client/gamemode/CreativeMode.cpp +++ b/src/client/gamemode/CreativeMode.cpp @@ -13,10 +13,10 @@ #include "../../network/packet/RemoveBlockPacket.h" #include "../../world/entity/player/Abilities.h" -static const int DestructionTickDelay = 10; +static const int DestructionTickDelay = 5; CreativeMode::CreativeMode(Minecraft* minecraft) -: super(minecraft) +: super(minecraft) { } @@ -29,12 +29,8 @@ void CreativeMode::startDestroyBlock(int x, int y, int z, int face) { } void CreativeMode::creativeDestroyBlock(int x, int y, int z, int face) { - //if (! - minecraft->level->extinguishFire(x, y, z, face) - //{ - ; - destroyBlock(x, y, z, face); - //} + minecraft->level->extinguishFire(x, y, z, face); + destroyBlock(x, y, z, face); } void CreativeMode::continueDestroyBlock(int x, int y, int z, int face) { @@ -46,6 +42,7 @@ void CreativeMode::continueDestroyBlock(int x, int y, int z, int face) { } void CreativeMode::stopDestroyBlock() { + destroyDelay = 0; } void CreativeMode::initAbilities( Abilities& abilities ) { diff --git a/src/client/gamemode/CreatorMode.cpp b/src/client/gamemode/CreatorMode.cpp index ae1ffd2..e06d624 100755 --- a/src/client/gamemode/CreatorMode.cpp +++ b/src/client/gamemode/CreatorMode.cpp @@ -9,7 +9,7 @@ #include "../../network/packet/RemoveBlockPacket.h" #include "../../world/entity/player/Abilities.h" -static const int DestructionTickDelay = 10; +static const int DestructionTickDelay = 5; class Creator: public ICreator { //virtual void getEvents(); @@ -60,12 +60,8 @@ void CreatorMode::startDestroyBlock(int x, int y, int z, int face) { } void CreatorMode::CreatorDestroyBlock(int x, int y, int z, int face) { - //if (! - minecraft->level->extinguishFire(x, y, z, face) - //{ - ; - destroyBlock(x, y, z, face); - //} + minecraft->level->extinguishFire(x, y, z, face); + destroyBlock(x, y, z, face); } void CreatorMode::continueDestroyBlock(int x, int y, int z, int face) { @@ -83,6 +79,7 @@ bool CreatorMode::useItemOn( Player* player, Level* level, ItemInstance* item, i } void CreatorMode::stopDestroyBlock() { + destroyDelay = 0; } void CreatorMode::initAbilities( Abilities& abilities ) { diff --git a/src/client/gui/Gui.cpp b/src/client/gui/Gui.cpp index 27f19e0..e4e6a87 100755 --- a/src/client/gui/Gui.cpp +++ b/src/client/gui/Gui.cpp @@ -1,6 +1,7 @@ #include "Gui.h" #include "Font.h" #include "client/Options.h" +#include "platform/input/Keyboard.h" #include "screens/IngameBlockSelectionScreen.h" #include "../Minecraft.h" #include "../player/LocalPlayer.h" @@ -87,14 +88,16 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) { // F: 3 int ySlot = screenHeight - 16 - 3; - if (minecraft->gameMode->canHurtPlayer()) { - minecraft->textures->loadAndBindTexture("gui/icons.png"); - Tesselator& t = Tesselator::instance; - t.beginOverride(); - t.colorABGR(0xffffffff); - renderHearts(); - renderBubbles(); - t.endOverrideAndDraw(); + if (!minecraft->options.getBooleanValue(OPTIONS_HIDEGUI)) { + if (minecraft->gameMode->canHurtPlayer()) { + minecraft->textures->loadAndBindTexture("gui/icons.png"); + Tesselator& t = Tesselator::instance; + t.beginOverride(); + t.colorABGR(0xffffffff); + renderHearts(); + renderBubbles(); + t.endOverrideAndDraw(); + } } if(minecraft->player->getSleepTimer() > 0) { @@ -106,7 +109,7 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) { glEnable(GL_ALPHA_TEST); glEnable(GL_DEPTH_TEST); } - + if (!minecraft->options.getBooleanValue(OPTIONS_HIDEGUI)) { renderToolBar(a, ySlot, screenWidth); glEnable(GL_BLEND); @@ -122,6 +125,7 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) { if (minecraft->options.getBooleanValue(OPTIONS_RENDER_DEBUG)) renderDebugInfo(); #endif + } glDisable(GL_BLEND); glEnable2(GL_ALPHA_TEST); @@ -201,6 +205,10 @@ void Gui::handleClick(int button, int x, int y) { void Gui::handleKeyPressed(int key) { + if (key == Keyboard::KEY_F1) { + minecraft->options.toggle(OPTIONS_HIDEGUI); + } + if (key == 99) { if (minecraft->player->inventory->selected > 0) @@ -516,7 +524,8 @@ void Gui::renderProgressIndicator( const bool isTouchInterface, const int screen ItemInstance* currentItem = minecraft->player->inventory->getSelected(); bool bowEquipped = currentItem != NULL ? currentItem->getItem() == Item::bow : false; bool itemInUse = currentItem != NULL ? currentItem->getItem() == minecraft->player->getUseItem()->getItem() : false; - if (!isTouchInterface || minecraft->options.getBooleanValue(OPTIONS_IS_JOY_TOUCH_AREA) || (bowEquipped && itemInUse)) { + if ((!isTouchInterface || minecraft->options.getBooleanValue(OPTIONS_IS_JOY_TOUCH_AREA) + || (bowEquipped && itemInUse)) && !minecraft->options.getBooleanValue(OPTIONS_HIDEGUI)) { minecraft->textures->loadAndBindTexture("gui/icons.png"); glEnable(GL_BLEND); glBlendFunc2(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR); @@ -585,11 +594,14 @@ void Gui::renderHearts() { int oh = minecraft->player->lastHealth; random.setSeed(tickCount * 312871); - int xx = 2;//screenWidth / 2 - getNumSlots() * 10; + int screenWidth = (int)(minecraft->width * InvGuiScale); + int screenHeight = (int)(minecraft->height * InvGuiScale); + + int xx = (minecraft->options.getBooleanValue(OPTIONS_BAR_ON_TOP)) ? screenWidth / 2 - getNumSlots() * 10 - 1 : 2; int armor = minecraft->player->getArmorValue(); for (int i = 0; i < Player::MAX_HEALTH / 2; i++) { - int yo = 2; + int yo = (minecraft->options.getBooleanValue(OPTIONS_BAR_ON_TOP)) ? screenHeight - 32 : 2; int ip2 = i + i + 1; if (armor > 0) { @@ -617,11 +629,15 @@ void Gui::renderHearts() { void Gui::renderBubbles() { if (minecraft->player->isUnderLiquid(Material::water)) { - int yo = 12; + int screenWidth = (int)(minecraft->width * InvGuiScale); + int screenHeight = (int)(minecraft->height * InvGuiScale); + + int xx = (minecraft->options.getBooleanValue(OPTIONS_BAR_ON_TOP)) ? screenWidth / 2 - getNumSlots() * 10 - 1 : 2; + int yo = (minecraft->options.getBooleanValue(OPTIONS_BAR_ON_TOP)) ? screenHeight - 42 : 12; int count = (int) std::ceil((minecraft->player->airSupply - 2) * 10.0f / Player::TOTAL_AIR_SUPPLY); int extra = (int) std::ceil((minecraft->player->airSupply) * 10.0f / Player::TOTAL_AIR_SUPPLY) - count; for (int i = 0; i < count + extra; i++) { - int xo = i * 8 + 2; + int xo = i * 8 + xx; if (i < count) blit(xo, yo, 16, 9 * 2, 9, 9); else blit(xo, yo, 16 + 9, 9 * 2, 9, 9); } diff --git a/src/client/gui/Screen.cpp b/src/client/gui/Screen.cpp index d9bcaea..ae39036 100755 --- a/src/client/gui/Screen.cpp +++ b/src/client/gui/Screen.cpp @@ -78,6 +78,14 @@ void Screen::updateEvents() void Screen::mouseEvent() { const MouseAction& e = Mouse::getEvent(); + // forward wheel events to subclasses + if (e.action == MouseAction::ACTION_WHEEL) { + int xm = e.x * width / minecraft->width; + int ym = e.y * height / minecraft->height - 1; + mouseWheel(e.dx, e.dy, xm, ym); + return; + } + if (!e.isButton()) return; diff --git a/src/client/gui/Screen.h b/src/client/gui/Screen.h index 72d3453..be44d5c 100755 --- a/src/client/gui/Screen.h +++ b/src/client/gui/Screen.h @@ -57,6 +57,9 @@ protected: virtual void mouseClicked(int x, int y, int buttonNum); virtual void mouseReleased(int x, int y, int buttonNum); + // mouse wheel movement (dx/dy are wheel deltas, xm/ym are GUI coords) + virtual void mouseWheel(int dx, int dy, int xm, int ym) {} + virtual void keyPressed(int eventKey); virtual void charPressed(char inputChar); public: diff --git a/src/client/gui/components/ScrollingPane.cpp b/src/client/gui/components/ScrollingPane.cpp index 78c0187..6ee7744 100755 --- a/src/client/gui/components/ScrollingPane.cpp +++ b/src/client/gui/components/ScrollingPane.cpp @@ -548,6 +548,14 @@ void ScrollingPane::stepThroughDecelerationAnimation(bool noAnimation) { } } +void ScrollingPane::scrollBy(float dx, float dy) { + // adjust the translation offsets fpx/fpy by the requested amount + float nfpx = fpx + dx; + float nfpy = fpy + dy; + // convert back to content offset (fpx = -contentOffset.x) + setContentOffset(Vec3(-nfpx, -nfpy, 0)); +} + void ScrollingPane::setContentOffset(float x, float y) { this->setContentOffsetWithAnimation(Vec3(x, y, 0), false); } diff --git a/src/client/gui/components/ScrollingPane.h b/src/client/gui/components/ScrollingPane.h index ff31e7c..0a2c321 100755 --- a/src/client/gui/components/ScrollingPane.h +++ b/src/client/gui/components/ScrollingPane.h @@ -51,6 +51,10 @@ public: void tick(); void render(int xm, int ym, float alpha); + // scroll the content by the given amount (dx horizontal, dy vertical) + // positive values move content downward/rightward + void scrollBy(float dx, float dy); + bool getGridItemFor_slow(int itemIndex, GridItem& out); void setSelected(int id, bool selected); diff --git a/src/client/gui/screens/CreditsScreen.cpp b/src/client/gui/screens/CreditsScreen.cpp index 1412fff..ae2c224 100644 --- a/src/client/gui/screens/CreditsScreen.cpp +++ b/src/client/gui/screens/CreditsScreen.cpp @@ -38,7 +38,8 @@ void CreditsScreen::init() { _lines.push_back("InviseDivine"); _lines.push_back("Kolyah35"); _lines.push_back(""); - _lines.push_back("[Gold]Join our Discord server:[/Gold] [Green]url.....[/Green]"); + // avoid color tags around the URL so it isn't mangled by the parser please + _lines.push_back("Join our Discord server: https://discord.gg/c58YesBxve"); _scrollSpeed = 0.5f; _scrollY = height; // start below screen } diff --git a/src/client/gui/screens/IngameBlockSelectionScreen.cpp b/src/client/gui/screens/IngameBlockSelectionScreen.cpp index 181d69f..863b1f8 100755 --- a/src/client/gui/screens/IngameBlockSelectionScreen.cpp +++ b/src/client/gui/screens/IngameBlockSelectionScreen.cpp @@ -202,6 +202,25 @@ void IngameBlockSelectionScreen::keyPressed(int eventKey) #endif } +//------------------------------------------------------------------------------ +// wheel support for creative inventory; scroll moves selection vertically +void IngameBlockSelectionScreen::mouseWheel(int dx, int dy, int xm, int ym) +{ + if (dy == 0) return; + // just move selection up/down one row; desktop UI doesn't have a pane + int cols = InventoryCols; + int maxIndex = InventorySize - 1; + int idx = selectedItem; + if (dy > 0) { + // wheel up -> previous row + if (idx >= cols) idx -= cols; + } else { + // wheel down -> next row + if (idx + cols <= maxIndex) idx += cols; + } + selectedItem = idx; +} + int IngameBlockSelectionScreen::getSelectedSlot(int x, int y) { int left = width / 2 - InventoryCols * 10; diff --git a/src/client/gui/screens/IngameBlockSelectionScreen.h b/src/client/gui/screens/IngameBlockSelectionScreen.h index df8bacf..8c3c82b 100755 --- a/src/client/gui/screens/IngameBlockSelectionScreen.h +++ b/src/client/gui/screens/IngameBlockSelectionScreen.h @@ -23,6 +23,9 @@ protected: virtual void buttonClicked(Button* button); + // wheel input for creative inventory scrolling + virtual void mouseWheel(int dx, int dy, int xm, int ym) override; + virtual void keyPressed(int eventKey); private: void renderSlots(); diff --git a/src/client/gui/screens/JoinByIPScreen.cpp b/src/client/gui/screens/JoinByIPScreen.cpp new file mode 100644 index 0000000..7044d77 --- /dev/null +++ b/src/client/gui/screens/JoinByIPScreen.cpp @@ -0,0 +1,140 @@ +#include "JoinByIPScreen.h" + +#include "JoinGameScreen.h" +#include "StartMenuScreen.h" +#include "ProgressScreen.h" +#include "../Font.h" +#include "../../../network/RakNetInstance.h" +#include "client/gui/components/TextBox.h" +#include "network/ClientSideNetworkHandler.h" + +JoinByIPScreen::JoinByIPScreen() : + tIP(0, "Server IP"), + bHeader(1, "Join on server"), + bJoin( 2, "Join Game"), + bBack( 3, "") +{ + bJoin.active = false; + //gamesList->yInertia = 0.5f; +} + +JoinByIPScreen::~JoinByIPScreen() +{ +} + +void JoinByIPScreen::buttonClicked(Button* button) +{ + if (button->id == bJoin.id) + { + minecraft->isLookingForMultiplayer = true; + minecraft->netCallback = new ClientSideNetworkHandler(minecraft, minecraft->raknetInstance); + + minecraft->joinMultiplayerFromString(tIP.text); + { + + bJoin.active = false; + bBack.active = false; + minecraft->setScreen(new ProgressScreen()); + } + } + if (button->id == bBack.id) + { + minecraft->cancelLocateMultiplayer(); + minecraft->screenChooser.setScreen(SCREEN_STARTMENU); + } +} + +bool JoinByIPScreen::handleBackEvent(bool isDown) +{ + if (!isDown) + { + minecraft->screenChooser.setScreen(SCREEN_STARTMENU); + } + return true; +} + +void JoinByIPScreen::tick() +{ + bJoin.active = !tIP.text.empty(); +} + +void JoinByIPScreen::init() +{ + ImageDef def; + def.name = "gui/touchgui.png"; + def.width = 34; + def.height = 26; + + def.setSrc(IntRectangle(150, 0, (int)def.width, (int)def.height)); + bBack.setImageDef(def, true); + + buttons.push_back(&bJoin); + buttons.push_back(&bBack); + buttons.push_back(&bHeader); + + textBoxes.push_back(&tIP); +#ifdef ANDROID + tabButtons.push_back(&bJoin); + tabButtons.push_back(&bBack); + tabButtons.push_back(&bHeader); +#endif +} + +void JoinByIPScreen::setupPositions() { + int tIpDiff = 40; + + bJoin.y = height * 2 / 3; + bBack.y = 0; + bHeader.y = 0; + + // Center buttons + //bJoin.x = width / 2 - 4 - bJoin.w; + bBack.x = width - bBack.width;//width / 2 + 4; + + bJoin.x = (width - bJoin.width) / 2; + + bHeader.x = 0; + bHeader.width = width - bBack.width; + + tIP.width = bJoin.width + tIpDiff; + tIP.height = 16; + tIP.x = bJoin.x - tIpDiff / 2; + tIP.y = ((height - bJoin.height) / 2) - tIP.height - 4; +} + +void JoinByIPScreen::mouseClicked(int x, int y, int buttonNum) { + int lvlTop = tIP.y - (Font::DefaultLineHeight + 4); + int lvlBottom = tIP.y + tIP.height; + int lvlLeft = tIP.x; + int lvlRight = tIP.x + tIP.width; + bool clickedIP = x >= lvlLeft && x < lvlRight && y >= lvlTop && y < lvlBottom; + + if (clickedIP) { + tIP.setFocus(minecraft); + } else { + tIP.loseFocus(minecraft); + } + + Screen::mouseClicked(x, y, buttonNum); +} +void JoinByIPScreen::render( int xm, int ym, float a ) +{ + renderBackground(); + Screen::render(xm, ym, a); +} + +void JoinByIPScreen::keyPressed(int eventKey) +{ + if (eventKey == Keyboard::KEY_ESCAPE) { + minecraft->screenChooser.setScreen(SCREEN_STARTMENU); + return; + } + // let base class handle navigation and text box keys + Screen::keyPressed(eventKey); +} + +void JoinByIPScreen::keyboardNewChar(char inputChar) +{ + // forward character input to focused textbox(s) + for (auto* tb : textBoxes) tb->handleChar(inputChar); +} \ No newline at end of file diff --git a/src/client/gui/screens/JoinByIPScreen.h b/src/client/gui/screens/JoinByIPScreen.h index e69de29..288ada9 100644 --- a/src/client/gui/screens/JoinByIPScreen.h +++ b/src/client/gui/screens/JoinByIPScreen.h @@ -0,0 +1,30 @@ + +#include "../Screen.h" +#include "../components/Button.h" +#include "../../Minecraft.h" +#include "client/gui/components/ImageButton.h" +#include "client/gui/components/TextBox.h" + +class JoinByIPScreen: public Screen +{ +public: + JoinByIPScreen(); + virtual ~JoinByIPScreen(); + + void init(); + void setupPositions(); + + virtual void tick(); + void render(int xm, int ym, float a); + + virtual void keyPressed(int eventKey); + virtual void keyboardNewChar(char inputChar); + void buttonClicked(Button* button); + virtual void mouseClicked(int x, int y, int buttonNum); + virtual bool handleBackEvent(bool isDown); +private: + TextBox tIP; + Touch::THeader bHeader; + Touch::TButton bJoin; + ImageButton bBack; +}; \ No newline at end of file diff --git a/src/client/gui/screens/OptionsScreen.cpp b/src/client/gui/screens/OptionsScreen.cpp index 9c37a22..6bd78c6 100755 --- a/src/client/gui/screens/OptionsScreen.cpp +++ b/src/client/gui/screens/OptionsScreen.cpp @@ -69,6 +69,7 @@ void OptionsScreen::init() { categoryButtons.push_back(new Touch::TButton(3, "Game")); categoryButtons.push_back(new Touch::TButton(4, "Controls")); categoryButtons.push_back(new Touch::TButton(5, "Graphics")); + categoryButtons.push_back(new Touch::TButton(6, "Tweaks")); btnCredits = new Touch::TButton(11, "Credits"); @@ -192,6 +193,7 @@ void OptionsScreen::generateOptionScreens() { optionPanes.push_back(new OptionsGroup("options.group.game")); optionPanes.push_back(new OptionsGroup("options.group.control")); optionPanes.push_back(new OptionsGroup("options.group.graphics")); + optionPanes.push_back(new OptionsGroup("options.group.tweaks")); // General Pane optionPanes[0]->addOptionItem(OPTIONS_USERNAME, minecraft) @@ -228,6 +230,9 @@ void OptionsScreen::generateOptionScreens() { .addOptionItem(OPTIONS_ANAGLYPH_3D, minecraft) .addOptionItem(OPTIONS_VIEW_BOBBING, minecraft) .addOptionItem(OPTIONS_AMBIENT_OCCLUSION, minecraft); + + optionPanes[4]->addOptionItem(OPTIONS_ALLOW_SPRINT, minecraft) + .addOptionItem(OPTIONS_BAR_ON_TOP, minecraft); } void OptionsScreen::mouseClicked(int x, int y, int buttonNum) { diff --git a/src/client/gui/screens/ScreenChooser.cpp b/src/client/gui/screens/ScreenChooser.cpp index 12bedd5..2591f5b 100755 --- a/src/client/gui/screens/ScreenChooser.cpp +++ b/src/client/gui/screens/ScreenChooser.cpp @@ -5,6 +5,7 @@ #include "PauseScreen.h" #include "RenameMPLevelScreen.h" #include "IngameBlockSelectionScreen.h" +#include "JoinByIPScreen.h" #include "touch/TouchStartMenuScreen.h" #include "touch/TouchSelectWorldScreen.h" #include "touch/TouchJoinGameScreen.h" @@ -20,13 +21,13 @@ Screen* ScreenChooser::createScreen( ScreenId id ) if (_mc->useTouchscreen()) { switch (id) { - case SCREEN_STARTMENU: screen = new Touch::StartMenuScreen(); break; - case SCREEN_SELECTWORLD:screen = new Touch::SelectWorldScreen();break; - case SCREEN_JOINGAME: screen = new Touch::JoinGameScreen(); break; - case SCREEN_PAUSE: screen = new PauseScreen(false); break; - case SCREEN_PAUSEPREV: screen = new PauseScreen(true); break; - case SCREEN_BLOCKSELECTION: screen = new Touch::IngameBlockSelectionScreen(); break; - + case SCREEN_STARTMENU: screen = new Touch::StartMenuScreen(); break; + case SCREEN_SELECTWORLD: screen = new Touch::SelectWorldScreen();break; + case SCREEN_JOINGAME: screen = new Touch::JoinGameScreen(); break; + case SCREEN_PAUSE: screen = new PauseScreen(false); break; + case SCREEN_PAUSEPREV: screen = new PauseScreen(true); break; + case SCREEN_BLOCKSELECTION: screen = new Touch::IngameBlockSelectionScreen(); break; + case SCREEN_JOINBYIP: screen = new JoinByIPScreen(); break; case SCREEN_NONE: default: // Do nothing @@ -34,12 +35,13 @@ Screen* ScreenChooser::createScreen( ScreenId id ) } } else { switch (id) { - case SCREEN_STARTMENU: screen = new StartMenuScreen(); break; - case SCREEN_SELECTWORLD:screen = new SelectWorldScreen();break; - case SCREEN_JOINGAME: screen = new JoinGameScreen(); break; - case SCREEN_PAUSE: screen = new PauseScreen(false); break; - case SCREEN_PAUSEPREV: screen = new PauseScreen(true); break; - case SCREEN_BLOCKSELECTION: screen = new IngameBlockSelectionScreen(); break; + case SCREEN_STARTMENU: screen = new StartMenuScreen(); break; + case SCREEN_SELECTWORLD: screen = new SelectWorldScreen();break; + case SCREEN_JOINGAME: screen = new JoinGameScreen(); break; + case SCREEN_PAUSE: screen = new PauseScreen(false); break; + case SCREEN_PAUSEPREV: screen = new PauseScreen(true); break; + case SCREEN_BLOCKSELECTION: screen = new IngameBlockSelectionScreen(); break; + case SCREEN_JOINBYIP: screen = new JoinByIPScreen(); break; case SCREEN_NONE: default: diff --git a/src/client/gui/screens/ScreenChooser.h b/src/client/gui/screens/ScreenChooser.h index 0979ade..e63fe99 100755 --- a/src/client/gui/screens/ScreenChooser.h +++ b/src/client/gui/screens/ScreenChooser.h @@ -8,7 +8,8 @@ enum ScreenId { SCREEN_PAUSE, SCREEN_PAUSEPREV, SCREEN_SELECTWORLD, - SCREEN_BLOCKSELECTION + SCREEN_BLOCKSELECTION, + SCREEN_JOINBYIP }; class Screen; diff --git a/src/client/gui/screens/SelectWorldScreen.cpp b/src/client/gui/screens/SelectWorldScreen.cpp index b0f583c..596ea1a 100755 --- a/src/client/gui/screens/SelectWorldScreen.cpp +++ b/src/client/gui/screens/SelectWorldScreen.cpp @@ -356,7 +356,7 @@ void SelectWorldScreen::render( int xm, int ym, float a ) worldsList->setComponentSelected(bWorldView.selected); // #ifdef PLATFORM_DESKTOP - // We should add scrolling with mouse wheel but currently i dont know how to implement it + // desktop: render the list normally (mouse wheel handled separately below) if (_mouseHasBeenUp) worldsList->render(xm, ym, a); else { @@ -412,6 +412,28 @@ std::string SelectWorldScreen::getUniqueLevelName( const std::string& level ) bool SelectWorldScreen::isInGameScreen() { return true; } +void SelectWorldScreen::mouseWheel(int dx, int dy, int xm, int ym) +{ + if (!worldsList) + return; + if (dy == 0) + return; + int num = worldsList->getNumberOfItems(); + int idx = worldsList->selectedItem; + if (dy > 0) { + if (idx > 0) { + idx--; + worldsList->stepLeft(); + } + } else { + if (idx < num - 1) { + idx++; + worldsList->stepRight(); + } + } + worldsList->selectedItem = idx; +} + void SelectWorldScreen::keyPressed( int eventKey ) { if (bWorldView.selected) { diff --git a/src/client/gui/screens/SelectWorldScreen.h b/src/client/gui/screens/SelectWorldScreen.h index 7c36085..12c6c7e 100755 --- a/src/client/gui/screens/SelectWorldScreen.h +++ b/src/client/gui/screens/SelectWorldScreen.h @@ -89,6 +89,9 @@ public: void render(int xm, int ym, float a); + // mouse wheel scroll (new in desktop implementation) + virtual void mouseWheel(int dx, int dy, int xm, int ym); + bool isInGameScreen(); private: void loadLevelSource(); diff --git a/src/client/gui/screens/SimpleChooseLevelScreen.cpp b/src/client/gui/screens/SimpleChooseLevelScreen.cpp index 353748a..f839725 100755 --- a/src/client/gui/screens/SimpleChooseLevelScreen.cpp +++ b/src/client/gui/screens/SimpleChooseLevelScreen.cpp @@ -12,11 +12,13 @@ SimpleChooseLevelScreen::SimpleChooseLevelScreen(const std::string& levelName) : bHeader(0), bGamemode(0), + bCheats(0), bBack(0), bCreate(0), levelName(levelName), hasChosen(false), gamemode(GameType::Survival), + cheatsEnabled(false), tLevelName(0, "World name"), tSeed(1, "World seed") { @@ -26,12 +28,20 @@ SimpleChooseLevelScreen::~SimpleChooseLevelScreen() { if (bHeader) delete bHeader; delete bGamemode; + delete bCheats; delete bBack; delete bCreate; } void SimpleChooseLevelScreen::init() { + // make sure the base class loads the existing level list; the + // derived screen uses ChooseLevelScreen::getUniqueLevelName(), which + // depends on `levels` being populated. omitting this used to result + // in duplicate IDs ("creating the second world would load the + // first") when the name already existed. + ChooseLevelScreen::init(); + tLevelName.text = "New world"; // header + close button @@ -48,18 +58,22 @@ void SimpleChooseLevelScreen::init() } if (minecraft->useTouchscreen()) { bGamemode = new Touch::TButton(1, "Survival mode"); + bCheats = new Touch::TButton(4, "Cheats: Off"); bCreate = new Touch::TButton(3, "Create"); } else { bGamemode = new Button(1, "Survival mode"); + bCheats = new Button(4, "Cheats: Off"); bCreate = new Button(3, "Create"); } buttons.push_back(bHeader); buttons.push_back(bBack); buttons.push_back(bGamemode); + buttons.push_back(bCheats); buttons.push_back(bCreate); tabButtons.push_back(bGamemode); + tabButtons.push_back(bCheats); tabButtons.push_back(bBack); tabButtons.push_back(bCreate); @@ -94,16 +108,26 @@ void SimpleChooseLevelScreen::setupPositions() tSeed.x = tLevelName.x; tSeed.y = tLevelName.y + 30; - bGamemode->width = 140; - bGamemode->x = centerX - bGamemode->width / 2; - // compute vertical centre for gamemode in remaining space + const int buttonWidth = 120; + const int buttonSpacing = 10; + const int totalButtonWidth = buttonWidth * 2 + buttonSpacing; + + bGamemode->width = buttonWidth; + bCheats->width = buttonWidth; + + bGamemode->x = centerX - totalButtonWidth / 2; + bCheats->x = bGamemode->x + buttonWidth + buttonSpacing; + + // compute vertical centre for buttons in remaining space { int bottomPad = 20; int availTop = buttonHeight + 20 + 30 + 10; // just below seed int availBottom = height - bottomPad - bCreate->height - 10; // leave some gap before create int availHeight = availBottom - availTop; if (availHeight < 0) availHeight = 0; - bGamemode->y = availTop + (availHeight - bGamemode->height) / 2; + int y = availTop + (availHeight - bGamemode->height) / 2; + bGamemode->y = y; + bCheats->y = y; } bCreate->width = 100; @@ -124,14 +148,14 @@ void SimpleChooseLevelScreen::render( int xm, int ym, float a ) renderDirtBackground(0); glEnable2(GL_BLEND); - const char* str = NULL; + const char* modeDesc = NULL; if (gamemode == GameType::Survival) { - str = "Mobs, health and gather resources"; + modeDesc = "Mobs, health and gather resources"; } else if (gamemode == GameType::Creative) { - str = "Unlimited resources and flying"; + modeDesc = "Unlimited resources and flying"; } - if (str) { - drawCenteredString(minecraft->font, str, width/2, bGamemode->y + bGamemode->height + 4, 0xffcccccc); + if (modeDesc) { + drawCenteredString(minecraft->font, modeDesc, width / 2, bGamemode->y + bGamemode->height + 4, 0xffcccccc); } drawString(minecraft->font, "World name:", tLevelName.x, tLevelName.y - Font::DefaultLineHeight - 2, 0xffcccccc); @@ -188,6 +212,12 @@ void SimpleChooseLevelScreen::buttonClicked( Button* button ) return; } + if (button == bCheats) { + cheatsEnabled = !cheatsEnabled; + bCheats->msg = cheatsEnabled ? "Cheats: On" : "Cheats: Off"; + return; + } + if (button == bCreate && !tLevelName.text.empty()) { int seed = getEpochTimeS(); if (!tSeed.text.empty()) { @@ -200,7 +230,7 @@ void SimpleChooseLevelScreen::buttonClicked( Button* button ) } } std::string levelId = getUniqueLevelName(tLevelName.text); - LevelSettings settings(seed, gamemode); + LevelSettings settings(seed, gamemode, cheatsEnabled); minecraft->selectLevel(levelId, levelId, settings); minecraft->hostMultiplayer(); minecraft->setScreen(new ProgressScreen()); diff --git a/src/client/gui/screens/SimpleChooseLevelScreen.h b/src/client/gui/screens/SimpleChooseLevelScreen.h index 457a8db..1c88d06 100755 --- a/src/client/gui/screens/SimpleChooseLevelScreen.h +++ b/src/client/gui/screens/SimpleChooseLevelScreen.h @@ -28,12 +28,14 @@ public: private: Touch::THeader* bHeader; Button* bGamemode; + Button* bCheats; ImageButton* bBack; Button* bCreate; bool hasChosen; std::string levelName; int gamemode; + bool cheatsEnabled; TextBox tLevelName; TextBox tSeed; diff --git a/src/client/gui/screens/StartMenuScreen.cpp b/src/client/gui/screens/StartMenuScreen.cpp index 040ede3..83ecacf 100755 --- a/src/client/gui/screens/StartMenuScreen.cpp +++ b/src/client/gui/screens/StartMenuScreen.cpp @@ -6,6 +6,7 @@ #include "OptionsScreen.h" #include "PauseScreen.h" #include "PrerenderTilesScreen.h" // test button +#include "../components/ImageButton.h" #include "../../../util/Mth.h" @@ -24,7 +25,8 @@ StartMenuScreen::StartMenuScreen() : bHost( 2, 0, 0, 160, 24, "Start Game"), bJoin( 3, 0, 0, 160, 24, "Join Game"), - bOptions( 4, 0, 0, 78, 22, "Options") + bOptions( 4, 0, 0, 78, 22, "Options"), + bQuit( 5, "") { } @@ -53,10 +55,18 @@ void StartMenuScreen::init() tabButtons.push_back(&bOptions); #endif - #ifdef DEMO_MODE - buttons.push_back(&bBuy); - tabButtons.push_back(&bBuy); - #endif + // add quit button (top right X icon) – match OptionsScreen style + { + ImageDef def; + def.name = "gui/touchgui.png"; + def.width = 34; + def.height = 26; + def.setSrc(IntRectangle(150, 0, (int)def.width, (int)def.height)); + bQuit.setImageDef(def, true); + bQuit.scaleWhenPressed = false; + buttons.push_back(&bQuit); + // don't include in tab navigation + } copyright = "\xffMojang AB";//. Do not distribute!"; @@ -99,8 +109,9 @@ void StartMenuScreen::setupPositions() { bJoin.x = (width - bJoin.width) / 2; bOptions.x = (width - bJoin.width) / 2; - copyrightPosX = width - minecraft->font->width(copyright) - 1; - versionPosX = (width - minecraft->font->width(version)) / 2;// - minecraft->font->width(version) - 2; + // position quit icon at top-right (use image-defined size) + bQuit.x = width - bQuit.width; + bQuit.y = 0; } void StartMenuScreen::tick() { @@ -129,6 +140,10 @@ void StartMenuScreen::buttonClicked(Button* button) { { minecraft->setScreen(new OptionsScreen()); } + if (button == &bQuit) + { + minecraft->quit(); + } } bool StartMenuScreen::isInGameScreen() { return false; } @@ -137,6 +152,10 @@ void StartMenuScreen::render( int xm, int ym, float a ) { renderBackground(); + // Show current username in the top-left corner + std::string username = minecraft->options.username.empty() ? "unknown" : minecraft->options.username; + drawString(font, std::string("Username: ") + username, 2, 2, 0xffffffff); + #if defined(RPI) TextureId id = minecraft->textures->loadTexture("gui/pi_title.png"); #else diff --git a/src/client/gui/screens/StartMenuScreen.h b/src/client/gui/screens/StartMenuScreen.h index a14ebd8..dd0ccb3 100755 --- a/src/client/gui/screens/StartMenuScreen.h +++ b/src/client/gui/screens/StartMenuScreen.h @@ -3,6 +3,7 @@ #include "../Screen.h" #include "../components/Button.h" +#include "../components/ImageButton.h" class StartMenuScreen: public Screen { @@ -25,6 +26,7 @@ private: Button bHost; Button bJoin; Button bOptions; + ImageButton bQuit; // X button in top-right corner std::string copyright; int copyrightPosX; diff --git a/src/client/gui/screens/UsernameScreen.cpp b/src/client/gui/screens/UsernameScreen.cpp index 43e334e..57f1f9b 100644 --- a/src/client/gui/screens/UsernameScreen.cpp +++ b/src/client/gui/screens/UsernameScreen.cpp @@ -33,15 +33,16 @@ void UsernameScreen::setupPositions() int cx = width / 2; int cy = height / 2; - _btnDone.width = 120; - _btnDone.height = 20; + // Make the done button match the touch-style option tabs + _btnDone.width = 66; + _btnDone.height = 26; _btnDone.x = (width - _btnDone.width) / 2; _btnDone.y = height / 2 + 52; - tUsername.x = _btnDone.x; - tUsername.y = _btnDone.y - 60; tUsername.width = 120; tUsername.height = 20; + tUsername.x = (width - tUsername.width) / 2; + tUsername.y = _btnDone.y - 60; } void UsernameScreen::tick() @@ -58,9 +59,10 @@ void UsernameScreen::keyPressed(int eventKey) } // deliberately do NOT call super::keyPressed — that would close the screen on Escape - _btnDone.active = !tUsername.text.empty(); - Screen::keyPressed(eventKey); + + // enable the Done button only when there is some text (and ensure it updates after backspace) + _btnDone.active = !tUsername.text.empty(); } void UsernameScreen::removed() diff --git a/src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.cpp b/src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.cpp index db0b3ef..d88025d 100755 --- a/src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.cpp +++ b/src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.cpp @@ -153,6 +153,11 @@ int IngameBlockSelectionScreen::getSlotPosY(int slotY) { return height - 16 - 3 - 22 * 2 - 22 * slotY; } +int IngameBlockSelectionScreen::getSlotHeight() { + // same as non-touch implementation + return 22; +} + void IngameBlockSelectionScreen::mouseClicked(int x, int y, int buttonNum) { _pendingClose = _blockList->_clickArea->isInside((float)x, (float)y); if (!_pendingClose) @@ -166,6 +171,24 @@ void IngameBlockSelectionScreen::mouseReleased(int x, int y, int buttonNum) { super::mouseReleased(x, y, buttonNum); } +void IngameBlockSelectionScreen::mouseWheel(int dx, int dy, int xm, int ym) +{ + if (dy == 0) return; + if (_blockList) { + float amount = -dy * getSlotHeight(); + _blockList->scrollBy(0, amount); + } + int cols = InventoryColumns; + int maxIndex = InventorySize - 1; + int idx = selectedItem; + if (dy > 0) { + if (idx >= cols) idx -= cols; + } else { + if (idx + cols <= maxIndex) idx += cols; + } + selectedItem = idx; +} + bool IngameBlockSelectionScreen::addItem(const InventoryPane* pane, int itemId) { Inventory* inventory = minecraft->player->inventory; diff --git a/src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.h b/src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.h index 1bfa25b..ed05c0b 100755 --- a/src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.h +++ b/src/client/gui/screens/touch/TouchIngameBlockSelectionScreen.h @@ -39,12 +39,16 @@ public: protected: virtual void mouseClicked(int x, int y, int buttonNum); virtual void mouseReleased(int x, int y, int buttonNum); + + // also support wheel scrolling + virtual void mouseWheel(int dx, int dy, int xm, int ym) override; private: void renderDemoOverlay(); //int getLinearSlotId(int x, int y); int getSlotPosX(int slotX); int getSlotPosY(int slotY); + int getSlotHeight(); private: int selectedItem; diff --git a/src/client/gui/screens/touch/TouchJoinGameScreen.cpp b/src/client/gui/screens/touch/TouchJoinGameScreen.cpp index 181343d..f8ad00a 100755 --- a/src/client/gui/screens/touch/TouchJoinGameScreen.cpp +++ b/src/client/gui/screens/touch/TouchJoinGameScreen.cpp @@ -129,6 +129,11 @@ void JoinGameScreen::buttonClicked(Button* button) //minecraft->locateMultiplayer(); //minecraft->setScreen(new JoinGameScreen()); } + if(button->id == bJoinByIp.id) { + minecraft->cancelLocateMultiplayer(); + minecraft->screenChooser.setScreen(SCREEN_JOINBYIP); + } + if (button->id == bBack.id) { minecraft->cancelLocateMultiplayer(); diff --git a/src/client/gui/screens/touch/TouchSelectWorldScreen.cpp b/src/client/gui/screens/touch/TouchSelectWorldScreen.cpp index 377fe53..0ed793d 100755 --- a/src/client/gui/screens/touch/TouchSelectWorldScreen.cpp +++ b/src/client/gui/screens/touch/TouchSelectWorldScreen.cpp @@ -389,6 +389,26 @@ static char ILLEGAL_FILE_CHARACTERS[] = { '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' }; +void SelectWorldScreen::mouseWheel(int dx, int dy, int xm, int ym) +{ + if (!worldsList) return; + if (dy == 0) return; + int num = worldsList->getNumberOfItems(); + int idx = worldsList->selectedItem; + if (dy > 0) { + if (idx > 0) { + idx--; + worldsList->stepLeft(); + } + } else { + if (idx < num - 1) { + idx++; + worldsList->stepRight(); + } + } + worldsList->selectedItem = idx; +} + void SelectWorldScreen::tick() { #if 0 diff --git a/src/client/gui/screens/touch/TouchSelectWorldScreen.h b/src/client/gui/screens/touch/TouchSelectWorldScreen.h index 1f62139..f02f5e0 100755 --- a/src/client/gui/screens/touch/TouchSelectWorldScreen.h +++ b/src/client/gui/screens/touch/TouchSelectWorldScreen.h @@ -97,6 +97,9 @@ public: virtual void buttonClicked(Button* button); virtual void keyPressed(int eventKey); + // support for mouse wheel when desktop code uses touch variant + virtual void mouseWheel(int dx, int dy, int xm, int ym) override; + bool isInGameScreen(); private: void loadLevelSource(); diff --git a/src/client/gui/screens/touch/TouchStartMenuScreen.cpp b/src/client/gui/screens/touch/TouchStartMenuScreen.cpp index 16d755e..e4c8d0f 100755 --- a/src/client/gui/screens/touch/TouchStartMenuScreen.cpp +++ b/src/client/gui/screens/touch/TouchStartMenuScreen.cpp @@ -29,7 +29,8 @@ namespace Touch { StartMenuScreen::StartMenuScreen() : bHost( 2, "Start Game"), bJoin( 3, "Join Game"), - bOptions( 4, "Options") + bOptions( 4, "Options"), + bQuit( 5, "") { ImageDef def; bJoin.width = 75; @@ -58,6 +59,17 @@ void StartMenuScreen::init() buttons.push_back(&bJoin); buttons.push_back(&bOptions); + // add quit icon (same look as options header) + { + ImageDef def; + def.name = "gui/touchgui.png"; + def.width = 34; + def.height = 26; + def.setSrc(IntRectangle(150, 0, (int)def.width, (int)def.height)); + bQuit.setImageDef(def, true); + bQuit.scaleWhenPressed = false; + buttons.push_back(&bQuit); + } tabButtons.push_back(&bHost); tabButtons.push_back(&bJoin); @@ -106,6 +118,10 @@ void StartMenuScreen::setupPositions() { bHost.x = 1*buttonWidth + (int)(2*spacing); bOptions.x = 2*buttonWidth + (int)(3*spacing); + // quit icon top-right (use size assigned in init) + bQuit.x = width - bQuit.width; + bQuit.y = 0; + copyrightPosX = width - minecraft->font->width(copyright) - 1; versionPosX = (width - minecraft->font->width(version)) / 2;// - minecraft->font->width(version) - 2; } @@ -133,6 +149,10 @@ void StartMenuScreen::buttonClicked(::Button* button) { { minecraft->setScreen(new OptionsScreen()); } + if (button == &bQuit) + { + minecraft->quit(); + } } bool StartMenuScreen::isInGameScreen() { return false; } @@ -140,6 +160,10 @@ bool StartMenuScreen::isInGameScreen() { return false; } void StartMenuScreen::render( int xm, int ym, float a ) { renderBackground(); + + // Show current username in the top-left corner + std::string username = minecraft->options.username.empty() ? "unknown" : minecraft->options.username; + drawString(font, std::string("Username: ") + username, 2, 2, 0xffffffff); glEnable2(GL_BLEND); diff --git a/src/client/gui/screens/touch/TouchStartMenuScreen.h b/src/client/gui/screens/touch/TouchStartMenuScreen.h index 82030b4..b9bcb5b 100755 --- a/src/client/gui/screens/touch/TouchStartMenuScreen.h +++ b/src/client/gui/screens/touch/TouchStartMenuScreen.h @@ -3,6 +3,7 @@ #include "../../Screen.h" #include "../../components/LargeImageButton.h" +#include "../../components/ImageButton.h" #include "../../components/TextBox.h" namespace Touch { @@ -27,6 +28,7 @@ private: LargeImageButton bHost; LargeImageButton bJoin; LargeImageButton bOptions; + ImageButton bQuit; // X close icon std::string copyright; int copyrightPosX; diff --git a/src/client/model/HumanoidModel.cpp b/src/client/model/HumanoidModel.cpp index 985019c..5a64657 100755 --- a/src/client/model/HumanoidModel.cpp +++ b/src/client/model/HumanoidModel.cpp @@ -4,34 +4,46 @@ #include "../../world/entity/player/Player.h" #include "../../world/entity/player/Inventory.h" -HumanoidModel::HumanoidModel( float g /*= 0*/, float yOffset /*= 0*/ ) -: holdingLeftHand(false), +HumanoidModel::HumanoidModel( float g /*= 0*/, float yOffset /*= 0*/, int texW /*= 64*/, int texH /*= 32*/ ) +: holdingLeftHand(false), holdingRightHand(false), sneaking(false), bowAndArrow(false), head(0, 0), + hair(32, 0), //ear (24, 0), - //hair(32, 0), body(16, 16), arm0(24 + 16, 16), arm1(24 + 16, 16), leg0(0, 16), leg1(0, 16) { + texWidth = texW; + texHeight = texH; + head.setModel(this); + hair.setModel(this); body.setModel(this); arm0.setModel(this); arm1.setModel(this); leg0.setModel(this); leg1.setModel(this); + // If the texture is 64x64, use the modern skin layout for arms/legs and add overlay layers. + bool modernSkin = (texWidth == 64 && texHeight == 64); + if (modernSkin) { + // Left arm and left leg are located in the bottom half of a 64x64 skin. + arm1.texOffs(32, 48); + leg1.texOffs(16, 48); + } + head.addBox(-4, -8, -4, 8, 8, 8, g); // Head head.setPos(0, 0 + yOffset, 0); - //ear.addBox(-3, -6, -1, 6, 6, 1, g); // Ear - - //hair.addBox(-4, -8, -4, 8, 8, 8, g + 0.5f); // Head - // hair.setPos(0, 0 + yOffset, 0); + if (modernSkin) { + hair.addBox(-4, -8, -4, 8, 8, 8, g + 0.5f); // Outer head layer (hat) + hair.setPos(0, 0 + yOffset, 0); + } body.addBox(-4, 0, -2, 8, 12, 4, g); // Body body.setPos(0, 0 + yOffset, 0); @@ -49,6 +61,24 @@ HumanoidModel::HumanoidModel( float g /*= 0*/, float yOffset /*= 0*/ ) leg1.mirror = true; leg1.addBox(-2, 0, -2, 4, 12, 4, g); // Leg1 leg1.setPos(2, 12 + yOffset, 0); + + if (modernSkin) { + // Overlay layers for 64x64 skins (same geometry, different texture regions) + body.texOffs(16, 32); + body.addBox(-4, 0, -2, 8, 12, 4, g + 0.5f); + + arm0.texOffs(40, 32); + arm0.addBox(-3, -2, -2, 4, 12, 4, g + 0.5f); + + arm1.texOffs(48, 48); + arm1.addBox(-1, -2, -2, 4, 12, 4, g + 0.5f); + + leg0.texOffs(0, 32); + leg0.addBox(-2, 0, -2, 4, 12, 4, g + 0.5f); + + leg1.texOffs(0, 48); + leg1.addBox(-2, 0, -2, 4, 12, 4, g + 0.5f); + } } void HumanoidModel::render(Entity* e, float time, float r, float bob, float yRot, float xRot, float scale ) @@ -68,14 +98,24 @@ void HumanoidModel::render(Entity* e, float time, float r, float bob, float yRot setupAnim(time, r, bob, yRot, xRot, scale); + // Sync overlay with head rotation/position + if (texWidth == 64 && texHeight == 64) { + hair.xRot = head.xRot; + hair.yRot = head.yRot; + hair.zRot = head.zRot; + hair.y = head.y; + } + head.render(scale); + if (texWidth == 64 && texHeight == 64) { + hair.render(scale); + } body.render(scale); arm0.render(scale); arm1.render(scale); leg0.render(scale); leg1.render(scale); bowAndArrow = false; - //hair.render(scale); } void HumanoidModel::render( HumanoidModel* model, float scale ) @@ -83,8 +123,12 @@ void HumanoidModel::render( HumanoidModel* model, float scale ) head.yRot = model->head.yRot; head.y = model->head.y; head.xRot = model->head.xRot; - //hair.yRot = head.yRot; - //hair.xRot = head.xRot; + if (texWidth == 64 && texHeight == 64) { + hair.yRot = head.yRot; + hair.xRot = head.xRot; + hair.zRot = head.zRot; + hair.y = head.y; + } arm0.xRot = model->arm0.xRot; arm0.zRot = model->arm0.zRot; @@ -96,12 +140,14 @@ void HumanoidModel::render( HumanoidModel* model, float scale ) leg1.xRot = model->leg1.xRot; head.render(scale); + if (texWidth == 64 && texHeight == 64) { + hair.render(scale); + } body.render(scale); arm0.render(scale); arm1.render(scale); leg0.render(scale); leg1.render(scale); - //hair.render(scale); } void HumanoidModel::renderHorrible( float time, float r, float bob, float yRot, float xRot, float scale ) diff --git a/src/client/model/HumanoidModel.h b/src/client/model/HumanoidModel.h index 14dc60e..d97a7be 100755 --- a/src/client/model/HumanoidModel.h +++ b/src/client/model/HumanoidModel.h @@ -9,7 +9,7 @@ class ItemInstance; class HumanoidModel: public Model { public: - HumanoidModel(float g = 0, float yOffset = 0); +HumanoidModel(float g = 0, float yOffset = 0, int texW = 64, int texH = 32); void setupAnim(float time, float r, float bob, float yRot, float xRot, float scale); @@ -18,7 +18,7 @@ public: void renderHorrible(float time, float r, float bob, float yRot, float xRot, float scale); void onGraphicsReset(); - ModelPart head, /*hair,*/ body, arm0, arm1, leg0, leg1;//, ear; + ModelPart head, hair, body, arm0, arm1, leg0, leg1;//, ear; bool holdingLeftHand; bool holdingRightHand; bool sneaking; diff --git a/src/client/model/geom/Cube.cpp b/src/client/model/geom/Cube.cpp index e1da77d..bb3c030 100755 --- a/src/client/model/geom/Cube.cpp +++ b/src/client/model/geom/Cube.cpp @@ -50,12 +50,12 @@ Cube::Cube(ModelPart* modelPart, int xTexOffs, int yTexOffs, float x0, float y0, VertexPT* l2 = ++ptr; VertexPT* l3 = ++ptr; - polygons[0] = PolygonQuad(l1, u1, u2, l2, xTexOffs + d + w, yTexOffs + d, xTexOffs + d + w + d, yTexOffs + d + h); // Right - polygons[1] = PolygonQuad(u0, l0, l3, u3, xTexOffs + 0, yTexOffs + d, xTexOffs + d, yTexOffs + d + h); // Left - polygons[2] = PolygonQuad(l1, l0, u0, u1, xTexOffs + d, yTexOffs + 0, xTexOffs + d + w, yTexOffs + d); // Up - polygons[3] = PolygonQuad(u2, u3, l3, l2, xTexOffs + d + w, yTexOffs + d, xTexOffs + d + w + w, yTexOffs); // Down - polygons[4] = PolygonQuad(u1, u0, u3, u2, xTexOffs + d, yTexOffs + d, xTexOffs + d + w, yTexOffs + d + h); // Front - polygons[5] = PolygonQuad(l0, l1, l2, l3, xTexOffs + d + w + d, yTexOffs + d, xTexOffs + d + w + d + w, yTexOffs + d + h); // Back + polygons[0] = PolygonQuad(l1, u1, u2, l2, xTexOffs + d + w, yTexOffs + d, xTexOffs + d + w + d, yTexOffs + d + h, modelPart->xTexSize, modelPart->yTexSize); // Right + polygons[1] = PolygonQuad(u0, l0, l3, u3, xTexOffs + 0, yTexOffs + d, xTexOffs + d, yTexOffs + d + h, modelPart->xTexSize, modelPart->yTexSize); // Left + polygons[2] = PolygonQuad(l1, l0, u0, u1, xTexOffs + d, yTexOffs + 0, xTexOffs + d + w, yTexOffs + d, modelPart->xTexSize, modelPart->yTexSize); // Up + polygons[3] = PolygonQuad(u2, u3, l3, l2, xTexOffs + d + w, yTexOffs + d, xTexOffs + d + w + w, yTexOffs, modelPart->xTexSize, modelPart->yTexSize); // Down + polygons[4] = PolygonQuad(u1, u0, u3, u2, xTexOffs + d, yTexOffs + d, xTexOffs + d + w, yTexOffs + d + h, modelPart->xTexSize, modelPart->yTexSize); // Front + polygons[5] = PolygonQuad(l0, l1, l2, l3, xTexOffs + d + w + d, yTexOffs + d, xTexOffs + d + w + d + w, yTexOffs + d + h, modelPart->xTexSize, modelPart->yTexSize); // Back if (modelPart->mirror) { for (int i = 0; i < 6; i++) diff --git a/src/client/model/geom/Polygon.cpp b/src/client/model/geom/Polygon.cpp index db531f2..bfb1467 100755 --- a/src/client/model/geom/Polygon.cpp +++ b/src/client/model/geom/Polygon.cpp @@ -12,15 +12,15 @@ PolygonQuad::PolygonQuad(VertexPT* v0, VertexPT* v1, VertexPT* v2, VertexPT* v3) } PolygonQuad::PolygonQuad( VertexPT* v0, VertexPT* v1, VertexPT* v2, VertexPT* v3, - int uu0, int vv0, int uu1, int vv1) -: _flipNormal(false) + int uu0, int vv0, int uu1, int vv1, float texW, float texH) +: _flipNormal(false) { - const float us = -0.002f / 64.0f;//0.1f / 64.0f; - const float vs = -0.002f / 32.0f;//0.1f / 32.0f; - vertices[0] = v0->remap(uu1 / 64.0f - us, vv0 / 32.0f + vs); - vertices[1] = v1->remap(uu0 / 64.0f + us, vv0 / 32.0f + vs); - vertices[2] = v2->remap(uu0 / 64.0f + us, vv1 / 32.0f - vs); - vertices[3] = v3->remap(uu1 / 64.0f - us, vv1 / 32.0f - vs); + const float us = -0.002f / texW; + const float vs = -0.002f / texH; + vertices[0] = v0->remap(uu1 / texW - us, vv0 / texH + vs); + vertices[1] = v1->remap(uu0 / texW + us, vv0 / texH + vs); + vertices[2] = v2->remap(uu0 / texW + us, vv1 / texH - vs); + vertices[3] = v3->remap(uu1 / texW - us, vv1 / texH - vs); } PolygonQuad::PolygonQuad( VertexPT* v0, VertexPT* v1, VertexPT* v2, VertexPT* v3, diff --git a/src/client/model/geom/Polygon.h b/src/client/model/geom/Polygon.h index 875b0a6..7731f9c 100755 --- a/src/client/model/geom/Polygon.h +++ b/src/client/model/geom/Polygon.h @@ -11,7 +11,7 @@ class PolygonQuad public: PolygonQuad() {} PolygonQuad(VertexPT*,VertexPT*,VertexPT*,VertexPT*); - PolygonQuad(VertexPT*,VertexPT*,VertexPT*,VertexPT*, int u0, int v0, int u1, int v1); + PolygonQuad(VertexPT*,VertexPT*,VertexPT*,VertexPT*, int u0, int v0, int u1, int v1, float texW = 64.0f, float texH = 32.0f); PolygonQuad(VertexPT*,VertexPT*,VertexPT*,VertexPT*, float u0, float v0, float u1, float v1); void mirror(); diff --git a/src/client/player/LocalPlayer.cpp b/src/client/player/LocalPlayer.cpp index 8b98293..f0b5b3c 100755 --- a/src/client/player/LocalPlayer.cpp +++ b/src/client/player/LocalPlayer.cpp @@ -18,6 +18,20 @@ #include "../../network/packet/SendInventoryPacket.h" #include "../../network/packet/EntityEventPacket.h" #include "../../network/packet/PlayerActionPacket.h" +#include +#include +#include "../../platform/log.h" +#include "../../platform/HttpClient.h" +#include "../../platform/CThread.h" +#include "../../util/StringUtils.h" + +#if defined(_WIN32) +#include +#else +#include +#include +#endif + #ifndef STANDALONE_SERVER #include "../gui/Screen.h" #include "../gui/screens/FurnaceScreen.h" @@ -32,6 +46,250 @@ #include "../../world/item/ArmorItem.h" #include "../../network/packet/PlayerArmorEquipmentPacket.h" +namespace { + +static bool isBase64(unsigned char c) { + return (std::isalnum(c) || (c == '+') || (c == '/')); +} + +static std::string base64Decode(const std::string& encoded) { + static const std::string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::string out; + int in_len = (int)encoded.size(); + int i = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + + while (in_len-- && (encoded[in_] != '=') && isBase64(encoded[in_])) { + char_array_4[i++] = encoded[in_]; in_++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_array_4[i] = (unsigned char)base64Chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; i < 3; i++) + out += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (int j = i; j < 4; j++) + char_array_4[j] = 0; + for (int j = 0; j < 4; j++) + char_array_4[j] = (unsigned char)base64Chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (int j = 0; (j < i - 1); j++) + out += char_array_3[j]; + } + return out; +} + +static std::string extractJsonString(const std::string& json, const std::string& key) { + std::string search = "\"" + key + "\""; + size_t pos = json.find(search); + if (pos == std::string::npos) return ""; + pos = json.find(':', pos + search.size()); + if (pos == std::string::npos) return ""; + pos++; + while (pos < json.size() && std::isspace((unsigned char)json[pos])) pos++; + if (pos >= json.size() || json[pos] != '"') return ""; + pos++; + size_t end = json.find('"', pos); + if (end == std::string::npos) return ""; + return json.substr(pos, end - pos); +} + +static std::string getTextureUrlForUsername(const std::string& username, const std::string& textureKey) { + if (username.empty()) { + LOGI("[%s] username empty\n", textureKey.c_str()); + return ""; + } + + LOGI("[%s] resolving UUID for user '%s'...\n", textureKey.c_str(), username.c_str()); + std::vector body; + std::string apiUrl = "http://api.mojang.com/users/profiles/minecraft/" + username; + if (!HttpClient::download(apiUrl, body)) { + LOGW("[%s] failed to download UUID for %s\n", textureKey.c_str(), username.c_str()); + return ""; + } + + std::string response(body.begin(), body.end()); + std::string uuid = extractJsonString(response, "id"); + if (uuid.empty()) { + LOGW("[%s] no UUID found in Mojang response for %s\n", textureKey.c_str(), username.c_str()); + return ""; + } + + LOGI("[%s] UUID=%s for user %s\n", textureKey.c_str(), uuid.c_str(), username.c_str()); + + std::string profileUrl = "http://sessionserver.mojang.com/session/minecraft/profile/" + uuid; + if (!HttpClient::download(profileUrl, body)) { + LOGW("[%s] failed to download profile for UUID %s\n", textureKey.c_str(), uuid.c_str()); + return ""; + } + + response.assign(body.begin(), body.end()); + std::string encoded = extractJsonString(response, "value"); + if (encoded.empty()) { + LOGW("[%s] no value field in profile response for UUID %s\n", textureKey.c_str(), uuid.c_str()); + return ""; + } + + std::string decoded = base64Decode(encoded); + + std::string searchKey = "\"" + textureKey + "\""; + size_t texturePos = decoded.find(searchKey); + if (texturePos == std::string::npos) { + LOGW("[%s] no %s entry in decoded profile for UUID %s\n", textureKey.c_str(), textureKey.c_str(), uuid.c_str()); + return ""; + } + size_t urlPos = decoded.find("\"url\"", texturePos); + if (urlPos == std::string::npos) { + LOGW("[%s] no url field under %s for UUID %s\n", textureKey.c_str(), textureKey.c_str(), uuid.c_str()); + return ""; + } + + // extract the URL value from the substring starting at urlPos + std::string urlFragment = decoded.substr(urlPos); + std::string textureUrl = extractJsonString(urlFragment, "url"); + if (textureUrl.empty()) { + LOGW("[%s] failed to parse %s URL for UUID %s\n", textureKey.c_str(), textureKey.c_str(), uuid.c_str()); + return ""; + } + + LOGI("[%s] %s URL for %s: %s\n", textureKey.c_str(), textureKey.c_str(), username.c_str(), textureUrl.c_str()); + return textureUrl; +} + +static std::string getSkinUrlForUsername(const std::string& username) { + return getTextureUrlForUsername(username, "SKIN"); +} + +static std::string getCapeUrlForUsername(const std::string& username) { + return getTextureUrlForUsername(username, "CAPE"); +} + +static bool ensureDirectoryExists(const std::string& path) { +#if defined(_WIN32) + return _mkdir(path.c_str()) == 0 || errno == EEXIST; +#else + struct stat st; + if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) + return true; + return mkdir(path.c_str(), 0755) == 0 || errno == EEXIST; +#endif +} + +static bool fileExists(const std::string& path) { + struct stat st; + if (stat(path.c_str(), &st) != 0) + return false; + +#if defined(_WIN32) + return (st.st_mode & _S_IFREG) != 0; +#else + return S_ISREG(st.st_mode); +#endif +} + +static void* fetchSkinForPlayer(void* param) { + LocalPlayer* player = (LocalPlayer*)param; + if (!player) return NULL; + + LOGI("[Skin] starting skin download for %s\n", player->name.c_str()); + + const std::string cacheDir = "data/images/skins"; + if (!ensureDirectoryExists(cacheDir)) { + LOGW("[Skin] failed to create cache directory %s\n", cacheDir.c_str()); + } + + std::string cacheFile = cacheDir + "/" + player->name + ".png"; + if (fileExists(cacheFile)) { + LOGI("[Skin] using cached skin for %s\n", player->name.c_str()); + player->setTextureName("skins/" + player->name + ".png"); + return NULL; + } + + std::string skinUrl = getSkinUrlForUsername(player->name); + if (skinUrl.empty()) { + LOGW("[Skin] skin URL lookup failed for %s\n", player->name.c_str()); + return NULL; + } + + LOGI("[Skin] downloading skin from %s\n", skinUrl.c_str()); + std::vector skinData; + if (!HttpClient::download(skinUrl, skinData) || skinData.empty()) { + LOGW("[Skin] download failed for %s\n", skinUrl.c_str()); + return NULL; + } + + // Save to cache + FILE* fp = fopen(cacheFile.c_str(), "wb"); + if (fp) { + fwrite(skinData.data(), 1, skinData.size(), fp); + fclose(fp); + LOGI("[Skin] cached skin to %s\n", cacheFile.c_str()); + } else { + LOGW("[Skin] failed to write skin cache %s\n", cacheFile.c_str()); + } + + player->setTextureName("skins/" + player->name + ".png"); + return NULL; +} + +static void* fetchCapeForPlayer(void* param) { + LocalPlayer* player = (LocalPlayer*)param; + if (!player) return NULL; + + LOGI("[Cape] starting cape download for %s\n", player->name.c_str()); + + const std::string cacheDir = "data/images/capes"; + if (!ensureDirectoryExists(cacheDir)) { + LOGW("[Cape] failed to create cache directory %s\n", cacheDir.c_str()); + } + + std::string cacheFile = cacheDir + "/" + player->name + ".png"; + if (fileExists(cacheFile)) { + LOGI("[Cape] using cached cape for %s\n", player->name.c_str()); + player->setCapeTextureName("capes/" + player->name + ".png"); + return NULL; + } + + std::string capeUrl = getCapeUrlForUsername(player->name); + if (capeUrl.empty()) { + LOGW("[Cape] cape URL lookup failed for %s\n", player->name.c_str()); + return NULL; + } + + LOGI("[Cape] downloading cape from %s\n", capeUrl.c_str()); + std::vector capeData; + if (!HttpClient::download(capeUrl, capeData) || capeData.empty()) { + LOGW("[Cape] download failed for %s\n", capeUrl.c_str()); + return NULL; + } + + // Save to cache + FILE* fp = fopen(cacheFile.c_str(), "wb"); + if (fp) { + fwrite(capeData.data(), 1, capeData.size(), fp); + fclose(fp); + LOGI("[Cape] cached cape to %s\n", cacheFile.c_str()); + } else { + LOGW("[Cape] failed to write cape cache %s\n", cacheFile.c_str()); + } + + player->setCapeTextureName("capes/" + player->name + ".png"); + return NULL; +} + //@note: doesn't work completely, since it doesn't care about stairs rotation static bool isJumpable(int tileId) { return tileId != Tile::fence->id @@ -43,6 +301,8 @@ static bool isJumpable(int tileId) { && (Tile::tiles[tileId] != NULL && Tile::tiles[tileId]->getRenderShape() != Tile::SHAPE_STAIRS); } +} // anonymous namespace + LocalPlayer::LocalPlayer(Minecraft* minecraft, Level* level, User* user, int dimension, bool isCreative) : Player(level, isCreative), minecraft(minecraft), @@ -58,10 +318,11 @@ LocalPlayer::LocalPlayer(Minecraft* minecraft, Level* level, User* user, int dim this->dimension = dimension; _init(); - if (user != NULL) { - if (user->name.length() > 0) - //customTextureUrl = "http://s3.amazonaws.com/MinecraftSkins/" + user.name + ".png"; - this->name = user->name; + if (user != NULL && !user->name.empty()) { + this->name = user->name; + // Fetch user skin and cape from Mojang servers in the background (avoids blocking the main thread) + new CThread(fetchSkinForPlayer, this); + new CThread(fetchCapeForPlayer, this); } } @@ -184,7 +445,7 @@ void LocalPlayer::aiStep() { // Sprint: detect W double-tap { bool forwardHeld = (input->ya > 0); - if (forwardHeld && !prevForwardHeld) { + if (forwardHeld && !prevForwardHeld && minecraft->options.useSprinting) { // leading edge of W press if (sprintDoubleTapTimer > 0) sprinting = true; @@ -273,7 +534,7 @@ void LocalPlayer::move(float xa, float ya, float za) { float newX = x, newZ = z; - if (autoJumpTime <= 0 && autoJumpEnabled) + if (autoJumpTime <= 0 && minecraft->options.autoJump) { // auto-jump when crossing the middle of a tile, and the tile in the front is blocked bool jump = false; diff --git a/src/client/player/LocalPlayer.h b/src/client/player/LocalPlayer.h index fc9de84..03d52d9 100755 --- a/src/client/player/LocalPlayer.h +++ b/src/client/player/LocalPlayer.h @@ -105,6 +105,8 @@ private: bool sprinting; int sprintDoubleTapTimer; bool prevForwardHeld; +public: + void setSprinting(bool sprint) { sprinting = sprint; } }; #endif /*NET_MINECRAFT_CLIENT_PLAYER__LocalPlayer_H__*/ diff --git a/src/client/renderer/Chunk.cpp b/src/client/renderer/Chunk.cpp index ca6cb37..13ac27c 100755 --- a/src/client/renderer/Chunk.cpp +++ b/src/client/renderer/Chunk.cpp @@ -137,12 +137,6 @@ void Chunk::rebuild() Tile* tile = Tile::tiles[tileId]; int renderLayer = tile->getRenderLayer(); -// if (renderLayer == l) -// rendered |= tileRenderer.tesselateInWorld(tile, x, y, z); -// else { -// _layerChunks[_layerChunkCount[renderLayer]++] = cindex; -// } - if (renderLayer > l) { renderNextLayer = true; doRenderLayer[renderLayer] = true; diff --git a/src/client/renderer/GameRenderer.cpp b/src/client/renderer/GameRenderer.cpp index ea60c10..be75b88 100755 --- a/src/client/renderer/GameRenderer.cpp +++ b/src/client/renderer/GameRenderer.cpp @@ -373,7 +373,7 @@ void GameRenderer::renderLevel(float a) { // glDisable2(GL_FOG); setupFog(1); - if (zoom == 1) { + if (zoom == 1 && !mc->options.F1) { TIMER_POP_PUSH("hand"); glClear(GL_DEPTH_BUFFER_BIT); renderItemInHand(a, i); diff --git a/src/client/renderer/ItemInHandRenderer.cpp b/src/client/renderer/ItemInHandRenderer.cpp index e63fd96..f279a9c 100755 --- a/src/client/renderer/ItemInHandRenderer.cpp +++ b/src/client/renderer/ItemInHandRenderer.cpp @@ -354,7 +354,7 @@ void ItemInHandRenderer::render( float a ) glRotatef2(-swing3 * 20, 0, 0, 1); // glRotatef2(-swing2 * 80, 1, 0, 0); - mc->textures->loadAndBindTexture("mob/char.png"); + mc->textures->loadAndBindTexture(player->getTexture()); glTranslatef2(-1.0f, +3.6f, +3.5f); glRotatef2(120, 0, 0, 1); glRotatef2(180 + 20, 1, 0, 0); diff --git a/src/client/renderer/TextureData.h b/src/client/renderer/TextureData.h index ea2a6e8..2c97797 100755 --- a/src/client/renderer/TextureData.h +++ b/src/client/renderer/TextureData.h @@ -16,7 +16,7 @@ typedef struct TextureData { TextureData() : w(0), h(0), - data(NULL), + data(nullptr), numBytes(0), transparent(true), memoryHandledExternally(false), diff --git a/src/client/renderer/Textures.cpp b/src/client/renderer/Textures.cpp index 6be65b9..9fce411 100755 --- a/src/client/renderer/Textures.cpp +++ b/src/client/renderer/Textures.cpp @@ -5,6 +5,7 @@ #include "../Options.h" #include "../../platform/time.h" #include "../../AppPlatform.h" +#include "../../util/StringUtils.h" /*static*/ int Textures::textureChanges = 0; /*static*/ bool Textures::MIPMAP = false; @@ -64,7 +65,8 @@ TextureId Textures::loadTexture( const std::string& resourceName, bool inTexture if (it != idMap.end()) return it->second; - TextureData texdata = platform->loadTexture(resourceName, inTextureFolder); + bool isUrl = Util::startsWith(resourceName, "http://") || Util::startsWith(resourceName, "https://"); + TextureData texdata = platform->loadTexture(resourceName, isUrl ? false : inTextureFolder); if (texdata.data) return assignTexture(resourceName, texdata); else if (texdata.identifier != InvalidId) { diff --git a/src/client/renderer/TileRenderer.cpp b/src/client/renderer/TileRenderer.cpp index 16c6bf7..a98e82b 100755 --- a/src/client/renderer/TileRenderer.cpp +++ b/src/client/renderer/TileRenderer.cpp @@ -1,4 +1,5 @@ #include "TileRenderer.h" +#include "Chunk.h" #include "../Minecraft.h" #include "Tesselator.h" @@ -56,6 +57,7 @@ bool TileRenderer::tesselateBlockInWorld( Tile* tt, int x, int y, int z, float r float c2 = 0.8f; float c3 = 0.6f; + float r11 = c11 * r; float g11 = c11 * g; float b11 = c11 * b; @@ -128,6 +130,7 @@ bool TileRenderer::tesselateBlockInWorld( Tile* tt, int x, int y, int z, float r return changed; } + void TileRenderer::tesselateInWorld( Tile* tile, int x, int y, int z, int fixedTexture ) { this->fixedTexture = fixedTexture; diff --git a/src/client/renderer/entity/EntityRenderDispatcher.cpp b/src/client/renderer/entity/EntityRenderDispatcher.cpp index 6c0e72e..a3148fc 100755 --- a/src/client/renderer/entity/EntityRenderDispatcher.cpp +++ b/src/client/renderer/entity/EntityRenderDispatcher.cpp @@ -51,7 +51,7 @@ EntityRenderDispatcher::EntityRenderDispatcher() assign( ER_SPIDER_RENDERER, new SpiderRenderer()); assign( ER_TNT_RENDERER, new TntRenderer()); assign( ER_ARROW_RENDERER, new ArrowRenderer()); - assign( ER_PLAYER_RENDERER, new PlayerRenderer(new HumanoidModel(), 0)); + assign( ER_PLAYER_RENDERER, new PlayerRenderer(new HumanoidModel(0, 0, 64, 64), 0)); assign( ER_THROWNEGG_RENDERER, new ItemSpriteRenderer(Item::egg->getIcon(0))); assign( ER_SNOWBALL_RENDERER, new ItemSpriteRenderer(Item::snowBall->getIcon(0))); assign( ER_PAINTING_RENDERER, new PaintingRenderer()); diff --git a/src/client/renderer/entity/HumanoidMobRenderer.cpp b/src/client/renderer/entity/HumanoidMobRenderer.cpp index b9efe21..b6f68f0 100755 --- a/src/client/renderer/entity/HumanoidMobRenderer.cpp +++ b/src/client/renderer/entity/HumanoidMobRenderer.cpp @@ -2,6 +2,7 @@ #include "EntityRenderDispatcher.h" #include "../ItemInHandRenderer.h" #include "../TileRenderer.h" +#include "../Tesselator.h" #include "../../model/HumanoidModel.h" #include "../../../world/level/tile/Tile.h" #include "../../../world/entity/player/Player.h" @@ -12,7 +13,9 @@ HumanoidMobRenderer::HumanoidMobRenderer(HumanoidModel* humanoidModel, float shadow) : super(humanoidModel, shadow), - humanoidModel(humanoidModel) + humanoidModel(humanoidModel), + lastCapeXRot(0), + lastCapeZRot(0) { } @@ -68,6 +71,143 @@ void HumanoidMobRenderer::additionalRendering(Mob* mob, float a) { entityRenderDispatcher->itemInHandRenderer->renderItem(mob, item); glPopMatrix2(); } + + // Render player cape if available +{ + Player* player = Player::asPlayer(mob); + if (player) { + const std::string capeTex = player->getCapeTexture(); + if (!capeTex.empty()) { + + bindTexture(capeTex); + + glPushMatrix2(); + + // Attach to player body + humanoidModel->body.translateTo(1 / 16.0f); + + // Convert model units (pixels) to world units + glScalef2(1.0f / 16.0f, 1.0f / 16.0f, 1.0f / 16.0f); + + // Position cape slightly down and behind the shoulders + glTranslatef2(0.0f, 1.0f, 2.0f); + + // Java-like cape physics (interpolated inertia + body motion) + float pt = a; + + double capeX = player->getCapePrevX() + (player->getCapeX() - player->getCapePrevX()) * pt; + double capeY = player->getCapePrevY() + (player->getCapeY() - player->getCapePrevY()) * pt; + double capeZ = player->getCapePrevZ() + (player->getCapeZ() - player->getCapePrevZ()) * pt; + + double px = player->xo + (player->x - player->xo) * pt; + double py = player->yo + (player->y - player->yo) * pt; + double pz = player->zo + (player->z - player->zo) * pt; + + double dx = capeX - px; + double dy = capeY - py; + double dz = capeZ - pz; + + float bodyYaw = player->yBodyRotO + (player->yBodyRot - player->yBodyRotO) * pt; + + float rad = bodyYaw * Mth::PI / 180.0f; + double sinYaw = Mth::sin(rad); + double cosYaw = -Mth::cos(rad); + + float forward = (float)(dx * sinYaw + dz * cosYaw) * 100.0f; + float sideways = (float)(dx * cosYaw - dz * sinYaw) * 100.0f; + if (forward < 0.0f) forward = 0.0f; + + float lift = (float)dy * 10.0f; + if (lift < -6.0f) lift = -6.0f; + if (lift > 32.0f) lift = 32.0f; + + float walk = + Mth::sin((player->walkAnimPos + player->walkAnimSpeed) * 6.0f) * + 32.0f * + player->walkAnimSpeed; + + float capeXRot = 6.0f + forward / 2.0f + lift + walk; + float capeZRot = sideways / 2.0f; + + // Smooth out jitter by lerping from the previous frame + const float smooth = 0.3f; + capeXRot = lastCapeXRot + (capeXRot - lastCapeXRot) * smooth; + capeZRot = lastCapeZRot + (capeZRot - lastCapeZRot) * smooth; + + lastCapeXRot = capeXRot; + lastCapeZRot = capeZRot; + + glRotatef2(capeXRot, 1.0f, 0.0f, 0.0f); + glRotatef2(capeZRot, 0.0f, 0.0f, 1.0f); + + Tesselator& t = Tesselator::instance; + t.begin(); + + // UV coordinates (64x32 skin layout) + const float u0 = 1.0f / 64.0f; + const float u1 = 11.0f / 64.0f; + const float u2 = 12.0f / 64.0f; + const float u3 = 22.0f / 64.0f; + + const float uL0 = 0.0f / 64.0f; + const float uL1 = 1.0f / 64.0f; + + const float uR0 = 11.0f / 64.0f; + const float uR1 = 12.0f / 64.0f; + + const float v0 = 0.0f / 32.0f; + const float v1 = 1.0f / 32.0f; + + const float vTop = 1.0f / 32.0f; + const float vBottom = 17.0f / 32.0f; + + // Cape size (10x16x1 pixels) + const float halfW = 5.0f; + const float height = 16.0f; + const float depth = 1.0f; + + // Front + t.tex(u0, vTop); t.vertex(-halfW, 0.0f, 0.0f); + t.tex(u1, vTop); t.vertex(halfW, 0.0f, 0.0f); + t.tex(u1, vBottom); t.vertex(halfW, height, 0.0f); + t.tex(u0, vBottom); t.vertex(-halfW, height, 0.0f); + + // Back + t.tex(u2, vTop); t.vertex(halfW, 0.0f, depth); + t.tex(u3, vTop); t.vertex(-halfW, 0.0f, depth); + t.tex(u3, vBottom); t.vertex(-halfW, height, depth); + t.tex(u2, vBottom); t.vertex(halfW, height, depth); + + // Left + t.tex(uL0, vTop); t.vertex(-halfW, 0.0f, depth); + t.tex(uL1, vTop); t.vertex(-halfW, 0.0f, 0.0f); + t.tex(uL1, vBottom); t.vertex(-halfW, height, 0.0f); + t.tex(uL0, vBottom); t.vertex(-halfW, height, depth); + + // Right + t.tex(uR0, vTop); t.vertex(halfW, 0.0f, 0.0f); + t.tex(uR1, vTop); t.vertex(halfW, 0.0f, depth); + t.tex(uR1, vBottom); t.vertex(halfW, height, depth); + t.tex(uR0, vBottom); t.vertex(halfW, height, 0.0f); + + // Top + t.tex(u0, v0); t.vertex(-halfW, 0.0f, depth); + t.tex(u1, v0); t.vertex(halfW, 0.0f, depth); + t.tex(u1, v1); t.vertex(halfW, 0.0f, 0.0f); + t.tex(u0, v1); t.vertex(-halfW, 0.0f, 0.0f); + + // Bottom + t.tex(u2, v0); t.vertex(halfW, height, 0.0f); + t.tex(u3, v0); t.vertex(-halfW, height, 0.0f); + t.tex(u3, v1); t.vertex(-halfW, height, depth); + t.tex(u2, v1); t.vertex(halfW, height, depth); + + t.draw(); + + glPopMatrix2(); + } + } +} } void HumanoidMobRenderer::render( Entity* mob_, float x, float y, float z, float rot, float a ) { diff --git a/src/client/renderer/entity/HumanoidMobRenderer.h b/src/client/renderer/entity/HumanoidMobRenderer.h index 59c6b0c..e10c27c 100755 --- a/src/client/renderer/entity/HumanoidMobRenderer.h +++ b/src/client/renderer/entity/HumanoidMobRenderer.h @@ -21,6 +21,10 @@ protected: private: HumanoidModel* humanoidModel; + + // Last rotation values for cape smoothing (reduces jitter) + float lastCapeXRot; + float lastCapeZRot; }; #endif /*NET_MINECRAFT_CLIENT_RENDERER_ENTITY__HumanoidMobRenderer_H__*/ diff --git a/src/client/renderer/entity/PlayerRenderer.cpp b/src/client/renderer/entity/PlayerRenderer.cpp index 590cbc8..b184bcb 100755 --- a/src/client/renderer/entity/PlayerRenderer.cpp +++ b/src/client/renderer/entity/PlayerRenderer.cpp @@ -14,8 +14,8 @@ static const std::string armorFilenames[10] = { PlayerRenderer::PlayerRenderer( HumanoidModel* humanoidModel, float shadow ) : super(humanoidModel, shadow), - armorParts1(new HumanoidModel(1.0f)), - armorParts2(new HumanoidModel(0.5f)) + armorParts1(new HumanoidModel(1.0f, 0, 64, 64)), + armorParts2(new HumanoidModel(0.5f, 0, 64, 64)) { } diff --git a/src/main_glfw.h b/src/main_glfw.h index a9acc36..027393e 100755 --- a/src/main_glfw.h +++ b/src/main_glfw.h @@ -27,6 +27,7 @@ int transformKey(int glfwkey) { case GLFW_KEY_BACKSPACE: return Keyboard::KEY_BACKSPACE; case GLFW_KEY_LEFT_SHIFT: return Keyboard::KEY_LSHIFT; case GLFW_KEY_ENTER: return Keyboard::KEY_RETURN; + case GLFW_KEY_LEFT_CONTROL: return Keyboard::KEY_LEFT_CTRL; default: return glfwkey; } } diff --git a/src/main_win32.h b/src/main_win32.h index 931ae64..a008a07 100755 --- a/src/main_win32.h +++ b/src/main_win32.h @@ -113,6 +113,14 @@ LRESULT WINAPI windowProc ( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) Multitouch::feed(0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); break; } + case WM_MOUSEWHEEL: { + // wheel delta is multiples of WHEEL_DELTA (120); convert to +/-1 + int delta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA; + short x = GET_X_LPARAM(lParam); + short y = GET_Y_LPARAM(lParam); + Mouse::feed(MouseAction::ACTION_WHEEL, 0, x, y, 0, delta); + break; + } default: if (uMsg == WM_NCDESTROY) g_running = false; else { diff --git a/src/platform/HttpClient.cpp b/src/platform/HttpClient.cpp new file mode 100644 index 0000000..3647373 --- /dev/null +++ b/src/platform/HttpClient.cpp @@ -0,0 +1,338 @@ +#include "HttpClient.h" +#include "log.h" + +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#include +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "winhttp.lib") +#else +#include +#include +#include +#include +#include +#endif + +namespace { + +bool startsWith(const std::string& s, const std::string& prefix) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); +} + +bool parseUrl(const std::string& url, std::string& scheme, std::string& host, int& port, std::string& path); + +#if defined(_WIN32) + +bool downloadHttpsWinHttp(const std::string& url, std::vector& outBody) { + outBody.clear(); + + std::string scheme, host, path; + int port = 0; + if (!parseUrl(url, scheme, host, port, path)) + return false; + + // WinHTTP expects the path to include the leading '/'. + HINTERNET hSession = WinHttpOpen(L"MinecraftPE/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (!hSession) + return false; + + HINTERNET hConnect = WinHttpConnect(hSession, std::wstring(host.begin(), host.end()).c_str(), port, 0); + if (!hConnect) { + WinHttpCloseHandle(hSession); + return false; + } + + HINTERNET hRequest = WinHttpOpenRequest( + hConnect, + L"GET", + std::wstring(path.begin(), path.end()).c_str(), + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_SECURE + ); + + if (!hRequest) { + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + DWORD redirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS; + WinHttpSetOption(hRequest, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, sizeof(redirectPolicy)); + + BOOL result = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + if (!result) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + result = WinHttpReceiveResponse(hRequest, NULL); + if (!result) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + DWORD bytesAvailable = 0; + while (WinHttpQueryDataAvailable(hRequest, &bytesAvailable) && bytesAvailable > 0) { + std::vector buffer(bytesAvailable); + DWORD bytesRead = 0; + if (!WinHttpReadData(hRequest, buffer.data(), bytesAvailable, &bytesRead) || bytesRead == 0) + break; + outBody.insert(outBody.end(), buffer.begin(), buffer.begin() + bytesRead); + } + + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + + return !outBody.empty(); +} + +#endif + +std::string toLower(const std::string& s) { + std::string out = s; + std::transform(out.begin(), out.end(), out.begin(), ::tolower); + return out; +} + +bool parseUrl(const std::string& url, std::string& scheme, std::string& host, int& port, std::string& path) { + scheme.clear(); + host.clear(); + path.clear(); + port = 0; + + // Very simple URL parser. + // url format: scheme://host[:port]/path + auto pos = url.find("://"); + if (pos == std::string::npos) return false; + scheme = toLower(url.substr(0, pos)); + size_t start = pos + 3; + + size_t slash = url.find('/', start); + std::string hostPort = (slash == std::string::npos) ? url.substr(start) : url.substr(start, slash - start); + path = (slash == std::string::npos) ? "/" : url.substr(slash); + + size_t colon = hostPort.find(':'); + if (colon != std::string::npos) { + host = hostPort.substr(0, colon); + port = atoi(hostPort.c_str() + colon + 1); + } else { + host = hostPort; + } + + if (scheme == "http") { + if (port == 0) port = 80; + } else if (scheme == "https") { + if (port == 0) port = 443; + } + + return !host.empty() && !scheme.empty(); +} + +bool readAll(int sockfd, std::vector& out) { + const int BUF_SIZE = 4096; + unsigned char buffer[BUF_SIZE]; + + while (true) { + int received = recv(sockfd, (char*)buffer, BUF_SIZE, 0); + if (received <= 0) + break; + out.insert(out.end(), buffer, buffer + received); + } + return true; +} + +bool resolveAndConnect(const std::string& host, int port, int& outSock) { +#if defined(_WIN32) + static bool initialized = false; + if (!initialized) { + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); + initialized = true; + } +#endif + + struct addrinfo hints; + struct addrinfo* result = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + std::ostringstream portStream; + portStream << port; + const std::string portStr = portStream.str(); + if (getaddrinfo(host.c_str(), portStr.c_str(), &hints, &result) != 0) + return false; + + int sock = -1; + for (struct addrinfo* rp = result; rp != NULL; rp = rp->ai_next) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock < 0) continue; + if (connect(sock, rp->ai_addr, (int)rp->ai_addrlen) == 0) + break; // success + +#if defined(_WIN32) + closesocket(sock); +#else + close(sock); +#endif + sock = -1; + } + + freeaddrinfo(result); + + if (sock < 0) + return false; + + outSock = sock; + return true; +} + +std::string getHeaderValue(const std::string& headers, const std::string& key) { + std::string lower = toLower(headers); + std::string lowerKey = toLower(key); + size_t pos = lower.find(lowerKey); + if (pos == std::string::npos) return ""; + size_t colon = lower.find(':', pos + lowerKey.size()); + if (colon == std::string::npos) return ""; + size_t start = colon + 1; + while (start < lower.size() && (lower[start] == ' ' || lower[start] == '\t')) start++; + size_t end = lower.find('\r', start); + if (end == std::string::npos) end = lower.find('\n', start); + if (end == std::string::npos) end = lower.size(); + return headers.substr(start, end - start); +} + +bool extractStatusCode(const std::string& headers, int& outStatus) { + size_t pos = headers.find(" "); + if (pos == std::string::npos) return false; + size_t pos2 = headers.find(" ", pos + 1); + if (pos2 == std::string::npos) return false; + std::string code = headers.substr(pos + 1, pos2 - pos - 1); + outStatus = atoi(code.c_str()); + return true; +} + +} // anonymous namespace + +namespace HttpClient { + +bool download(const std::string& url, std::vector& outBody) { + outBody.clear(); + + std::string currentUrl = url; + for (int redirect = 0; redirect < 3; ++redirect) { + std::string scheme, host, path; + int port = 0; + if (!parseUrl(currentUrl, scheme, host, port, path)) { + LOGW("[HttpClient] parseUrl failed for '%s'\n", currentUrl.c_str()); + return false; + } + + if (scheme == "https") { +#if defined(_WIN32) + LOGI("[HttpClient] using WinHTTP for HTTPS URL %s\n", currentUrl.c_str()); + return downloadHttpsWinHttp(currentUrl, outBody); +#else + LOGW("[HttpClient] HTTPS not supported on this platform: %s\n", currentUrl.c_str()); + return false; +#endif + } + + if (scheme != "http") { + LOGW("[HttpClient] unsupported scheme '%s' for URL '%s'\n", scheme.c_str(), currentUrl.c_str()); + return false; + } + + int sock = -1; + if (!resolveAndConnect(host, port, sock)) { + LOGW("[HttpClient] resolve/connect failed for %s:%d\n", host.c_str(), port); + return false; + } + + std::string request = "GET " + path + " HTTP/1.1\r\n"; + request += "Host: " + host + "\r\n"; + request += "User-Agent: MinecraftPE\r\n"; + request += "Connection: close\r\n"; + request += "\r\n"; + + send(sock, request.c_str(), (int)request.size(), 0); + + std::vector raw; + readAll(sock, raw); + +#if defined(_WIN32) + closesocket(sock); +#else + close(sock); +#endif + + if (raw.empty()) { + LOGW("[HttpClient] no response data from %s\n", currentUrl.c_str()); + return false; + } + + // split headers and body + const std::string delim = "\r\n\r\n"; + auto it = std::search(raw.begin(), raw.end(), delim.begin(), delim.end()); + if (it == raw.end()) + return false; + + size_t headerLen = it - raw.begin(); + std::string headers(reinterpret_cast(raw.data()), headerLen); + size_t bodyStart = headerLen + delim.size(); + + int status = 0; + if (!extractStatusCode(headers, status)) + return false; + + if (status == 301 || status == 302 || status == 307 || status == 308) { + std::string location = getHeaderValue(headers, "Location"); + if (location.empty()) { + LOGW("[HttpClient] redirect without Location header for %s\n", currentUrl.c_str()); + return false; + } + LOGI("[HttpClient] redirect %s -> %s\n", currentUrl.c_str(), location.c_str()); + currentUrl = location; + continue; + } + + if (status != 200) { + std::string bodySnippet; + if (!outBody.empty()) { + size_t len = std::min(outBody.size(), (size_t)1024); + bodySnippet.assign(outBody.begin(), outBody.begin() + len); + } + LOGW("[HttpClient] HTTP status %d for %s\n", status, currentUrl.c_str()); + LOGW("[HttpClient] Headers:\n%s\n", headers.c_str()); + LOGW("[HttpClient] Body (up to 1024 bytes):\n%s\n", bodySnippet.c_str()); + return false; + } + + outBody.assign(raw.begin() + bodyStart, raw.end()); + return true; + } + + return false; +} + +} // namespace HttpClient diff --git a/src/platform/HttpClient.h b/src/platform/HttpClient.h new file mode 100644 index 0000000..a0f828e --- /dev/null +++ b/src/platform/HttpClient.h @@ -0,0 +1,16 @@ +#ifndef HTTPCLIENT_H__ +#define HTTPCLIENT_H__ + +#include +#include + +namespace HttpClient { + +/// Download the given URL into "outBody". +/// Returns true if the download completed successfully (HTTP 200) and the body is in outBody. +/// This function supports plain HTTP only (no TLS). It will follow up to 3 redirects. +bool download(const std::string& url, std::vector& outBody); + +} // namespace HttpClient + +#endif /* HTTPCLIENT_H__ */ diff --git a/src/platform/PngLoader.cpp b/src/platform/PngLoader.cpp new file mode 100644 index 0000000..06569fd --- /dev/null +++ b/src/platform/PngLoader.cpp @@ -0,0 +1,94 @@ +#include "PngLoader.h" + +#include +#include + +struct MemoryReader { + const unsigned char* data; + size_t size; + size_t pos; +}; + +static void pngMemoryRead(png_structp pngPtr, png_bytep outBytes, png_size_t byteCountToRead) { + MemoryReader* reader = (MemoryReader*)png_get_io_ptr(pngPtr); + if (!reader) + return; + + if (reader->pos + byteCountToRead > reader->size) { + png_error(pngPtr, "Read past end of buffer"); + return; + } + + memcpy(outBytes, reader->data + reader->pos, byteCountToRead); + reader->pos += byteCountToRead; +} + +TextureData loadPngFromMemory(const unsigned char* data, size_t size) { + TextureData out; + if (!data || size == 0) return out; + + png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!pngPtr) return out; + + png_infop infoPtr = png_create_info_struct(pngPtr); + if (!infoPtr) { + png_destroy_read_struct(&pngPtr, NULL, NULL); + return out; + } + + if (setjmp(png_jmpbuf(pngPtr))) { + png_destroy_read_struct(&pngPtr, &infoPtr, NULL); + return out; + } + + MemoryReader reader; + reader.data = data; + reader.size = size; + reader.pos = 0; + + png_set_read_fn(pngPtr, &reader, pngMemoryRead); + png_read_info(pngPtr, infoPtr); + + // Convert any color type to 8-bit RGBA + if (png_get_color_type(pngPtr, infoPtr) == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(pngPtr); + if (png_get_color_type(pngPtr, infoPtr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(pngPtr, infoPtr) < 8) + png_set_expand_gray_1_2_4_to_8(pngPtr); + if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(pngPtr); + if (png_get_bit_depth(pngPtr, infoPtr) == 16) + png_set_strip_16(pngPtr); + + // Ensure we always have RGBA (4 bytes per pixel) + // Only add alpha if the image lacks it (e.g., RGB skin files). + png_set_gray_to_rgb(pngPtr); + + // Handle interlaced PNGs properly + int number_passes = png_set_interlace_handling(pngPtr); + + png_read_update_info(pngPtr, infoPtr); + + int colorType = png_get_color_type(pngPtr, infoPtr); + if (colorType == PNG_COLOR_TYPE_RGB) { + png_set_filler(pngPtr, 0xFF, PNG_FILLER_AFTER); + } + + out.w = png_get_image_width(pngPtr, infoPtr); + out.h = png_get_image_height(pngPtr, infoPtr); + + png_bytep* rowPtrs = new png_bytep[out.h]; + out.data = new unsigned char[4 * out.w * out.h]; + out.memoryHandledExternally = false; + + int rowStrideBytes = 4 * out.w; + for (int i = 0; i < out.h; i++) { + rowPtrs[i] = (png_bytep)&out.data[i*rowStrideBytes]; + } + + png_read_image(pngPtr, rowPtrs); + + png_destroy_read_struct(&pngPtr, &infoPtr, NULL); + delete[] rowPtrs; + + return out; +} diff --git a/src/platform/PngLoader.h b/src/platform/PngLoader.h new file mode 100644 index 0000000..cc1d0a4 --- /dev/null +++ b/src/platform/PngLoader.h @@ -0,0 +1,12 @@ +#ifndef PNGLOADER_H__ +#define PNGLOADER_H__ + +#include "../client/renderer/TextureData.h" + +#include + +/// Decode a PNG (from memory) into a TextureData. +/// Returns an empty TextureData on failure. +TextureData loadPngFromMemory(const unsigned char* data, size_t size); + +#endif // PNGLOADER_H__ diff --git a/src/platform/input/Keyboard.h b/src/platform/input/Keyboard.h index eef7ef8..d577416 100755 --- a/src/platform/input/Keyboard.h +++ b/src/platform/input/Keyboard.h @@ -71,9 +71,11 @@ public: static const int KEY_F11 = 122; static const int KEY_F12 = 123; + static const int KEY_ESCAPE = 27; static const int KEY_SPACE = 32; static const int KEY_LSHIFT = 10; + static const int KEY_LEFT_CTRL = 232; static bool isKeyDown(int keyCode) { return _states[keyCode] == KeyboardAction::KEYDOWN; diff --git a/src/raknet/RakPeer.cpp b/src/raknet/RakPeer.cpp index 698374e..a24a2b5 100755 --- a/src/raknet/RakPeer.cpp +++ b/src/raknet/RakPeer.cpp @@ -124,7 +124,7 @@ static const unsigned int MAX_OFFLINE_DATA_LENGTH=400; // I set this because I l #pragma warning(disable:4309) // 'initializing' : truncation of constant value #endif // Make sure highest bit is 0, so isValid in DatagramHeaderFormat is false -static const char OFFLINE_MESSAGE_DATA_ID[16]={0x00,0xFF,0xFF,0x00,0xFE,0xFE,0xFE,0xFE,0xFD,0xFD,0xFD,0xFD,0x12,0x34,0x56,0x78}; +static const unsigned char OFFLINE_MESSAGE_DATA_ID[16]={0x00,0xFF,0xFF,0x00,0xFE,0xFE,0xFE,0xFE,0xFD,0xFD,0xFD,0xFD,0x12,0x34,0x56,0x78}; struct PacketFollowedByData { diff --git a/src/world/entity/Mob.cpp b/src/world/entity/Mob.cpp index 8e7fec6..cc784f0 100755 --- a/src/world/entity/Mob.cpp +++ b/src/world/entity/Mob.cpp @@ -29,6 +29,7 @@ Mob::Mob(Level* level) invulnerableDuration(20), //hasHair(false), textureName("mob/char.png"), + capeTextureName(""), allowAlpha(true), modelName(""), bobStrength(1), @@ -82,6 +83,15 @@ Mob::Mob(Level* level) yRot = (float) (Mth::random() * Mth::PI * 2); this->footSize = 0.5f; + + // Initialize cape inertia positions + xCape = x; + yCape = y; + zCape = z; + + xc = xCape; + yc = yCape; + zc = zCape; } Mob::~Mob() { @@ -110,6 +120,21 @@ std::string Mob::getTexture() return textureName; } +void Mob::setTextureName(const std::string& name) +{ + textureName = name; +} + +std::string Mob::getCapeTexture() +{ + return capeTextureName; +} + +void Mob::setCapeTextureName(const std::string& name) +{ + capeTextureName = name; +} + bool Mob::isPickable() { return !removed; @@ -269,6 +294,10 @@ void Mob::superTick() void Mob::tick() { + xc = xCape; + yc = yCape; + zc = zCape; + super::tick(); if (arrowCount > 0) { @@ -373,6 +402,18 @@ void Mob::tick() while (xRot - xRotO >= 180) xRotO += 360; animStep += walkSpeed; + + // Reduce jitter by using a smaller interpolation factor (more lag, smoother motion) + double dxCape = x - xCape; + double dyCape = y - yCape; + double dzCape = z - zCape; + + const double interp = 0.15; // small value for smoother cape motion + const double interpY = 0.12; // extra smoothing on vertical movement + + xCape += dxCape * interp; + yCape += dyCape * interpY; + zCape += dzCape * interp; } void Mob::setSize( float w, float h ) diff --git a/src/world/entity/Mob.h b/src/world/entity/Mob.h index f9c901f..7e90196 100755 --- a/src/world/entity/Mob.h +++ b/src/world/entity/Mob.h @@ -42,10 +42,16 @@ public: virtual void spawnAnim(); virtual std::string getTexture(); + virtual void setTextureName(const std::string& name); - virtual bool isAlive(); + // Optional player cape texture (non-null on clients when available) + virtual std::string getCapeTexture(); + virtual void setCapeTextureName(const std::string& name); + + virtual bool isAlive(); virtual bool isPickable(); virtual bool isPushable(); + virtual bool isShootable(); MoveControl* getMoveControl(); @@ -212,7 +218,22 @@ protected: float walkingSpeed; float flyingSpeed; + // Cape inertia positions + double xCape, yCape, zCape; + double xc, yc, zc; + +public: + // Cape position accessors (for renderers) + double getCapeX() const { return xCape; } + double getCapeY() const { return yCape; } + double getCapeZ() const { return zCape; } + + double getCapePrevX() const { return xc; } + double getCapePrevY() const { return yc; } + double getCapePrevZ() const { return zc; } + std::string textureName; + std::string capeTextureName; std::string modelName; int deathScore; float oRun, run; diff --git a/src/world/entity/player/Inventory.cpp b/src/world/entity/player/Inventory.cpp index 35a7263..5a46535 100755 --- a/src/world/entity/player/Inventory.cpp +++ b/src/world/entity/player/Inventory.cpp @@ -255,16 +255,16 @@ void Inventory::setupDefault() { } else { #if defined(WIN32) // Survival - addItem(new ItemInstance(Item::ironIngot, 64)); - addItem(new ItemInstance(Item::ironIngot, 34)); - addItem(new ItemInstance(Tile::stonecutterBench)); - addItem(new ItemInstance(Tile::workBench)); - addItem(new ItemInstance(Tile::furnace)); - addItem(new ItemInstance(Tile::wood, 54)); - addItem(new ItemInstance(Item::stick, 14)); - addItem(new ItemInstance(Item::coal, 31)); - addItem(new ItemInstance(Tile::sand, 6)); - addItem(new ItemInstance(Item::dye_powder, 23, DyePowderItem::PURPLE)); + // addItem(new ItemInstance(Item::ironIngot, 64)); + // addItem(new ItemInstance(Item::ironIngot, 34)); + // addItem(new ItemInstance(Tile::stonecutterBench)); + // addItem(new ItemInstance(Tile::workBench)); + // addItem(new ItemInstance(Tile::furnace)); + // addItem(new ItemInstance(Tile::wood, 54)); + // addItem(new ItemInstance(Item::stick, 14)); + // addItem(new ItemInstance(Item::coal, 31)); + // addItem(new ItemInstance(Tile::sand, 6)); + // addItem(new ItemInstance(Item::dye_powder, 23, DyePowderItem::PURPLE)); #endif } #endif diff --git a/src/world/level/LevelSettings.h b/src/world/level/LevelSettings.h index efb8fa9..98cdde3 100755 --- a/src/world/level/LevelSettings.h +++ b/src/world/level/LevelSettings.h @@ -14,13 +14,14 @@ namespace GameType { class LevelSettings { public: - LevelSettings(long seed, int gameType) + LevelSettings(long seed, int gameType, bool allowCheats = false) : seed(seed), - gameType(gameType) + gameType(gameType), + allowCheats(allowCheats) { } static LevelSettings None() { - return LevelSettings(-1,-1); + return LevelSettings(-1,-1,false); } long getSeed() const { @@ -31,6 +32,10 @@ public: return gameType; } + bool getAllowCheats() const { + return allowCheats; + } + // // Those two should actually not be here // @todo: Move out when we add LevelSettings.cpp :p @@ -53,6 +58,7 @@ public: private: const long seed; const int gameType; + const bool allowCheats; }; #endif /*NET_MINECRAFT_WORLD_LEVEL__LevelSettings_H__*/ diff --git a/src/world/level/levelgen/RandomLevelSource.cpp b/src/world/level/levelgen/RandomLevelSource.cpp index 2164f9e..7ef40e3 100755 --- a/src/world/level/levelgen/RandomLevelSource.cpp +++ b/src/world/level/levelgen/RandomLevelSource.cpp @@ -287,53 +287,58 @@ void RandomLevelSource::postProcess(ChunkSource* parent, int xt, int zt) { feature.place(level, &random, x, y, z); } - for (int i = 0; i < 20; i++) { + // Coal: common, wide Y range, moderate vein size + for (int i = 0; i < 16; i++) { int x = xo + random.nextInt(16); int y = random.nextInt(128); int z = zo + random.nextInt(16); - OreFeature feature(Tile::coalOre->id, 16); - feature.place(level, &random, x, y, z); + OreFeature feature(Tile::coalOre->id, 14); + feature.place(level, &random, x, y, z); } - for (int i = 0; i < 20; i++) { + // Iron: common, limited to upper underground + for (int i = 0; i < 14; i++) { int x = xo + random.nextInt(16); int y = random.nextInt(64); int z = zo + random.nextInt(16); - OreFeature feature(Tile::ironOre->id, 8); - feature.place(level, &random, x, y, z); + OreFeature feature(Tile::ironOre->id, 10); + feature.place(level, &random, x, y, z); } + // Gold: rarer and deeper for (int i = 0; i < 2; i++) { int x = xo + random.nextInt(16); int y = random.nextInt(32); int z = zo + random.nextInt(16); - OreFeature feature(Tile::goldOre->id, 8); - feature.place(level, &random, x, y, z); + OreFeature feature(Tile::goldOre->id, 9); + feature.place(level, &random, x, y, z); } - for (int i = 0; i < 8; i++) { + // Redstone: somewhat common at low depths + for (int i = 0; i < 6; i++) { int x = xo + random.nextInt(16); int y = random.nextInt(16); int z = zo + random.nextInt(16); - OreFeature feature(Tile::redStoneOre->id, 7); - feature.place(level, &random, x, y, z); + OreFeature feature(Tile::redStoneOre->id, 8); + feature.place(level, &random, x, y, z); } - for (int i = 0; i < 1; i++) { + // Emerald (diamond-equivalent): still rare but slightly more than vanilla + for (int i = 0; i < 3; i++) { int x = xo + random.nextInt(16); int y = random.nextInt(16); int z = zo + random.nextInt(16); - OreFeature feature(Tile::emeraldOre->id, 7); - feature.place(level, &random, x, y, z); + OreFeature feature(Tile::emeraldOre->id, 6); + feature.place(level, &random, x, y, z); } - // lapis ore + // Lapis: rare and not in very high Y for (int i = 0; i < 1; i++) { int x = xo + random.nextInt(16); int y = random.nextInt(16) + random.nextInt(16); int z = zo + random.nextInt(16); OreFeature feature(Tile::lapisOre->id, 6); - feature.place(level, &random, x, y, z); + feature.place(level, &random, x, y, z); } const float ss = 0.5f; @@ -504,7 +509,8 @@ LevelChunk* RandomLevelSource::getChunk(int xOffs, int zOffs) { prepareHeights(xOffs, zOffs, blocks, 0, temperatures);//biomes, temperatures); buildSurfaces(xOffs, zOffs, blocks, biomes); - //caveFeature.apply(this, level, xOffs, zOffs, blocks, LevelChunk::ChunkBlockCount); + // Carve caves into the chunk + caveFeature.apply(this, level, xOffs, zOffs, blocks, LevelChunk::ChunkBlockCount); levelChunk->recalcHeightmap(); return levelChunk; diff --git a/src/world/level/storage/LevelData.cpp b/src/world/level/storage/LevelData.cpp index c40e5d3..4cafdf9 100755 --- a/src/world/level/storage/LevelData.cpp +++ b/src/world/level/storage/LevelData.cpp @@ -12,8 +12,8 @@ LevelData::LevelData() dimension(Dimension::NORMAL), playerDataVersion(-1), storageVersion(0), - gameType(GameType::Default), - loadedPlayerTag(NULL) + gameType(GameType::Default), spawnMobs(false), + allowCheats(false), loadedPlayerTag(NULL) { //LOGI("ctor 1: %p\n", this); spawnMobs = (gameType == GameType::Survival); @@ -21,8 +21,7 @@ LevelData::LevelData() LevelData::LevelData( const LevelSettings& settings, const std::string& levelName, int generatorVersion /*= -1*/ ) : seed(settings.getSeed()), - gameType(settings.getGameType()), - levelName(levelName), + gameType(settings.getGameType()), allowCheats(settings.getAllowCheats()), levelName(levelName), xSpawn(128), ySpawn(64), zSpawn(128), @@ -62,6 +61,7 @@ LevelData::LevelData( const LevelData& rhs ) playerDataVersion(rhs.playerDataVersion), generatorVersion(rhs.generatorVersion), spawnMobs(rhs.spawnMobs), + allowCheats(rhs.allowCheats), loadedPlayerTag(NULL), playerData(rhs.playerData) { @@ -84,6 +84,7 @@ LevelData& LevelData::operator=( const LevelData& rhs ) time = rhs.time; dimension = rhs.dimension; spawnMobs = rhs.spawnMobs; + allowCheats = rhs.allowCheats; playerData = rhs.playerData; playerDataVersion = rhs.playerDataVersion; generatorVersion = rhs.generatorVersion; @@ -161,6 +162,7 @@ void LevelData::setTagData( CompoundTag* tag, CompoundTag* playerTag ) if (!tag) return; tag->putLong("RandomSeed", seed); tag->putInt("GameType", gameType); + tag->putBoolean("AllowCommands", allowCheats); tag->putInt("SpawnX", xSpawn); tag->putInt("SpawnY", ySpawn); tag->putInt("SpawnZ", zSpawn); @@ -181,6 +183,7 @@ void LevelData::getTagData( const CompoundTag* tag ) if (!tag) return; seed = (long)tag->getLong("RandomSeed"); gameType = tag->getInt("GameType"); + allowCheats = tag->getBoolean("AllowCommands"); xSpawn = tag->getInt("SpawnX"); ySpawn = tag->getInt("SpawnY"); zSpawn = tag->getInt("SpawnZ"); @@ -362,3 +365,13 @@ void LevelData::setSpawnMobs( bool doSpawn ) { spawnMobs = doSpawn; } + +bool LevelData::getAllowCheats() const +{ + return allowCheats; +} + +void LevelData::setAllowCheats( bool allow ) +{ + allowCheats = allow; +} diff --git a/src/world/level/storage/LevelData.h b/src/world/level/storage/LevelData.h index 3bf69db..c3f0a79 100755 --- a/src/world/level/storage/LevelData.h +++ b/src/world/level/storage/LevelData.h @@ -72,6 +72,9 @@ public: bool getSpawnMobs() const; void setSpawnMobs(bool doSpawn); + bool getAllowCheats() const; + void setAllowCheats(bool allow); + public: PlayerData playerData; int playerDataVersion; @@ -89,6 +92,7 @@ private: int gameType; int storageVersion; bool spawnMobs; + bool allowCheats; //@note: This version is never written or loaded to disk. The only purpose // is to use it in the level generator on server and clients. int generatorVersion; diff --git a/src/world/level/tile/DoorTile.cpp b/src/world/level/tile/DoorTile.cpp index 6b6e962..7aea82f 100755 --- a/src/world/level/tile/DoorTile.cpp +++ b/src/world/level/tile/DoorTile.cpp @@ -165,7 +165,8 @@ void DoorTile::neighborChanged(Level* level, int x, int y, int z, int type) { } if (spawn) { if (!level->isClientSide) { - spawnResources(level, x, y, z, data, 0); + // use default chance (1.0) so the drop always occurs + spawnResources(level, x, y, z, data); } } else { bool signal = level->hasNeighborSignal(x, y, z) || level->hasNeighborSignal(x, y + 1, z); @@ -174,13 +175,12 @@ void DoorTile::neighborChanged(Level* level, int x, int y, int z, int type) { } } } else { + // upper half: removal should not drop a second door. the + // lower half neighbour handler takes care of spawning the item + // whenever the door is broken from either end. if (level->getTile(x, y - 1, z) != id) { level->setTile(x, y, z, 0); - if(material == Material::metal) { - popResource(level, x, y, z, ItemInstance(Item::door_iron)); - } else { - popResource(level, x, y, z, ItemInstance(Item::door_wood)); - } + // no resource spawn here } if (type > 0 && type != id) { neighborChanged(level, x, y - 1, z, type); @@ -189,7 +189,11 @@ void DoorTile::neighborChanged(Level* level, int x, int y, int z, int type) { } int DoorTile::getResource(int data, Random* random) { - if ((data & 8) != 0) return 0; + // only the lower half should return a resource ID; the upper half + // itself never drops anything and playerDestroy suppresses spawning + // from the top. This prevents duplicate drops if the bottom half is + // mined. + if ((data & UPPER_BIT) != 0) return 0; if (material == Material::metal) return Item::door_iron->id; return Item::door_wood->id; } @@ -199,6 +203,14 @@ HitResult DoorTile::clip(Level* level, int xt, int yt, int zt, const Vec3& a, co return super::clip(level, xt, yt, zt, a, b); } +// override to prevent double-dropping when top half is directly mined +void DoorTile::playerDestroy(Level* level, Player* player, int x, int y, int z, int data) { + if ((data & UPPER_BIT) == 0) { + // only let the lower half handle the actual spawning + super::playerDestroy(level, player, x, y, z, data); + } +} + int DoorTile::getDir(LevelSource* level, int x, int y, int z) { return getCompositeData(level, x, y, z) & C_DIR_MASK; } diff --git a/src/world/level/tile/DoorTile.h b/src/world/level/tile/DoorTile.h index 98020e2..6124da1 100755 --- a/src/world/level/tile/DoorTile.h +++ b/src/world/level/tile/DoorTile.h @@ -49,6 +49,9 @@ public: int getResource(int data, Random* random); + // override to avoid duplicate drops when upper half is mined directly + void playerDestroy(Level* level, Player* player, int x, int y, int z, int data) override; + HitResult clip(Level* level, int xt, int yt, int zt, const Vec3& a, const Vec3& b); int getDir(LevelSource* level, int x, int y, int z);