Merge remote-tracking branch 'refs/remotes/origin/main'

This commit is contained in:
Kolyah35
2026-03-14 14:51:25 +03:00
82 changed files with 1998 additions and 221 deletions

View File

@@ -5,11 +5,11 @@ include(cmake/CPM.cmake)
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_POLICY_VERSION_MINIMUM 3.10)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 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") set(CMAKE_CXX_FLAGS "-Wno-c++11-narrowing -Wno-narrowing -Wno-invalid-source-encoding -Wno-reserved-user-defined-literal")
endif() endif()
find_package(Threads REQUIRED)
CPMAddPackage("gh:madler/zlib@1.3.2") CPMAddPackage("gh:madler/zlib@1.3.2")
CPMAddPackage( CPMAddPackage(
NAME "libpng" NAME "libpng"
@@ -44,8 +44,191 @@ CPMAddPackage(
"GLFW_BUILD_DOCS OFF" "GLFW_BUILD_DOCS OFF"
"BUILD_SHARED_LIBS ON" "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/*.cpp"
"src/client/gamemode/*.cpp" "src/client/gamemode/*.cpp"
@@ -129,25 +312,29 @@ endif()
if(PLATFORM STREQUAL "PLATFORM_WIN32") if(PLATFORM STREQUAL "PLATFORM_WIN32")
list(APPEND SOURCES "src/AppPlatform_win32.cpp") list(APPEND CLIENT_SOURCES "src/AppPlatform_win32.cpp")
endif() endif()
if(PLATFORM STREQUAL "PLATFORM_GLFW") if(PLATFORM STREQUAL "PLATFORM_GLFW")
list(APPEND SOURCES "src/AppPlatform_glfw.cpp") list(APPEND CLIENT_SOURCES "src/AppPlatform_glfw.cpp")
endif() endif()
# Explicitly list files added after the initial glob so they are always included # 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/ConsoleScreen.cpp"
"src/client/gui/screens/UsernameScreen.cpp" "src/client/gui/screens/UsernameScreen.cpp"
"src/client/gui/screens/CreditsScreen.cpp" "src/client/gui/screens/CreditsScreen.cpp"
) )
add_executable(${PROJECT_NAME} add_executable(${PROJECT_NAME}
${SOURCES} ${CLIENT_SOURCES}
"glad/src/glad.c" "glad/src/glad.c"
) )
#add_executable("${PROJECT_NAME}-server"
# ${SERVER_SOURCES}
#)
if(WIN32) if(WIN32)
set(EXTRA_LIBS "ws2_32") set(EXTRA_LIBS "ws2_32")
target_compile_definitions(${PROJECT_NAME} PUBLIC "_CRT_SECURE_NO_WARNINGS") 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") target_compile_definitions(${PROJECT_NAME} PUBLIC "PLATFORM_DESKTOP")
endif() endif()
target_include_directories(${PROJECT_NAME} PUBLIC target_include_directories(${PROJECT_NAME} PUBLIC
"${CMAKE_SOURCE_DIR}/glad/include/" "${CMAKE_SOURCE_DIR}/glad/include/"
"${CMAKE_SOURCE_DIR}/src" "${CMAKE_SOURCE_DIR}/src"
@@ -165,6 +353,16 @@ target_include_directories(${PROJECT_NAME} PUBLIC
"lib/include" "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_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}) target_link_libraries(${PROJECT_NAME} zlib png_shared alsoft.common OpenAL::OpenAL glfw ${EXTRA_LIBS})

View File

@@ -1,6 +1,6 @@
# MinecraftPE # MinecraftPE
> [!Important] > [!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. 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 . cmake .. -B .
make -j4 make -j4
``` ```
or
```
mkdir build && cd build
cmake --build . --config Release -j 10
```
## Visual Studio ## Visual Studio
1. Open the repository folder in **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. 4. Press **Run** (or F5) to build and launch the game.
## Android ## 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) # Full build (NDK + Java + APK + install)
.\build.ps1 .\build.ps1
# Skip NDK recompile (Java/assets changed only) # Skip C++ compilation (Java/assets changed only)
.\build.ps1 -NoJava
# Skip Java recompile (C++ changed only)
.\build.ps1 -NoCpp .\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 .\build.ps1 -NoBuild
``` ```

View File

@@ -220,14 +220,15 @@ if (-not $NoCpp -and -not $NoBuild) {
Write-Step "NDK build (arm64-v8a)" Write-Step "NDK build (arm64-v8a)"
# NDK r14b on Windows hits the 32K CreateProcess limit with long paths. # 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. # Work around it by building through a short junction C:\m -> repo root.
$junctionBase = "C:\m" # Use forward slashes in the build paths to prevent the NDK toolchain from stripping backslashes.
if (-not (Test-Path $junctionBase)) { $junctionBase = "C:/m"
& cmd.exe /c "mklink /J `"$junctionBase`" `"$repo`"" | Out-Null if (-not (Test-Path "C:\m")) {
& cmd.exe /c "mklink /J `"C:\m`" `"$repo`"" | Out-Null
} }
Push-Location "$junctionBase\project\android\jni" Push-Location "$junctionBase/project/android/jni"
$env:NDK_MODULE_PATH = "$junctionBase\project\lib_projects" $env:NDK_MODULE_PATH = "$junctionBase/project/lib_projects"
# run ndk-build and capture everything; let user see full output for debugging # 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 # dump entire output for diagnosis
Write-Host "---- NDK BUILD OUTPUT BEGIN ----" Write-Host "---- NDK BUILD OUTPUT BEGIN ----"
$ndkOutput | ForEach-Object { Write-Host $_ } $ndkOutput | ForEach-Object { Write-Host $_ }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -149,6 +149,10 @@ options.group.general=General
options.group.game=Game options.group.game=Game
options.group.controls=Controls options.group.controls=Controls
options.group.graphics=Graphics 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.thirdperson=Third Person
options.servervisible=Server Visible options.servervisible=Server Visible
options.sensitivity=Sensitivity options.sensitivity=Sensitivity

View File

@@ -1,4 +1,6 @@
LOCAL_PATH := $(call my-dir) LOCAL_PATH := $(call my-dir)
# Convert Windows backslashes to forward slashes so NDK toolchain doesnt treat them as escapes.
LOCAL_PATH := $(subst \,/,$(LOCAL_PATH))
include $(CLEAR_VARS) include $(CLEAR_VARS)
@@ -12,6 +14,7 @@ LOCAL_SRC_FILES := ../../../src/main.cpp \
../../../src/platform/input/Multitouch.cpp \ ../../../src/platform/input/Multitouch.cpp \
../../../src/platform/time.cpp \ ../../../src/platform/time.cpp \
../../../src/platform/CThread.cpp \ ../../../src/platform/CThread.cpp \
../../../src/platform/HttpClient.cpp \
../../../src/NinecraftApp.cpp \ ../../../src/NinecraftApp.cpp \
../../../src/Performance.cpp \ ../../../src/Performance.cpp \
../../../src/SharedConstants.cpp \ ../../../src/SharedConstants.cpp \
@@ -68,6 +71,7 @@ LOCAL_SRC_FILES := ../../../src/main.cpp \
../../../src/client/gui/screens/SelectWorldScreen.cpp \ ../../../src/client/gui/screens/SelectWorldScreen.cpp \
../../../src/client/gui/screens/StartMenuScreen.cpp \ ../../../src/client/gui/screens/StartMenuScreen.cpp \
../../../src/client/gui/screens/TextEditScreen.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/TouchIngameBlockSelectionScreen.cpp \
../../../src/client/gui/screens/touch/TouchJoinGameScreen.cpp \ ../../../src/client/gui/screens/touch/TouchJoinGameScreen.cpp \
../../../src/client/gui/screens/touch/TouchSelectWorldScreen.cpp \ ../../../src/client/gui/screens/touch/TouchSelectWorldScreen.cpp \

View File

@@ -2,4 +2,5 @@ APP_PLATFORM := android-21
APP_STL := gnustl_static APP_STL := gnustl_static
APP_OPTIM := release APP_OPTIM := release
APP_ABI := arm64-v8a APP_ABI := arm64-v8a
APP_SHORT_COMMANDS := true
#APP_ABI := armeabi-v7a x86 #APP_ABI := armeabi-v7a x86

View File

@@ -468,17 +468,16 @@ public class MainActivity extends NativeActivity {
_userInputStatus = 1; _userInputStatus = 1;
InputMethodManager inputManager = (InputMethodManager)getSystemService("input_method"); InputMethodManager inputManager = (InputMethodManager)getSystemService("input_method");
boolean result = inputManager.showSoftInput(this.getCurrentFocus(), InputMethodManager.SHOW_IMPLICIT); View focused = this.getCurrentFocus();
} if (focused != null) {
boolean result = inputManager.showSoftInput(focused, InputMethodManager.SHOW_IMPLICIT);
protected void onStart() { } else {
//System.out.println("onStart"); // fallback: try to show using decor view token
super.onStart(); View decor = getWindow().getDecorView();
} if (decor != null) {
inputManager.showSoftInput(decor, InputMethodManager.SHOW_IMPLICIT);
protected void onResume() { }
//System.out.println("onResume"); }
super.onResume();
} }
protected void onPause() { protected void onPause() {

View File

@@ -583,6 +583,15 @@ public class MainActivity extends Activity {
return kcm.get(keyCode, metaState); 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) { public String getPlatformStringVar(int id) {
if (id == 0) return android.os.Build.MODEL; if (id == 0) return android.os.Build.MODEL;
return null; return null;

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.7) cmake_minimum_required(VERSION 3.5.0)
find_package (Threads) find_package (Threads)
include_directories("${PROJECT_SOURCE_DIR}/../lib_projects/raknet/jni/RaknetSources") include_directories("${PROJECT_SOURCE_DIR}/../lib_projects/raknet/jni/RaknetSources")
add_subdirectory("${PROJECT_SOURCE_DIR}/../lib_projects/raknet/jni" "${CMAKE_CURRENT_BINARY_DIR}/raknet") 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/TileEntity.cpp
../../src/world/level/tile/entity/FurnaceTileEntity.cpp ../../src/world/level/tile/entity/FurnaceTileEntity.cpp
../../src/world/phys/HitResult.cpp) ../../src/world/phys/HitResult.cpp)
message(${CMAKE_LIBRARY_ARCHITECTURE})
add_executable(mcpe_server ${CompileFiles}) add_executable(mcpe_server ${CompileFiles})
target_link_libraries(mcpe_server raknet ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(mcpe_server raknet ${CMAKE_THREAD_LIBS_INIT})
target_include_directories(mcpe_server PUBLIC
"../../src/"
)

View File

@@ -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 #pragma warning(disable:4309) // 'initializing' : truncation of constant value
#endif #endif
// Make sure highest bit is 0, so isValid in DatagramHeaderFormat is false // 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 struct PacketFollowedByData
{ {

View File

@@ -71,6 +71,7 @@ public:
virtual void saveScreenshot(const std::string& filename, int glWidth, int glHeight) {} virtual void saveScreenshot(const std::string& filename, int glWidth, int glHeight) {}
virtual TextureData loadTexture(const std::string& filename_, bool textureFolder) { return TextureData(); } 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) {} virtual void playSound(const std::string& fn, float volume, float pitch) {}

View File

@@ -151,6 +151,8 @@ public:
_methodGetPixelsPerMillimeter = env->GetMethodID( _activityClass, "getPixelsPerMillimeter", "()F"); _methodGetPixelsPerMillimeter = env->GetMethodID( _activityClass, "getPixelsPerMillimeter", "()F");
_methodGetPlatformStringVar = env->GetMethodID( _activityClass, "getPlatformStringVar", "(I)Ljava/lang/String;"); _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")); _classWindow = (jclass)env->NewGlobalRef(env->FindClass("android/view/Window"));
_classContext = (jclass)env->NewGlobalRef(env->FindClass("android/content/Context")); _classContext = (jclass)env->NewGlobalRef(env->FindClass("android/content/Context"));
@@ -465,7 +467,15 @@ public:
env->ReleaseStringUTFChars(stringVar, str); env->ReleaseStringUTFChars(stringVar, str);
return out; 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() { virtual void finish() {
if (!_isInited) return; if (!_isInited) return;
if (!_methodFinish) return; if (!_methodFinish) return;
@@ -616,6 +626,7 @@ private:
jmethodID _methodIsNetworkEnabled; jmethodID _methodIsNetworkEnabled;
jmethodID _methodGetPlatformStringVar; jmethodID _methodGetPlatformStringVar;
jmethodID _methodOpenURL; // new JNI method for launching browser
jclass _classWindow; jclass _classWindow;
jclass _classContext; jclass _classContext;

View File

@@ -3,6 +3,8 @@
#include "AppPlatform.h" #include "AppPlatform.h"
#include "platform/log.h" #include "platform/log.h"
#include "platform/HttpClient.h"
#include "platform/PngLoader.h"
#include "client/renderer/gles.h" #include "client/renderer/gles.h"
#include "world/level/storage/FolderMethods.h" #include "world/level/storage/FolderMethods.h"
#include <png.h> #include <png.h>
@@ -11,6 +13,7 @@
#include <sstream> #include <sstream>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <ctime> #include <ctime>
#include "util/StringUtils.h"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
@@ -55,10 +58,19 @@ public:
TextureData loadTexture(const std::string& filename_, bool textureFolder) 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<unsigned char> body;
if (HttpClient::download(filename_, body) && !body.empty()) {
return loadTextureFromMemory(body.data(), body.size());
}
return TextureData();
}
TextureData out; TextureData out;
std::string filename = textureFolder? "data/images/" + filename_ std::string filename = textureFolder? "data/images/" + filename_
: filename_; : filename_;
std::ifstream source(filename.c_str(), std::ios::binary); std::ifstream source(filename.c_str(), std::ios::binary);
if (source) { 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; time_t tm = s;
char mbstr[100]; char mbstr[100];

View File

@@ -3,8 +3,11 @@
#include "AppPlatform.h" #include "AppPlatform.h"
#include "platform/log.h" #include "platform/log.h"
#include "platform/HttpClient.h"
#include "platform/PngLoader.h"
#include "client/renderer/gles.h" #include "client/renderer/gles.h"
#include "world/level/storage/FolderMethods.h" #include "world/level/storage/FolderMethods.h"
#include "util/StringUtils.h"
#include <png.h> #include <png.h>
#include <cmath> #include <cmath>
#include <fstream> #include <fstream>
@@ -50,10 +53,19 @@ public:
TextureData loadTexture(const std::string& filename_, bool textureFolder) 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<unsigned char> body;
if (HttpClient::download(filename_, body) && !body.empty()) {
return loadTextureFromMemory(body.data(), body.size());
}
return TextureData();
}
TextureData out; TextureData out;
std::string filename = textureFolder? "data/images/" + filename_ std::string filename = textureFolder? "data/images/" + filename_
: filename_; : filename_;
std::ifstream source(filename.c_str(), std::ios::binary); std::ifstream source(filename.c_str(), std::ios::binary);
if (source) { 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; time_t tm = s;
char mbstr[100]; char mbstr[100];

View File

@@ -1,5 +1,10 @@
#include "Minecraft.h" #include "Minecraft.h"
#include "client/player/input/IBuildInput.h" #include "client/player/input/IBuildInput.h"
#include "platform/input/Keyboard.h"
#include "world/item/Item.h"
#include "world/item/ItemInstance.h"
#include <string>
#include <cstdlib>
#if defined(APPLE_DEMO_PROMOTION) #if defined(APPLE_DEMO_PROMOTION)
#define NO_NETWORK #define NO_NETWORK
@@ -8,7 +13,6 @@
#if defined(RPI) #if defined(RPI)
#define CREATORMODE #define CREATORMODE
#endif #endif
#include "../network/RakNetInstance.h" #include "../network/RakNetInstance.h"
#include "../network/ClientSideNetworkHandler.h" #include "../network/ClientSideNetworkHandler.h"
#include "../network/ServerSideNetworkHandler.h" #include "../network/ServerSideNetworkHandler.h"
@@ -56,6 +60,7 @@
#endif #endif
#include "renderer/Chunk.h"
#include "player/input/MouseTurnInput.h" #include "player/input/MouseTurnInput.h"
#include "../world/entity/MobFactory.h" #include "../world/entity/MobFactory.h"
#include "../world/level/MobSpawner.h" #include "../world/level/MobSpawner.h"
@@ -112,7 +117,7 @@ static void checkGlError(const char* tag) {
} }
#endif /*GLDEBUG*/ #endif /*GLDEBUG*/
} }
#include <fstream>
/*static*/ /*static*/
const char* Minecraft::progressMessages[] = { const char* Minecraft::progressMessages[] = {
"Locating server", "Locating server",
@@ -450,20 +455,21 @@ void Minecraft::update() {
// } // }
//} //}
if (pause && level != NULL) { // If we're paused (local world / invisible server), freeze gameplay and
float lastA = timer.a; // networking and only keep UI responsive.
timer.advanceTime(); bool freezeGame = pause;
timer.a = lastA;
} else { if (!freezeGame) {
timer.advanceTime(); timer.advanceTime();
} }
if (raknetInstance) { if (raknetInstance && !freezeGame) {
raknetInstance->runEvents(netCallback); raknetInstance->runEvents(netCallback);
} }
TIMER_PUSH("tick"); 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) for (int i = 0; i < toTick; ++i, ++ticks)
tick(i, toTick-1); tick(i, toTick-1);
@@ -588,7 +594,9 @@ void Minecraft::tick(int nTick, int maxTick) {
#endif #endif
} }
TIMER_POP_PUSH("particles"); TIMER_POP_PUSH("particles");
particleEngine->tick(); if (!pause) {
particleEngine->tick();
}
if (screen) { if (screen) {
screenMutex = true; screenMutex = true;
screen->tick(); screen->tick();
@@ -722,16 +730,29 @@ void Minecraft::tickInput() {
} }
#endif #endif
#if defined(PLATFORM_DESKTOP) #if defined(PLATFORM_DESKTOP)
if (key == Keyboard::KEY_LEFT_CTRL) {
player->setSprinting(true);
}
if (key == Keyboard::KEY_E) { if (key == Keyboard::KEY_E) {
screenChooser.setScreen(SCREEN_BLOCKSELECTION); screenChooser.setScreen(SCREEN_BLOCKSELECTION);
} }
if (!screen && key == Keyboard::KEY_T && level) { if (!screen && key == Keyboard::KEY_T && level) {
setScreen(new ConsoleScreen()); setScreen(new ConsoleScreen());
} }
if (!screen && key == Keyboard::KEY_O || key == 250) { if (!screen && key == Keyboard::KEY_O || key == 250) {
releaseMouse(); 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) { if (key == Keyboard::KEY_F5) {
options.toggle(OPTIONS_THIRD_PERSON_VIEW); options.toggle(OPTIONS_THIRD_PERSON_VIEW);
/* /*
@@ -740,12 +761,6 @@ void Minecraft::tickInput() {
printf("%d\t%f\n", i, noise.grad2(i, 3, 8)); 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) if (key == Keyboard::KEY_L)
@@ -819,9 +834,6 @@ void Minecraft::tickInput() {
if (player->inventory->getItem(i)) if (player->inventory->getItem(i))
player->inventory->dropSlot(i, false); player->inventory->dropSlot(i, false);
} }
if (key == Keyboard::KEY_F3) {
options.renderDebug = !options.renderDebug;
}
if (key == Keyboard::KEY_M) { if (key == Keyboard::KEY_M) {
options.difficulty = (options.difficulty == Difficulty::PEACEFUL)? options.difficulty = (options.difficulty == Difficulty::PEACEFUL)?
Difficulty::NORMAL : Difficulty::PEACEFUL; Difficulty::NORMAL : Difficulty::PEACEFUL;
@@ -1017,6 +1029,17 @@ bool Minecraft::isOnline()
} }
void Minecraft::pauseGame(bool isBackPaused) { 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 #ifndef STANDALONE_SERVER
if (screen != NULL) return; if (screen != NULL) return;
screenChooser.setScreen(isBackPaused? SCREEN_PAUSEPREV : SCREEN_PAUSE); screenChooser.setScreen(isBackPaused? SCREEN_PAUSEPREV : SCREEN_PAUSE);
@@ -1069,6 +1092,8 @@ void Minecraft::setScreen( Screen* screen )
//noRender = false; //noRender = false;
} else { } else {
// Closing a screen and returning to the game should unpause.
pause = false;
grabMouse(); grabMouse();
} }
#endif #endif
@@ -1288,6 +1313,31 @@ bool Minecraft::joinMultiplayer( const PingedCompatibleServer& server )
return false; 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) { void Minecraft::hostMultiplayer(int port) {
// Tear down last instance // Tear down last instance
raknetInstance->disconnect(); raknetInstance->disconnect();
@@ -1455,6 +1505,12 @@ LevelStorageSource* Minecraft::getLevelSource()
return storageSource; return storageSource;
} }
// int Minecraft::getLicenseId() {
// if (!LicenseCodes::isReady(_licenseId))
// _licenseId = platform()->checkLicense();
// return _licenseId;
// }
void Minecraft::audioEngineOn() { void Minecraft::audioEngineOn() {
#ifndef STANDALONE_SERVER #ifndef STANDALONE_SERVER
soundEngine->enable(true); soundEngine->enable(true);

View File

@@ -80,6 +80,7 @@ public:
void locateMultiplayer(); void locateMultiplayer();
void cancelLocateMultiplayer(); void cancelLocateMultiplayer();
bool joinMultiplayer(const PingedCompatibleServer& server); bool joinMultiplayer(const PingedCompatibleServer& server);
bool joinMultiplayerFromString(const std::string& server);
void hostMultiplayer(int port=19132); void hostMultiplayer(int port=19132);
Player* respawnPlayer(int playerId); Player* respawnPlayer(int playerId);
void respawnPlayer(); void respawnPlayer();

View File

@@ -6,14 +6,19 @@ const char* OptionStrings::Multiplayer_ServerVisible = "mp_server_visible_defa
const char* OptionStrings::Graphics_Fancy = "gfx_fancygraphics"; const char* OptionStrings::Graphics_Fancy = "gfx_fancygraphics";
const char* OptionStrings::Graphics_LowQuality = "gfx_lowquality"; const char* OptionStrings::Graphics_LowQuality = "gfx_lowquality";
const char* OptionStrings::Graphics_Vsync = "gfx_vsync"; 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_Sensitivity = "ctrl_sensitivity";
const char* OptionStrings::Controls_InvertMouse = "ctrl_invertmouse"; const char* OptionStrings::Controls_InvertMouse = "ctrl_invertmouse";
const char* OptionStrings::Controls_UseTouchScreen = "ctrl_usetouchscreen"; const char* OptionStrings::Controls_UseTouchScreen = "ctrl_usetouchscreen";
const char* OptionStrings::Controls_UseTouchJoypad = "ctrl_usetouchjoypad"; const char* OptionStrings::Controls_UseTouchJoypad = "ctrl_usetouchjoypad";
const char* OptionStrings::Controls_IsLefthanded = "ctrl_islefthanded"; 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_FeedbackVibration = "feedback_vibration";
const char* OptionStrings::Controls_AutoJump = "ctrl_autojump";
const char* OptionStrings::Game_DifficultyLevel = "game_difficulty"; const char* OptionStrings::Game_DifficultyLevel = "game_difficulty";

View File

@@ -10,14 +10,27 @@ public:
static const char* Graphics_LowQuality; static const char* Graphics_LowQuality;
static const char* Graphics_GUIScale; static const char* Graphics_GUIScale;
static const char* Graphics_Vsync; 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_Sensitivity;
static const char* Controls_InvertMouse; static const char* Controls_InvertMouse;
static const char* Controls_UseTouchScreen; static const char* Controls_UseTouchScreen;
static const char* Controls_UseTouchJoypad; static const char* Controls_UseTouchJoypad;
static const char* Controls_IsLefthanded; static const char* Controls_IsLefthanded;
static const char* Controls_FeedbackVibration; 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* Game_DifficultyLevel;
static const char* Tweaks_Sprint;
static const char* Tweaks_BarOnTop;
}; };
#endif /*NET_MINECRAFT_CLIENT__OptionsStrings_H__*/ #endif /*NET_MINECRAFT_CLIENT__OptionsStrings_H__*/

View File

@@ -13,10 +13,10 @@
#include "../../network/packet/RemoveBlockPacket.h" #include "../../network/packet/RemoveBlockPacket.h"
#include "../../world/entity/player/Abilities.h" #include "../../world/entity/player/Abilities.h"
static const int DestructionTickDelay = 10; static const int DestructionTickDelay = 5;
CreativeMode::CreativeMode(Minecraft* minecraft) 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) { void CreativeMode::creativeDestroyBlock(int x, int y, int z, int face) {
//if (! minecraft->level->extinguishFire(x, y, z, face);
minecraft->level->extinguishFire(x, y, z, face) destroyBlock(x, y, z, face);
//{
;
destroyBlock(x, y, z, face);
//}
} }
void CreativeMode::continueDestroyBlock(int x, int y, int z, int 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() { void CreativeMode::stopDestroyBlock() {
destroyDelay = 0;
} }
void CreativeMode::initAbilities( Abilities& abilities ) { void CreativeMode::initAbilities( Abilities& abilities ) {

View File

@@ -9,7 +9,7 @@
#include "../../network/packet/RemoveBlockPacket.h" #include "../../network/packet/RemoveBlockPacket.h"
#include "../../world/entity/player/Abilities.h" #include "../../world/entity/player/Abilities.h"
static const int DestructionTickDelay = 10; static const int DestructionTickDelay = 5;
class Creator: public ICreator { class Creator: public ICreator {
//virtual void getEvents(); //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) { void CreatorMode::CreatorDestroyBlock(int x, int y, int z, int face) {
//if (! minecraft->level->extinguishFire(x, y, z, face);
minecraft->level->extinguishFire(x, y, z, face) destroyBlock(x, y, z, face);
//{
;
destroyBlock(x, y, z, face);
//}
} }
void CreatorMode::continueDestroyBlock(int x, int y, int z, int 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() { void CreatorMode::stopDestroyBlock() {
destroyDelay = 0;
} }
void CreatorMode::initAbilities( Abilities& abilities ) { void CreatorMode::initAbilities( Abilities& abilities ) {

View File

@@ -1,6 +1,7 @@
#include "Gui.h" #include "Gui.h"
#include "Font.h" #include "Font.h"
#include "client/Options.h" #include "client/Options.h"
#include "platform/input/Keyboard.h"
#include "screens/IngameBlockSelectionScreen.h" #include "screens/IngameBlockSelectionScreen.h"
#include "../Minecraft.h" #include "../Minecraft.h"
#include "../player/LocalPlayer.h" #include "../player/LocalPlayer.h"
@@ -87,14 +88,16 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) {
// F: 3 // F: 3
int ySlot = screenHeight - 16 - 3; int ySlot = screenHeight - 16 - 3;
if (minecraft->gameMode->canHurtPlayer()) { if (!minecraft->options.getBooleanValue(OPTIONS_HIDEGUI)) {
minecraft->textures->loadAndBindTexture("gui/icons.png"); if (minecraft->gameMode->canHurtPlayer()) {
Tesselator& t = Tesselator::instance; minecraft->textures->loadAndBindTexture("gui/icons.png");
t.beginOverride(); Tesselator& t = Tesselator::instance;
t.colorABGR(0xffffffff); t.beginOverride();
renderHearts(); t.colorABGR(0xffffffff);
renderBubbles(); renderHearts();
t.endOverrideAndDraw(); renderBubbles();
t.endOverrideAndDraw();
}
} }
if(minecraft->player->getSleepTimer() > 0) { 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_ALPHA_TEST);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
} }
if (!minecraft->options.getBooleanValue(OPTIONS_HIDEGUI)) {
renderToolBar(a, ySlot, screenWidth); renderToolBar(a, ySlot, screenWidth);
glEnable(GL_BLEND); 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)) if (minecraft->options.getBooleanValue(OPTIONS_RENDER_DEBUG))
renderDebugInfo(); renderDebugInfo();
#endif #endif
}
glDisable(GL_BLEND); glDisable(GL_BLEND);
glEnable2(GL_ALPHA_TEST); glEnable2(GL_ALPHA_TEST);
@@ -201,6 +205,10 @@ void Gui::handleClick(int button, int x, int y) {
void Gui::handleKeyPressed(int key) void Gui::handleKeyPressed(int key)
{ {
if (key == Keyboard::KEY_F1) {
minecraft->options.toggle(OPTIONS_HIDEGUI);
}
if (key == 99) if (key == 99)
{ {
if (minecraft->player->inventory->selected > 0) 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(); ItemInstance* currentItem = minecraft->player->inventory->getSelected();
bool bowEquipped = currentItem != NULL ? currentItem->getItem() == Item::bow : false; bool bowEquipped = currentItem != NULL ? currentItem->getItem() == Item::bow : false;
bool itemInUse = currentItem != NULL ? currentItem->getItem() == minecraft->player->getUseItem()->getItem() : 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"); minecraft->textures->loadAndBindTexture("gui/icons.png");
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc2(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR); glBlendFunc2(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
@@ -585,11 +594,14 @@ void Gui::renderHearts() {
int oh = minecraft->player->lastHealth; int oh = minecraft->player->lastHealth;
random.setSeed(tickCount * 312871); 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(); int armor = minecraft->player->getArmorValue();
for (int i = 0; i < Player::MAX_HEALTH / 2; i++) { 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; int ip2 = i + i + 1;
if (armor > 0) { if (armor > 0) {
@@ -617,11 +629,15 @@ void Gui::renderHearts() {
void Gui::renderBubbles() { void Gui::renderBubbles() {
if (minecraft->player->isUnderLiquid(Material::water)) { 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 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; int extra = (int) std::ceil((minecraft->player->airSupply) * 10.0f / Player::TOTAL_AIR_SUPPLY) - count;
for (int i = 0; i < count + extra; i++) { 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); if (i < count) blit(xo, yo, 16, 9 * 2, 9, 9);
else blit(xo, yo, 16 + 9, 9 * 2, 9, 9); else blit(xo, yo, 16 + 9, 9 * 2, 9, 9);
} }

View File

@@ -78,6 +78,14 @@ void Screen::updateEvents()
void Screen::mouseEvent() void Screen::mouseEvent()
{ {
const MouseAction& e = Mouse::getEvent(); 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()) if (!e.isButton())
return; return;

View File

@@ -57,6 +57,9 @@ protected:
virtual void mouseClicked(int x, int y, int buttonNum); virtual void mouseClicked(int x, int y, int buttonNum);
virtual void mouseReleased(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 keyPressed(int eventKey);
virtual void charPressed(char inputChar); virtual void charPressed(char inputChar);
public: public:

View File

@@ -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) { void ScrollingPane::setContentOffset(float x, float y) {
this->setContentOffsetWithAnimation(Vec3(x, y, 0), false); this->setContentOffsetWithAnimation(Vec3(x, y, 0), false);
} }

View File

@@ -51,6 +51,10 @@ public:
void tick(); void tick();
void render(int xm, int ym, float alpha); 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); bool getGridItemFor_slow(int itemIndex, GridItem& out);
void setSelected(int id, bool selected); void setSelected(int id, bool selected);

View File

@@ -38,7 +38,8 @@ void CreditsScreen::init() {
_lines.push_back("InviseDivine"); _lines.push_back("InviseDivine");
_lines.push_back("Kolyah35"); _lines.push_back("Kolyah35");
_lines.push_back(""); _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; _scrollSpeed = 0.5f;
_scrollY = height; // start below screen _scrollY = height; // start below screen
} }

View File

@@ -202,6 +202,25 @@ void IngameBlockSelectionScreen::keyPressed(int eventKey)
#endif #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 IngameBlockSelectionScreen::getSelectedSlot(int x, int y)
{ {
int left = width / 2 - InventoryCols * 10; int left = width / 2 - InventoryCols * 10;

View File

@@ -23,6 +23,9 @@ protected:
virtual void buttonClicked(Button* button); 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); virtual void keyPressed(int eventKey);
private: private:
void renderSlots(); void renderSlots();

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -69,6 +69,7 @@ void OptionsScreen::init() {
categoryButtons.push_back(new Touch::TButton(3, "Game")); categoryButtons.push_back(new Touch::TButton(3, "Game"));
categoryButtons.push_back(new Touch::TButton(4, "Controls")); categoryButtons.push_back(new Touch::TButton(4, "Controls"));
categoryButtons.push_back(new Touch::TButton(5, "Graphics")); categoryButtons.push_back(new Touch::TButton(5, "Graphics"));
categoryButtons.push_back(new Touch::TButton(6, "Tweaks"));
btnCredits = new Touch::TButton(11, "Credits"); 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.game"));
optionPanes.push_back(new OptionsGroup("options.group.control")); optionPanes.push_back(new OptionsGroup("options.group.control"));
optionPanes.push_back(new OptionsGroup("options.group.graphics")); optionPanes.push_back(new OptionsGroup("options.group.graphics"));
optionPanes.push_back(new OptionsGroup("options.group.tweaks"));
// General Pane // General Pane
optionPanes[0]->addOptionItem(OPTIONS_USERNAME, minecraft) optionPanes[0]->addOptionItem(OPTIONS_USERNAME, minecraft)
@@ -228,6 +230,9 @@ void OptionsScreen::generateOptionScreens() {
.addOptionItem(OPTIONS_ANAGLYPH_3D, minecraft) .addOptionItem(OPTIONS_ANAGLYPH_3D, minecraft)
.addOptionItem(OPTIONS_VIEW_BOBBING, minecraft) .addOptionItem(OPTIONS_VIEW_BOBBING, minecraft)
.addOptionItem(OPTIONS_AMBIENT_OCCLUSION, 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) { void OptionsScreen::mouseClicked(int x, int y, int buttonNum) {

View File

@@ -5,6 +5,7 @@
#include "PauseScreen.h" #include "PauseScreen.h"
#include "RenameMPLevelScreen.h" #include "RenameMPLevelScreen.h"
#include "IngameBlockSelectionScreen.h" #include "IngameBlockSelectionScreen.h"
#include "JoinByIPScreen.h"
#include "touch/TouchStartMenuScreen.h" #include "touch/TouchStartMenuScreen.h"
#include "touch/TouchSelectWorldScreen.h" #include "touch/TouchSelectWorldScreen.h"
#include "touch/TouchJoinGameScreen.h" #include "touch/TouchJoinGameScreen.h"
@@ -20,13 +21,13 @@ Screen* ScreenChooser::createScreen( ScreenId id )
if (_mc->useTouchscreen()) { if (_mc->useTouchscreen()) {
switch (id) { switch (id) {
case SCREEN_STARTMENU: screen = new Touch::StartMenuScreen(); break; case SCREEN_STARTMENU: screen = new Touch::StartMenuScreen(); break;
case SCREEN_SELECTWORLD:screen = new Touch::SelectWorldScreen();break; case SCREEN_SELECTWORLD: screen = new Touch::SelectWorldScreen();break;
case SCREEN_JOINGAME: screen = new Touch::JoinGameScreen(); break; case SCREEN_JOINGAME: screen = new Touch::JoinGameScreen(); break;
case SCREEN_PAUSE: screen = new PauseScreen(false); break; case SCREEN_PAUSE: screen = new PauseScreen(false); break;
case SCREEN_PAUSEPREV: screen = new PauseScreen(true); break; case SCREEN_PAUSEPREV: screen = new PauseScreen(true); break;
case SCREEN_BLOCKSELECTION: screen = new Touch::IngameBlockSelectionScreen(); break; case SCREEN_BLOCKSELECTION: screen = new Touch::IngameBlockSelectionScreen(); break;
case SCREEN_JOINBYIP: screen = new JoinByIPScreen(); break;
case SCREEN_NONE: case SCREEN_NONE:
default: default:
// Do nothing // Do nothing
@@ -34,12 +35,13 @@ Screen* ScreenChooser::createScreen( ScreenId id )
} }
} else { } else {
switch (id) { switch (id) {
case SCREEN_STARTMENU: screen = new StartMenuScreen(); break; case SCREEN_STARTMENU: screen = new StartMenuScreen(); break;
case SCREEN_SELECTWORLD:screen = new SelectWorldScreen();break; case SCREEN_SELECTWORLD: screen = new SelectWorldScreen();break;
case SCREEN_JOINGAME: screen = new JoinGameScreen(); break; case SCREEN_JOINGAME: screen = new JoinGameScreen(); break;
case SCREEN_PAUSE: screen = new PauseScreen(false); break; case SCREEN_PAUSE: screen = new PauseScreen(false); break;
case SCREEN_PAUSEPREV: screen = new PauseScreen(true); break; case SCREEN_PAUSEPREV: screen = new PauseScreen(true); break;
case SCREEN_BLOCKSELECTION: screen = new IngameBlockSelectionScreen(); break; case SCREEN_BLOCKSELECTION: screen = new IngameBlockSelectionScreen(); break;
case SCREEN_JOINBYIP: screen = new JoinByIPScreen(); break;
case SCREEN_NONE: case SCREEN_NONE:
default: default:

View File

@@ -8,7 +8,8 @@ enum ScreenId {
SCREEN_PAUSE, SCREEN_PAUSE,
SCREEN_PAUSEPREV, SCREEN_PAUSEPREV,
SCREEN_SELECTWORLD, SCREEN_SELECTWORLD,
SCREEN_BLOCKSELECTION SCREEN_BLOCKSELECTION,
SCREEN_JOINBYIP
}; };
class Screen; class Screen;

View File

@@ -356,7 +356,7 @@ void SelectWorldScreen::render( int xm, int ym, float a )
worldsList->setComponentSelected(bWorldView.selected); worldsList->setComponentSelected(bWorldView.selected);
// #ifdef PLATFORM_DESKTOP // #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) if (_mouseHasBeenUp)
worldsList->render(xm, ym, a); worldsList->render(xm, ym, a);
else { else {
@@ -412,6 +412,28 @@ std::string SelectWorldScreen::getUniqueLevelName( const std::string& level )
bool SelectWorldScreen::isInGameScreen() { return true; } 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 ) void SelectWorldScreen::keyPressed( int eventKey )
{ {
if (bWorldView.selected) { if (bWorldView.selected) {

View File

@@ -89,6 +89,9 @@ public:
void render(int xm, int ym, float a); 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(); bool isInGameScreen();
private: private:
void loadLevelSource(); void loadLevelSource();

View File

@@ -12,11 +12,13 @@
SimpleChooseLevelScreen::SimpleChooseLevelScreen(const std::string& levelName) SimpleChooseLevelScreen::SimpleChooseLevelScreen(const std::string& levelName)
: bHeader(0), : bHeader(0),
bGamemode(0), bGamemode(0),
bCheats(0),
bBack(0), bBack(0),
bCreate(0), bCreate(0),
levelName(levelName), levelName(levelName),
hasChosen(false), hasChosen(false),
gamemode(GameType::Survival), gamemode(GameType::Survival),
cheatsEnabled(false),
tLevelName(0, "World name"), tLevelName(0, "World name"),
tSeed(1, "World seed") tSeed(1, "World seed")
{ {
@@ -26,12 +28,20 @@ SimpleChooseLevelScreen::~SimpleChooseLevelScreen()
{ {
if (bHeader) delete bHeader; if (bHeader) delete bHeader;
delete bGamemode; delete bGamemode;
delete bCheats;
delete bBack; delete bBack;
delete bCreate; delete bCreate;
} }
void SimpleChooseLevelScreen::init() 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"; tLevelName.text = "New world";
// header + close button // header + close button
@@ -48,18 +58,22 @@ void SimpleChooseLevelScreen::init()
} }
if (minecraft->useTouchscreen()) { if (minecraft->useTouchscreen()) {
bGamemode = new Touch::TButton(1, "Survival mode"); bGamemode = new Touch::TButton(1, "Survival mode");
bCheats = new Touch::TButton(4, "Cheats: Off");
bCreate = new Touch::TButton(3, "Create"); bCreate = new Touch::TButton(3, "Create");
} else { } else {
bGamemode = new Button(1, "Survival mode"); bGamemode = new Button(1, "Survival mode");
bCheats = new Button(4, "Cheats: Off");
bCreate = new Button(3, "Create"); bCreate = new Button(3, "Create");
} }
buttons.push_back(bHeader); buttons.push_back(bHeader);
buttons.push_back(bBack); buttons.push_back(bBack);
buttons.push_back(bGamemode); buttons.push_back(bGamemode);
buttons.push_back(bCheats);
buttons.push_back(bCreate); buttons.push_back(bCreate);
tabButtons.push_back(bGamemode); tabButtons.push_back(bGamemode);
tabButtons.push_back(bCheats);
tabButtons.push_back(bBack); tabButtons.push_back(bBack);
tabButtons.push_back(bCreate); tabButtons.push_back(bCreate);
@@ -94,16 +108,26 @@ void SimpleChooseLevelScreen::setupPositions()
tSeed.x = tLevelName.x; tSeed.x = tLevelName.x;
tSeed.y = tLevelName.y + 30; tSeed.y = tLevelName.y + 30;
bGamemode->width = 140; const int buttonWidth = 120;
bGamemode->x = centerX - bGamemode->width / 2; const int buttonSpacing = 10;
// compute vertical centre for gamemode in remaining space 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 bottomPad = 20;
int availTop = buttonHeight + 20 + 30 + 10; // just below seed int availTop = buttonHeight + 20 + 30 + 10; // just below seed
int availBottom = height - bottomPad - bCreate->height - 10; // leave some gap before create int availBottom = height - bottomPad - bCreate->height - 10; // leave some gap before create
int availHeight = availBottom - availTop; int availHeight = availBottom - availTop;
if (availHeight < 0) availHeight = 0; 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; bCreate->width = 100;
@@ -124,14 +148,14 @@ void SimpleChooseLevelScreen::render( int xm, int ym, float a )
renderDirtBackground(0); renderDirtBackground(0);
glEnable2(GL_BLEND); glEnable2(GL_BLEND);
const char* str = NULL; const char* modeDesc = NULL;
if (gamemode == GameType::Survival) { if (gamemode == GameType::Survival) {
str = "Mobs, health and gather resources"; modeDesc = "Mobs, health and gather resources";
} else if (gamemode == GameType::Creative) { } else if (gamemode == GameType::Creative) {
str = "Unlimited resources and flying"; modeDesc = "Unlimited resources and flying";
} }
if (str) { if (modeDesc) {
drawCenteredString(minecraft->font, str, width/2, bGamemode->y + bGamemode->height + 4, 0xffcccccc); 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); drawString(minecraft->font, "World name:", tLevelName.x, tLevelName.y - Font::DefaultLineHeight - 2, 0xffcccccc);
@@ -188,6 +212,12 @@ void SimpleChooseLevelScreen::buttonClicked( Button* button )
return; return;
} }
if (button == bCheats) {
cheatsEnabled = !cheatsEnabled;
bCheats->msg = cheatsEnabled ? "Cheats: On" : "Cheats: Off";
return;
}
if (button == bCreate && !tLevelName.text.empty()) { if (button == bCreate && !tLevelName.text.empty()) {
int seed = getEpochTimeS(); int seed = getEpochTimeS();
if (!tSeed.text.empty()) { if (!tSeed.text.empty()) {
@@ -200,7 +230,7 @@ void SimpleChooseLevelScreen::buttonClicked( Button* button )
} }
} }
std::string levelId = getUniqueLevelName(tLevelName.text); std::string levelId = getUniqueLevelName(tLevelName.text);
LevelSettings settings(seed, gamemode); LevelSettings settings(seed, gamemode, cheatsEnabled);
minecraft->selectLevel(levelId, levelId, settings); minecraft->selectLevel(levelId, levelId, settings);
minecraft->hostMultiplayer(); minecraft->hostMultiplayer();
minecraft->setScreen(new ProgressScreen()); minecraft->setScreen(new ProgressScreen());

View File

@@ -28,12 +28,14 @@ public:
private: private:
Touch::THeader* bHeader; Touch::THeader* bHeader;
Button* bGamemode; Button* bGamemode;
Button* bCheats;
ImageButton* bBack; ImageButton* bBack;
Button* bCreate; Button* bCreate;
bool hasChosen; bool hasChosen;
std::string levelName; std::string levelName;
int gamemode; int gamemode;
bool cheatsEnabled;
TextBox tLevelName; TextBox tLevelName;
TextBox tSeed; TextBox tSeed;

View File

@@ -6,6 +6,7 @@
#include "OptionsScreen.h" #include "OptionsScreen.h"
#include "PauseScreen.h" #include "PauseScreen.h"
#include "PrerenderTilesScreen.h" // test button #include "PrerenderTilesScreen.h" // test button
#include "../components/ImageButton.h"
#include "../../../util/Mth.h" #include "../../../util/Mth.h"
@@ -24,7 +25,8 @@
StartMenuScreen::StartMenuScreen() StartMenuScreen::StartMenuScreen()
: bHost( 2, 0, 0, 160, 24, "Start Game"), : bHost( 2, 0, 0, 160, 24, "Start Game"),
bJoin( 3, 0, 0, 160, 24, "Join 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); tabButtons.push_back(&bOptions);
#endif #endif
#ifdef DEMO_MODE // add quit button (top right X icon) match OptionsScreen style
buttons.push_back(&bBuy); {
tabButtons.push_back(&bBuy); ImageDef def;
#endif 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!"; copyright = "\xffMojang AB";//. Do not distribute!";
@@ -99,8 +109,9 @@ void StartMenuScreen::setupPositions() {
bJoin.x = (width - bJoin.width) / 2; bJoin.x = (width - bJoin.width) / 2;
bOptions.x = (width - bJoin.width) / 2; bOptions.x = (width - bJoin.width) / 2;
copyrightPosX = width - minecraft->font->width(copyright) - 1; // position quit icon at top-right (use image-defined size)
versionPosX = (width - minecraft->font->width(version)) / 2;// - minecraft->font->width(version) - 2; bQuit.x = width - bQuit.width;
bQuit.y = 0;
} }
void StartMenuScreen::tick() { void StartMenuScreen::tick() {
@@ -129,6 +140,10 @@ void StartMenuScreen::buttonClicked(Button* button) {
{ {
minecraft->setScreen(new OptionsScreen()); minecraft->setScreen(new OptionsScreen());
} }
if (button == &bQuit)
{
minecraft->quit();
}
} }
bool StartMenuScreen::isInGameScreen() { return false; } bool StartMenuScreen::isInGameScreen() { return false; }
@@ -137,6 +152,10 @@ void StartMenuScreen::render( int xm, int ym, float a )
{ {
renderBackground(); 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) #if defined(RPI)
TextureId id = minecraft->textures->loadTexture("gui/pi_title.png"); TextureId id = minecraft->textures->loadTexture("gui/pi_title.png");
#else #else

View File

@@ -3,6 +3,7 @@
#include "../Screen.h" #include "../Screen.h"
#include "../components/Button.h" #include "../components/Button.h"
#include "../components/ImageButton.h"
class StartMenuScreen: public Screen class StartMenuScreen: public Screen
{ {
@@ -25,6 +26,7 @@ private:
Button bHost; Button bHost;
Button bJoin; Button bJoin;
Button bOptions; Button bOptions;
ImageButton bQuit; // X button in top-right corner
std::string copyright; std::string copyright;
int copyrightPosX; int copyrightPosX;

View File

@@ -33,15 +33,16 @@ void UsernameScreen::setupPositions()
int cx = width / 2; int cx = width / 2;
int cy = height / 2; int cy = height / 2;
_btnDone.width = 120; // Make the done button match the touch-style option tabs
_btnDone.height = 20; _btnDone.width = 66;
_btnDone.height = 26;
_btnDone.x = (width - _btnDone.width) / 2; _btnDone.x = (width - _btnDone.width) / 2;
_btnDone.y = height / 2 + 52; _btnDone.y = height / 2 + 52;
tUsername.x = _btnDone.x;
tUsername.y = _btnDone.y - 60;
tUsername.width = 120; tUsername.width = 120;
tUsername.height = 20; tUsername.height = 20;
tUsername.x = (width - tUsername.width) / 2;
tUsername.y = _btnDone.y - 60;
} }
void UsernameScreen::tick() 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 // deliberately do NOT call super::keyPressed — that would close the screen on Escape
_btnDone.active = !tUsername.text.empty();
Screen::keyPressed(eventKey); 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() void UsernameScreen::removed()

View File

@@ -153,6 +153,11 @@ int IngameBlockSelectionScreen::getSlotPosY(int slotY) {
return height - 16 - 3 - 22 * 2 - 22 * 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) { void IngameBlockSelectionScreen::mouseClicked(int x, int y, int buttonNum) {
_pendingClose = _blockList->_clickArea->isInside((float)x, (float)y); _pendingClose = _blockList->_clickArea->isInside((float)x, (float)y);
if (!_pendingClose) if (!_pendingClose)
@@ -166,6 +171,24 @@ void IngameBlockSelectionScreen::mouseReleased(int x, int y, int buttonNum) {
super::mouseReleased(x, y, 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) bool IngameBlockSelectionScreen::addItem(const InventoryPane* pane, int itemId)
{ {
Inventory* inventory = minecraft->player->inventory; Inventory* inventory = minecraft->player->inventory;

View File

@@ -39,12 +39,16 @@ public:
protected: protected:
virtual void mouseClicked(int x, int y, int buttonNum); virtual void mouseClicked(int x, int y, int buttonNum);
virtual void mouseReleased(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: private:
void renderDemoOverlay(); void renderDemoOverlay();
//int getLinearSlotId(int x, int y); //int getLinearSlotId(int x, int y);
int getSlotPosX(int slotX); int getSlotPosX(int slotX);
int getSlotPosY(int slotY); int getSlotPosY(int slotY);
int getSlotHeight();
private: private:
int selectedItem; int selectedItem;

View File

@@ -129,6 +129,11 @@ void JoinGameScreen::buttonClicked(Button* button)
//minecraft->locateMultiplayer(); //minecraft->locateMultiplayer();
//minecraft->setScreen(new JoinGameScreen()); //minecraft->setScreen(new JoinGameScreen());
} }
if(button->id == bJoinByIp.id) {
minecraft->cancelLocateMultiplayer();
minecraft->screenChooser.setScreen(SCREEN_JOINBYIP);
}
if (button->id == bBack.id) if (button->id == bBack.id)
{ {
minecraft->cancelLocateMultiplayer(); minecraft->cancelLocateMultiplayer();

View File

@@ -389,6 +389,26 @@ static char ILLEGAL_FILE_CHARACTERS[] = {
'/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' '/', '\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() void SelectWorldScreen::tick()
{ {
#if 0 #if 0

View File

@@ -97,6 +97,9 @@ public:
virtual void buttonClicked(Button* button); virtual void buttonClicked(Button* button);
virtual void keyPressed(int eventKey); 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(); bool isInGameScreen();
private: private:
void loadLevelSource(); void loadLevelSource();

View File

@@ -29,7 +29,8 @@ namespace Touch {
StartMenuScreen::StartMenuScreen() StartMenuScreen::StartMenuScreen()
: bHost( 2, "Start Game"), : bHost( 2, "Start Game"),
bJoin( 3, "Join Game"), bJoin( 3, "Join Game"),
bOptions( 4, "Options") bOptions( 4, "Options"),
bQuit( 5, "")
{ {
ImageDef def; ImageDef def;
bJoin.width = 75; bJoin.width = 75;
@@ -58,6 +59,17 @@ void StartMenuScreen::init()
buttons.push_back(&bJoin); buttons.push_back(&bJoin);
buttons.push_back(&bOptions); 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(&bHost);
tabButtons.push_back(&bJoin); tabButtons.push_back(&bJoin);
@@ -106,6 +118,10 @@ void StartMenuScreen::setupPositions() {
bHost.x = 1*buttonWidth + (int)(2*spacing); bHost.x = 1*buttonWidth + (int)(2*spacing);
bOptions.x = 2*buttonWidth + (int)(3*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; copyrightPosX = width - minecraft->font->width(copyright) - 1;
versionPosX = (width - minecraft->font->width(version)) / 2;// - minecraft->font->width(version) - 2; 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()); minecraft->setScreen(new OptionsScreen());
} }
if (button == &bQuit)
{
minecraft->quit();
}
} }
bool StartMenuScreen::isInGameScreen() { return false; } bool StartMenuScreen::isInGameScreen() { return false; }
@@ -140,6 +160,10 @@ bool StartMenuScreen::isInGameScreen() { return false; }
void StartMenuScreen::render( int xm, int ym, float a ) void StartMenuScreen::render( int xm, int ym, float a )
{ {
renderBackground(); 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); glEnable2(GL_BLEND);

View File

@@ -3,6 +3,7 @@
#include "../../Screen.h" #include "../../Screen.h"
#include "../../components/LargeImageButton.h" #include "../../components/LargeImageButton.h"
#include "../../components/ImageButton.h"
#include "../../components/TextBox.h" #include "../../components/TextBox.h"
namespace Touch { namespace Touch {
@@ -27,6 +28,7 @@ private:
LargeImageButton bHost; LargeImageButton bHost;
LargeImageButton bJoin; LargeImageButton bJoin;
LargeImageButton bOptions; LargeImageButton bOptions;
ImageButton bQuit; // X close icon
std::string copyright; std::string copyright;
int copyrightPosX; int copyrightPosX;

View File

@@ -4,34 +4,46 @@
#include "../../world/entity/player/Player.h" #include "../../world/entity/player/Player.h"
#include "../../world/entity/player/Inventory.h" #include "../../world/entity/player/Inventory.h"
HumanoidModel::HumanoidModel( float g /*= 0*/, float yOffset /*= 0*/ ) HumanoidModel::HumanoidModel( float g /*= 0*/, float yOffset /*= 0*/, int texW /*= 64*/, int texH /*= 32*/ )
: holdingLeftHand(false), : holdingLeftHand(false),
holdingRightHand(false), holdingRightHand(false),
sneaking(false), sneaking(false),
bowAndArrow(false), bowAndArrow(false),
head(0, 0), head(0, 0),
hair(32, 0),
//ear (24, 0), //ear (24, 0),
//hair(32, 0),
body(16, 16), body(16, 16),
arm0(24 + 16, 16), arm0(24 + 16, 16),
arm1(24 + 16, 16), arm1(24 + 16, 16),
leg0(0, 16), leg0(0, 16),
leg1(0, 16) leg1(0, 16)
{ {
texWidth = texW;
texHeight = texH;
head.setModel(this); head.setModel(this);
hair.setModel(this);
body.setModel(this); body.setModel(this);
arm0.setModel(this); arm0.setModel(this);
arm1.setModel(this); arm1.setModel(this);
leg0.setModel(this); leg0.setModel(this);
leg1.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.addBox(-4, -8, -4, 8, 8, 8, g); // Head
head.setPos(0, 0 + yOffset, 0); head.setPos(0, 0 + yOffset, 0);
//ear.addBox(-3, -6, -1, 6, 6, 1, g); // Ear if (modernSkin) {
hair.addBox(-4, -8, -4, 8, 8, 8, g + 0.5f); // Outer head layer (hat)
//hair.addBox(-4, -8, -4, 8, 8, 8, g + 0.5f); // Head hair.setPos(0, 0 + yOffset, 0);
// hair.setPos(0, 0 + yOffset, 0); }
body.addBox(-4, 0, -2, 8, 12, 4, g); // Body body.addBox(-4, 0, -2, 8, 12, 4, g); // Body
body.setPos(0, 0 + yOffset, 0); body.setPos(0, 0 + yOffset, 0);
@@ -49,6 +61,24 @@ HumanoidModel::HumanoidModel( float g /*= 0*/, float yOffset /*= 0*/ )
leg1.mirror = true; leg1.mirror = true;
leg1.addBox(-2, 0, -2, 4, 12, 4, g); // Leg1 leg1.addBox(-2, 0, -2, 4, 12, 4, g); // Leg1
leg1.setPos(2, 12 + yOffset, 0); 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 ) 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); 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); head.render(scale);
if (texWidth == 64 && texHeight == 64) {
hair.render(scale);
}
body.render(scale); body.render(scale);
arm0.render(scale); arm0.render(scale);
arm1.render(scale); arm1.render(scale);
leg0.render(scale); leg0.render(scale);
leg1.render(scale); leg1.render(scale);
bowAndArrow = false; bowAndArrow = false;
//hair.render(scale);
} }
void HumanoidModel::render( HumanoidModel* model, float 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.yRot = model->head.yRot;
head.y = model->head.y; head.y = model->head.y;
head.xRot = model->head.xRot; head.xRot = model->head.xRot;
//hair.yRot = head.yRot; if (texWidth == 64 && texHeight == 64) {
//hair.xRot = head.xRot; hair.yRot = head.yRot;
hair.xRot = head.xRot;
hair.zRot = head.zRot;
hair.y = head.y;
}
arm0.xRot = model->arm0.xRot; arm0.xRot = model->arm0.xRot;
arm0.zRot = model->arm0.zRot; arm0.zRot = model->arm0.zRot;
@@ -96,12 +140,14 @@ void HumanoidModel::render( HumanoidModel* model, float scale )
leg1.xRot = model->leg1.xRot; leg1.xRot = model->leg1.xRot;
head.render(scale); head.render(scale);
if (texWidth == 64 && texHeight == 64) {
hair.render(scale);
}
body.render(scale); body.render(scale);
arm0.render(scale); arm0.render(scale);
arm1.render(scale); arm1.render(scale);
leg0.render(scale); leg0.render(scale);
leg1.render(scale); leg1.render(scale);
//hair.render(scale);
} }
void HumanoidModel::renderHorrible( float time, float r, float bob, float yRot, float xRot, float scale ) void HumanoidModel::renderHorrible( float time, float r, float bob, float yRot, float xRot, float scale )

View File

@@ -9,7 +9,7 @@ class ItemInstance;
class HumanoidModel: public Model class HumanoidModel: public Model
{ {
public: 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); 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 renderHorrible(float time, float r, float bob, float yRot, float xRot, float scale);
void onGraphicsReset(); void onGraphicsReset();
ModelPart head, /*hair,*/ body, arm0, arm1, leg0, leg1;//, ear; ModelPart head, hair, body, arm0, arm1, leg0, leg1;//, ear;
bool holdingLeftHand; bool holdingLeftHand;
bool holdingRightHand; bool holdingRightHand;
bool sneaking; bool sneaking;

View File

@@ -50,12 +50,12 @@ Cube::Cube(ModelPart* modelPart, int xTexOffs, int yTexOffs, float x0, float y0,
VertexPT* l2 = ++ptr; VertexPT* l2 = ++ptr;
VertexPT* l3 = ++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[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); // Left 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); // Up 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); // Down 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); // Front 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); // Back 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) { if (modelPart->mirror) {
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)

View File

@@ -12,15 +12,15 @@ PolygonQuad::PolygonQuad(VertexPT* v0, VertexPT* v1, VertexPT* v2, VertexPT* v3)
} }
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) int uu0, int vv0, int uu1, int vv1, float texW, float texH)
: _flipNormal(false) : _flipNormal(false)
{ {
const float us = -0.002f / 64.0f;//0.1f / 64.0f; const float us = -0.002f / texW;
const float vs = -0.002f / 32.0f;//0.1f / 32.0f; const float vs = -0.002f / texH;
vertices[0] = v0->remap(uu1 / 64.0f - us, vv0 / 32.0f + vs); vertices[0] = v0->remap(uu1 / texW - us, vv0 / texH + vs);
vertices[1] = v1->remap(uu0 / 64.0f + us, vv0 / 32.0f + vs); vertices[1] = v1->remap(uu0 / texW + us, vv0 / texH + vs);
vertices[2] = v2->remap(uu0 / 64.0f + us, vv1 / 32.0f - vs); vertices[2] = v2->remap(uu0 / texW + us, vv1 / texH - vs);
vertices[3] = v3->remap(uu1 / 64.0f - us, vv1 / 32.0f - vs); vertices[3] = v3->remap(uu1 / texW - us, vv1 / texH - vs);
} }
PolygonQuad::PolygonQuad( VertexPT* v0, VertexPT* v1, VertexPT* v2, VertexPT* v3, PolygonQuad::PolygonQuad( VertexPT* v0, VertexPT* v1, VertexPT* v2, VertexPT* v3,

View File

@@ -11,7 +11,7 @@ class PolygonQuad
public: public:
PolygonQuad() {} PolygonQuad() {}
PolygonQuad(VertexPT*,VertexPT*,VertexPT*,VertexPT*); 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); PolygonQuad(VertexPT*,VertexPT*,VertexPT*,VertexPT*, float u0, float v0, float u1, float v1);
void mirror(); void mirror();

View File

@@ -18,6 +18,20 @@
#include "../../network/packet/SendInventoryPacket.h" #include "../../network/packet/SendInventoryPacket.h"
#include "../../network/packet/EntityEventPacket.h" #include "../../network/packet/EntityEventPacket.h"
#include "../../network/packet/PlayerActionPacket.h" #include "../../network/packet/PlayerActionPacket.h"
#include <vector>
#include <cctype>
#include "../../platform/log.h"
#include "../../platform/HttpClient.h"
#include "../../platform/CThread.h"
#include "../../util/StringUtils.h"
#if defined(_WIN32)
#include <direct.h>
#else
#include <sys/stat.h>
#include <sys/types.h>
#endif
#ifndef STANDALONE_SERVER #ifndef STANDALONE_SERVER
#include "../gui/Screen.h" #include "../gui/Screen.h"
#include "../gui/screens/FurnaceScreen.h" #include "../gui/screens/FurnaceScreen.h"
@@ -32,6 +46,250 @@
#include "../../world/item/ArmorItem.h" #include "../../world/item/ArmorItem.h"
#include "../../network/packet/PlayerArmorEquipmentPacket.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<unsigned char> 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<unsigned char> 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<unsigned char> 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 //@note: doesn't work completely, since it doesn't care about stairs rotation
static bool isJumpable(int tileId) { static bool isJumpable(int tileId) {
return tileId != Tile::fence->id 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); && (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) LocalPlayer::LocalPlayer(Minecraft* minecraft, Level* level, User* user, int dimension, bool isCreative)
: Player(level, isCreative), : Player(level, isCreative),
minecraft(minecraft), minecraft(minecraft),
@@ -58,10 +318,11 @@ LocalPlayer::LocalPlayer(Minecraft* minecraft, Level* level, User* user, int dim
this->dimension = dimension; this->dimension = dimension;
_init(); _init();
if (user != NULL) { if (user != NULL && !user->name.empty()) {
if (user->name.length() > 0) this->name = user->name;
//customTextureUrl = "http://s3.amazonaws.com/MinecraftSkins/" + user.name + ".png"; // Fetch user skin and cape from Mojang servers in the background (avoids blocking the main thread)
this->name = user->name; new CThread(fetchSkinForPlayer, this);
new CThread(fetchCapeForPlayer, this);
} }
} }
@@ -184,7 +445,7 @@ void LocalPlayer::aiStep() {
// Sprint: detect W double-tap // Sprint: detect W double-tap
{ {
bool forwardHeld = (input->ya > 0); bool forwardHeld = (input->ya > 0);
if (forwardHeld && !prevForwardHeld) { if (forwardHeld && !prevForwardHeld && minecraft->options.useSprinting) {
// leading edge of W press // leading edge of W press
if (sprintDoubleTapTimer > 0) if (sprintDoubleTapTimer > 0)
sprinting = true; sprinting = true;
@@ -273,7 +534,7 @@ void LocalPlayer::move(float xa, float ya, float za) {
float newX = x, newZ = z; 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 // auto-jump when crossing the middle of a tile, and the tile in the front is blocked
bool jump = false; bool jump = false;

View File

@@ -105,6 +105,8 @@ private:
bool sprinting; bool sprinting;
int sprintDoubleTapTimer; int sprintDoubleTapTimer;
bool prevForwardHeld; bool prevForwardHeld;
public:
void setSprinting(bool sprint) { sprinting = sprint; }
}; };
#endif /*NET_MINECRAFT_CLIENT_PLAYER__LocalPlayer_H__*/ #endif /*NET_MINECRAFT_CLIENT_PLAYER__LocalPlayer_H__*/

View File

@@ -137,12 +137,6 @@ void Chunk::rebuild()
Tile* tile = Tile::tiles[tileId]; Tile* tile = Tile::tiles[tileId];
int renderLayer = tile->getRenderLayer(); int renderLayer = tile->getRenderLayer();
// if (renderLayer == l)
// rendered |= tileRenderer.tesselateInWorld(tile, x, y, z);
// else {
// _layerChunks[_layerChunkCount[renderLayer]++] = cindex;
// }
if (renderLayer > l) { if (renderLayer > l) {
renderNextLayer = true; renderNextLayer = true;
doRenderLayer[renderLayer] = true; doRenderLayer[renderLayer] = true;

View File

@@ -373,7 +373,7 @@ void GameRenderer::renderLevel(float a) {
// glDisable2(GL_FOG); // glDisable2(GL_FOG);
setupFog(1); setupFog(1);
if (zoom == 1) { if (zoom == 1 && !mc->options.F1) {
TIMER_POP_PUSH("hand"); TIMER_POP_PUSH("hand");
glClear(GL_DEPTH_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT);
renderItemInHand(a, i); renderItemInHand(a, i);

View File

@@ -354,7 +354,7 @@ void ItemInHandRenderer::render( float a )
glRotatef2(-swing3 * 20, 0, 0, 1); glRotatef2(-swing3 * 20, 0, 0, 1);
// glRotatef2(-swing2 * 80, 1, 0, 0); // glRotatef2(-swing2 * 80, 1, 0, 0);
mc->textures->loadAndBindTexture("mob/char.png"); mc->textures->loadAndBindTexture(player->getTexture());
glTranslatef2(-1.0f, +3.6f, +3.5f); glTranslatef2(-1.0f, +3.6f, +3.5f);
glRotatef2(120, 0, 0, 1); glRotatef2(120, 0, 0, 1);
glRotatef2(180 + 20, 1, 0, 0); glRotatef2(180 + 20, 1, 0, 0);

View File

@@ -16,7 +16,7 @@ typedef struct TextureData {
TextureData() TextureData()
: w(0), : w(0),
h(0), h(0),
data(NULL), data(nullptr),
numBytes(0), numBytes(0),
transparent(true), transparent(true),
memoryHandledExternally(false), memoryHandledExternally(false),

View File

@@ -5,6 +5,7 @@
#include "../Options.h" #include "../Options.h"
#include "../../platform/time.h" #include "../../platform/time.h"
#include "../../AppPlatform.h" #include "../../AppPlatform.h"
#include "../../util/StringUtils.h"
/*static*/ int Textures::textureChanges = 0; /*static*/ int Textures::textureChanges = 0;
/*static*/ bool Textures::MIPMAP = false; /*static*/ bool Textures::MIPMAP = false;
@@ -64,7 +65,8 @@ TextureId Textures::loadTexture( const std::string& resourceName, bool inTexture
if (it != idMap.end()) if (it != idMap.end())
return it->second; 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) if (texdata.data)
return assignTexture(resourceName, texdata); return assignTexture(resourceName, texdata);
else if (texdata.identifier != InvalidId) { else if (texdata.identifier != InvalidId) {

View File

@@ -1,4 +1,5 @@
#include "TileRenderer.h" #include "TileRenderer.h"
#include "Chunk.h"
#include "../Minecraft.h" #include "../Minecraft.h"
#include "Tesselator.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 c2 = 0.8f;
float c3 = 0.6f; float c3 = 0.6f;
float r11 = c11 * r; float r11 = c11 * r;
float g11 = c11 * g; float g11 = c11 * g;
float b11 = c11 * b; float b11 = c11 * b;
@@ -128,6 +130,7 @@ bool TileRenderer::tesselateBlockInWorld( Tile* tt, int x, int y, int z, float r
return changed; return changed;
} }
void TileRenderer::tesselateInWorld( Tile* tile, int x, int y, int z, int fixedTexture ) void TileRenderer::tesselateInWorld( Tile* tile, int x, int y, int z, int fixedTexture )
{ {
this->fixedTexture = fixedTexture; this->fixedTexture = fixedTexture;

View File

@@ -51,7 +51,7 @@ EntityRenderDispatcher::EntityRenderDispatcher()
assign( ER_SPIDER_RENDERER, new SpiderRenderer()); assign( ER_SPIDER_RENDERER, new SpiderRenderer());
assign( ER_TNT_RENDERER, new TntRenderer()); assign( ER_TNT_RENDERER, new TntRenderer());
assign( ER_ARROW_RENDERER, new ArrowRenderer()); 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_THROWNEGG_RENDERER, new ItemSpriteRenderer(Item::egg->getIcon(0)));
assign( ER_SNOWBALL_RENDERER, new ItemSpriteRenderer(Item::snowBall->getIcon(0))); assign( ER_SNOWBALL_RENDERER, new ItemSpriteRenderer(Item::snowBall->getIcon(0)));
assign( ER_PAINTING_RENDERER, new PaintingRenderer()); assign( ER_PAINTING_RENDERER, new PaintingRenderer());

View File

@@ -2,6 +2,7 @@
#include "EntityRenderDispatcher.h" #include "EntityRenderDispatcher.h"
#include "../ItemInHandRenderer.h" #include "../ItemInHandRenderer.h"
#include "../TileRenderer.h" #include "../TileRenderer.h"
#include "../Tesselator.h"
#include "../../model/HumanoidModel.h" #include "../../model/HumanoidModel.h"
#include "../../../world/level/tile/Tile.h" #include "../../../world/level/tile/Tile.h"
#include "../../../world/entity/player/Player.h" #include "../../../world/entity/player/Player.h"
@@ -12,7 +13,9 @@
HumanoidMobRenderer::HumanoidMobRenderer(HumanoidModel* humanoidModel, float shadow) HumanoidMobRenderer::HumanoidMobRenderer(HumanoidModel* humanoidModel, float shadow)
: super(humanoidModel, 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); entityRenderDispatcher->itemInHandRenderer->renderItem(mob, item);
glPopMatrix2(); 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 ) { void HumanoidMobRenderer::render( Entity* mob_, float x, float y, float z, float rot, float a ) {

View File

@@ -21,6 +21,10 @@ protected:
private: private:
HumanoidModel* humanoidModel; HumanoidModel* humanoidModel;
// Last rotation values for cape smoothing (reduces jitter)
float lastCapeXRot;
float lastCapeZRot;
}; };
#endif /*NET_MINECRAFT_CLIENT_RENDERER_ENTITY__HumanoidMobRenderer_H__*/ #endif /*NET_MINECRAFT_CLIENT_RENDERER_ENTITY__HumanoidMobRenderer_H__*/

View File

@@ -14,8 +14,8 @@ static const std::string armorFilenames[10] = {
PlayerRenderer::PlayerRenderer( HumanoidModel* humanoidModel, float shadow ) PlayerRenderer::PlayerRenderer( HumanoidModel* humanoidModel, float shadow )
: super(humanoidModel, shadow), : super(humanoidModel, shadow),
armorParts1(new HumanoidModel(1.0f)), armorParts1(new HumanoidModel(1.0f, 0, 64, 64)),
armorParts2(new HumanoidModel(0.5f)) armorParts2(new HumanoidModel(0.5f, 0, 64, 64))
{ {
} }

View File

@@ -27,6 +27,7 @@ int transformKey(int glfwkey) {
case GLFW_KEY_BACKSPACE: return Keyboard::KEY_BACKSPACE; case GLFW_KEY_BACKSPACE: return Keyboard::KEY_BACKSPACE;
case GLFW_KEY_LEFT_SHIFT: return Keyboard::KEY_LSHIFT; case GLFW_KEY_LEFT_SHIFT: return Keyboard::KEY_LSHIFT;
case GLFW_KEY_ENTER: return Keyboard::KEY_RETURN; case GLFW_KEY_ENTER: return Keyboard::KEY_RETURN;
case GLFW_KEY_LEFT_CONTROL: return Keyboard::KEY_LEFT_CTRL;
default: return glfwkey; default: return glfwkey;
} }
} }

View File

@@ -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); Multitouch::feed(0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
break; 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: default:
if (uMsg == WM_NCDESTROY) g_running = false; if (uMsg == WM_NCDESTROY) g_running = false;
else { else {

338
src/platform/HttpClient.cpp Normal file
View File

@@ -0,0 +1,338 @@
#include "HttpClient.h"
#include "log.h"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <string>
#include <sstream>
#include <vector>
#if defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#include <winhttp.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "winhttp.lib")
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#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<unsigned char>& 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<unsigned char> 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<unsigned char>& 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<unsigned char>& 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<unsigned char> 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<const char*>(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

16
src/platform/HttpClient.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef HTTPCLIENT_H__
#define HTTPCLIENT_H__
#include <string>
#include <vector>
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<unsigned char>& outBody);
} // namespace HttpClient
#endif /* HTTPCLIENT_H__ */

View File

@@ -0,0 +1,94 @@
#include "PngLoader.h"
#include <png.h>
#include <cstring>
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;
}

12
src/platform/PngLoader.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef PNGLOADER_H__
#define PNGLOADER_H__
#include "../client/renderer/TextureData.h"
#include <cstddef>
/// 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__

View File

@@ -71,9 +71,11 @@ public:
static const int KEY_F11 = 122; static const int KEY_F11 = 122;
static const int KEY_F12 = 123; static const int KEY_F12 = 123;
static const int KEY_ESCAPE = 27; static const int KEY_ESCAPE = 27;
static const int KEY_SPACE = 32; static const int KEY_SPACE = 32;
static const int KEY_LSHIFT = 10; static const int KEY_LSHIFT = 10;
static const int KEY_LEFT_CTRL = 232;
static bool isKeyDown(int keyCode) { static bool isKeyDown(int keyCode) {
return _states[keyCode] == KeyboardAction::KEYDOWN; return _states[keyCode] == KeyboardAction::KEYDOWN;

View File

@@ -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 #pragma warning(disable:4309) // 'initializing' : truncation of constant value
#endif #endif
// Make sure highest bit is 0, so isValid in DatagramHeaderFormat is false // 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 struct PacketFollowedByData
{ {

View File

@@ -29,6 +29,7 @@ Mob::Mob(Level* level)
invulnerableDuration(20), invulnerableDuration(20),
//hasHair(false), //hasHair(false),
textureName("mob/char.png"), textureName("mob/char.png"),
capeTextureName(""),
allowAlpha(true), allowAlpha(true),
modelName(""), modelName(""),
bobStrength(1), bobStrength(1),
@@ -82,6 +83,15 @@ Mob::Mob(Level* level)
yRot = (float) (Mth::random() * Mth::PI * 2); yRot = (float) (Mth::random() * Mth::PI * 2);
this->footSize = 0.5f; this->footSize = 0.5f;
// Initialize cape inertia positions
xCape = x;
yCape = y;
zCape = z;
xc = xCape;
yc = yCape;
zc = zCape;
} }
Mob::~Mob() { Mob::~Mob() {
@@ -110,6 +120,21 @@ std::string Mob::getTexture()
return textureName; 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() bool Mob::isPickable()
{ {
return !removed; return !removed;
@@ -269,6 +294,10 @@ void Mob::superTick()
void Mob::tick() void Mob::tick()
{ {
xc = xCape;
yc = yCape;
zc = zCape;
super::tick(); super::tick();
if (arrowCount > 0) { if (arrowCount > 0) {
@@ -373,6 +402,18 @@ void Mob::tick()
while (xRot - xRotO >= 180) while (xRot - xRotO >= 180)
xRotO += 360; xRotO += 360;
animStep += walkSpeed; 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 ) void Mob::setSize( float w, float h )

View File

@@ -42,10 +42,16 @@ public:
virtual void spawnAnim(); virtual void spawnAnim();
virtual std::string getTexture(); 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 isPickable();
virtual bool isPushable(); virtual bool isPushable();
virtual bool isShootable(); virtual bool isShootable();
MoveControl* getMoveControl(); MoveControl* getMoveControl();
@@ -212,7 +218,22 @@ protected:
float walkingSpeed; float walkingSpeed;
float flyingSpeed; 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 textureName;
std::string capeTextureName;
std::string modelName; std::string modelName;
int deathScore; int deathScore;
float oRun, run; float oRun, run;

View File

@@ -255,16 +255,16 @@ void Inventory::setupDefault() {
} else { } else {
#if defined(WIN32) #if defined(WIN32)
// Survival // Survival
addItem(new ItemInstance(Item::ironIngot, 64)); // addItem(new ItemInstance(Item::ironIngot, 64));
addItem(new ItemInstance(Item::ironIngot, 34)); // addItem(new ItemInstance(Item::ironIngot, 34));
addItem(new ItemInstance(Tile::stonecutterBench)); // addItem(new ItemInstance(Tile::stonecutterBench));
addItem(new ItemInstance(Tile::workBench)); // addItem(new ItemInstance(Tile::workBench));
addItem(new ItemInstance(Tile::furnace)); // addItem(new ItemInstance(Tile::furnace));
addItem(new ItemInstance(Tile::wood, 54)); // addItem(new ItemInstance(Tile::wood, 54));
addItem(new ItemInstance(Item::stick, 14)); // addItem(new ItemInstance(Item::stick, 14));
addItem(new ItemInstance(Item::coal, 31)); // addItem(new ItemInstance(Item::coal, 31));
addItem(new ItemInstance(Tile::sand, 6)); // addItem(new ItemInstance(Tile::sand, 6));
addItem(new ItemInstance(Item::dye_powder, 23, DyePowderItem::PURPLE)); // addItem(new ItemInstance(Item::dye_powder, 23, DyePowderItem::PURPLE));
#endif #endif
} }
#endif #endif

View File

@@ -14,13 +14,14 @@ namespace GameType {
class LevelSettings class LevelSettings
{ {
public: public:
LevelSettings(long seed, int gameType) LevelSettings(long seed, int gameType, bool allowCheats = false)
: seed(seed), : seed(seed),
gameType(gameType) gameType(gameType),
allowCheats(allowCheats)
{ {
} }
static LevelSettings None() { static LevelSettings None() {
return LevelSettings(-1,-1); return LevelSettings(-1,-1,false);
} }
long getSeed() const { long getSeed() const {
@@ -31,6 +32,10 @@ public:
return gameType; return gameType;
} }
bool getAllowCheats() const {
return allowCheats;
}
// //
// Those two should actually not be here // Those two should actually not be here
// @todo: Move out when we add LevelSettings.cpp :p // @todo: Move out when we add LevelSettings.cpp :p
@@ -53,6 +58,7 @@ public:
private: private:
const long seed; const long seed;
const int gameType; const int gameType;
const bool allowCheats;
}; };
#endif /*NET_MINECRAFT_WORLD_LEVEL__LevelSettings_H__*/ #endif /*NET_MINECRAFT_WORLD_LEVEL__LevelSettings_H__*/

View File

@@ -287,53 +287,58 @@ void RandomLevelSource::postProcess(ChunkSource* parent, int xt, int zt) {
feature.place(level, &random, x, y, z); 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 x = xo + random.nextInt(16);
int y = random.nextInt(128); int y = random.nextInt(128);
int z = zo + random.nextInt(16); int z = zo + random.nextInt(16);
OreFeature feature(Tile::coalOre->id, 16); OreFeature feature(Tile::coalOre->id, 14);
feature.place(level, &random, x, y, z); 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 x = xo + random.nextInt(16);
int y = random.nextInt(64); int y = random.nextInt(64);
int z = zo + random.nextInt(16); int z = zo + random.nextInt(16);
OreFeature feature(Tile::ironOre->id, 8); OreFeature feature(Tile::ironOre->id, 10);
feature.place(level, &random, x, y, z); feature.place(level, &random, x, y, z);
} }
// Gold: rarer and deeper
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
int x = xo + random.nextInt(16); int x = xo + random.nextInt(16);
int y = random.nextInt(32); int y = random.nextInt(32);
int z = zo + random.nextInt(16); int z = zo + random.nextInt(16);
OreFeature feature(Tile::goldOre->id, 8); OreFeature feature(Tile::goldOre->id, 9);
feature.place(level, &random, x, y, z); 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 x = xo + random.nextInt(16);
int y = random.nextInt(16); int y = random.nextInt(16);
int z = zo + random.nextInt(16); int z = zo + random.nextInt(16);
OreFeature feature(Tile::redStoneOre->id, 7); OreFeature feature(Tile::redStoneOre->id, 8);
feature.place(level, &random, x, y, z); 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 x = xo + random.nextInt(16);
int y = random.nextInt(16); int y = random.nextInt(16);
int z = zo + random.nextInt(16); int z = zo + random.nextInt(16);
OreFeature feature(Tile::emeraldOre->id, 7); OreFeature feature(Tile::emeraldOre->id, 6);
feature.place(level, &random, x, y, z); feature.place(level, &random, x, y, z);
} }
// lapis ore // Lapis: rare and not in very high Y
for (int i = 0; i < 1; i++) { for (int i = 0; i < 1; i++) {
int x = xo + random.nextInt(16); int x = xo + random.nextInt(16);
int y = random.nextInt(16) + random.nextInt(16); int y = random.nextInt(16) + random.nextInt(16);
int z = zo + random.nextInt(16); int z = zo + random.nextInt(16);
OreFeature feature(Tile::lapisOre->id, 6); 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; const float ss = 0.5f;
@@ -504,7 +509,8 @@ LevelChunk* RandomLevelSource::getChunk(int xOffs, int zOffs) {
prepareHeights(xOffs, zOffs, blocks, 0, temperatures);//biomes, temperatures); prepareHeights(xOffs, zOffs, blocks, 0, temperatures);//biomes, temperatures);
buildSurfaces(xOffs, zOffs, blocks, biomes); 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(); levelChunk->recalcHeightmap();
return levelChunk; return levelChunk;

View File

@@ -12,8 +12,8 @@ LevelData::LevelData()
dimension(Dimension::NORMAL), dimension(Dimension::NORMAL),
playerDataVersion(-1), playerDataVersion(-1),
storageVersion(0), storageVersion(0),
gameType(GameType::Default), gameType(GameType::Default), spawnMobs(false),
loadedPlayerTag(NULL) allowCheats(false), loadedPlayerTag(NULL)
{ {
//LOGI("ctor 1: %p\n", this); //LOGI("ctor 1: %p\n", this);
spawnMobs = (gameType == GameType::Survival); spawnMobs = (gameType == GameType::Survival);
@@ -21,8 +21,7 @@ LevelData::LevelData()
LevelData::LevelData( const LevelSettings& settings, const std::string& levelName, int generatorVersion /*= -1*/ ) LevelData::LevelData( const LevelSettings& settings, const std::string& levelName, int generatorVersion /*= -1*/ )
: seed(settings.getSeed()), : seed(settings.getSeed()),
gameType(settings.getGameType()), gameType(settings.getGameType()), allowCheats(settings.getAllowCheats()), levelName(levelName),
levelName(levelName),
xSpawn(128), xSpawn(128),
ySpawn(64), ySpawn(64),
zSpawn(128), zSpawn(128),
@@ -62,6 +61,7 @@ LevelData::LevelData( const LevelData& rhs )
playerDataVersion(rhs.playerDataVersion), playerDataVersion(rhs.playerDataVersion),
generatorVersion(rhs.generatorVersion), generatorVersion(rhs.generatorVersion),
spawnMobs(rhs.spawnMobs), spawnMobs(rhs.spawnMobs),
allowCheats(rhs.allowCheats),
loadedPlayerTag(NULL), loadedPlayerTag(NULL),
playerData(rhs.playerData) playerData(rhs.playerData)
{ {
@@ -84,6 +84,7 @@ LevelData& LevelData::operator=( const LevelData& rhs )
time = rhs.time; time = rhs.time;
dimension = rhs.dimension; dimension = rhs.dimension;
spawnMobs = rhs.spawnMobs; spawnMobs = rhs.spawnMobs;
allowCheats = rhs.allowCheats;
playerData = rhs.playerData; playerData = rhs.playerData;
playerDataVersion = rhs.playerDataVersion; playerDataVersion = rhs.playerDataVersion;
generatorVersion = rhs.generatorVersion; generatorVersion = rhs.generatorVersion;
@@ -161,6 +162,7 @@ void LevelData::setTagData( CompoundTag* tag, CompoundTag* playerTag )
if (!tag) return; if (!tag) return;
tag->putLong("RandomSeed", seed); tag->putLong("RandomSeed", seed);
tag->putInt("GameType", gameType); tag->putInt("GameType", gameType);
tag->putBoolean("AllowCommands", allowCheats);
tag->putInt("SpawnX", xSpawn); tag->putInt("SpawnX", xSpawn);
tag->putInt("SpawnY", ySpawn); tag->putInt("SpawnY", ySpawn);
tag->putInt("SpawnZ", zSpawn); tag->putInt("SpawnZ", zSpawn);
@@ -181,6 +183,7 @@ void LevelData::getTagData( const CompoundTag* tag )
if (!tag) return; if (!tag) return;
seed = (long)tag->getLong("RandomSeed"); seed = (long)tag->getLong("RandomSeed");
gameType = tag->getInt("GameType"); gameType = tag->getInt("GameType");
allowCheats = tag->getBoolean("AllowCommands");
xSpawn = tag->getInt("SpawnX"); xSpawn = tag->getInt("SpawnX");
ySpawn = tag->getInt("SpawnY"); ySpawn = tag->getInt("SpawnY");
zSpawn = tag->getInt("SpawnZ"); zSpawn = tag->getInt("SpawnZ");
@@ -362,3 +365,13 @@ void LevelData::setSpawnMobs( bool doSpawn )
{ {
spawnMobs = doSpawn; spawnMobs = doSpawn;
} }
bool LevelData::getAllowCheats() const
{
return allowCheats;
}
void LevelData::setAllowCheats( bool allow )
{
allowCheats = allow;
}

View File

@@ -72,6 +72,9 @@ public:
bool getSpawnMobs() const; bool getSpawnMobs() const;
void setSpawnMobs(bool doSpawn); void setSpawnMobs(bool doSpawn);
bool getAllowCheats() const;
void setAllowCheats(bool allow);
public: public:
PlayerData playerData; PlayerData playerData;
int playerDataVersion; int playerDataVersion;
@@ -89,6 +92,7 @@ private:
int gameType; int gameType;
int storageVersion; int storageVersion;
bool spawnMobs; bool spawnMobs;
bool allowCheats;
//@note: This version is never written or loaded to disk. The only purpose //@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. // is to use it in the level generator on server and clients.
int generatorVersion; int generatorVersion;

View File

@@ -165,7 +165,8 @@ void DoorTile::neighborChanged(Level* level, int x, int y, int z, int type) {
} }
if (spawn) { if (spawn) {
if (!level->isClientSide) { 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 { } else {
bool signal = level->hasNeighborSignal(x, y, z) || level->hasNeighborSignal(x, y + 1, z); 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 { } 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) { if (level->getTile(x, y - 1, z) != id) {
level->setTile(x, y, z, 0); level->setTile(x, y, z, 0);
if(material == Material::metal) { // no resource spawn here
popResource(level, x, y, z, ItemInstance(Item::door_iron));
} else {
popResource(level, x, y, z, ItemInstance(Item::door_wood));
}
} }
if (type > 0 && type != id) { if (type > 0 && type != id) {
neighborChanged(level, x, y - 1, z, type); 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) { 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; if (material == Material::metal) return Item::door_iron->id;
return Item::door_wood->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); 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) { int DoorTile::getDir(LevelSource* level, int x, int y, int z) {
return getCompositeData(level, x, y, z) & C_DIR_MASK; return getCompositeData(level, x, y, z) & C_DIR_MASK;
} }

View File

@@ -49,6 +49,9 @@ public:
int getResource(int data, Random* random); 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); 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); int getDir(LevelSource* level, int x, int y, int z);