From 2fc323639ad4a4301cd6cd2389dfb4533507a857 Mon Sep 17 00:00:00 2001 From: Michal Schiller Date: Tue, 10 Mar 2026 19:31:40 +0100 Subject: [PATCH] Fixes and enhancements from my MCPE repository. (https://github.com/mschiller890/mcpe64) --- .gitignore | 203 +++++++++++- CMakeLists.txt | 7 + build.ps1 | 302 ++++++++++++++++++ data/images/gui/logo/github.png | Bin 0 -> 5965 bytes data/lang/en_US.lang | 9 + project/android/AndroidManifest.xml | 4 +- project/android/assets/lang/en_US.lang | 1 + project/android/jni/Android.mk | 14 +- project/android/jni/Application.mk | 6 +- project/android_java/AndroidManifest.xml | 78 +++-- .../com/mojang/minecraftpe/MainActivity.java | 66 ++-- .../raknet/jni/RaknetSources/FileList.cpp | 2 +- src/AppPlatform.h | 2 + src/AppPlatform_android.h | 10 +- src/AppPlatform_glfw.cpp | 69 ++++ src/AppPlatform_glfw.h | 13 + src/AppPlatform_win32.h | 6 + src/NinecraftApp.cpp | 4 + src/SharedConstants.cpp | 8 +- src/client/Minecraft.cpp | 285 +++++++++-------- src/client/OptionStrings.cpp | 2 + src/client/OptionStrings.h | 3 +- src/client/Options.cpp | 24 +- src/client/Options.h | 15 +- src/client/OptionsFile.cpp | 20 +- src/client/gui/Gui.cpp | 166 +++++++++- src/client/gui/Gui.h | 6 + src/client/gui/Screen.cpp | 17 +- src/client/gui/Screen.h | 4 +- src/client/gui/components/OptionsGroup.cpp | 38 ++- src/client/gui/components/Slider.cpp | 25 +- src/client/gui/components/TextBox.cpp | 126 ++++---- src/client/gui/components/TextBox.h | 8 +- src/client/gui/screens/ConsoleScreen.cpp | 223 +++++++++++++ src/client/gui/screens/ConsoleScreen.h | 34 ++ src/client/gui/screens/CreditsScreen.cpp | 118 +++++++ src/client/gui/screens/CreditsScreen.h | 32 ++ src/client/gui/screens/OptionsScreen.cpp | 227 +++++++++---- src/client/gui/screens/OptionsScreen.h | 18 +- src/client/gui/screens/SelectWorldScreen.cpp | 81 +---- src/client/gui/screens/SelectWorldScreen.h | 3 - .../gui/screens/SimpleChooseLevelScreen.cpp | 280 ++++++++++------ .../gui/screens/SimpleChooseLevelScreen.h | 13 +- src/client/gui/screens/StartMenuScreen.cpp | 42 ++- src/client/gui/screens/StartMenuScreen.h | 1 + src/client/gui/screens/UsernameScreen.cpp | 128 ++++++++ src/client/gui/screens/UsernameScreen.h | 37 +++ .../screens/touch/TouchSelectWorldScreen.cpp | 19 +- .../screens/touch/TouchSelectWorldScreen.h | 3 - .../screens/touch/TouchStartMenuScreen.cpp | 111 +++++-- .../gui/screens/touch/TouchStartMenuScreen.h | 10 + src/client/player/LocalPlayer.cpp | 28 +- src/client/player/LocalPlayer.h | 6 + src/client/renderer/GameRenderer.cpp | 18 +- src/client/renderer/gles.h | 6 +- src/client/sound/Sound.h | 3 +- src/client/sound/SoundEngine.cpp | 5 +- src/client/sound/SoundEngine.h | 5 +- src/main_android.cpp | 11 +- src/main_android_java.cpp | 55 +++- src/main_glfw.h | 33 +- src/network/ServerSideNetworkHandler.cpp | 10 + src/network/ServerSideNetworkHandler.h | 6 +- src/network/packet/SetTimePacket.h | 5 +- src/network/packet/StartGamePacket.h | 5 +- src/platform/CThread.cpp | 3 +- src/platform/audio/SoundSystemAL.cpp | 14 +- src/platform/audio/SoundSystemSL.cpp | 10 +- src/world/level/Level.cpp | 2 +- 69 files changed, 2509 insertions(+), 639 deletions(-) create mode 100644 build.ps1 create mode 100644 data/images/gui/logo/github.png create mode 100644 src/client/gui/screens/ConsoleScreen.cpp create mode 100644 src/client/gui/screens/ConsoleScreen.h create mode 100644 src/client/gui/screens/CreditsScreen.cpp create mode 100644 src/client/gui/screens/CreditsScreen.h create mode 100644 src/client/gui/screens/UsernameScreen.cpp create mode 100644 src/client/gui/screens/UsernameScreen.h diff --git a/.gitignore b/.gitignore index e213499..bfd2b60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,202 @@ +# Build output build/ -linux-build/ -.cache/ \ No newline at end of file +out/ +bin/ +lib/ +cmake-build-*/ +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake +Makefile +*.cmake +!cmake/CPM.cmake + +# Compiled object files +*.o +*.obj +*.a +*.lib +*.so +*.dylib +*.dll +*.exe +*.out + +# CMake generated +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_CPack_Packages/ +*.cbp + +# Visual Studio +*.sdf +*.suo +*.user +*.userosscache +*.sln.docstates +*.VC.db +*.VC.opendb +.vs/ +ipch/ +x64/ +x86/ +Debug/ +Release/ +RelWithDebInfo/ +MinSizeRel/ + +# Xcode +*.xcworkspace +xcuserdata/ +*.xccheckout +*.moved-aside +DerivedData/ +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +# Android +*.apk +*.aab +*.ap_ +local.properties +.gradle/ +project/.gradle/ +project/android/bin/ +project/android/gen/ +project/android/obj/ +project/android_java/bin/ +project/android_java/gen/ +project/android_java/obj/ +project/android/libs/*.so + +# macOS +.DS_Store +.AppleDouble +.LSOverride +._* + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# Linux +*~ +.nfs* + +# CLion / JetBrains +.idea/ +*.iml +*.ipr +*.iws + +# Eclipse CDT +.project +.cproject +.settings/ + +# Logs +*.log + +# Ninja +.ninja_deps +.ninja_log +build.ninja + +# VS Code +.vscode/ +*.code-workspace + +# Visual Studio additional +*.ncb +*.aps +*.pdb +*.ilk +*.exp +*.iobj +*.ipdb +*.tlog +*.lastbuildstate +*.pch +*.ipch +*.VC.db-shm +*.VC.db-wal +*.svclog +*.vcxproj.user + +# CMake generated project files +ALL_BUILD.vcxproj +ALL_BUILD.vcxproj.filters +INSTALL.vcxproj +INSTALL.vcxproj.filters +PACKAGE.vcxproj +PACKAGE.vcxproj.filters +ZERO_CHECK.vcxproj +ZERO_CHECK.vcxproj.filters +MinecraftPE.vcxproj +MinecraftPE.vcxproj.filters +MinecraftPE.sln +CPackConfig.cmake +CPackSourceConfig.cmake +cpm-package-lock.cmake +MinecraftPE.dir/ +_deps/ + +# Test / coverage +Testing/ +CTestResults/ +*.gcno +*.gcda +*.gcov +coverage/ +lcov.info + +# Precompiled headers +*.gch + +# Compiler / linker intermediates +*.map +*.res +*.d + +# Patch / diff leftovers +*.orig +*.rej +*.patch + +# Backup / temp +*.bak +*.swp +*.swo +*.tmp +*~.nib + +# Tags +tags +TAGS +.tags +ctags + +# macOS extra +.Spotlight-V100 +.Trashes +.fseventsd +Icon\r + +# Windows extra +*.lnk +[Dd]esktop.ini + +# User / local config +options.txt +local.cmake +games/ + +# Dependency cache +vcpkg_installed/ +.cache/ +conan/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b7357e6..3fb3032 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,13 @@ if(PLATFORM STREQUAL "PLATFORM_GLFW") list(APPEND SOURCES "src/AppPlatform_glfw.cpp") endif() +# Explicitly list files added after the initial glob so they are always included +list(APPEND SOURCES + "src/client/gui/screens/ConsoleScreen.cpp" + "src/client/gui/screens/UsernameScreen.cpp" + "src/client/gui/screens/CreditsScreen.cpp" +) + add_executable(${PROJECT_NAME} ${SOURCES} "glad/src/glad.c" diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..c13697f --- /dev/null +++ b/build.ps1 @@ -0,0 +1,302 @@ +# ============================================================ +# MCPE 0.4.0 Android Build Script — from-scratch capable +# Works on a clean machine; creates all dirs, keystore and +# stub Java files automatically. +# +# Usage: +# .\build.ps1 # full build (NDK + Java + APK + install) +# .\build.ps1 -NoCpp # skip NDK rebuild (Java/assets changed) +# .\build.ps1 -NoJava # skip Java recompile (C++ changed only) +# .\build.ps1 -NoBuild # repackage + install only (no recompile) +# ============================================================ +param( + [switch]$NoCpp, + [switch]$NoJava, + [switch]$NoBuild +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +# ── Paths ──────────────────────────────────────────────────── +$repo = $PSScriptRoot +$apkbuild = "C:\apkbuild" +$ndk = "C:\android-ndk-r14b" +$sdkTools = "$env:LOCALAPPDATA\Android\Sdk\build-tools\35.0.0" +$androidJar = "$env:LOCALAPPDATA\Android\Sdk\platforms\android-36\android.jar" +$adb = "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" +$keystore = "$apkbuild\debug.keystore" +$pkg = "com.mojang.minecraftpe" + +# Auto-detect keytool from JAVA_HOME, then from common install locations +$keytool = if ($env:JAVA_HOME) { "$env:JAVA_HOME\bin\keytool.exe" } else { + $found = Get-ChildItem "C:\Program Files\Java","C:\Program Files\Eclipse Adoptium" ` + -Filter keytool.exe -Recurse -ErrorAction SilentlyContinue | + Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName + if (-not $found) { throw "keytool not found. Set JAVA_HOME or install a JDK." } + $found +} + +$jniDir = "$repo\project\android\jni" +$libSrc = "$repo\project\android\libs\arm64-v8a\libminecraftpe.so" +$libDst = "$apkbuild\lib\arm64-v8a\libminecraftpe.so" +$manifest = "$repo\project\android_java\AndroidManifest.xml" +$res = "$repo\project\android_java\res" +$javaSrc = "$repo\project\android_java\src" +$stubsDir = "$apkbuild\stubs" +$rJava = "$apkbuild\gen\R.java" +$classesDir = "$apkbuild\classes" +$dexOut = "$apkbuild\classes.dex" +$dataDir = "$repo\data" + +$unsigned = "$apkbuild\minecraftpe-unsigned.apk" +$aligned = "$apkbuild\minecraftpe-aligned.apk" +$signed = "$apkbuild\minecraftpe-debug.apk" + +Add-Type -Assembly "System.IO.Compression.FileSystem" + +function Write-Step([string]$msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan } +function Assert-ExitCode([string]$step) { + if ($LASTEXITCODE -ne 0) { Write-Host "FAILED: $step (exit $LASTEXITCODE)" -ForegroundColor Red; exit 1 } +} +function New-Dir([string]$path) { New-Item $path -ItemType Directory -Force | Out-Null } +function Write-Stub([string]$rel, [string]$content) { + $full = "$stubsDir\$rel" + New-Dir (Split-Path $full -Parent) + if (-not (Test-Path $full)) { [System.IO.File]::WriteAllText($full, $content); Write-Host " stub: $rel" } +} + +# ── 0. Bootstrap ───────────────────────────────────────────── +Write-Step "Bootstrap" + +New-Dir $apkbuild +New-Dir "$apkbuild\lib\arm64-v8a" +New-Dir "$apkbuild\gen" +New-Dir $stubsDir + +if (-not (Test-Path $keystore)) { + Write-Host " generating debug.keystore..." + $eap = $ErrorActionPreference; $ErrorActionPreference = "Continue" + & $keytool -genkeypair ` + -keystore $keystore -storepass android -keypass android ` + -alias androiddebugkey -keyalg RSA -keysize 2048 -validity 10000 ` + -dname "CN=Android Debug,O=Android,C=US" 2>&1 | Out-Null + $ErrorActionPreference = $eap + Assert-ExitCode "keytool" + Write-Host " keystore created" +} else { Write-Host " keystore OK" } + +Write-Stub "com\mojang\android\StringValue.java" "package com.mojang.android;`npublic interface StringValue { String getStringValue(); }`n" + +Write-Stub "com\mojang\android\licensing\LicenseCodes.java" "package com.mojang.android.licensing;`npublic class LicenseCodes { public static final int LICENSE_OK = 0; }`n" + +Write-Stub "com\mojang\android\EditTextAscii.java" @" +package com.mojang.android; +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.widget.EditText; +public class EditTextAscii extends EditText implements TextWatcher { + public EditTextAscii(Context c) { super(c); addTextChangedListener(this); } + public EditTextAscii(Context c, AttributeSet a) { super(c,a); addTextChangedListener(this); } + public EditTextAscii(Context c, AttributeSet a, int d) { super(c,a,d); addTextChangedListener(this); } + @Override public void onTextChanged(CharSequence s,int st,int b,int co){} + public void beforeTextChanged(CharSequence s,int st,int co,int aft){} + public void afterTextChanged(Editable e){ + String s=e.toString(),san=sanitize(s); + if(!s.equals(san))e.replace(0,e.length(),san); + } + static public String sanitize(String s){ + StringBuilder sb=new StringBuilder(); + for(int i=0;iSurvival?Survival:i); + int id=_type==Survival?R.string.gamemode_survival_summary:R.string.gamemode_creative_summary; + String desc=getContext().getString(id); + View v=getRootView().findViewById(R.id.labelGameModeDesc); + if(desc!=null&&v instanceof TextView)((TextView)v).setText(desc); + } + public String getStringValue(){return new String[]{"creative","survival"}[_type];} + static public String getStringForType(int i){int c=iSurvival?Survival:i);return new String[]{"creative","survival"}[c];} +} +"@ + +Write-Host " stubs OK" + +# ── 1. NDK build ───────────────────────────────────────────── +if (-not $NoCpp -and -not $NoBuild) { + Write-Step "NDK build (arm64-v8a)" + # NDK r14b on Windows hits the 32K CreateProcess limit with long paths. + # Work around it by building through a short junction C:\m -> repo root. + $junctionBase = "C:\m" + if (-not (Test-Path $junctionBase)) { + & cmd.exe /c "mklink /J `"$junctionBase`" `"$repo`"" | Out-Null + } + Push-Location "$junctionBase\project\android\jni" + $env:NDK_MODULE_PATH = "$junctionBase\project\lib_projects" + # run ndk-build and capture everything; let user see full output for debugging + $ndkOutput = & "$ndk\ndk-build.cmd" NDK_PROJECT_PATH="$junctionBase\project\android" APP_BUILD_SCRIPT="$junctionBase\project\android\jni\Android.mk" 2>&1 | Tee-Object -Variable ndkOutput + # dump entire output for diagnosis + Write-Host "---- NDK BUILD OUTPUT BEGIN ----" + $ndkOutput | ForEach-Object { Write-Host $_ } + Write-Host "---- NDK BUILD OUTPUT END ----" + # optionally highlight errors/warnings afterwards + $ndkOutput | Where-Object { $_ -match "error:|warning:|libminecraftpe|In file included" } + Pop-Location + Assert-ExitCode "ndk-build" + Copy-Item $libSrc $libDst -Force + Write-Host " .so -> $libDst" +} + +# ── 2. Java compile ────────────────────────────────────────── +if (-not $NoJava -and -not $NoBuild) { + Write-Step "Java compile" + + New-Dir (Split-Path $rJava -Parent) + & "$sdkTools\aapt.exe" package -f -M $manifest -S $res -I $androidJar -J "$apkbuild\gen" -F "$apkbuild\_rgen.apk" 2>&1 | Out-Null + Assert-ExitCode "aapt R.java" + Remove-Item "$apkbuild\_rgen.apk" -ea SilentlyContinue + + $srcs = @( + Get-ChildItem $javaSrc -Recurse -Filter "*.java" | Select-Object -Exp FullName + Get-ChildItem $stubsDir -Recurse -Filter "*.java" | Select-Object -Exp FullName + $rJava + ) + + Remove-Item $classesDir -Recurse -Force -ea SilentlyContinue + New-Dir $classesDir + $eap = $ErrorActionPreference; $ErrorActionPreference = "Continue" + $errors = & javac --release 8 -cp $androidJar -d $classesDir @srcs 2>&1 | + Where-Object { $_ -match "error:" } + $ErrorActionPreference = $eap + if ($errors) { Write-Host $errors -ForegroundColor Red; exit 1 } + Write-Host " javac OK" + + $classFiles = Get-ChildItem $classesDir -Recurse -Filter "*.class" | Select-Object -Exp FullName + & "$sdkTools\d8.bat" --min-api 21 --output $apkbuild $classFiles + Assert-ExitCode "d8" + Write-Host " d8 -> $dexOut" +} + +# ── 3. Package APK ─────────────────────────────────────────── +Write-Step "Package APK" +Remove-Item $unsigned,$aligned,$signed -ea SilentlyContinue + +& "$sdkTools\aapt.exe" package -f -M $manifest -S $res -I $androidJar -F $unsigned +Assert-ExitCode "aapt package" + +$zip = [System.IO.Compression.ZipFile]::Open($unsigned, 'Update') +try { + [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip,$dexOut,"classes.dex",[System.IO.Compression.CompressionLevel]::Fastest)|Out-Null + [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip,$libDst,"lib/arm64-v8a/libminecraftpe.so",[System.IO.Compression.CompressionLevel]::NoCompression)|Out-Null + Get-ChildItem $dataDir -Recurse -File | ForEach-Object { + $rel=$_.FullName.Substring("$dataDir\".Length).Replace('\','/') + [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip,$_.FullName,"assets/$rel",[System.IO.Compression.CompressionLevel]::NoCompression)|Out-Null + } +} finally { $zip.Dispose() } +Write-Host " APK assembled" + +& "$sdkTools\zipalign.exe" -p 4 $unsigned $aligned; Assert-ExitCode "zipalign" +& "$sdkTools\apksigner.bat" sign --ks $keystore --ks-pass pass:android --key-pass pass:android --out $signed $aligned; Assert-ExitCode "apksigner" +Write-Host " signed -> $signed" + +# ── 4. Install ─────────────────────────────────────────────── +Write-Step "Install" +& $adb shell am force-stop $pkg +& $adb uninstall $pkg 2>$null +& $adb install --no-incremental $signed +Assert-ExitCode "adb install" + +Write-Host "`nDone." -ForegroundColor Green \ No newline at end of file diff --git a/data/images/gui/logo/github.png b/data/images/gui/logo/github.png new file mode 100644 index 0000000000000000000000000000000000000000..456291686d5fc64c2b218129fbb4fea039c294f0 GIT binary patch literal 5965 zcmZ8l2RK|^w;p|zVFc035Q6ALi9U!Hee@ndh+d+1Mu{Xu??eltM`xl(CyACIN{C_j zNYqj9k?+6vKKGvI*|XoX&N^$K*=z5$-nHX(wN)V`3?v{B2%@g0tPk|7Hw{b({K_?c z2?RPEZ+%rbsAhs`3%J2|RMb)gf$CqAUf2=<_rxA*rrsbBdGAfbq2bmD2ZZ!KDkeS# z?hZbFwqEuiU0WwN9|3I@1lxT9VF6*`1n}(%K&jQpIyER2H-q6B&~pgG0j|CPG{_~!ca90!EP```Zmj|u_-50i*M_<*jE zh^P=w;lFlo#v~~G*H$`!Z!TQGGVWhZeJM!*{pT%VA)%Y67Q41*#OTTn5Qqk=uB?dg zx7uCOak=x&l4#%Ex9!XP*M$#xJnnw6DJsP5Fy#F0TWTDsc-!`wZNkcC#GZy1H?MUBQ#U--pC5UHIy~RTswlwi5C22kNrny26}2cH#V;f zmMp`5)SjK_v5%v?-X0-BFelqx#z&w*lS;9+h>B4c6B^KKocE)jaF20hwq|#mgTpK0 zl``MpM3@OIo7VVFTJ_<9DE4cvqN~C#wkM09Mi^8nMx9cO#dM%M17`5|dG~)ZEmC`e z?QVk?qMVB?F!^1CAjvLGtg3uA2T5rM%= zLWsANEZtbO+ft~&HB$~#tOvBP5K>8Yt59;b_H4!(ctA>&gcRivGpJ+He)qK!_wOnz9_#na*11Em z-=a0J?5_m1TOSBx_ztjQfBu_tX9iXw?J`Q_Xn(>XS@?tqYW!IdoM$Fn}HQ zIieqqdjf-KW)(4dSUCRbm862L?|&9~W;+JF=_scpAubhs7$@X#l_lH=6Jwn1(?_*_ znhZXScN|n*YQIFtr4wY47Fl2VXf6B58q1BveYwwEv+2;l>%bK)vn0gWAX%Lp56y-= zn=!MHs(WoU5^M6%^u;{@)!5svAbr3AEB4Ly_A5viaNC(M>rtheNVG#=;8XM1}r zlS*~bxcJy4ydgxI5tIr7m^6YcNv_2Em+=Z06`)75a~GksVLS{(Nfl$B%zWb+f7Pby76s|7zdi!|&)Dry$FHTQZT^TE-e_y6fqkr5y5w0E zBVrZ2pHlQO2dynbxL;_-J$JL{RZSAbKxws+Eu70j_6~iOPp8cC;Vs(r&pZrKwu#Ts zF$_p&KklM$)eC#WFZ)gny?Qch^Q1Qa*xpWdYVJUtd=Am+$=T3uadRJj`7^V!n7_R! ztm?~>`ssw}fyaBQ;8_`$g8LLLA!PU5+k~b-($^NShvx@P15tonaNW*U%YxW2@0F03_tTKQO{!}vxiHae zV1rcX8|P#@@fPe6Gwhla~XuKU#fyza)jSY==Yr~Wg4X!Uhc$889JmjJ!EMfad=`B`HmzRfeIZ% z9`S@{FXlHpRjw+-E~3KDINU!Tz}<`n{5pKk-PZ@-o#II%-KN*biV1B5B#69MYvdzO zHLkLt=~Xda)j`J$8C|3E1&b;3kz+073?(8mhzUd2CjBZn?+%E1=)B#gyhcBhU77GfWifL0C#@M3#k{AqKiHY^~;8$=Pm zWQM0z*&w1sY_XQp1a_g`^B<7^hQZe4gU3!_0f*Mgd;#RkU_rVAN$&|NVr$)_KKHi3y2v4%B~U@3K0BKbrhkRkpS+5nb-Md| zA4{DlNXAgbfJE60;O@?rEY(K$i*WZ%zujI*)RAF~4dr8SOn_Gqe8+nv;@^YTJCk|c z(JhSt51i@Fq?ZYoy$3u-f9zYX3&XGLsd`99sL#ig{>IV`rp`XtJw3qSK_IJQ_xcPP_$2Bcy?ve^`|GI^-Endxl4Ej=@g)X-tt&k;}PZQ+qkqT|SUf8f+fLbARfFE_;qu~*@!Q}^hfPnqpC2BWTk5ynaw9vhqaax85a26*S74o7 z%V_z95w>H@%VU}?h|`V8@fO-}jX!vlp4*6_kj)}-lfD1S2^Bu}RPTaxpTlWtrKjTx zim1CKSH_@V`E|8*f`fd;1wFgF`3fVW?{xCRGcMP%HlG+-x91{9P_y}ua=jgIaj|(z zL3@4&M+=o#UJC|Fo;_}yH-G79?B*zHo3ZU?+S9qgmQPfHQVa|BY@j;ksNyqk_wC?6 z?q<6yX}D_Ki>m4{bmXrIuUZ*fpqazr>Dh$KNB86fstNh@EhAr4t&m{=LJ)H3H9iHw zLJ2V7=%nuw(krbKdo89KvF==`hhizm@Z7rTwHhKCjAcS>PTh>L`4qb z?={G~e+r^x(q7-I*5{7uDB@F_x!j4RYzxXQK9%7<9<QOo-+7?j{(w|dp(yiIxu1=*aw$@pHK8TyI%fRWH z_uQ>(x=M_N#=cmWiIbBCzU^+<_(ZouTP-GOIolDI+^#(oT^Be*vtjS-QUcHoE6!<7 zPAa&wC0V+F#vExD6xW|qLP#7x3lpOcrXRgS4zuGartS>^#)J)72&Te^?%B^v0_YAr z^Q%{t`q-rU)X!kpcinTr}gt{>HPdeBN~V95-zN3G!!-tHk|=8L}`g#yrKpdI3c+r~^1@G@lN&Xb|u}j&6IBF8+6Rd7dP{6+R(1ScIhyb2}L-A#d z`ir6NyeeC?js|vZysv*=Rj_C2%QqX~k1I*287n5Y+yxb4Upn-iQGU4dk6^5UK;m@P7Z6q&RWYzy_% z6m?a~UFK_@Sn&0u?%yPh^3jULw7qWkJ+BB({QN}xeQbN#UR(Y>R`yQ?tcTaw!Sm=; zK+9q>=D&%J-Ju16P)0oHtIzN<7&3Z!!518Ka2%9S@l3#6qzg@E+}!4$!z%?`*LwO4a&8Y5qy5jGE?iB zXX!wd^V9uuSi@}vvZ1+ZXtTDow3qp9U}EY47P-P!Il33h(4L&;6lZ?6fiVK(?EKg+ zcIaQl3X?5K3Ng-#PjXhO_{f`s#F^)kDI6n*X8s93&S?-a6i)OfNGKQ9=W;_bBHf;d z2g?OG_m+=BKvU<>ulzt4m5ceD?n|WT?5nF_SK+YoNbwBelQjKWxlAQrCo=~ijaT}ewHw9 z(I|Gm-CiOB={Cc%97_xOQ~HyQ0hHJZuTr}7aw+U$zr!m>19kL| z`;e9k?kC#2(#7pXC|wx!bK1m!HvZeOo1ZwWrMG8y%WF2@kUT!h6G2=T&W3IdWEU}d zi84kFslVfm^Mua>JW_O1=GYm{$PpKRDXrBv1^IwNzmMsXE(1b$fpm@1_OG&RK*fiq zUlK70O0d^DF%`iO(@|P- za+)NM7(L7Rm2CGGTISosn$K$26}+e5c2XhUK)ZOAP*+_tDSH2_oY$kKVBUD+JVLsr zPOY>MQY5Muu9C2sl~6HppqwVeme#Re=N#9qj1pgxt+u5ea_ADJgU=O0=EHru!o;^+|EgN3!PN>GuN}LonsL2kHXvZoAh* za;3@Cfr8p2__WZ2nyClZ7D=LkbR(FQmNFED*u57d!b4R{Ix}Kvq%rq2(IVGSO zP0NnFHFFE`?BQ{|(eTHlot{l{6tts70ZwLCZo}jVCZ_ z(oU+~hSWa;$urQkWgzahFwL2o3ofTns5*BAQ+#s2c1NT6^`^_s*u8({O5DwRg}&PW zB{A1L;B4a;UrS-+s4A)jydU=G>JvkF)6t(UwL$Is!eG2YVw+un-p`8ADfnQXOAl{` z8pdOSDnIL*QqQeI%I3`lDypk{Lq8R>Vt=Y!Nf;t2mlK9}i1Ls;*DG_KiMyH7>Hike z9D5teEbutCL2O0896V$+N~`=L%KC)b1eBa0R3MOx;JbWL`-{R3`|;zz)XQ4`cc$3=88o%xYmciT?4n<0&ZF2UrAg>L`zU=`Egg-xL_$)P|$h z->|7aE-zOE3e}u*V!{}!1TNRqt#hIHSghE^#>bH5bF}=mmD@zZw2@^geQ!cj>lb~` z98s+ue(q{2%k;Snt78Fo*OaWUUg&@>lXQtv{wf&L2Rk4ATYIn0E~NVJEVqM4@qpS2 z*1TPVb4mqjnsp-5-urX*(F=rAxOueS<9@j{<-Q{13!Ez4$|7arYJ$SYU@ZBpF2gb5Q$o&$d^eH+qGwA@d!B!aF)Req`qK0<9hfMSqz^md>OkkqTi$Kw!#H<~cnt z61&ub(BnNXD$U_9!Nb>lF>i39M_~A?Jc-w`@^5lXNDx_(bShg=cy{l~rt)oMt9I&* z01IUoic!Q$C3AA@UzCqg_jBKYqYnzGDtvWJMcxn4Y!qCd0i@33B=tzK`kaS@_68E@ z?u3r0A*Vhqef{>Z36L_w)s_ru5*ihA9UGQ(=mDcsvc49M5CzfUkXxo+{Q<5C+p5jy zJfq6skeje$-m)z1JrS_NrF0RIA-dW59bhl^MP~%|ywRwYZY>_`REoQW%hD$#(b@W0yuRt7IK?E@HB8QMfAfFxnY~q)dSjr1to|88<4*{!5=X z{S2#O{*;~mLeH-)F38+aFCb>5&l}me%v9CpPd>@;rXybkJvMLJ)km3-27TT#Bd?h= zI2I+*0~m{v<<}F7$ux>1;7rF8C`9n<(gqj}s3oI0@Q-u+by1BLK}Wyt%Q&$Jm2d3- z!%PL7=9J-~MS&AQ=c3LNY>q&n)!v{mgdu%z@u=WMLCn%zl<**(UE}sO4*pJXY>dP3 R(9MCcx{9`P4csR3zX0r5A - + diff --git a/project/android/assets/lang/en_US.lang b/project/android/assets/lang/en_US.lang index 8f2aea8..e6bfe4c 100755 --- a/project/android/assets/lang/en_US.lang +++ b/project/android/assets/lang/en_US.lang @@ -170,6 +170,7 @@ options.guiScale.auto=Auto options.guiScale.small=Small options.guiScale.normal=Normal options.guiScale.large=Large +options.guiScale.larger=Larger options.advancedOpengl=Advanced OpenGL options.renderClouds=Clouds options.farWarning1=A 64 bit Java installation is recommended diff --git a/project/android/jni/Android.mk b/project/android/jni/Android.mk index 41e57f5..866e870 100755 --- a/project/android/jni/Android.mk +++ b/project/android/jni/Android.mk @@ -4,7 +4,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := minecraftpe LOCAL_SRC_FILES := ../../../src/main.cpp \ - ../../../src/main_android.cpp \ + ../../../src/main_android_java.cpp \ ../../../src/platform/audio/SoundSystemSL.cpp \ ../../../src/platform/input/Controller.cpp \ ../../../src/platform/input/Keyboard.cpp \ @@ -40,6 +40,7 @@ LOCAL_SRC_FILES := ../../../src/main.cpp \ ../../../src/client/gui/components/ScrolledSelectionList.cpp \ ../../../src/client/gui/components/ScrollingPane.cpp \ ../../../src/client/gui/components/Slider.cpp \ +../../../src/client/gui/components/TextBox.cpp \ ../../../src/client/gui/components/SmallButton.cpp \ ../../../src/client/gui/Font.cpp \ ../../../src/client/gui/Gui.cpp \ @@ -48,6 +49,10 @@ LOCAL_SRC_FILES := ../../../src/main.cpp \ ../../../src/client/gui/screens/ScreenChooser.cpp \ ../../../src/client/gui/screens/ArmorScreen.cpp \ ../../../src/client/gui/screens/ChatScreen.cpp \ +../../../src/client/gui/screens/ChooseLevelScreen.cpp \ +../../../src/client/gui/screens/SimpleChooseLevelScreen.cpp \ +../../../src/client/gui/screens/ConsoleScreen.cpp \ +../../../src/client/gui/screens/UsernameScreen.cpp \ ../../../src/client/gui/screens/ConfirmScreen.cpp \ ../../../src/client/gui/screens/ChestScreen.cpp \ ../../../src/client/gui/screens/DeathScreen.cpp \ @@ -56,7 +61,8 @@ LOCAL_SRC_FILES := ../../../src/main.cpp \ ../../../src/client/gui/screens/IngameBlockSelectionScreen.cpp \ ../../../src/client/gui/screens/JoinGameScreen.cpp \ ../../../src/client/gui/screens/OptionsScreen.cpp \ -../../../src/client/gui/screens/PauseScreen.cpp \ + ../../../src/client/gui/screens/CreditsScreen.cpp \ + ../../../src/client/gui/screens/PauseScreen.cpp \ ../../../src/client/gui/screens/ProgressScreen.cpp \ ../../../src/client/gui/screens/RenameMPLevelScreen.cpp \ ../../../src/client/gui/screens/SelectWorldScreen.cpp \ @@ -245,7 +251,9 @@ LOCAL_SRC_FILES := ../../../src/main.cpp \ ../../../src/world/level/tile/entity/FurnaceTileEntity.cpp \ ../../../src/world/phys/HitResult.cpp -LOCAL_CFLAGS := -Wno-psabi $(LOCAL_CFLAGS) +LOCAL_CFLAGS := -DPLATFORM_ANDROID -DPRE_ANDROID23 -Wno-narrowing $(LOCAL_CFLAGS) +LOCAL_CPPFLAGS := -std=c++11 +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../src #LOCAL_CFLAGS := -DANDROID_PUBLISH -DDEMO_MODE $(LOCAL_CFLAGS) #LOCAL_CFLAGS := -DANDROID_PUBLISH $(LOCAL_CFLAGS) diff --git a/project/android/jni/Application.mk b/project/android/jni/Application.mk index 4d20cc6..bea0653 100755 --- a/project/android/jni/Application.mk +++ b/project/android/jni/Application.mk @@ -1,5 +1,5 @@ -APP_PLATFORM := android-9 -APP_STL := stlport_static +APP_PLATFORM := android-21 +APP_STL := gnustl_static APP_OPTIM := release -APP_ABI := armeabi-v7a +APP_ABI := arm64-v8a #APP_ABI := armeabi-v7a x86 diff --git a/project/android_java/AndroidManifest.xml b/project/android_java/AndroidManifest.xml index 5860076..bb7580f 100755 --- a/project/android_java/AndroidManifest.xml +++ b/project/android_java/AndroidManifest.xml @@ -2,43 +2,73 @@ - + android:versionName="0.4.0"> - + - + + - + - + - + - - + + - - - - - - + + + + + + + + + + + + - + \ No newline at end of file diff --git a/project/android_java/src/com/mojang/minecraftpe/MainActivity.java b/project/android_java/src/com/mojang/minecraftpe/MainActivity.java index 200274c..64cab75 100755 --- a/project/android_java/src/com/mojang/minecraftpe/MainActivity.java +++ b/project/android_java/src/com/mojang/minecraftpe/MainActivity.java @@ -40,7 +40,6 @@ import android.os.Vibrator; import android.preference.PreferenceManager; import android.util.DisplayMetrics; import android.util.Log; -import android.view.Display; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -58,6 +57,8 @@ public class MainActivity extends Activity { private GLView _glView; public float invScale = 1.0f;// / 1.5f; + private int _screenWidth = 0; + private int _screenHeight = 0; Vector _touchEvents = new Vector(); Vector _keyEvents = new Vector(); @@ -67,8 +68,13 @@ public class MainActivity extends Activity { setVolumeControlStream(AudioManager.STREAM_MUSIC); super.onCreate(savedInstanceState); nativeRegisterThis(); - - nativeOnCreate(); + + // Cache screen dimensions once to avoid re-entrant getDisplayMetrics() callbacks + android.util.DisplayMetrics _dm = getResources().getDisplayMetrics(); + _screenWidth = Math.max(_dm.widthPixels, _dm.heightPixels); + _screenHeight = Math.min(_dm.widthPixels, _dm.heightPixels); + + nativeOnCreate(_screenWidth, _screenHeight); _glView = new GLView(getApplication(), this); //_glView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); @@ -105,15 +111,22 @@ public class MainActivity extends Activity { } public boolean isTouchscreen() { return true; } + public boolean supportsTouchscreen() { return true; } static public boolean isXperiaPlay() { return false; } static private boolean _isPowerVr = false; public void setIsPowerVR(boolean status) { MainActivity._isPowerVr = status; } static public boolean isPowerVR() { return _isPowerVr; } + @SuppressWarnings("deprecation") public void vibrate(int milliSeconds) { - Vibrator v = (Vibrator)this.getSystemService(VIBRATOR_SERVICE); - v.vibrate(milliSeconds); + Vibrator v = (Vibrator) getSystemService(VIBRATOR_SERVICE); + if (android.os.Build.VERSION.SDK_INT >= 26) { + v.vibrate(android.os.VibrationEffect.createOneShot(milliSeconds, + android.os.VibrationEffect.DEFAULT_AMPLITUDE)); + } else { + v.vibrate(milliSeconds); + } } private void createAlertDialog(boolean hasOkButton, boolean hasCancelButton, boolean preventBackKey) { @@ -144,9 +157,16 @@ public class MainActivity extends Activity { @Override public void onWindowFocusChanged(boolean hasFocus) { - // TODO Auto-generated method stub - //System.out.println("Focus has changed. Has Focus? " + hasFocus); super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } } @Override @@ -184,9 +204,12 @@ public class MainActivity extends Activity { return; } - if (event.getAction() == KeyEvent.ACTION_DOWN) + if (event.getAction() == KeyEvent.ACTION_DOWN) { nativeOnKeyDown(keyCode); - else if (event.getAction() == KeyEvent.ACTION_UP) + int unicodeChar = event.getUnicodeChar(event.getMetaState()); + if (unicodeChar > 0) + nativeTextChar(unicodeChar); + } else if (event.getAction() == KeyEvent.ACTION_UP) nativeOnKeyUp(keyCode); } @@ -347,22 +370,15 @@ public class MainActivity extends Activity { } public int getScreenWidth() { - Display display = ((WindowManager)this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - int out = Math.max(display.getWidth(), display.getHeight()); - //System.out.println("getwidth: " + out); - return out; + return _screenWidth; } public int getScreenHeight() { - Display display = ((WindowManager)this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - int out = Math.min(display.getWidth(), display.getHeight()); - //System.out.println("getheight: " + out); - return out; + return _screenHeight; } public float getPixelsPerMillimeter() { - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); + android.util.DisplayMetrics metrics = getResources().getDisplayMetrics(); return (metrics.xdpi + metrics.ydpi) * 0.5f / 25.4f; } @@ -465,8 +481,8 @@ public class MainActivity extends Activity { MainActivity.this.mDialog.show(); MainActivity.this.mDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - MainActivity.this.mDialog.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); - //MainActivity.this.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + MainActivity.this.mDialog.getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + //MainActivity.this.getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } }); } @@ -516,6 +532,11 @@ public class MainActivity extends Activity { public void buyGame() {} + public int getKeyFromKeyCode(int keyCode, int metaState, int deviceId) { + android.view.KeyCharacterMap kcm = android.view.KeyCharacterMap.load(deviceId); + return kcm.get(keyCode, metaState); + } + public String getPlatformStringVar(int id) { if (id == 0) return android.os.Build.MODEL; return null; @@ -604,10 +625,11 @@ public class MainActivity extends Activity { // native void nativeRegisterThis(); native void nativeUnregisterThis(); - native static void nativeOnCreate(); + native static void nativeOnCreate(int screenWidth, int screenHeight); native static void nativeOnDestroy(); native static void nativeOnKeyDown(int key); native static void nativeOnKeyUp(int key); + native static void nativeTextChar(int unicodeChar); native static boolean nativeHandleBack(boolean isDown); native static void nativeMouseDown(int pointerId, int buttonId, float x, float y); native static void nativeMouseUp(int pointerId, int buttonId, float x, float y); diff --git a/project/lib_projects/raknet/jni/RaknetSources/FileList.cpp b/project/lib_projects/raknet/jni/RaknetSources/FileList.cpp index f4345c2..bd12c63 100755 --- a/project/lib_projects/raknet/jni/RaknetSources/FileList.cpp +++ b/project/lib_projects/raknet/jni/RaknetSources/FileList.cpp @@ -5,7 +5,7 @@ #include // RAKNET_DEBUG_PRINTF #include "RakAssert.h" #if defined(ANDROID) -#include +// not needed and unavailable on arm64 #elif defined(_WIN32) || defined(__CYGWIN__) #include diff --git a/src/AppPlatform.h b/src/AppPlatform.h index 60d5d96..04fb166 100755 --- a/src/AppPlatform.h +++ b/src/AppPlatform.h @@ -119,6 +119,8 @@ public: virtual void buyGame() {} + virtual void openURL(const std::string& url) {} + virtual void finish() {} virtual bool supportsTouchscreen() { return false; } diff --git a/src/AppPlatform_android.h b/src/AppPlatform_android.h index 54304dd..f2e7205 100755 --- a/src/AppPlatform_android.h +++ b/src/AppPlatform_android.h @@ -104,6 +104,7 @@ public: int getScreenWidth() { return _screenWidth; } int getScreenHeight() { return _screenHeight; } + void setScreenDimensions(int w, int h) { _screenWidth = w; _screenHeight = h; } // Note, this has to be called from the main thread (e.g. do it from JNI_onLoad) // Somewhere between calling this, and calling the AppPlatform methods, @@ -192,14 +193,20 @@ public: // be rewritten later on anyway int initConsts() { + LOGI("initConsts: start\n"); JVMAttacher ta(_vm); JNIEnv* env = ta.getEnv(); + LOGI("initConsts: getting method IDs\n"); jmethodID fWidth = env->GetMethodID( _activityClass, "getScreenWidth", "()I"); + LOGI("initConsts: got fWidth=%p\n", fWidth); jmethodID fHeight = env->GetMethodID( _activityClass, "getScreenHeight", "()I"); + LOGI("initConsts: got fHeight=%p, calling getScreenWidth\n", fHeight); _screenWidth = env->CallIntMethod(instance, fWidth); + LOGI("initConsts: screenWidth=%d, calling getScreenHeight\n", _screenWidth); _screenHeight = env->CallIntMethod(instance, fHeight); + LOGI("initConsts: screenHeight=%d, done\n", _screenHeight); } void tick() { @@ -376,8 +383,9 @@ public: JVMAttacher ta(_vm); JNIEnv* env = ta.getEnv(); + std::string path = textureFolder ? "images/" + filename : filename; jintArray arr = (jintArray)env->CallObjectMethod( - instance, _getImageData, env->NewStringUTF(filename.c_str())); + instance, _getImageData, env->NewStringUTF(path.c_str())); if (!arr) return TextureData(); diff --git a/src/AppPlatform_glfw.cpp b/src/AppPlatform_glfw.cpp index 01fd7b4..8067649 100755 --- a/src/AppPlatform_glfw.cpp +++ b/src/AppPlatform_glfw.cpp @@ -1,5 +1,74 @@ #include "AppPlatform_glfw.h" +#ifdef WIN32 +#include +#endif + +// Loader that tries GLFW first, then falls back to opengl32.dll for any +// function that glfwGetProcAddress can't resolve. +void* winGLLoader(const char* name) { + void* p = (void*)glfwGetProcAddress(name); +#ifdef WIN32 + if (!p) { + static HMODULE opengl32 = LoadLibraryA("opengl32.dll"); + if (opengl32) p = (void*)GetProcAddress(opengl32, name); + } +#endif + return p; +} + +// ----------------------------------------------------------------------- +// Compatibility wrappers for OpenGL ES functions absent from desktop GL. +// The desktop equivalents use double parameters and are not declared in +// the GLES1 GLAD header, so look them up at runtime via winGLLoader. +// ----------------------------------------------------------------------- +static void APIENTRY compat_glDepthRangef(GLfloat n, GLfloat f) { + typedef void(APIENTRY *fn_t)(double, double); + static fn_t fn = (fn_t)winGLLoader("glDepthRange"); + if (fn) fn((double)n, (double)f); +} +static void APIENTRY compat_glClearDepthf(GLfloat d) { + typedef void(APIENTRY *fn_t)(double); + static fn_t fn = (fn_t)winGLLoader("glClearDepth"); + if (fn) fn((double)d); +} +static void APIENTRY compat_glOrthof(GLfloat l, GLfloat r, GLfloat b, + GLfloat t, GLfloat n, GLfloat f) { + typedef void(APIENTRY *fn_t)(double,double,double,double,double,double); + static fn_t fn = (fn_t)winGLLoader("glOrtho"); + if (fn) fn(l, r, b, t, n, f); +} +static void APIENTRY compat_glFrustumf(GLfloat l, GLfloat r, GLfloat b, + GLfloat t, GLfloat n, GLfloat f) { + typedef void(APIENTRY *fn_t)(double,double,double,double,double,double); + static fn_t fn = (fn_t)winGLLoader("glFrustum"); + if (fn) fn(l, r, b, t, n, f); +} + +// glFogx / glFogxv are OpenGL ES fixed-point variants that don't exist in +// desktop opengl32.dll. Wrap them via the float equivalents. For fog-mode +// enum params the GLfixed value IS the enum integer, so the cast is exact. +static void APIENTRY compat_glFogx(GLenum pname, GLfixed param) { + glFogf(pname, (GLfloat)param); +} +static void APIENTRY compat_glFogxv(GLenum pname, const GLfixed* params) { + // Only GL_FOG_COLOR uses an array; convert each element. + GLfloat fp[4] = { + (GLfloat)params[0], (GLfloat)params[1], + (GLfloat)params[2], (GLfloat)params[3] + }; + glFogfv(pname, fp); +} + +void glPatchDesktopCompat() { + if (!glad_glDepthRangef) glad_glDepthRangef = compat_glDepthRangef; + if (!glad_glClearDepthf) glad_glClearDepthf = compat_glClearDepthf; + if (!glad_glOrthof) glad_glOrthof = compat_glOrthof; + if (!glad_glFrustumf) glad_glFrustumf = compat_glFrustumf; + if (!glad_glFogx) glad_glFogx = compat_glFogx; + if (!glad_glFogxv) glad_glFogxv = compat_glFogxv; +} + float AppPlatform_glfw::getPixelsPerMillimeter() { GLFWmonitor* monitor = glfwGetPrimaryMonitor(); diff --git a/src/AppPlatform_glfw.h b/src/AppPlatform_glfw.h index a9c5b3e..8d3ee28 100755 --- a/src/AppPlatform_glfw.h +++ b/src/AppPlatform_glfw.h @@ -10,6 +10,10 @@ #include #include #include +#ifdef _WIN32 +#include +#include +#endif static void png_funcReadFile(png_structp pngPtr, png_bytep data, png_size_t length) { ((std::istream*)png_get_io_ptr(pngPtr))->read((char*)data, length); @@ -123,7 +127,16 @@ public: virtual bool supportsTouchscreen() { return true; } virtual bool hasBuyButtonWhenInvalidLicense() { return false; } + virtual void openURL(const std::string& url) { +#ifdef _WIN32 + ShellExecuteA(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); +#endif + } + private: }; +void* winGLLoader(const char* name); +void glPatchDesktopCompat(); + #endif /*APPPLATFORM_GLFW_H__*/ diff --git a/src/AppPlatform_win32.h b/src/AppPlatform_win32.h index 8faca4a..dd19f78 100755 --- a/src/AppPlatform_win32.h +++ b/src/AppPlatform_win32.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include static void png_funcReadFile(png_structp pngPtr, png_bytep data, png_size_t length) { ((std::istream*)png_get_io_ptr(pngPtr))->read((char*)data, length); @@ -122,6 +124,10 @@ public: virtual bool supportsTouchscreen(); virtual bool hasBuyButtonWhenInvalidLicense(); + virtual void openURL(const std::string& url) { + ShellExecuteA(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL); + } + private: }; diff --git a/src/NinecraftApp.cpp b/src/NinecraftApp.cpp index cdc24d8..be52b16 100755 --- a/src/NinecraftApp.cpp +++ b/src/NinecraftApp.cpp @@ -18,6 +18,7 @@ //#include "world/level/storage/FolderMethods.h" #ifndef STANDALONE_SERVER #include "client/gui/screens/StartMenuScreen.h" +#include "client/gui/screens/UsernameScreen.h" #endif #include "client/player/LocalPlayer.h" #ifndef STANDALONE_SERVER @@ -110,6 +111,9 @@ void NinecraftApp::init() #ifndef STANDALONE_SERVER LOGI("This: %p\n", this); screenChooser.setScreen(SCREEN_STARTMENU); + if (options.username.empty()) { + setScreen(new UsernameScreen()); + } #else user->name = "Server"; hostMultiplayer(); diff --git a/src/SharedConstants.cpp b/src/SharedConstants.cpp index 676cc0b..4a31175 100755 --- a/src/SharedConstants.cpp +++ b/src/SharedConstants.cpp @@ -4,7 +4,13 @@ namespace Common { std::string getGameVersionString(const std::string& versionSuffix /* = "" */) { - return std::string("v0.6.1") + versionSuffix + " alpha"; + std::string result = std::string("v0.6.1") + versionSuffix; + // append 64-bit port marker only on Android 64‑bit targets + #if defined(ANDROID) && (defined(__aarch64__) || defined(__x86_64__)) + result += " (64-bit port)"; + #endif + result += " alpha"; + return result; } }; diff --git a/src/client/Minecraft.cpp b/src/client/Minecraft.cpp index dbc7091..96c9051 100755 --- a/src/client/Minecraft.cpp +++ b/src/client/Minecraft.cpp @@ -31,6 +31,7 @@ #include "gui/Screen.h" #include "gui/Font.h" #include "gui/screens/RenameMPLevelScreen.h" +#include "gui/screens/ConsoleScreen.h" #include "sound/SoundEngine.h" #endif #include "../platform/CThread.h" @@ -490,15 +491,14 @@ void Minecraft::update() { checkGlError("Update finished"); if (options.renderDebug) { +#ifndef PLATFORM_DESKTOP if (!PerfTimer::enabled) { PerfTimer::reset(); PerfTimer::enabled = true; } - - //TIMER_PUSH("debugfps"); _perfRenderer->renderFpsMeter(1); checkGlError("render debug"); - //TIMER_POP(); +#endif } else { PerfTimer::enabled = false; } @@ -721,10 +721,13 @@ void Minecraft::tickInput() { #endif } #endif - #if defined(PLATFORM_DESKTOP) || defined (_WIN32) + #if defined(PLATFORM_DESKTOP) if (key == Keyboard::KEY_E) { screenChooser.setScreen(SCREEN_BLOCKSELECTION); } + if (!screen && key == Keyboard::KEY_T && level) { + setScreen(new ConsoleScreen()); + } if (!screen && key == Keyboard::KEY_O || key == 250) { releaseMouse(); } @@ -737,109 +740,99 @@ void Minecraft::tickInput() { printf("%d\t%f\n", i, noise.grad2(i, 3, 8)); */ } - - // Change distance - if (key == Keyboard::KEY_F) + #endif + #if defined(WIN32) + if (key == Keyboard::KEY_F) { + options.isFlying = !options.isFlying; + player->noPhysics = options.isFlying; + } + + + if (key == Keyboard::KEY_L) options.viewDistance = (options.viewDistance + 1) % 4; - #ifdef CHEATS - if (key == Keyboard::KEY_J) { - options.isFlying = !options.isFlying; - player->noPhysics = options.isFlying; + if (key == Keyboard::KEY_U) { + onGraphicsReset(); + player->heal(100); + } + + if (key == Keyboard::KEY_B || key == 108) // Toggle the game mode + setIsCreativeMode(!isCreativeMode()); + + if (key == Keyboard::KEY_P) // Step forward in time + level->setTime( level->getTime() + 1000); + + if (key == Keyboard::KEY_G) { + setScreen(new ArmorScreen()); + /* + std::vector& boxs = level->getCubes(NULL, AABB(128.1f, 73, 128.1f, 128.9f, 74.9f, 128.9f)); + LOGI("boxes: %d\n", (int)boxs.size()); + */ + } + + if (key == Keyboard::KEY_Y) { + textures->reloadAll(); + player->hurtTo(2); + } + if (key == Keyboard::KEY_Z || key == 108) { + for (int i = 0; i < 1; ++i) { + Mob* mob = NULL; + int forceId = 0;//MobTypes::Sheep; + + int types[] = { + MobTypes::Sheep, + MobTypes::Pig, + MobTypes::Chicken, + MobTypes::Cow, + }; + + int mobType = (forceId > 0)? forceId : types[Mth::random(sizeof(types) / sizeof(int))]; + mob = MobFactory::CreateMob(mobType, level); + + //((Animal*)mob)->setAge(-1000); + float dx = 4 - 8 * Mth::random() + 4 * Mth::sin(Mth::DEGRAD * player->yRot); + float dz = 4 - 8 * Mth::random() + 4 * Mth::cos(Mth::DEGRAD * player->yRot); + if (mob && !MobSpawner::addMob(level, mob, player->x + dx, player->y, player->z + dz, Mth::random()*360, 0, true)) + delete mob; } + } - if (key == Keyboard::KEY_O) { - useAmbientOcclusion = !useAmbientOcclusion; - options.ambientOcclusion = useAmbientOcclusion; - levelRenderer->allChanged(); + if (key == Keyboard::KEY_X) { + const EntityList& entities = level->getAllEntities(); + for (int i = entities.size()-1; i >= 0; --i) { + Entity* e = entities[i]; + if (!e->isPlayer()) + level->removeEntity(e); } + } - if (key == Keyboard::KEY_L) - options.viewDistance = (options.viewDistance + 1) % 4; + if (key == Keyboard::KEY_C /*|| key == 4*/) { + player->inventory->clearInventoryWithDefault(); + // @todo: Add saving here for benchmarking + } + if (key == Keyboard::KEY_H) { + setScreen( new PrerenderTilesScreen() ); + } - if (key == Keyboard::KEY_U) { - onGraphicsReset(); - player->heal(100); + if (key == Keyboard::KEY_O) { + for (int i = Inventory::MAX_SELECTION_SIZE; i < player->inventory->getContainerSize(); ++i) + if (player->inventory->getItem(i)) + player->inventory->dropSlot(i, false); + } + if (key == Keyboard::KEY_F3) { + options.renderDebug = !options.renderDebug; + } + if (key == Keyboard::KEY_M) { + options.difficulty = (options.difficulty == Difficulty::PEACEFUL)? + Difficulty::NORMAL : Difficulty::PEACEFUL; + //setIsCreativeMode( !isCreativeMode() ); + } + + if (options.renderDebug) { + if (key >= '0' && key <= '9') { + _perfRenderer->debugFpsMeterKeyPress(key - '0'); } - - if (key == Keyboard::KEY_B || key == 108) // Toggle the game mode - setIsCreativeMode(!isCreativeMode()); - - if (key == Keyboard::KEY_P) // Step forward in time - level->setTime( level->getTime() + 1000); - - if (key == Keyboard::KEY_G) { - setScreen(new ArmorScreen()); - /* - std::vector& boxs = level->getCubes(NULL, AABB(128.1f, 73, 128.1f, 128.9f, 74.9f, 128.9f)); - LOGI("boxes: %d\n", (int)boxs.size()); - */ - } - - if (key == Keyboard::KEY_Y) { - textures->reloadAll(); - player->hurtTo(2); - } - if (key == Keyboard::KEY_Z || key == 108) { - for (int i = 0; i < 1; ++i) { - Mob* mob = NULL; - int forceId = 0;//MobTypes::Sheep; - - int types[] = { - MobTypes::Sheep, - MobTypes::Pig, - MobTypes::Chicken, - MobTypes::Cow, - }; - - int mobType = (forceId > 0)? forceId : types[Mth::random(sizeof(types) / sizeof(int))]; - mob = MobFactory::CreateMob(mobType, level); - - //((Animal*)mob)->setAge(-1000); - float dx = 4 - 8 * Mth::random() + 4 * Mth::sin(Mth::DEGRAD * player->yRot); - float dz = 4 - 8 * Mth::random() + 4 * Mth::cos(Mth::DEGRAD * player->yRot); - if (mob && !MobSpawner::addMob(level, mob, player->x + dx, player->y, player->z + dz, Mth::random()*360, 0, true)) - delete mob; - } - } - - if (key == Keyboard::KEY_X) { - const EntityList& entities = level->getAllEntities(); - for (int i = entities.size()-1; i >= 0; --i) { - Entity* e = entities[i]; - if (!e->isPlayer()) - level->removeEntity(e); - } - } - - if (key == Keyboard::KEY_C /*|| key == 4*/) { - player->inventory->clearInventoryWithDefault(); - // @todo: Add saving here for benchmarking - } - if (key == Keyboard::KEY_H) { - setScreen( new PrerenderTilesScreen() ); - } - - if (key == Keyboard::KEY_O) { - for (int i = Inventory::MAX_SELECTION_SIZE; i < player->inventory->getContainerSize(); ++i) - if (player->inventory->getItem(i)) - player->inventory->dropSlot(i, false); - } - if (key == Keyboard::KEY_F3) { - options.renderDebug = !options.renderDebug; - } - if (key == Keyboard::KEY_M) { - options.difficulty = (options.difficulty == Difficulty::PEACEFUL)? - Difficulty::NORMAL : Difficulty::PEACEFUL; - //setIsCreativeMode( !isCreativeMode() ); - } - - if (options.renderDebug) { - if (key >= '0' && key <= '9') { - _perfRenderer->debugFpsMeterKeyPress(key - '0'); - } - } - #endif + } #endif #ifndef PLATFORM_DESKTOP @@ -871,35 +864,40 @@ void Minecraft::tickInput() { static bool prevMouseDownLeft = false; - // Destroy and attack is on same button - if (Mouse::isButtonDown(MouseAction::ACTION_LEFT)) { - auto baiFlags = BuildActionIntention::BAI_REMOVE | BuildActionIntention::BAI_ATTACK; - - if (!prevMouseDownLeft) baiFlags |= BuildActionIntention::BAI_FIRSTREMOVE; - - BuildActionIntention bai(baiFlags); - handleBuildAction(&bai); - } - - if (Mouse::getButtonState(MouseAction::ACTION_LEFT) == 0) { - gameMode->stopDestroyBlock(); - } - - prevMouseDownLeft = Mouse::isButtonDown(MouseAction::ACTION_LEFT); - - static int buildHoldTicks = 0; - - // Build and use/interact is on same button - // USPESHNO spizheno - if (Mouse::isButtonDown(MouseAction::ACTION_RIGHT)) { - if (buildHoldTicks >= 5) buildHoldTicks = 0; - - if (++buildHoldTicks == 1) { - BuildActionIntention bai(BuildActionIntention::BAI_BUILD | BuildActionIntention::BAI_INTERACT); + if (useTouchscreen()) { + // Touch: gesture recognizer classifies the action type (turn/destroy/build) + BuildActionIntention bai; + if (inputHolder && inputHolder->getBuildInput()->tickBuild(player, &bai)) { handleBuildAction(&bai); } } else { - buildHoldTicks = 0; + // Desktop: left mouse = destroy/attack + if (Mouse::isButtonDown(MouseAction::ACTION_LEFT)) { + auto baiFlags = BuildActionIntention::BAI_REMOVE | BuildActionIntention::BAI_ATTACK; + + if (!prevMouseDownLeft) baiFlags |= BuildActionIntention::BAI_FIRSTREMOVE; + + BuildActionIntention bai(baiFlags); + handleBuildAction(&bai); + } + + prevMouseDownLeft = Mouse::isButtonDown(MouseAction::ACTION_LEFT); + + // Build and use/interact is on same button + // USPESHNO spizheno + static int buildHoldTicks = 0; + if (Mouse::isButtonDown(MouseAction::ACTION_RIGHT)) { + BuildActionIntention bai(BuildActionIntention::BAI_BUILD | BuildActionIntention::BAI_INTERACT); + handleBuildAction(&bai); + if (buildHoldTicks >= 5) buildHoldTicks = 0; + + if (++buildHoldTicks == 1) { + BuildActionIntention bai(BuildActionIntention::BAI_BUILD | BuildActionIntention::BAI_INTERACT); + handleBuildAction(&bai); + } + } else { + buildHoldTicks = 0; + } } lastTickTime = getTimeMs(); @@ -1146,24 +1144,37 @@ void Minecraft::setSize(int w, int h) { width = w; height = h; - if (width >= 1000) { + // determine gui scale, optionally overriding auto + if (options.guiScale != 0) { + // manual selection: 1->small, 2->normal, 3->large, 4->larger + switch (options.guiScale) { + case 1: Gui::GuiScale = 2.0f; break; + case 2: Gui::GuiScale = 3.0f; break; + case 3: Gui::GuiScale = 4.0f; break; + case 4: Gui::GuiScale = 5.0f; break; // bigger than large + default: Gui::GuiScale = 1.0f; break; // auto + } + } else { + // auto compute from resolution + if (width >= 1000) { #ifdef __APPLE__ Gui::GuiScale = (width > 2000)? 8.0f : 4.0f; #else Gui::GuiScale = 4.0f; #endif } - else if (width >= 800) { + else if (width >= 800) { #ifdef __APPLE__ Gui::GuiScale = 4.0f; #else Gui::GuiScale = 3.0f; #endif } - else if (width >= 400) - Gui::GuiScale = 2.0f; - else - Gui::GuiScale = 1.0f; + else if (width >= 400) + Gui::GuiScale = 2.0f; + else + Gui::GuiScale = 1.0f; + } Gui::InvGuiScale = 1.0f / Gui::GuiScale; int screenWidth = (int)(width * Gui::InvGuiScale); @@ -1216,7 +1227,12 @@ void Minecraft::_reloadInput() { #ifndef STANDALONE_SERVER delete inputHolder; - if (useTouchscreen() && !PLATFORM_DESKTOP) { +#ifdef PLATFORM_DESKTOP + const bool useTouchHolder = false; +#else + const bool useTouchHolder = useTouchscreen(); +#endif + if (useTouchHolder) { inputHolder = new TouchInputHolder(this, &options); } else { #if defined(ANDROID) || defined(__APPLE__) @@ -1526,5 +1542,8 @@ void Minecraft::optionUpdated( const Options::Option* option, float value ) { } void Minecraft::optionUpdated( const Options::Option* option, int value ) { - + if(option == &Options::Option::GUI_SCALE) { + // reapply screen scaling using current window size + setSize(width, height); + } } diff --git a/src/client/OptionStrings.cpp b/src/client/OptionStrings.cpp index ba068d7..9edeb85 100755 --- a/src/client/OptionStrings.cpp +++ b/src/client/OptionStrings.cpp @@ -5,6 +5,8 @@ const char* OptionStrings::Multiplayer_ServerVisible = "mp_server_visible_defa const char* OptionStrings::Graphics_Fancy = "gfx_fancygraphics"; const char* OptionStrings::Graphics_LowQuality = "gfx_lowquality"; +const char* OptionStrings::Graphics_Vsync = "gfx_vsync"; +const char* OptionStrings::Graphics_GUIScale = "gfx_guiscale"; const char* OptionStrings::Controls_Sensitivity = "ctrl_sensitivity"; const char* OptionStrings::Controls_InvertMouse = "ctrl_invertmouse"; diff --git a/src/client/OptionStrings.h b/src/client/OptionStrings.h index d536fa0..0065dc6 100755 --- a/src/client/OptionStrings.h +++ b/src/client/OptionStrings.h @@ -8,7 +8,8 @@ public: static const char* Graphics_Fancy; static const char* Graphics_LowQuality; - + static const char* Graphics_GUIScale; + static const char* Graphics_Vsync; static const char* Controls_Sensitivity; static const char* Controls_InvertMouse; static const char* Controls_UseTouchScreen; diff --git a/src/client/Options.cpp b/src/client/Options.cpp index 0866c5e..eda251c 100755 --- a/src/client/Options.cpp +++ b/src/client/Options.cpp @@ -34,6 +34,7 @@ void Options::initDefaultValues() { bobView = true; anaglyph3d = false; limitFramerate = false; + vsync = true; fancyGraphics = true;//false; ambientOcclusion = false; if(minecraft->supportNonTouchScreen()) @@ -44,7 +45,7 @@ void Options::initDefaultValues() { //useMouseForDigging = true; //skin = "Default"; - username = "Steve"; + username = ""; serverVisible = true; keyUp = KeyMapping("key.forward", Keyboard::KEY_W); @@ -144,7 +145,8 @@ const Options::Option Options::Option::USE_TOUCHSCREEN (16, "options.usetouchscreen", false, true), Options::Option::USE_TOUCH_JOYPAD (17, "options.usetouchpad", false, true), Options::Option::DESTROY_VIBRATION (18, "options.destroyvibration", false, true), - Options::Option::PIXELS_PER_MILLIMETER(19, "options.pixelspermilimeter", true, false); + Options::Option::PIXELS_PER_MILLIMETER(19, "options.pixelspermilimeter", true, false), + Options::Option::VSYNC (20, "options.vsync", false, true); /* private */ const float Options::SOUND_MIN_VALUE = 0.0f; @@ -181,7 +183,8 @@ const char* Options::GUI_SCALE[] = { "options.guiScale.auto", "options.guiScale.small", "options.guiScale.normal", - "options.guiScale.large" + "options.guiScale.large", + "options.guiScale.larger" }; void Options::update() @@ -235,6 +238,13 @@ void Options::update() fancyGraphics = false; } } + // Graphics extras + if (key == OptionStrings::Graphics_Vsync) + readBool(value, vsync); + if (key == OptionStrings::Graphics_GUIScale) { + int v; + if (readInt(value, v)) guiScale = v % 5; + } // Game if (key == OptionStrings::Game_DifficultyLevel) { readInt(value, difficulty); @@ -294,6 +304,8 @@ void Options::load() void Options::save() { StringVector stringVec; + // Login + addOptionToSaveOutput(stringVec, OptionStrings::Multiplayer_Username, username); // Game addOptionToSaveOutput(stringVec, OptionStrings::Multiplayer_ServerVisible, serverVisible); addOptionToSaveOutput(stringVec, OptionStrings::Game_DifficultyLevel, difficulty); @@ -305,6 +317,8 @@ void Options::save() addOptionToSaveOutput(stringVec, OptionStrings::Controls_UseTouchScreen, useTouchScreen); addOptionToSaveOutput(stringVec, OptionStrings::Controls_UseTouchJoypad, isJoyTouchArea); addOptionToSaveOutput(stringVec, OptionStrings::Controls_FeedbackVibration, destroyVibration); + addOptionToSaveOutput(stringVec, OptionStrings::Graphics_Vsync, vsync); + addOptionToSaveOutput(stringVec, OptionStrings::Graphics_GUIScale, guiScale); // // static const Option MUSIC; // static const Option SOUND; @@ -348,6 +362,7 @@ void Options::save() // System.out.println("Failed to save options"); // e.printStackTrace(); //} + optionsFile.save(stringVec); } void Options::addOptionToSaveOutput(StringVector& stringVector, std::string name, bool boolValue) { std::stringstream ss; @@ -364,6 +379,9 @@ void Options::addOptionToSaveOutput(StringVector& stringVector, std::string name ss << name << ":" << intValue; stringVector.push_back(ss.str()); } +void Options::addOptionToSaveOutput(StringVector& stringVector, std::string name, const std::string& strValue) { + stringVector.push_back(name + ":" + strValue); +} std::string Options::getMessage( const Option* item ) { diff --git a/src/client/Options.h b/src/client/Options.h index 4420ba7..5332880 100755 --- a/src/client/Options.h +++ b/src/client/Options.h @@ -48,6 +48,7 @@ public: static const Option DESTROY_VIBRATION; static const Option PIXELS_PER_MILLIMETER; + static const Option VSYNC; /* static Option* getItem(int id) { @@ -113,6 +114,7 @@ public: bool bobView; bool anaglyph3d; bool limitFramerate; + bool vsync; bool fancyGraphics; bool ambientOcclusion; bool useMouseForDigging; @@ -206,19 +208,21 @@ public: } else if (item == &Option::PIXELS_PER_MILLIMETER) { pixelsPerMillimeter = value; } - notifyOptionUpdate(item, value); - } + notifyOptionUpdate(item, value); save(); } void set(const Option* item, int value) { if(item == &Option::DIFFICULTY) { difficulty = value; + } else if(item == &Option::GUI_SCALE) { + guiScale = value % 5; } notifyOptionUpdate(item, value); + save(); } void toggle(const Option* option, int dir) { if (option == &Option::INVERT_MOUSE) invertYMouse = !invertYMouse; if (option == &Option::RENDER_DISTANCE) viewDistance = (viewDistance + dir) & 3; - if (option == &Option::GUI_SCALE) guiScale = (guiScale + dir) & 3; + if (option == &Option::GUI_SCALE) guiScale = (guiScale + dir) % 5; if (option == &Option::VIEW_BOBBING) bobView = !bobView; if (option == &Option::THIRD_PERSON) thirdPersonView = !thirdPersonView; if (option == &Option::HIDE_GUI) hideGui = !hideGui; @@ -232,6 +236,7 @@ public: //minecraft->textures.reloadAll(); } if (option == &Option::LIMIT_FRAMERATE) limitFramerate = !limitFramerate; + if (option == &Option::VSYNC) vsync = !vsync; if (option == &Option::DIFFICULTY) difficulty = (difficulty + dir) & 3; if (option == &Option::GRAPHICS) { fancyGraphics = !fancyGraphics; @@ -247,6 +252,7 @@ public: int getIntValue(const Option* item) { if(item == &Option::DIFFICULTY) return difficulty; + if(item == &Option::GUI_SCALE) return guiScale; return 0; } @@ -267,6 +273,8 @@ public: return anaglyph3d; if (item == &Option::LIMIT_FRAMERATE) return limitFramerate; + if (item == &Option::VSYNC) + return vsync; if (item == &Option::AMBIENT_OCCLUSION) return ambientOcclusion; if (item == &Option::THIRD_PERSON) @@ -312,6 +320,7 @@ public: void addOptionToSaveOutput(StringVector& stringVector, std::string name, bool boolValue); void addOptionToSaveOutput(StringVector& stringVector, std::string name, float floatValue); void addOptionToSaveOutput(StringVector& stringVector, std::string name, int intValue); + void addOptionToSaveOutput(StringVector& stringVector, std::string name, const std::string& strValue); void notifyOptionUpdate(const Option* option, bool value); void notifyOptionUpdate(const Option* option, float value); void notifyOptionUpdate(const Option* option, int value); diff --git a/src/client/OptionsFile.cpp b/src/client/OptionsFile.cpp index 49375a0..a227471 100755 --- a/src/client/OptionsFile.cpp +++ b/src/client/OptionsFile.cpp @@ -1,6 +1,7 @@ #include "OptionsFile.h" #include #include +#include OptionsFile::OptionsFile() { #ifdef __APPLE__ @@ -19,19 +20,32 @@ void OptionsFile::save(const StringVector& settings) { fprintf(pFile, "%s\n", it->c_str()); } fclose(pFile); + } else { + LOGI("OptionsFile::save failed to open '%s' for writing: %s", settingsPath.c_str(), strerror(errno)); } } StringVector OptionsFile::getOptionStrings() { StringVector returnVector; - FILE* pFile = fopen(settingsPath.c_str(), "w"); + FILE* pFile = fopen(settingsPath.c_str(), "r"); if(pFile != NULL) { char lineBuff[128]; while(fgets(lineBuff, sizeof lineBuff, pFile)) { - if(strlen(lineBuff) > 2) - returnVector.push_back(std::string(lineBuff)); + // Strip trailing newline + size_t len = strlen(lineBuff); + while(len > 0 && (lineBuff[len-1] == '\n' || lineBuff[len-1] == '\r')) + lineBuff[--len] = '\0'; + if(len < 3) continue; + // Split "key:value" into two separate entries to match update() pairing + char* colon = strchr(lineBuff, ':'); + if(colon) { + returnVector.push_back(std::string(lineBuff, colon - lineBuff)); + returnVector.push_back(std::string(colon + 1)); + } } fclose(pFile); + } else { + LOGI("OptionsFile::getOptionStrings failed to open '%s' for reading: %s", settingsPath.c_str(), strerror(errno)); } return returnVector; } diff --git a/src/client/gui/Gui.cpp b/src/client/gui/Gui.cpp index 8e17ede..d6ddc14 100755 --- a/src/client/gui/Gui.cpp +++ b/src/client/gui/Gui.cpp @@ -21,6 +21,8 @@ #include "../../platform/input/Mouse.h" #include "../../world/level/Level.h" #include "../../world/PosTranslator.h" +#include "../../platform/time.h" +#include float Gui::InvGuiScale = 1.0f / 3.0f; float Gui::GuiScale = 1.0f / Gui::InvGuiScale; @@ -116,6 +118,9 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) { #endif #if defined(RPI) renderDebugInfo(); +#elif defined(PLATFORM_DESKTOP) + if (minecraft->options.renderDebug) + renderDebugInfo(); #endif glDisable(GL_BLEND); @@ -632,17 +637,87 @@ void Gui::onLevelGenerated() { } void Gui::renderDebugInfo() { - static char buf[256]; - float xx = minecraft->player->x; - float yy = minecraft->player->y - minecraft->player->heightOffset; - float zz = minecraft->player->z; - posTranslator.to(xx, yy, zz); - sprintf(buf, "pos: %3.1f, %3.1f, %3.1f\n", xx, yy, zz); + // FPS counter (updates once per second) + static float fps = 0.0f; + static float fpsLastTime = 0.0f; + static int fpsFrames = 0; + float now = getTimeS(); + fpsFrames++; + if (now - fpsLastTime >= 1.0f) { + fps = fpsFrames / (now - fpsLastTime); + fpsFrames = 0; + fpsLastTime = now; + } + + LocalPlayer* p = minecraft->player; + Level* lvl = minecraft->level; + + // Position + float px = p->x, py = p->y - p->heightOffset, pz = p->z; + posTranslator.to(px, py, pz); + int bx = (int)floorf(px), by = (int)floorf(py), bz = (int)floorf(pz); + int cx = bx >> 4, cz = bz >> 4; + + // Facing direction + float yMod = fmodf(p->yRot, 360.0f); + if (yMod < 0) yMod += 360.0f; + const char* facing; + const char* axis; + if (yMod < 45 || yMod >= 315) { facing = "South"; axis = "+Z"; } + else if (yMod < 135) { facing = "West"; axis = "-X"; } + else if (yMod < 225) { facing = "North"; axis = "-Z"; } + else { facing = "East"; axis = "+X"; } + + // Biome + const char* biomeName = "unknown"; + if (lvl) { + Biome* biome = lvl->getBiome(bx, bz); + if (biome) biomeName = biome->name.c_str(); + } + + // Time + long worldTime = lvl ? lvl->getTime() : 0; + long dayTime = worldTime % Level::TICKS_PER_DAY; + long day = worldTime / Level::TICKS_PER_DAY; + long seed = lvl ? lvl->getSeed() : 0; + + // Build lines (NULL entry = blank gap) + static char ln[8][96]; + sprintf(ln[0], "Minecraft PE 0.6.1 alpha (mcpe64)"); + sprintf(ln[1], "%.1f fps", fps); + ln[2][0] = '\0'; // blank separator + sprintf(ln[3], "XYZ: %.3f / %.3f / %.3f", px, py, pz); + sprintf(ln[4], "Block: %d %d %d Chunk: %d %d", bx, by, bz, cx, cz); + sprintf(ln[5], "Facing: %s (%s) (%.1f / %.1f)", facing, axis, p->yRot, p->xRot); + sprintf(ln[6], "Biome: %s", biomeName); + sprintf(ln[7], "Day %ld Time: %ld Seed: %ld", day, dayTime, seed); + + const int N = 8; + const float LH = (float)Font::DefaultLineHeight; // 10 font-pixels + const float MGN = 2.0f; // left/top margin in font-pixels + const float PAD = 2.0f; // horizontal padding for background + Font* font = minecraft->font; + + // 1) Draw semi-transparent background boxes behind each line + for (int i = 0; i < N; i++) { + if (ln[i][0] == '\0') continue; + float w = (float)font->width(ln[i]); + float x0 = MGN - PAD; + float y0 = MGN + i * LH - 1.0f; + float x1 = MGN + w + PAD; + float y1 = MGN + (i + 1) * LH - 1.0f; + fill(x0, y0, x1, y1, 0x90000000); + } + + // 2) Draw text (no extra scale — font coords are in GUI units, same as fill) Tesselator& t = Tesselator::instance; t.beginOverride(); - t.scale2d(InvGuiScale, InvGuiScale); - minecraft->font->draw(buf, 2, 2, 0xffffff); - t.resetScale(); + for (int i = 0; i < N; i++) { + if (ln[i][0] == '\0') continue; + float y = MGN + i * LH; + int col = (i == 0) ? 0xffFFFF55 : 0xffffffff; // title yellow, rest white + font->draw(ln[i], MGN, y, col); + } t.endOverrideAndDraw(); } @@ -676,6 +751,70 @@ void Gui::renderOnSelectItemNameText( const int screenWidth, Font* font, int ySl } } + + +// helper structure used by drawColoredString +struct ColorSegment { + std::string text; + uint32_t color; +}; + +// parse [tag] and [/tag] markers; tags may contain a color name (gold, green, etc.) +static void parseColorTags(const std::string& in, std::vector& out) { + uint32_t curColor = 0xffffff; + size_t pos = 0; + while (pos < in.size()) { + size_t open = in.find('[', pos); + if (open == std::string::npos) { + out.push_back({in.substr(pos), curColor}); + break; + } + if (open > pos) { + out.push_back({in.substr(pos, open - pos), curColor}); + } + size_t close = in.find(']', open); + if (close == std::string::npos) { + out.push_back({in.substr(open), curColor}); + break; + } + std::string tag = in.substr(open + 1, close - open - 1); + if (!tag.empty() && tag[0] == '/') { + curColor = 0xffffff; + } else { + std::string lower; + lower.resize(tag.size()); + std::transform(tag.begin(), tag.end(), lower.begin(), ::tolower); + if (lower.find("gold") != std::string::npos) curColor = 0xffd700; + else if (lower.find("green") != std::string::npos) curColor = 0x00ff00; + else if (lower.find("yellow") != std::string::npos) curColor = 0xffff00; + else if (lower.find("red") != std::string::npos) curColor = 0xff0000; + else if (lower.find("blue") != std::string::npos) curColor = 0x0000ff; + } + pos = close + 1; + } +} + +void Gui::drawColoredString(Font* font, const std::string& text, float x, float y, int alpha) { + std::vector segs; + parseColorTags(text, segs); + float cx = x; + for (auto &s : segs) { + int color = s.color + (alpha << 24); + font->drawShadow(s.text, cx, y, color); + cx += font->width(s.text); + } +} + +float Gui::getColoredWidth(Font* font, const std::string& text) { + std::vector segs; + parseColorTags(text, segs); + float w = 0; + for (auto &s : segs) { + w += font->width(s.text); + } + return w; +} + void Gui::renderChatMessages( const int screenHeight, unsigned int max, bool isChatting, Font* font ) { // if (minecraft.screen instanceof ChatScreen) { // max = 20; @@ -709,7 +848,14 @@ void Gui::renderChatMessages( const int screenHeight, unsigned int max, bool isC this->fill(x, y - 1, x + MAX_MESSAGE_WIDTH, y + 8, (alpha / 2) << 24); glEnable(GL_BLEND); - font->drawShadow(msg, x, y, 0xffffff + (alpha << 24)); + // special-case join/leave announcements + int baseColor = 0xffffff; + if (msg.find(" joined the game") != std::string::npos || + msg.find(" left the game") != std::string::npos) { + baseColor = 0xffff00; // yellow + } + // replace previous logic; allow full colour tags now + Gui::drawColoredString(font, msg, x, y, alpha); } } } diff --git a/src/client/gui/Gui.h b/src/client/gui/Gui.h index b1f4293..0e2d177 100755 --- a/src/client/gui/Gui.h +++ b/src/client/gui/Gui.h @@ -47,6 +47,12 @@ public: void renderChatMessages( const int screenHeight, unsigned int max, bool isChatting, Font* font ); + // draw a string containing simple [color]...[/color] tags; color names are matched + // case-insensitively and default to white. alpha is applied to each segment. + // draw tagged string (ignores simple [color]…[/color] tags) + static void drawColoredString(Font* font, const std::string& text, float x, float y, int alpha); + static float getColoredWidth(Font* font, const std::string& text); + void renderOnSelectItemNameText( const int screenWidth, Font* font, int ySlot ); void renderSleepAnimation( const int screenWidth, const int screenHeight ); diff --git a/src/client/gui/Screen.cpp b/src/client/gui/Screen.cpp index 17c9e10..cc6c0c7 100755 --- a/src/client/gui/Screen.cpp +++ b/src/client/gui/Screen.cpp @@ -26,6 +26,7 @@ void Screen::render( int xm, int ym, float a ) button->render(minecraft, xm, ym); } + // render any text boxes after buttons for (unsigned int i = 0; i < textBoxes.size(); i++) { TextBox* textbox = textBoxes[i]; textbox->render(minecraft, xm, ym); @@ -163,6 +164,7 @@ void Screen::keyPressed( int eventKey ) //minecraft->grabMouse(); } + // pass key events to any text boxes first for (auto& textbox : textBoxes) { textbox->handleKey(eventKey); } @@ -191,14 +193,6 @@ void Screen::keyPressed( int eventKey ) updateTabButtonSelection(); } -void Screen::keyboardNewChar(char inputChar) { - // yeah im using these modern cpp features in this project :sunglasses: - - for (auto& textbox : textBoxes) { - textbox->handleChar(inputChar); - } -} - void Screen::updateTabButtonSelection() { if (minecraft->useTouchscreen()) @@ -229,6 +223,7 @@ void Screen::mouseClicked( int x, int y, int buttonNum ) } } + // let textboxes see the click regardless for (auto& textbox : textBoxes) { textbox->mouseClicked(minecraft, x, y, buttonNum); } @@ -275,9 +270,3 @@ void Screen::toGUICoordinate( int& x, int& y ) { x = x * width / minecraft->width; y = y * height / minecraft->height - 1; } - -void Screen::tick() { - for (auto& textbox : textBoxes) { - textbox->tick(minecraft); - } -} \ No newline at end of file diff --git a/src/client/gui/Screen.h b/src/client/gui/Screen.h index ff48c25..eebe588 100755 --- a/src/client/gui/Screen.h +++ b/src/client/gui/Screen.h @@ -31,7 +31,7 @@ public: virtual void keyboardTextEvent(); virtual bool handleBackEvent(bool isDown); - virtual void tick(); + virtual void tick() {} virtual void removed() {} @@ -58,7 +58,7 @@ protected: virtual void mouseReleased(int x, int y, int buttonNum); virtual void keyPressed(int eventKey); - virtual void keyboardNewChar(char inputChar); + virtual void keyboardNewChar(char inputChar) {} public: int width; int height; diff --git a/src/client/gui/components/OptionsGroup.cpp b/src/client/gui/components/OptionsGroup.cpp index 7cd1a3a..3879adb 100755 --- a/src/client/gui/components/OptionsGroup.cpp +++ b/src/client/gui/components/OptionsGroup.cpp @@ -10,7 +10,7 @@ OptionsGroup::OptionsGroup( std::string labelID ) { void OptionsGroup::setupPositions() { // First we write the header and then we add the items - int curY = y + 10; + int curY = y + 18; for(std::vector::iterator it = children.begin(); it != children.end(); ++it) { (*it)->width = width - 5; @@ -23,7 +23,9 @@ void OptionsGroup::setupPositions() { } void OptionsGroup::render( Minecraft* minecraft, int xm, int ym ) { - minecraft->font->draw(label, (float)x + 2, (float)y, 0xffffffff, false); + float padX = 10.0f; + float padY = 5.0f; + minecraft->font->draw(label, (float)x + padX, (float)y + padY, 0xffffffff, false); super::render(minecraft, xm, ym); } @@ -45,6 +47,7 @@ void OptionsGroup::createToggle( const Options::Option* option, Minecraft* minec def.height = 20 * 0.7f; OptionButton* element = new OptionButton(option); element->setImageDef(def, true); + element->updateImage(&minecraft->options); std::string itemLabel = I18n::get(option->getCaptionId()); OptionsItem* item = new OptionsItem(itemLabel, element); addChild(item); @@ -58,11 +61,38 @@ void OptionsGroup::createProgressSlider( const Options::Option* option, Minecraf minecraft->options.getProgrssMax(option)); element->width = 100; element->height = 20; - OptionsItem* item = new OptionsItem(label, element); + std::string itemLabel = I18n::get(option->getCaptionId()); + OptionsItem* item = new OptionsItem(itemLabel, element); addChild(item); setupPositions(); } void OptionsGroup::createStepSlider( const Options::Option* option, Minecraft* minecraft ) { - + // integer-valued option; use step slider + std::vector steps; + // render distance was removed; fall through to other cases + if(option == &Options::Option::DIFFICULTY) { + steps.push_back(0); + steps.push_back(1); + steps.push_back(2); + steps.push_back(3); + } else if(option == &Options::Option::GUI_SCALE) { + // slider order: small,normal,large,larger,auto + steps.push_back(1); + steps.push_back(2); + steps.push_back(3); + steps.push_back(4); + steps.push_back(0); + } else { + // fallback: use single value; duplicate so numSteps>1 and avoid divide-by-zero + steps.push_back(0); + steps.push_back(0); + } + Slider* element = new Slider(minecraft, option, steps); + element->width = 100; + element->height = 20; + std::string itemLabel = I18n::get(option->getCaptionId()); + OptionsItem* item = new OptionsItem(itemLabel, element); + addChild(item); + setupPositions(); } diff --git a/src/client/gui/components/Slider.cpp b/src/client/gui/components/Slider.cpp index 2b978df..791058e 100755 --- a/src/client/gui/components/Slider.cpp +++ b/src/client/gui/components/Slider.cpp @@ -25,13 +25,17 @@ Slider::Slider(Minecraft* minecraft, const Options::Option* option, const std::v assert(stepVec.size() > 1); numSteps = sliderSteps.size(); if(option != NULL) { - curStepValue; - int curStep; + // initialize slider position based on the current option value curStepValue = minecraft->options.getIntValue(option); - std::vector::iterator currentItem = std::find(sliderSteps.begin(), sliderSteps.end(), curStepValue); + auto currentItem = std::find(sliderSteps.begin(), sliderSteps.end(), curStepValue); if(currentItem != sliderSteps.end()) { - curStep = currentItem - sliderSteps.begin(); + curStep = static_cast(currentItem - sliderSteps.begin()); + } else { + // fallback to first step + curStep = 0; + curStepValue = sliderSteps[0]; } + percentage = float(curStep) / float(numSteps - 1); } } @@ -46,10 +50,15 @@ void Slider::render( Minecraft* minecraft, int xm, int ym ) { //fill(x, y + 8, x + (int)(width * percentage), y + height, 0xffff00ff); fill(xSliderStart, ySliderStart, xSliderEnd, ySliderEnd, 0xff606060); if(sliderType == SliderStep) { - int stepDistance = barWidth / (numSteps -1); - for(int a = 0; a <= numSteps - 1; ++a) { - int renderSliderStepPosX = xSliderStart + a * stepDistance + 1; - fill(renderSliderStepPosX - 1, ySliderStart - 2, renderSliderStepPosX + 1, ySliderEnd + 2, 0xff606060); + // numSteps should be >=2; protect against bad input (zero division) + if(numSteps <= 1) { + // nothing to render + } else { + int stepDistance = barWidth / (numSteps -1); + for(int a = 0; a <= numSteps - 1; ++a) { + int renderSliderStepPosX = xSliderStart + a * stepDistance + 1; + fill(renderSliderStepPosX - 1, ySliderStart - 2, renderSliderStepPosX + 1, ySliderEnd + 2, 0xff606060); + } } } minecraft->textures->loadAndBindTexture("gui/touchgui.png"); diff --git a/src/client/gui/components/TextBox.cpp b/src/client/gui/components/TextBox.cpp index 8035e53..2a05ba8 100755 --- a/src/client/gui/components/TextBox.cpp +++ b/src/client/gui/components/TextBox.cpp @@ -1,90 +1,100 @@ #include "TextBox.h" +#include "../Gui.h" #include "../../Minecraft.h" #include "../../../AppPlatform.h" -#include "platform/input/Mouse.h" +#include "../../../platform/input/Mouse.h" -TextBox::TextBox( int id, const std::string& msg ) - : TextBox(id, 0, 0, msg) {} +// delegate constructors +TextBox::TextBox(int id, const std::string& msg) + : TextBox(id, 0, 0, msg) +{ +} -TextBox::TextBox( int id, int x, int y, const std::string& msg ) - : TextBox(id, x, y, 24, msg) {} +TextBox::TextBox(int id, int x, int y, const std::string& msg) + : TextBox(id, x, y, 24, Font::DefaultLineHeight + 4, msg) +{ +} -TextBox::TextBox( int id, int x, int y, int w, const std::string& msg ) - : GuiElement(true, true, x, y, w, Font::DefaultLineHeight + 4), - id(id), hint(msg), focused(false), blink(false) {} +TextBox::TextBox(int id, int x, int y, int w, int h, const std::string& msg) + : GuiElement(true, true, x, y, w, h), + id(id), hint(msg), focused(false), blink(false), blinkTicks(0) +{ +} void TextBox::setFocus(Minecraft* minecraft) { - if(!focused) { - minecraft->platform()->showKeyboard(); - focused = true; - - blinkTicks = 0; - blink = false; - } + if (!focused) { + minecraft->platform()->showKeyboard(); + focused = true; + blinkTicks = 0; + blink = false; + } } bool TextBox::loseFocus(Minecraft* minecraft) { - if(focused) { - minecraft->platform()->showKeyboard(); - focused = false; - return true; - } - return false; + if (focused) { + minecraft->platform()->hideKeyboard(); + focused = false; + return true; + } + return false; } void TextBox::mouseClicked(Minecraft* minecraft, int x, int y, int buttonNum) { - if (buttonNum == MouseAction::ACTION_LEFT) { - if (pointInside(x, y)) { - setFocus(minecraft); - } else { - loseFocus(minecraft); - } - } + if (buttonNum == MouseAction::ACTION_LEFT) { + if (pointInside(x, y)) { + setFocus(minecraft); + } else { + loseFocus(minecraft); + } + } } void TextBox::handleChar(char c) { - if (focused) { - text.push_back(c); - } + if (focused && c >= 32 && c < 127 && (int)text.size() < 256) { + text.push_back(c); + } } void TextBox::handleKey(int key) { - if (focused && key == Keyboard::KEY_BACKSPACE && !text.empty()) { - text.pop_back(); - } + if (focused && key == Keyboard::KEY_BACKSPACE && !text.empty()) { + text.pop_back(); + } } void TextBox::tick(Minecraft* minecraft) { - blinkTicks++; - - if (blinkTicks >= 5) { - blink = !blink; - blinkTicks = 0; - } + blinkTicks++; + if (blinkTicks >= 5) { + blink = !blink; + blinkTicks = 0; + } } -void TextBox::render( Minecraft* minecraft, int xm, int ym ) { - // textbox like in beta 1.7.3 - fill(x, y, x + width, y + height, 0xffa0a0a0); - fill(x + 1, y + 1, x + width - 1, y + height - 1, 0xff000000); +void TextBox::render(Minecraft* minecraft, int xm, int ym) { + // textbox like in beta 1.7.3 + // change appearance when focused so the user can tell it's active + // active background darker gray with a subtle border + uint32_t bgColor = focused ? 0xffa0a0a0 : 0xffa0a0a0; + uint32_t borderColor = focused ? 0xff000000 : 0xff000000; + fill(x, y, x + width, y + height, bgColor); + fill(x + 1, y + 1, x + width - 1, y + height - 1, borderColor); - glEnable2(GL_SCISSOR_TEST); - glScissor( - Gui::GuiScale * (x + 2), - minecraft->height - Gui::GuiScale * (y + height - 2), - Gui::GuiScale * (width - 2), - Gui::GuiScale * (height - 2) - ); + glEnable2(GL_SCISSOR_TEST); + glScissor( + Gui::GuiScale * (x + 2), + minecraft->height - Gui::GuiScale * (y + height - 2), + Gui::GuiScale * (width - 2), + Gui::GuiScale * (height - 2) + ); - if (text.empty() && !focused) { - drawString(minecraft->font, hint, x + 2, y + 2, 0xff5e5e5e); - } + if (text.empty() && !focused) { + drawString(minecraft->font, hint, x + 2, y + 2, 0xff5e5e5e); + } - if (focused && blink) text.push_back('_'); + if (focused && blink) text.push_back('_'); - drawString(minecraft->font, text, x + 2, y + 2, 0xffffffff); + drawString(minecraft->font, text, x + 2, y + 2, 0xffffffff); - if (focused && blink) text.pop_back(); + if (focused && blink) text.pop_back(); - glDisable2(GL_SCISSOR_TEST); + glDisable2(GL_SCISSOR_TEST); } diff --git a/src/client/gui/components/TextBox.h b/src/client/gui/components/TextBox.h index 822728b..5a2d4c6 100755 --- a/src/client/gui/components/TextBox.h +++ b/src/client/gui/components/TextBox.h @@ -6,6 +6,8 @@ #include #include "GuiElement.h" #include "../../Options.h" +#include "../../../platform/input/Mouse.h" +#include "../../../platform/input/Keyboard.h" class Font; class Minecraft; @@ -14,15 +16,15 @@ class TextBox: public GuiElement { public: TextBox(int id, const std::string& msg); - TextBox(int id, int x, int y, const std::string& msg); - TextBox(int id, int x, int y, int w, const std::string& msg); + TextBox(int id, int x, int y, const std::string& msg); + TextBox(int id, int x, int y, int w, int h, const std::string& msg); virtual void mouseClicked(Minecraft* minecraft, int x, int y, int buttonNum); virtual void setFocus(Minecraft* minecraft); virtual bool loseFocus(Minecraft* minecraft); - virtual void render(Minecraft* minecraft, int xm, int ym); + virtual void render(Minecraft* minecraft, int xm, int ym); virtual void handleKey(int key); virtual void handleChar(char c); diff --git a/src/client/gui/screens/ConsoleScreen.cpp b/src/client/gui/screens/ConsoleScreen.cpp new file mode 100644 index 0000000..869eded --- /dev/null +++ b/src/client/gui/screens/ConsoleScreen.cpp @@ -0,0 +1,223 @@ +#include "ConsoleScreen.h" +#include "../Gui.h" +#include "../../Minecraft.h" +#include "../../player/LocalPlayer.h" +#include "../../../platform/input/Keyboard.h" +#include "../../../world/level/Level.h" +#include "../../../network/RakNetInstance.h" +#include "../../../network/ServerSideNetworkHandler.h" +#include "../../../network/packet/ChatPacket.h" +#include "../../../platform/log.h" + +#include +#include +#include + +ConsoleScreen::ConsoleScreen() +: _input(""), + _cursorBlink(0) +{ +} + +void ConsoleScreen::init() +{ +} + +void ConsoleScreen::tick() +{ + _cursorBlink++; +} + +bool ConsoleScreen::handleBackEvent(bool /*isDown*/) +{ + minecraft->setScreen(NULL); + return true; +} + +void ConsoleScreen::keyPressed(int eventKey) +{ + if (eventKey == Keyboard::KEY_ESCAPE) { + minecraft->setScreen(NULL); + } else if (eventKey == Keyboard::KEY_RETURN) { + execute(); + } else if (eventKey == Keyboard::KEY_BACKSPACE) { + if (!_input.empty()) + _input.erase(_input.size() - 1, 1); + } else { + super::keyPressed(eventKey); + } +} + +void ConsoleScreen::keyboardNewChar(char inputChar) +{ + if (inputChar >= 32 && inputChar < 127) + _input += inputChar; +} + +// --------------------------------------------------------------------------- +// execute: run _input as a command, print result, close screen +// --------------------------------------------------------------------------- +void ConsoleScreen::execute() +{ + if (_input.empty()) { + minecraft->setScreen(NULL); + return; + } + + if (_input[0] == '/') { + // Command + std::string result = processCommand(_input); + if (!result.empty()) + minecraft->gui.addMessage(result); + } else { + // Chat message: message + std::string msg = std::string("<") + minecraft->player->name + "> " + _input; + if (minecraft->netCallback && minecraft->raknetInstance->isServer()) { + // Hosting a LAN game: displayGameMessage shows locally + broadcasts MessagePacket to clients + static_cast(minecraft->netCallback)->displayGameMessage(msg); + } else if (minecraft->netCallback) { + // Connected client: send ChatPacket to server; server echoes it back as MessagePacket + ChatPacket chatPkt(msg); + minecraft->raknetInstance->send(chatPkt); + } else { + // Singleplayer: show locally only + minecraft->gui.addMessage(msg); + } + } + + minecraft->setScreen(NULL); +} + +// --------------------------------------------------------------------------- +// processCommand +// --------------------------------------------------------------------------- +static std::string trim(const std::string& s) { + size_t a = s.find_first_not_of(" \t"); + if (a == std::string::npos) return ""; + size_t b = s.find_last_not_of(" \t"); + return s.substr(a, b - a + 1); +} + +std::string ConsoleScreen::processCommand(const std::string& raw) +{ + // strip leading '/' + std::string line = raw; + if (!line.empty() && line[0] == '/') line = line.substr(1); + line = trim(line); + + // tokenise + std::vector args; + { + std::istringstream ss(line); + std::string tok; + while (ss >> tok) args.push_back(tok); + } + + if (args.empty()) return ""; + + Level* level = minecraft->level; + if (!level) return "No level loaded."; + + // ----------------------------------------------------------------------- + // /time ... + // ----------------------------------------------------------------------- + if (args[0] == "time") { + if (args.size() < 2) + return "Usage: /time ..."; + + const std::string& sub = args[1]; + + // -- time add ----------------------------------------------- + if (sub == "add") { + if (args.size() < 3) return "Usage: /time add "; + long delta = std::atol(args[2].c_str()); + long newTime = level->getTime() + delta; + level->setTime(newTime); + std::ostringstream out; + out << "Set the time to " << (newTime % Level::TICKS_PER_DAY); + return out.str(); + } + + // -- time set ----------------------- + if (sub == "set") { + if (args.size() < 3) return "Usage: /time set "; + const std::string& val = args[2]; + + long t = -1; + if (val == "day") t = 1000; + else if (val == "noon") t = 6000; + else if (val == "night") t = 13000; + else if (val == "midnight") t = 18000; + else { + // numeric — accept positive integers only + bool numeric = true; + for (char c : val) + if (!std::isdigit((unsigned char)c)) { numeric = false; break; } + if (!numeric) return std::string("Unknown value: ") + val; + t = std::atol(val.c_str()); + } + + // Preserve the total day count so only the time-of-day changes + long dayCount = level->getTime() / Level::TICKS_PER_DAY; + long newTime = dayCount * Level::TICKS_PER_DAY + (t % Level::TICKS_PER_DAY); + level->setTime(newTime); + std::ostringstream out; + out << "Set the time to " << t; + return out.str(); + } + + // -- time query ------------------------------ + if (sub == "query") { + if (args.size() < 3) return "Usage: /time query "; + const std::string& what = args[2]; + + long total = level->getTime(); + long daytime = total % Level::TICKS_PER_DAY; + long day = total / Level::TICKS_PER_DAY; + + std::ostringstream out; + if (what == "daytime") { out << "The time of day is " << daytime; } + else if (what == "gametime") { out << "The game time is " << total; } + else if (what == "day") { out << "The day is " << day; } + else return std::string("Unknown query: ") + what; + return out.str(); + } + + return "Unknown sub-command. Usage: /time ..."; + } + + return std::string("Unknown command: /") + args[0]; +} + +// --------------------------------------------------------------------------- +// render +// --------------------------------------------------------------------------- +void ConsoleScreen::render(int /*xm*/, int /*ym*/, float /*a*/) +{ + // Dim the game world slightly + fillGradient(0, 0, width, height, 0x00000000, 0x40000000); + + const int boxH = 12; + const int boxY = height - boxH - 2; + const int boxX0 = 2; + const int boxX1 = width - 2; + + // Input box background + fill(boxX0, boxY, boxX1, boxY + boxH, 0xc0000000); + // Border + fill(boxX0, boxY, boxX1, boxY + 1, 0xff808080); + fill(boxX0, boxY + boxH - 1, boxX1, boxY + boxH, 0xff808080); + fill(boxX0, boxY, boxX0 + 1, boxY + boxH, 0xff808080); + fill(boxX1 - 1, boxY, boxX1, boxY + boxH, 0xff808080); + + // Input text + blinking cursor + std::string displayed = _input; + if ((_cursorBlink / 10) % 2 == 0) + displayed += '_'; + + // Placeholder hint when empty + if (_input.empty() && (_cursorBlink / 10) % 2 != 0) + font->drawShadow("Type a message or /command", (float)(boxX0 + 2), (float)(boxY + 2), 0xff606060); + else + font->drawShadow(displayed, (float)(boxX0 + 2), (float)(boxY + 2), 0xffffffff); +} diff --git a/src/client/gui/screens/ConsoleScreen.h b/src/client/gui/screens/ConsoleScreen.h new file mode 100644 index 0000000..f2e7bce --- /dev/null +++ b/src/client/gui/screens/ConsoleScreen.h @@ -0,0 +1,34 @@ +#ifndef NET_MINECRAFT_CLIENT_GUI_SCREENS__ConsoleScreen_H__ +#define NET_MINECRAFT_CLIENT_GUI_SCREENS__ConsoleScreen_H__ + +#include "../Screen.h" +#include + +class ConsoleScreen: public Screen +{ + typedef Screen super; +public: + ConsoleScreen(); + virtual ~ConsoleScreen() {} + + void init(); + void render(int xm, int ym, float a); + void tick(); + + virtual bool renderGameBehind() { return true; } + virtual bool isInGameScreen() { return true; } + virtual bool isPauseScreen() { return false; } + + virtual void keyPressed(int eventKey); + virtual void keyboardNewChar(char inputChar); + virtual bool handleBackEvent(bool isDown); + +private: + void execute(); + std::string processCommand(const std::string& cmd); + + std::string _input; + int _cursorBlink; // tick counter for cursor blink +}; + +#endif /*NET_MINECRAFT_CLIENT_GUI_SCREENS__ConsoleScreen_H__*/ diff --git a/src/client/gui/screens/CreditsScreen.cpp b/src/client/gui/screens/CreditsScreen.cpp new file mode 100644 index 0000000..a6089d8 --- /dev/null +++ b/src/client/gui/screens/CreditsScreen.cpp @@ -0,0 +1,118 @@ +#include "CreditsScreen.h" +#include "StartMenuScreen.h" +#include "OptionsScreen.h" +#include "../../Minecraft.h" +#include "../components/Button.h" +#include "../components/ImageButton.h" + +CreditsScreen::CreditsScreen() +: bHeader(NULL), btnBack(NULL) +{} +CreditsScreen::~CreditsScreen() { + if (bHeader) delete bHeader; + if (btnBack) delete btnBack; +} + +void CreditsScreen::init() { + bHeader = new Touch::THeader(0, "Credits"); + btnBack = new ImageButton(1, ""); + { + ImageDef def; + def.name = "gui/touchgui.png"; + def.width = 34; + def.height = 26; + def.setSrc(IntRectangle(150, 0, (int)def.width, (int)def.height)); + btnBack->setImageDef(def, true); + } + buttons.push_back(bHeader); + buttons.push_back(btnBack); + + // prepare text lines + _lines.clear(); + _lines.push_back("Minecraft: Pocket Edition"); + _lines.push_back("Original game by Mojang"); + _lines.push_back(""); + _lines.push_back("Programmers:"); + _lines.push_back("mschiller890"); + _lines.push_back("InviseDivine"); + _lines.push_back("Kolyah35"); + _lines.push_back(""); + _lines.push_back("[Gold]Join our Discord server:[/Gold] [Green]url.....[/Green]"); + _scrollSpeed = 0.5f; + _scrollY = height; // start below screen +} + +void CreditsScreen::setupPositions() { + int buttonHeight = btnBack->height; + btnBack->x = width - btnBack->width; + btnBack->y = 0; + if (bHeader) { + bHeader->x = 0; + bHeader->y = 0; + bHeader->width = width - btnBack->width; + bHeader->height = btnBack->height; + } + + // reset scroll starting position when screen size changes + _scrollY = height; +} + +void CreditsScreen::tick() { + // move text upward + _scrollY -= _scrollSpeed; + // if text has scrolled off the top, restart + float totalHeight = _lines.size() * (minecraft->font->lineHeight + 8); + if (_scrollY + totalHeight < 0) { + _scrollY = height; + } +} + +void CreditsScreen::render(int xm, int ym, float a) { + renderBackground(); + super::render(xm, ym, a); + int w = width; + Font* font = minecraft->font; + float y = _scrollY; + const float lineHeight = font->lineHeight + 8; + for (size_t i = 0; i < _lines.size(); ++i) { + const std::string& line = _lines[i]; + // use color-tag-aware drawing, centre by total width + float lineWidth = Gui::getColoredWidth(font, line); + Gui::drawColoredString(font, line, w/2 - lineWidth/2, (int)y, 255); + // underline hyperlink lines manually + if (line.find("http") != std::string::npos || line.find("discord.gg") != std::string::npos) { + float x0 = w/2 - lineWidth/2; + float y0 = y + font->lineHeight - 1; + this->fill(x0, y0, x0 + lineWidth, y0 + 1, 0xffffffff); + } + y += lineHeight; + } +} + +void CreditsScreen::buttonClicked(Button* button) { + if (button->id == 1) { + minecraft->setScreen(new OptionsScreen()); + } +} + +void CreditsScreen::mouseClicked(int x, int y, int buttonNum) { + // map click to a line in the scrolling text + const float lineHeight = minecraft->font->lineHeight + 8; + for (size_t i = 0; i < _lines.size(); ++i) { + float lineY = _scrollY + i * lineHeight; + if (y >= lineY && y < lineY + lineHeight) { + const std::string& line = _lines[i]; + size_t start = line.find("http"); + if (start == std::string::npos) + start = line.find("discord.gg"); + if (start != std::string::npos) { + // extract until space + size_t end = line.find(' ', start); + std::string url = line.substr(start, (end == std::string::npos) ? std::string::npos : end - start); + minecraft->platform()->openURL(url); + return; + } + } + } + super::mouseClicked(x, y, buttonNum); +} diff --git a/src/client/gui/screens/CreditsScreen.h b/src/client/gui/screens/CreditsScreen.h new file mode 100644 index 0000000..961b8d3 --- /dev/null +++ b/src/client/gui/screens/CreditsScreen.h @@ -0,0 +1,32 @@ +#ifndef NET_MINECRAFT_CLIENT_GUI_SCREENS__CreditsScreen_H__ +#define NET_MINECRAFT_CLIENT_GUI_SCREENS__CreditsScreen_H__ + +#include "../Screen.h" +#include "../components/Button.h" + +class ImageButton; + +#include +#include + +class CreditsScreen: public Screen { +public: + typedef Screen super; + CreditsScreen(); + virtual ~CreditsScreen(); + void init(); + void setupPositions(); + virtual void tick(); + void render(int xm, int ym, float a); + void buttonClicked(Button* button); + virtual void mouseClicked(int x, int y, int buttonNum); +private: + Touch::THeader* bHeader; + ImageButton* btnBack; + + std::vector _lines; + float _scrollY; + float _scrollSpeed; +}; + +#endif /* NET_MINECRAFT_CLIENT_GUI_SCREENS__CreditsScreen_H__ */ diff --git a/src/client/gui/screens/OptionsScreen.cpp b/src/client/gui/screens/OptionsScreen.cpp index a20d800..c73156f 100755 --- a/src/client/gui/screens/OptionsScreen.cpp +++ b/src/client/gui/screens/OptionsScreen.cpp @@ -1,46 +1,69 @@ #include "OptionsScreen.h" #include "StartMenuScreen.h" +#include "UsernameScreen.h" #include "DialogDefinitions.h" #include "../../Minecraft.h" #include "../../../AppPlatform.h" +#include "CreditsScreen.h" #include "../components/OptionsPane.h" #include "../components/ImageButton.h" #include "../components/OptionsGroup.h" + OptionsScreen::OptionsScreen() -: btnClose(NULL), - bHeader(NULL), - selectedCategory(0) { + : btnClose(NULL), + bHeader(NULL), + btnChangeUsername(NULL), + btnCredits(NULL), + selectedCategory(0) { } OptionsScreen::~OptionsScreen() { - if(btnClose != NULL) { + + if (btnClose != NULL) { delete btnClose; btnClose = NULL; } - if(bHeader != NULL) { - delete bHeader, + + if (bHeader != NULL) { + delete bHeader; bHeader = NULL; } - for(std::vector::iterator it = categoryButtons.begin(); it != categoryButtons.end(); ++it) { - if(*it != NULL) { - delete *it; + + if (btnChangeUsername != NULL) { + delete btnChangeUsername; + btnChangeUsername = NULL; + } + + if (btnCredits != NULL) { + delete btnCredits; + btnCredits = NULL; + } + + for (std::vector::iterator it = categoryButtons.begin(); it != categoryButtons.end(); ++it) { + if (*it != NULL) { + delete* it; *it = NULL; } } - for(std::vector::iterator it = optionPanes.begin(); it != optionPanes.end(); ++it) { - if(*it != NULL) { - delete *it; + + for (std::vector::iterator it = optionPanes.begin(); it != optionPanes.end(); ++it) { + if (*it != NULL) { + delete* it; *it = NULL; } } + categoryButtons.clear(); } void OptionsScreen::init() { + bHeader = new Touch::THeader(0, "Options"); + btnClose = new ImageButton(1, ""); + ImageDef def; def.name = "gui/touchgui.png"; def.width = 34; @@ -53,129 +76,199 @@ void OptionsScreen::init() { categoryButtons.push_back(new Touch::TButton(3, "Game")); categoryButtons.push_back(new Touch::TButton(4, "Controls")); categoryButtons.push_back(new Touch::TButton(5, "Graphics")); + + btnChangeUsername = new Button(10, "Username"); + btnCredits = new Button(11, "Credits"); + buttons.push_back(bHeader); buttons.push_back(btnClose); - for(std::vector::iterator it = categoryButtons.begin(); it != categoryButtons.end(); ++it) { + buttons.push_back(btnChangeUsername); + buttons.push_back(btnCredits); + + for (std::vector::iterator it = categoryButtons.begin(); it != categoryButtons.end(); ++it) { buttons.push_back(*it); tabButtons.push_back(*it); } - generateOptionScreens(); + generateOptionScreens(); + // start with first category selected + selectCategory(0); } + void OptionsScreen::setupPositions() { + int buttonHeight = btnClose->height; + btnClose->x = width - btnClose->width; btnClose->y = 0; + int offsetNum = 1; - for(std::vector::iterator it = categoryButtons.begin(); it != categoryButtons.end(); ++it) { + + for (std::vector::iterator it = categoryButtons.begin(); it != categoryButtons.end(); ++it) { + (*it)->x = 0; (*it)->y = offsetNum * buttonHeight; (*it)->selected = false; + offsetNum++; } + bHeader->x = 0; bHeader->y = 0; bHeader->width = width - btnClose->width; bHeader->height = btnClose->height; - for(std::vector::iterator it = optionPanes.begin(); it != optionPanes.end(); ++it) { - if(categoryButtons.size() > 0 && categoryButtons[0] != NULL) { + + // Username button (bottom-left) + if (btnChangeUsername != NULL) { + + btnChangeUsername->width = categoryButtons.empty() ? 80 : categoryButtons[0]->width; + btnChangeUsername->height = btnClose->height; + + btnChangeUsername->x = 0; + btnChangeUsername->y = height - btnChangeUsername->height; + } + + // Credits button (bottom-right) + if (btnCredits != NULL) { + + btnCredits->width = btnChangeUsername->width; + btnCredits->height = btnChangeUsername->height; + + btnCredits->x = width - btnCredits->width; + btnCredits->y = height - btnCredits->height; + } + + for (std::vector::iterator it = optionPanes.begin(); it != optionPanes.end(); ++it) { + + if (categoryButtons.size() > 0 && categoryButtons[0] != NULL) { + (*it)->x = categoryButtons[0]->width; (*it)->y = bHeader->height; (*it)->width = width - categoryButtons[0]->width; + (*it)->setupPositions(); } } - selectCategory(0); + + // don't override user selection on resize } -void OptionsScreen::render( int xm, int ym, float a ) { + +void OptionsScreen::render(int xm, int ym, float a) { + renderBackground(); + super::render(xm, ym, a); + int xmm = xm * width / minecraft->width; int ymm = ym * height / minecraft->height - 1; - if(currentOptionPane != NULL) + + if (currentOptionPane != NULL) currentOptionPane->render(minecraft, xmm, ymm); } -void OptionsScreen::removed() -{ +void OptionsScreen::removed() { } -void OptionsScreen::buttonClicked( Button* button ) { - if(button == btnClose) { - minecraft->reloadOptions(); + +void OptionsScreen::buttonClicked(Button* button) { + + if (button == btnClose) { + + minecraft->options.save(); minecraft->screenChooser.setScreen(SCREEN_STARTMENU); - } else if(button->id > 1 && button->id < 7) { - // This is a category button + + } + else if (button == btnChangeUsername) { + + minecraft->options.save(); + minecraft->setScreen(new UsernameScreen()); + + } + else if (button->id > 1 && button->id < 7) { + int categoryButton = button->id - categoryButtons[0]->id; selectCategory(categoryButton); + + } + else if (button == btnCredits) { + + minecraft->setScreen(new CreditsScreen()); + } } -void OptionsScreen::selectCategory( int index ) { +void OptionsScreen::selectCategory(int index) { + int currentIndex = 0; - for(std::vector::iterator it = categoryButtons.begin(); it != categoryButtons.end(); ++it) { - if(index == currentIndex) { + + for (std::vector::iterator it = categoryButtons.begin(); it != categoryButtons.end(); ++it) { + + if (index == currentIndex) (*it)->selected = true; - } else { + else (*it)->selected = false; - } + currentIndex++; } - if(index < (int)optionPanes.size()) + + if (index < (int)optionPanes.size()) currentOptionPane = optionPanes[index]; } void OptionsScreen::generateOptionScreens() { + optionPanes.push_back(new OptionsPane()); optionPanes.push_back(new OptionsPane()); optionPanes.push_back(new OptionsPane()); optionPanes.push_back(new OptionsPane()); - // Mojang Pane + + // Login Pane optionPanes[0]->createOptionsGroup("options.group.mojang") - //.addOptionItem(&Options::Option::THIRD_PERSON, minecraft); .addOptionItem(&Options::Option::SENSITIVITY, minecraft); -// int mojangGroup = optionPanes[0]->createOptionsGroup("Mojang"); -// static const int arr[] = {5,4,3,15}; -// std::vector vec (arr, arr + sizeof(arr) / sizeof(arr[0]) ); -// optionPanes[0]->createStepSlider(minecraft, mojangGroup, "This works?", &Options::Option::DIFFICULTY, vec); -// -// // Game Pane -// int gameGroup = optionPanes[1]->createOptionsGroup("Game"); -// optionPanes[1]->createToggle(gameGroup, "Third person camera", &Options::Option::THIRD_PERSON); -// optionPanes[1]->createToggle(gameGroup, "Server visible", &Options::Option::SERVER_VISIBLE); -// -// // Input Pane -// int controlsGroup = optionPanes[2]->createOptionsGroup("Controls"); -// optionPanes[2]->createToggle(controlsGroup, "Invert X-axis", &Options::Option::INVERT_MOUSE); -// optionPanes[2]->createToggle(controlsGroup, "Lefty", &Options::Option::LEFT_HANDED); -// optionPanes[2]->createToggle(controlsGroup, "Use touch screen", &Options::Option::USE_TOUCHSCREEN); -// optionPanes[2]->createToggle(controlsGroup, "Split touch controls", &Options::Option::USE_TOUCH_JOYPAD); -// int feedBackGroup = optionPanes[2]->createOptionsGroup("Feedback"); -// optionPanes[2]->createToggle(feedBackGroup, "Vibrate on destroy", &Options::Option::DESTROY_VIBRATION); -// -// int graphicsGroup = optionPanes[3]->createOptionsGroup("Graphics"); -// optionPanes[3]->createProgressSlider(minecraft, graphicsGroup, "Gui Scale", &Options::Option::PIXELS_PER_MILLIMETER, 3, 4); -// optionPanes[3]->createToggle(graphicsGroup, "Fancy Graphics", &Options::Option::INVERT_MOUSE); -// optionPanes[3]->createToggle(graphicsGroup, "Fancy Skies", &Options::Option::INVERT_MOUSE); -// optionPanes[3]->createToggle(graphicsGroup, "Animated water", &Options::Option::INVERT_MOUSE); -// int experimentalGraphicsGroup = optionPanes[3]->createOptionsGroup("Experimental graphics"); -// optionPanes[3]->createToggle(experimentalGraphicsGroup, "Soft shadows", &Options::Option::INVERT_MOUSE); + + // Game Pane + optionPanes[1]->createOptionsGroup("options.group.game") + .addOptionItem(&Options::Option::DIFFICULTY, minecraft) + .addOptionItem(&Options::Option::SERVER_VISIBLE, minecraft) + .addOptionItem(&Options::Option::THIRD_PERSON, minecraft) + .addOptionItem(&Options::Option::GUI_SCALE, minecraft); + + // Controls Pane + optionPanes[2]->createOptionsGroup("options.group.controls") + .addOptionItem(&Options::Option::INVERT_MOUSE, minecraft); + + // Graphics Pane + optionPanes[3]->createOptionsGroup("options.group.graphics") + .addOptionItem(&Options::Option::GRAPHICS, minecraft) + .addOptionItem(&Options::Option::VIEW_BOBBING, minecraft) + .addOptionItem(&Options::Option::AMBIENT_OCCLUSION, minecraft) + .addOptionItem(&Options::Option::ANAGLYPH, minecraft) + .addOptionItem(&Options::Option::LIMIT_FRAMERATE, minecraft) + .addOptionItem(&Options::Option::VSYNC, minecraft) + .addOptionItem(&Options::Option::MUSIC, minecraft) + .addOptionItem(&Options::Option::SOUND, minecraft); } -void OptionsScreen::mouseClicked( int x, int y, int buttonNum ) { - if(currentOptionPane != NULL) +void OptionsScreen::mouseClicked(int x, int y, int buttonNum) { + + if (currentOptionPane != NULL) currentOptionPane->mouseClicked(minecraft, x, y, buttonNum); + super::mouseClicked(x, y, buttonNum); } -void OptionsScreen::mouseReleased( int x, int y, int buttonNum ) { - if(currentOptionPane != NULL) +void OptionsScreen::mouseReleased(int x, int y, int buttonNum) { + + if (currentOptionPane != NULL) currentOptionPane->mouseReleased(minecraft, x, y, buttonNum); + super::mouseReleased(x, y, buttonNum); } void OptionsScreen::tick() { - if(currentOptionPane != NULL) + + if (currentOptionPane != NULL) currentOptionPane->tick(minecraft); + super::tick(); -} +} \ No newline at end of file diff --git a/src/client/gui/screens/OptionsScreen.h b/src/client/gui/screens/OptionsScreen.h index 95c1d98..10379ab 100755 --- a/src/client/gui/screens/OptionsScreen.h +++ b/src/client/gui/screens/OptionsScreen.h @@ -10,29 +10,37 @@ class OptionsPane; class OptionsScreen: public Screen { typedef Screen super; - void init(); + void init(); void generateOptionScreens(); public: OptionsScreen(); ~OptionsScreen(); + void setupPositions(); - void buttonClicked( Button* button ); + void buttonClicked(Button* button); void render(int xm, int ym, float a); void removed(); void selectCategory(int index); - virtual void mouseClicked( int x, int y, int buttonNum ); - virtual void mouseReleased( 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 tick(); + private: Touch::THeader* bHeader; ImageButton* btnClose; + + Button* btnChangeUsername; + Button* btnCredits; // <-- ADD THIS + std::vector categoryButtons; std::vector optionPanes; + OptionsPane* currentOptionPane; + int selectedCategory; }; -#endif /*NET_MINECRAFT_CLIENT_GUI_SCREENS__OptionsScreen_H__*/ +#endif /*NET_MINECRAFT_CLIENT_GUI_SCREENS__OptionsScreen_H__*/ \ No newline at end of file diff --git a/src/client/gui/screens/SelectWorldScreen.cpp b/src/client/gui/screens/SelectWorldScreen.cpp index ec92cfa..8ec3f3c 100755 --- a/src/client/gui/screens/SelectWorldScreen.cpp +++ b/src/client/gui/screens/SelectWorldScreen.cpp @@ -230,7 +230,6 @@ SelectWorldScreen::SelectWorldScreen() bBack (3, "Back"), bWorldView(4, ""), worldsList(NULL), - _state(_STATE_DEFAULT), _hasStartedLevel(false) { bDelete.active = false; @@ -244,17 +243,11 @@ SelectWorldScreen::~SelectWorldScreen() void SelectWorldScreen::buttonClicked(Button* button) { if (button->id == bCreate.id) { - //minecraft->setScreen( new CreateWorldScreen() ); - //minecraft->locateMultiplayer(); - //minecraft->setScreen(new JoinGameScreen()); - - //minecraft->hostMultiplayer(); - //minecraft->setScreen(new ProgressScreen()); - - if (_state == _STATE_DEFAULT && !_hasStartedLevel) { - minecraft->platform()->createUserInput(DialogDefinitions::DIALOG_CREATE_NEW_WORLD); - _state = _STATE_CREATEWORLD; - } + // open in-game world-creation screen instead of using platform dialog + if (!_hasStartedLevel) { + std::string name = getUniqueLevelName("world"); + minecraft->setScreen(new SimpleChooseLevelScreen(name)); + } } if (button->id == bDelete.id) { if (isIndexValid(worldsList->selectedItem)) { @@ -294,70 +287,6 @@ static char ILLEGAL_FILE_CHARACTERS[] = { void SelectWorldScreen::tick() { - if (_state == _STATE_CREATEWORLD) { - #if defined(RPI) - std::string levelId = getUniqueLevelName("world"); - LevelSettings settings(getEpochTimeS(), GameType::Creative); - minecraft->selectLevel(levelId, levelId, settings); - minecraft->hostMultiplayer(); - minecraft->setScreen(new ProgressScreen()); - _hasStartedLevel = true; - #elif defined(WIN32) - std::string name = getUniqueLevelName("perf"); - minecraft->setScreen(new SimpleChooseLevelScreen(name)); - #else - int status = minecraft->platform()->getUserInputStatus(); - if (status > -1) { - if (status == 1) { - StringVector sv = minecraft->platform()->getUserInput(); - - // Read the level name. - // 1) Trim name 2) Remove all bad chars 3) Append '-' chars 'til the name is unique - std::string levelName = Util::stringTrim(sv[0]); - std::string levelId = levelName; - - for (int i = 0; i < sizeof(ILLEGAL_FILE_CHARACTERS) / sizeof(char); ++i) - levelId = Util::stringReplace(levelId, std::string(1, ILLEGAL_FILE_CHARACTERS[i]), ""); - if ((int)levelId.length() == 0) { - levelId = "no_name"; - } - levelId = getUniqueLevelName(levelId); - - // Read the seed - int seed = getEpochTimeS(); - if (sv.size() >= 2) { - std::string seedString = Util::stringTrim(sv[1]); - if (seedString.length() > 0) { - int tmpSeed; - // Try to read it as an integer - if (sscanf(seedString.c_str(), "%d", &tmpSeed) > 0) { - seed = tmpSeed; - } // Hash the "seed" - else { - seed = Util::hashCode(seedString); - } - } - } - // Read the game mode - bool isCreative = true; - if (sv.size() >= 3 && sv[2] == "survival") - isCreative = false; - - // Start a new level with the given name and seed - LevelSettings settings(seed, isCreative? GameType::Creative : GameType::Survival); - LOGI("Creating a level with id '%s', name '%s' and seed '%d'\n", levelId.c_str(), levelName.c_str(), seed); - minecraft->selectLevel(levelId, levelName, settings); - minecraft->hostMultiplayer(); - minecraft->setScreen(new ProgressScreen()); - _hasStartedLevel = true; - } - _state = _STATE_DEFAULT; - } - #endif - - return; - } - worldsList->tick(); if (worldsList->hasPickedLevel) { diff --git a/src/client/gui/screens/SelectWorldScreen.h b/src/client/gui/screens/SelectWorldScreen.h index a4eaf35..8b45bb2 100755 --- a/src/client/gui/screens/SelectWorldScreen.h +++ b/src/client/gui/screens/SelectWorldScreen.h @@ -104,9 +104,6 @@ private: bool _mouseHasBeenUp; bool _hasStartedLevel; - int _state; - static const int _STATE_DEFAULT = 0; - static const int _STATE_CREATEWORLD = 1; //LevelStorageSource* levels; }; diff --git a/src/client/gui/screens/SimpleChooseLevelScreen.cpp b/src/client/gui/screens/SimpleChooseLevelScreen.cpp index ba667e0..1edc838 100755 --- a/src/client/gui/screens/SimpleChooseLevelScreen.cpp +++ b/src/client/gui/screens/SimpleChooseLevelScreen.cpp @@ -2,143 +2,233 @@ #include "ProgressScreen.h" #include "ScreenChooser.h" #include "../components/Button.h" +#include "../components/ImageButton.h" #include "../../Minecraft.h" #include "../../../world/level/LevelSettings.h" #include "../../../platform/time.h" -#include "client/gamemode/GameMode.h" +#include "../../../platform/input/Keyboard.h" +#include "../../../platform/log.h" SimpleChooseLevelScreen::SimpleChooseLevelScreen(const std::string& levelName) -: - // bCreative(0), - bGamemode(0), - bBack(0), - bCreate(0), - levelName(levelName), - hasChosen(false), - gamemode(GameType::Survival), - tLevelName(0, "World name"), - tSeed(1, "World seed") +: bHeader(0), + bGamemode(0), + bBack(0), + bCreate(0), + levelName(levelName), + hasChosen(false), + gamemode(GameType::Survival), + tLevelName(0, "World name"), + tSeed(1, "World seed") { } SimpleChooseLevelScreen::~SimpleChooseLevelScreen() { - // delete bCreative; - delete bGamemode; - delete bBack; + if (bHeader) delete bHeader; + delete bGamemode; + delete bBack; + delete bCreate; } void SimpleChooseLevelScreen::init() { - if (minecraft->useTouchscreen()) { - // bCreative = new Touch::TButton(1, "Creative mode"); - bGamemode = new Touch::TButton(2, "Survival mode"); - bBack = new Touch::TButton(3, "Back"); - bCreate = new Touch::TButton(4, "Create"); - } else { - // bCreative = new Button(1, "Creative mode"); - bGamemode = new Button(2, "Survival mode"); - bBack = new Button(3, "Back"); - bCreate = new Button(4, "Create"); - } - // buttons.push_back(bCreative); - buttons.push_back(bGamemode); - buttons.push_back(bBack); - buttons.push_back(bCreate); + // header + close button + bHeader = new Touch::THeader(0, "Create World"); + // create the back/X button as ImageButton like CreditsScreen + bBack = new ImageButton(2, ""); + { + 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); + } + if (minecraft->useTouchscreen()) { + bGamemode = new Touch::TButton(1, "Survival mode"); + bCreate = new Touch::TButton(3, "Create"); + } else { + bGamemode = new Button(1, "Survival mode"); + bCreate = new Button(3, "Create"); + } - textBoxes.push_back(&tLevelName); - textBoxes.push_back(&tSeed); + buttons.push_back(bHeader); + buttons.push_back(bBack); + buttons.push_back(bGamemode); + buttons.push_back(bCreate); - // tabButtons.push_back(bCreative); - tabButtons.push_back(bGamemode); - tabButtons.push_back(bBack); - tabButtons.push_back(bCreate); + tabButtons.push_back(bGamemode); + tabButtons.push_back(bBack); + tabButtons.push_back(bCreate); + + textBoxes.push_back(&tLevelName); + textBoxes.push_back(&tSeed); } void SimpleChooseLevelScreen::setupPositions() { - const int padding = 5; + int buttonHeight = bBack->height; - /* bCreative->width = */ bGamemode->width = 120; - tLevelName.width = tSeed.width = 120; - bBack->width = bCreate->width = 60 - padding; - // bCreative->x = (width - bCreative->width) / 2; - // bCreative->y = height/3 - 40; - bGamemode->x = (width - bGamemode->width) / 2; - bGamemode->y = 2*height/3 - 30; - bBack->x = bGamemode->x; - bCreate->x = bGamemode->x + bGamemode->width - bCreate->width; - bBack->y = bCreate->y = height - 40; + // position back button in upper-right + bBack->x = width - bBack->width; + bBack->y = 0; - tLevelName.x = tSeed.x = bGamemode->x; - tLevelName.y = 20; - tSeed.y = tLevelName.y + 30; + // header occupies remaining top bar + if (bHeader) { + bHeader->x = 0; + bHeader->y = 0; + bHeader->width = width - bBack->width; + bHeader->height = buttonHeight; + } + + // layout the form elements below the header + int centerX = width / 2; + const int padding = 5; + + tLevelName.width = tSeed.width = 200; + tLevelName.x = centerX - tLevelName.width / 2; + tLevelName.y = buttonHeight + 20; + + tSeed.x = tLevelName.x; + tSeed.y = tLevelName.y + 30; + + bGamemode->width = 140; + bGamemode->x = centerX - bGamemode->width / 2; + // compute vertical centre for gamemode in remaining space + { + int bottomPad = 20; + int availTop = buttonHeight + 20 + 30 + 10; // just below seed + int availBottom = height - bottomPad - bCreate->height - 10; // leave some gap before create + int availHeight = availBottom - availTop; + if (availHeight < 0) availHeight = 0; + bGamemode->y = availTop + (availHeight - bGamemode->height) / 2; + } + + bCreate->width = 100; + bCreate->x = centerX - bCreate->width / 2; + int bottomPadding = 20; + bCreate->y = height - bottomPadding - bCreate->height; +} + +void SimpleChooseLevelScreen::tick() +{ + // let any textboxes handle their own blinking/input + for (auto* tb : textBoxes) + tb->tick(minecraft); } void SimpleChooseLevelScreen::render( int xm, int ym, float a ) { - renderDirtBackground(0); + renderDirtBackground(0); glEnable2(GL_BLEND); - const char* str = NULL; + const char* str = NULL; + if (gamemode == GameType::Survival) { + str = "Mobs, health and gather resources"; + } else if (gamemode == GameType::Creative) { + str = "Unlimited resources and flying"; + } + if (str) { + drawCenteredString(minecraft->font, str, width/2, bGamemode->y + bGamemode->height + 4, 0xffcccccc); + } - if (gamemode == GameType::Survival) { - str = "Mobs, health and gather resources"; - } else if (gamemode == GameType::Creative) { - str = "Unlimited resources and flying"; - } + drawString(minecraft->font, "World name:", tLevelName.x, tLevelName.y - Font::DefaultLineHeight - 2, 0xffcccccc); + drawString(minecraft->font, "World seed:", tSeed.x, tSeed.y - Font::DefaultLineHeight - 2, 0xffcccccc); - if (str) { - drawCenteredString(minecraft->font, str, 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 seed:", tSeed.x, tSeed.y - Font::DefaultLineHeight - 2, 0xffcccccc); - - Screen::render(xm, ym, a); + Screen::render(xm, ym, a); glDisable2(GL_BLEND); } +// mouse clicks should also manage textbox focus explicitly +void SimpleChooseLevelScreen::mouseClicked(int x, int y, int buttonNum) +{ + if (buttonNum == MouseAction::ACTION_LEFT) { + // determine if the click landed on either textbox or its label above + int lvlTop = tLevelName.y - (Font::DefaultLineHeight + 4); + int lvlBottom = tLevelName.y + tLevelName.height; + int lvlLeft = tLevelName.x; + int lvlRight = tLevelName.x + tLevelName.width; + bool clickedLevel = x >= lvlLeft && x < lvlRight && y >= lvlTop && y < lvlBottom; + + int seedTop = tSeed.y - (Font::DefaultLineHeight + 4); + int seedBottom = tSeed.y + tSeed.height; + int seedLeft = tSeed.x; + int seedRight = tSeed.x + tSeed.width; + bool clickedSeed = x >= seedLeft && x < seedRight && y >= seedTop && y < seedBottom; + + if (clickedLevel) { + LOGI("SimpleChooseLevelScreen: level textbox clicked (%d,%d)\n", x, y); + tLevelName.setFocus(minecraft); + tSeed.loseFocus(minecraft); + } else if (clickedSeed) { + LOGI("SimpleChooseLevelScreen: seed textbox clicked (%d,%d)\n", x, y); + tSeed.setFocus(minecraft); + tLevelName.loseFocus(minecraft); + } else { + // click outside both fields -> blur both + tLevelName.loseFocus(minecraft); + tSeed.loseFocus(minecraft); + } + } + + // allow normal button and textbox handling too + Screen::mouseClicked(x, y, buttonNum); +} + void SimpleChooseLevelScreen::buttonClicked( Button* button ) { - if (button == bBack) { - minecraft->screenChooser.setScreen(SCREEN_STARTMENU); - return; - } - if (hasChosen) - return; + if (hasChosen) + return; - if (button == bGamemode) { - gamemode ^= 1; - bGamemode->msg = (gamemode == GameType::Survival) ? "Survival mode" : "Creative mode"; - } + if (button == bGamemode) { + gamemode ^= 1; + bGamemode->msg = (gamemode == GameType::Survival) ? "Survival mode" : "Creative mode"; + return; + } - if (button == bCreate) { - int seed = getEpochTimeS(); + if (button == bCreate) { + int seed = getEpochTimeS(); + if (!tSeed.text.empty()) { + std::string seedString = Util::stringTrim(tSeed.text); + int tmpSeed; + if (sscanf(seedString.c_str(), "%d", &tmpSeed) > 0) { + seed = tmpSeed; + } else { + seed = Util::hashCode(seedString); + } + } + std::string levelId = getUniqueLevelName(tLevelName.text); + LevelSettings settings(seed, gamemode); + minecraft->selectLevel(levelId, levelId, settings); + minecraft->hostMultiplayer(); + minecraft->setScreen(new ProgressScreen()); + hasChosen = true; + return; + } - if (!tSeed.text.empty()) { - std::string seedString = Util::stringTrim(tSeed.text); - int tmpSeed; - // Try to read it as an integer - if (sscanf(seedString.c_str(), "%d", &tmpSeed) > 0) { - seed = tmpSeed; - } // Hash the "seed" - else { - seed = Util::hashCode(seedString); - } - } + if (button == bBack) { + minecraft->screenChooser.setScreen(SCREEN_STARTMENU); + } +} - std::string levelId = getUniqueLevelName(tLevelName.text); - LevelSettings settings(seed, gamemode); - minecraft->selectLevel(levelId, levelId, settings); - minecraft->hostMultiplayer(); - minecraft->setScreen(new ProgressScreen()); - hasChosen = true; - } +void SimpleChooseLevelScreen::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 SimpleChooseLevelScreen::keyboardNewChar(char inputChar) +{ + // forward character input to focused textbox(s) + for (auto* tb : textBoxes) tb->handleChar(inputChar); } bool SimpleChooseLevelScreen::handleBackEvent(bool isDown) { if (!isDown) minecraft->screenChooser.setScreen(SCREEN_STARTMENU); - return true; + return true; } diff --git a/src/client/gui/screens/SimpleChooseLevelScreen.h b/src/client/gui/screens/SimpleChooseLevelScreen.h index 88c77af..893876d 100755 --- a/src/client/gui/screens/SimpleChooseLevelScreen.h +++ b/src/client/gui/screens/SimpleChooseLevelScreen.h @@ -3,8 +3,9 @@ #include "ChooseLevelScreen.h" #include "../components/TextBox.h" - +#include "../components/Button.h" // for Touch::THeader class Button; +class ImageButton; class SimpleChooseLevelScreen: public ChooseLevelScreen { @@ -14,23 +15,25 @@ public: virtual ~SimpleChooseLevelScreen(); void init(); - void setupPositions(); + void tick(); void render(int xm, int ym, float a); void buttonClicked(Button* button); bool handleBackEvent(bool isDown); + virtual void keyPressed(int eventKey); + virtual void keyboardNewChar(char inputChar); + virtual void mouseClicked(int x, int y, int buttonNum); private: - // Button* bCreative; + Touch::THeader* bHeader; Button* bGamemode; - Button* bBack; + ImageButton* bBack; Button* bCreate; bool hasChosen; std::string levelName; - int gamemode; TextBox tLevelName; diff --git a/src/client/gui/screens/StartMenuScreen.cpp b/src/client/gui/screens/StartMenuScreen.cpp index a216ad9..24c5459 100755 --- a/src/client/gui/screens/StartMenuScreen.cpp +++ b/src/client/gui/screens/StartMenuScreen.cpp @@ -1,4 +1,5 @@ #include "StartMenuScreen.h" +#include "UsernameScreen.h" #include "SelectWorldScreen.h" #include "ProgressScreen.h" #include "JoinGameScreen.h" @@ -38,6 +39,10 @@ StartMenuScreen::~StartMenuScreen() void StartMenuScreen::init() { + if (minecraft->options.username.empty()) { + return; // tick() will redirect to UsernameScreen + } + buttons.push_back(&bHost); buttons.push_back(&bJoin); //buttons.push_back(&bTest); @@ -57,11 +62,8 @@ void StartMenuScreen::init() copyright = "\xffMojang AB";//. Do not distribute!"; - #ifdef PRE_ANDROID23 - std::string versionString = Common::getGameVersionString("j"); - #else - std::string versionString = Common::getGameVersionString(); - #endif + // always show base version string, suffix was previously added for Android builds + std::string versionString = Common::getGameVersionString(); #ifdef DEMO_MODE #ifdef __APPLE__ @@ -106,6 +108,10 @@ void StartMenuScreen::setupPositions() { } void StartMenuScreen::tick() { + if (minecraft->options.username.empty()) { + minecraft->setScreen(new UsernameScreen()); + return; + } _updateLicense(); } @@ -181,8 +187,19 @@ void StartMenuScreen::render( int xm, int ym, float a ) drawString(font, version, versionPosX, 62, /*50,*/ 0xffcccccc);//0x666666); drawString(font, copyright, copyrightPosX, height - 10, 0xffffff); - - Screen::render(xm, ym, a); + glEnable2(GL_BLEND); + glBlendFunc2(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f2(1, 1, 1, 1); + if (Textures::isTextureIdValid(minecraft->textures->loadAndBindTexture("gui/logo/github.png"))) + blit(2, height - 10, 0, 0, 8, 8, 256, 256); +{ + std::string txt = "Kolyah35/minecraft-pe-0.6.1"; + float wtxt = font->width(txt); + Gui::drawColoredString(font, txt, 12, height - 10, 255); + // underline link + float y0 = height - 10 + font->lineHeight - 1; + this->fill(12, (int)y0, 12 + (int)wtxt, (int)(y0 + 1), 0xffffffff); + } } void StartMenuScreen::_updateLicense() @@ -202,6 +219,17 @@ void StartMenuScreen::_updateLicense() } } +void StartMenuScreen::mouseClicked(int x, int y, int buttonNum) { + const int logoX = 2; + const int logoW = 8 + 2 + font->width("Kolyah35/minecraft-pe-0.6.1"); + const int logoY = height - 10; + const int logoH = 10; + if (x >= logoX && x <= logoX + logoW && y >= logoY && y <= logoY + logoH) + minecraft->platform()->openURL("https://gitea.sffempire.ru/Kolyah35/minecraft-pe-0.6.1"); + else + Screen::mouseClicked(x, y, buttonNum); +} + bool StartMenuScreen::handleBackEvent( bool isDown ) { minecraft->quit(); return true; diff --git a/src/client/gui/screens/StartMenuScreen.h b/src/client/gui/screens/StartMenuScreen.h index 8d46c58..faac0f1 100755 --- a/src/client/gui/screens/StartMenuScreen.h +++ b/src/client/gui/screens/StartMenuScreen.h @@ -17,6 +17,7 @@ public: void render(int xm, int ym, float a); void buttonClicked(Button* button); + virtual void mouseClicked(int x, int y, int buttonNum); bool handleBackEvent(bool isDown); bool isInGameScreen(); private: diff --git a/src/client/gui/screens/UsernameScreen.cpp b/src/client/gui/screens/UsernameScreen.cpp new file mode 100644 index 0000000..871346d --- /dev/null +++ b/src/client/gui/screens/UsernameScreen.cpp @@ -0,0 +1,128 @@ +#include "UsernameScreen.h" +#include "StartMenuScreen.h" +#include "../../Minecraft.h" +#include "../../User.h" +#include "../Font.h" +#include "../components/Button.h" +#include "../../../platform/input/Keyboard.h" +#include "../../../AppPlatform.h" + +UsernameScreen::UsernameScreen() +: _btnDone(0, "Done"), + _input(""), + _cursorBlink(0) +{ +} + +UsernameScreen::~UsernameScreen() +{ +} + +void UsernameScreen::init() +{ + _input = ""; + _btnDone.active = false; // disabled until name typed + buttons.push_back(&_btnDone); + setupPositions(); +} + +void UsernameScreen::setupPositions() +{ + _btnDone.width = 120; + _btnDone.height = 20; + _btnDone.x = (width - _btnDone.width) / 2; + _btnDone.y = height / 2 + 52; +} + +void UsernameScreen::tick() +{ + _cursorBlink++; +} + +void UsernameScreen::keyPressed(int eventKey) +{ + if (eventKey == Keyboard::KEY_BACKSPACE) { + if (!_input.empty()) + _input.erase(_input.size() - 1, 1); + } else if (eventKey == Keyboard::KEY_RETURN) { + if (!_input.empty()) + buttonClicked(&_btnDone); + } + // deliberately do NOT call super::keyPressed — that would close the screen on Escape + _btnDone.active = !_input.empty(); +} + +void UsernameScreen::keyboardNewChar(char inputChar) +{ + if (_input.size() < 16 && inputChar >= 32 && inputChar < 127) + _input += inputChar; + _btnDone.active = !_input.empty(); +} + +void UsernameScreen::mouseClicked(int x, int y, int button) +{ + int cx = width / 2; + int cy = height / 2; + int boxW = 160; + int boxH = 18; + int boxX = cx - boxW / 2; + int boxY = cy - 5; + if (x >= boxX && x <= boxX + boxW && y >= boxY && y <= boxY + boxH) { + minecraft->platform()->showKeyboard(); + } else { + super::mouseClicked(x, y, button); + } +} + +void UsernameScreen::removed() +{ + minecraft->platform()->hideKeyboard(); +} + +void UsernameScreen::buttonClicked(Button* button) +{ + if (button == &_btnDone && !_input.empty()) { + minecraft->options.username = _input; + minecraft->options.save(); + minecraft->user->name = _input; + minecraft->setScreen(NULL); // goes to StartMenuScreen + } +} + +void UsernameScreen::render(int xm, int ym, float a) +{ + // Dark dirt background + renderBackground(); + + int cx = width / 2; + int cy = height / 2; + + // Title + drawCenteredString(font, "Enter your username", cx, cy - 70, 0xffffffff); + + // Subtitle + drawCenteredString(font, "Please choose a username so others can easily", cx, cy - 52, 0xffaaaaaa); + drawCenteredString(font, "identify you in chat. Don't worry, you can", cx, cy - 40, 0xffaaaaaa); + drawCenteredString(font, "change it anytime.", cx, cy - 28, 0xffaaaaaa); + + // Input box background + int boxW = 160; + int boxH = 18; + int boxX = cx - boxW / 2; + int boxY = cy - 5; + fill(boxX - 1, boxY - 1, boxX + boxW + 1, boxY + boxH + 1, 0xff000000); + fill(boxX, boxY, boxX + boxW, boxY + boxH, 0xff202020); + + // Build display string with cursor + std::string display = _input; + if ((_cursorBlink / 10) % 2 == 0) + display += '|'; + + font->draw(display, (float)(boxX + 4), (float)(boxY + (boxH - 8) / 2 + 1), 0xffffffff, false); + + // Hint below box + drawCenteredString(font, "Max 16 characters", cx, cy + 20, 0xff808080); + + // Buttons (Done) + super::render(xm, ym, a); +} diff --git a/src/client/gui/screens/UsernameScreen.h b/src/client/gui/screens/UsernameScreen.h new file mode 100644 index 0000000..5ffb816 --- /dev/null +++ b/src/client/gui/screens/UsernameScreen.h @@ -0,0 +1,37 @@ +#ifndef NET_MINECRAFT_CLIENT_GUI_SCREENS__UsernameScreen_H__ +#define NET_MINECRAFT_CLIENT_GUI_SCREENS__UsernameScreen_H__ + +#include "../Screen.h" +#include "../components/Button.h" +#include + +class UsernameScreen : public Screen +{ + typedef Screen super; +public: + UsernameScreen(); + virtual ~UsernameScreen(); + + void init(); + virtual void setupPositions() override; + void render(int xm, int ym, float a); + void tick(); + + virtual bool isPauseScreen() { return false; } + + virtual void keyPressed(int eventKey); + virtual void keyboardNewChar(char inputChar); + virtual bool handleBackEvent(bool isDown) { return true; } // block back/escape + virtual void removed(); + virtual void mouseClicked(int x, int y, int button); + +protected: + virtual void buttonClicked(Button* button); + +private: + Button _btnDone; + std::string _input; + int _cursorBlink; +}; + +#endif /*NET_MINECRAFT_CLIENT_GUI_SCREENS__UsernameScreen_H__*/ diff --git a/src/client/gui/screens/touch/TouchSelectWorldScreen.cpp b/src/client/gui/screens/touch/TouchSelectWorldScreen.cpp index 4ebf5fd..e294a6d 100755 --- a/src/client/gui/screens/touch/TouchSelectWorldScreen.cpp +++ b/src/client/gui/screens/touch/TouchSelectWorldScreen.cpp @@ -283,8 +283,7 @@ SelectWorldScreen::SelectWorldScreen() bHeader (0, "Select world"), bWorldView(4, ""), worldsList(NULL), - _hasStartedLevel(false), - _state(_STATE_DEFAULT) + _hasStartedLevel(false) { bDelete.active = false; @@ -349,9 +348,9 @@ void SelectWorldScreen::setupPositions() { void SelectWorldScreen::buttonClicked(Button* button) { if (button->id == bCreate.id) { - if (_state == _STATE_DEFAULT && !_hasStartedLevel) { - minecraft->platform()->createUserInput(DialogDefinitions::DIALOG_CREATE_NEW_WORLD); - _state = _STATE_CREATEWORLD; + if (!_hasStartedLevel) { + std::string name = getUniqueLevelName("World"); + minecraft->setScreen(new SimpleChooseLevelScreen(name)); } } if (button->id == bDelete.id) { @@ -392,9 +391,10 @@ static char ILLEGAL_FILE_CHARACTERS[] = { void SelectWorldScreen::tick() { +#if 0 if (_state == _STATE_CREATEWORLD) { #if defined(RPI) - std::string levelId = getUniqueLevelName("perf"); + std::string levelId = getUniqueLevelName("World"); //int seed = Util::hashCode("/r/Minecraft"); LevelSettings settings(getEpochTimeS(), GameType::Creative); minecraft->selectLevel(levelId, levelId, settings); @@ -402,7 +402,7 @@ void SelectWorldScreen::tick() minecraft->setScreen(new ProgressScreen()); _hasStartedLevel = true; #elif defined(PLATFORM_DESKTOP) - std::string name = getUniqueLevelName("perf"); + std::string name = getUniqueLevelName("World"); minecraft->setScreen(new SimpleChooseLevelScreen(name)); #else int status = minecraft->platform()->getUserInputStatus(); @@ -461,14 +461,15 @@ void SelectWorldScreen::tick() worldsList->hasPickedLevel = false; return; } +#endif worldsList->tick(); if (worldsList->hasPickedLevel) { if (worldsList->pickedIndex == worldsList->levels.size()) { worldsList->hasPickedLevel = false; - minecraft->platform()->createUserInput(DialogDefinitions::DIALOG_CREATE_NEW_WORLD); - _state = _STATE_CREATEWORLD; + std::string name = getUniqueLevelName("World"); + minecraft->setScreen(new SimpleChooseLevelScreen(name)); } else { minecraft->selectLevel(worldsList->pickedLevel.id, worldsList->pickedLevel.name, LevelSettings::None()); minecraft->hostMultiplayer(); diff --git a/src/client/gui/screens/touch/TouchSelectWorldScreen.h b/src/client/gui/screens/touch/TouchSelectWorldScreen.h index b187f2f..1f62139 100755 --- a/src/client/gui/screens/touch/TouchSelectWorldScreen.h +++ b/src/client/gui/screens/touch/TouchSelectWorldScreen.h @@ -112,9 +112,6 @@ private: bool _mouseHasBeenUp; bool _hasStartedLevel; - int _state; - static const int _STATE_DEFAULT = 0; - static const int _STATE_CREATEWORLD = 1; //LevelStorageSource* levels; }; }; diff --git a/src/client/gui/screens/touch/TouchStartMenuScreen.cpp b/src/client/gui/screens/touch/TouchStartMenuScreen.cpp index ee51e4a..92fb081 100755 --- a/src/client/gui/screens/touch/TouchStartMenuScreen.cpp +++ b/src/client/gui/screens/touch/TouchStartMenuScreen.cpp @@ -22,6 +22,62 @@ #include "../DialogDefinitions.h" #include "../SimpleChooseLevelScreen.h" +// +// Buy Button implementation +// +BuyButton::BuyButton(int id) +: super(id, "") +{ + ImageDef def; + // Setup the source rectangle + def.setSrc(IntRectangle(64, 182, 190, 55)); + def.width = 75;//rc.w / 3; + def.height = 75 * (55.0f / 190.0f);//rc.h / 3; + def.name = "gui/gui.png"; + + setImageDef(def, true); +} + +void BuyButton::render(Minecraft* minecraft, int xm, int ym) { + glColor4f2(1, 1, 1, 1); + bool hovered = active && (minecraft->useTouchscreen()? (xm >= x && ym >= y && xm < x + width && ym < y + height) : false); + renderBg(minecraft, xm, ym); + TextureId texId = (_imageDef.name.length() > 0)? minecraft->textures->loadAndBindTexture(_imageDef.name) : Textures::InvalidId; + if ( Textures::isTextureIdValid(texId) ) { + const ImageDef& d = _imageDef; + Tesselator& t = Tesselator::instance; + + t.begin(); + if (!active) t.color(0xff808080); + else if (hovered||selected) t.color(0xffcccccc); + //else t.color(0xffe0e0e0); + else t.color(0xffffffff); + + float hx = ((float) d.width) * 0.5f; + float hy = ((float) d.height) * 0.5f; + const float cx = ((float)x+d.x) + hx; + const float cy = ((float)y+d.y) + hy; + if (hovered) { + hx *= 0.95f; + hy *= 0.95f; + } + + const TextureData* td = minecraft->textures->getTemporaryTextureData(texId); + const IntRectangle* src = _imageDef.getSrc(); + if (td != NULL && src != NULL) { + float u0 = (src->x) / (float)td->w; + float u1 = (src->x+src->w) / (float)td->w; + float v0 = (src->y) / (float)td->h; + float v1 = (src->y+src->h) / (float)td->h; + t.vertexUV(cx-hx, cy-hy, blitOffset, u0, v0); + t.vertexUV(cx-hx, cy+hy, blitOffset, u0, v1); + t.vertexUV(cx+hx, cy+hy, blitOffset, u1, v1); + t.vertexUV(cx+hx, cy-hy, blitOffset, u1, v0); + } + t.draw(); + } +} + namespace Touch { // @@ -32,7 +88,8 @@ namespace Touch { StartMenuScreen::StartMenuScreen() : bHost( 2, "Start Game"), bJoin( 3, "Join Game"), - bOptions( 4, "Options") + bOptions( 4, "Options"), + bBuy( 5) { ImageDef def; bJoin.width = 75; @@ -61,19 +118,21 @@ void StartMenuScreen::init() buttons.push_back(&bJoin); buttons.push_back(&bOptions); - //buttons.push_back(&bTest); + tabButtons.push_back(&bHost); tabButtons.push_back(&bJoin); tabButtons.push_back(&bOptions); + #ifdef DEMO_MODE + buttons.push_back(&bBuy); + tabButtons.push_back(&bBuy); + #endif + copyright = "\xffMojang AB";//. Do not distribute!"; - #ifdef PRE_ANDROID23 - std::string versionString = Common::getGameVersionString("j"); - #else - std::string versionString = Common::getGameVersionString(); - #endif + // always show base version string + std::string versionString = Common::getGameVersionString(); #ifdef DEMO_MODE #ifdef __APPLE__ @@ -103,31 +162,26 @@ void StartMenuScreen::setupPositions() { bOptions.y = yBase; //#endif - //bTest.x = 0; //width - bTest.w; - //bTest.y = height - bTest.h; - // Center buttons bJoin.x = 0*buttonWidth + (int)(1*spacing); bHost.x = 1*buttonWidth + (int)(2*spacing); bOptions.x = 2*buttonWidth + (int)(3*spacing); + //bBuy.y = bOptions.y - bBuy.h - 6; + //bBuy.x = bOptions.x + bOptions.w - bBuy.w; + bBuy.y = height - bBuy.height - 3; + bBuy.x = (width - bBuy.width) / 2; + copyrightPosX = width - minecraft->font->width(copyright) - 1; versionPosX = (width - minecraft->font->width(version)) / 2;// - minecraft->font->width(version) - 2; } void StartMenuScreen::tick() { - Screen::tick(); - _updateLicense(); } void StartMenuScreen::buttonClicked(::Button* button) { - //if (button->id == bTest.id) { - // minecraft->selectLevel("Broken", "Broken", 1317199248); - // minecraft->hostMultiplayer(); - // minecraft->setScreen(new ProgressScreen()); - //} if (button->id == bHost.id) { #if defined(DEMO_MODE) || defined(APPLE_DEMO_PROMOTION) @@ -149,6 +203,11 @@ void StartMenuScreen::buttonClicked(::Button* button) { { minecraft->setScreen(new OptionsScreen()); } + if (button->id == bBuy.id) + { + minecraft->platform()->buyGame(); + //minecraft->setScreen(new BuyGameScreen()); + } } bool StartMenuScreen::isInGameScreen() { return false; } @@ -187,6 +246,10 @@ void StartMenuScreen::render( int xm, int ym, float a ) drawString(font, version, versionPosX, (int)(y+h)+2, /*50,*/ 0xffcccccc);//0x666666); drawString(font, copyright, copyrightPosX, height - 10, 0xffffff); + glColor4f2(1, 1, 1, 1); + if (Textures::isTextureIdValid(minecraft->textures->loadAndBindTexture("gui/logo/github.png"))) + blit(2, height - 10, 0, 0, 8, 8, 256, 256); + drawString(font, "Kolyah35/minecraft-pe-0.6.1", 12, height - 10, 0xffcccccc); //patch->draw(t, 0, 20); } Screen::render(xm, ym, a); @@ -210,9 +273,21 @@ void StartMenuScreen::_updateLicense() } } +void StartMenuScreen::mouseClicked(int x, int y, int buttonNum) { + const int logoX = 2; + const int logoW = 8 + 2 + font->width("Kolyah35/minecraft-pe-0.6.1"); + const int logoY = height - 10; + const int logoH = 10; + if (x >= logoX && x <= logoX + logoW && y >= logoY && y <= logoY + logoH) + minecraft->platform()->openURL("https://gitea.sffempire.ru/Kolyah35/minecraft-pe-0.6.1"); + else + Screen::mouseClicked(x, y, buttonNum); +} + bool StartMenuScreen::handleBackEvent( bool isDown ) { minecraft->quit(); return true; } -}; +} // namespace Touch + diff --git a/src/client/gui/screens/touch/TouchStartMenuScreen.h b/src/client/gui/screens/touch/TouchStartMenuScreen.h index 8ac2c70..46886e4 100755 --- a/src/client/gui/screens/touch/TouchStartMenuScreen.h +++ b/src/client/gui/screens/touch/TouchStartMenuScreen.h @@ -5,6 +5,14 @@ #include "../../components/LargeImageButton.h" #include "../../components/TextBox.h" +class BuyButton: public ImageButton { + typedef ImageButton super; +public: + BuyButton(int id); + void render(Minecraft* minecraft, int xm, int ym); +}; + + namespace Touch { class StartMenuScreen: public Screen @@ -20,6 +28,7 @@ public: void render(int xm, int ym, float a); void buttonClicked(Button* button); + virtual void mouseClicked(int x, int y, int buttonNum); bool handleBackEvent(bool isDown); bool isInGameScreen(); private: @@ -28,6 +37,7 @@ private: LargeImageButton bHost; LargeImageButton bJoin; LargeImageButton bOptions; + BuyButton bBuy; std::string copyright; int copyrightPosX; diff --git a/src/client/player/LocalPlayer.cpp b/src/client/player/LocalPlayer.cpp index 995d52c..fbc52c7 100755 --- a/src/client/player/LocalPlayer.cpp +++ b/src/client/player/LocalPlayer.cpp @@ -50,7 +50,10 @@ LocalPlayer::LocalPlayer(Minecraft* minecraft, Level* level, User* user, int dim sentInventoryItemId(-1), sentInventoryItemData(-1), autoJumpEnabled(true), - armorTypeHash(0) + armorTypeHash(0), + sprinting(false), + sprintDoubleTapTimer(0), + prevForwardHeld(false) { this->dimension = dimension; _init(); @@ -176,6 +179,25 @@ void LocalPlayer::aiStep() { if (!screenCovering) input->tick(this); + // Sprint: detect W double-tap + { + bool forwardHeld = (input->ya > 0); + if (forwardHeld && !prevForwardHeld) { + // leading edge of W press + if (sprintDoubleTapTimer > 0) + sprinting = true; + else + sprintDoubleTapTimer = 7; + } + if (!forwardHeld) { + sprinting = false; + } + if (sprintDoubleTapTimer > 0) sprintDoubleTapTimer--; + prevForwardHeld = forwardHeld; + } + if (input->sneaking || abilities.flying) + sprinting = false; + if (input->sneaking) { if (ySlideOffset < 0.2f) ySlideOffset = 0.2f; } @@ -296,6 +318,10 @@ void LocalPlayer::releaseAllKeys() if (input) input->releaseAllKeys(); } +float LocalPlayer::getWalkingSpeedModifier() { + return sprinting ? 1.3f : 1.0f; +} + float LocalPlayer::getFieldOfViewModifier() { float targetFov = 1.0f; if(abilities.flying) targetFov *= 1.1f; diff --git a/src/client/player/LocalPlayer.h b/src/client/player/LocalPlayer.h index e7b1fb4..fc9de84 100755 --- a/src/client/player/LocalPlayer.h +++ b/src/client/player/LocalPlayer.h @@ -70,6 +70,7 @@ public: void swing(); virtual void openTextEdit( TileEntity* tileEntity ); + virtual float getWalkingSpeedModifier(); private: void calculateFlight(float xa, float ya, float za); bool isSolidTile(int x, int y, int z); @@ -99,6 +100,11 @@ private: int sentInventoryItemData; int armorTypeHash; + + // sprinting + bool sprinting; + int sprintDoubleTapTimer; + bool prevForwardHeld; }; #endif /*NET_MINECRAFT_CLIENT_PLAYER__LocalPlayer_H__*/ diff --git a/src/client/renderer/GameRenderer.cpp b/src/client/renderer/GameRenderer.cpp index 0b8e74e..1ca6ce3 100755 --- a/src/client/renderer/GameRenderer.cpp +++ b/src/client/renderer/GameRenderer.cpp @@ -270,15 +270,15 @@ void GameRenderer::renderLevel(float a) { screenScissorArea.w, screenScissorArea.h); } -// if(mc->options.fancyGraphics) { -// setupFog(-1); -// TIMER_POP_PUSH("sky"); -// glFogf(GL_FOG_START, renderDistance * 0.2f); -// glFogf(GL_FOG_END, renderDistance *0.75); -// levelRenderer->renderSky(a); -// glFogf(GL_FOG_START, renderDistance * 0.6f); -// glFogf(GL_FOG_END, renderDistance); -// } + if(mc->options.fancyGraphics) { + setupFog(-1); + TIMER_POP_PUSH("sky"); + glFogf(GL_FOG_START, renderDistance * 0.2f); + glFogf(GL_FOG_END, renderDistance *0.75); + levelRenderer->renderSky(a); + glFogf(GL_FOG_START, renderDistance * 0.6f); + glFogf(GL_FOG_END, renderDistance); + } glEnable2(GL_FOG); setupFog(1); diff --git a/src/client/renderer/gles.h b/src/client/renderer/gles.h index 993b2c6..3e41c0c 100755 --- a/src/client/renderer/gles.h +++ b/src/client/renderer/gles.h @@ -16,11 +16,11 @@ #if defined(__APPLE__) #import #import + #elif defined(ANDROID) + #include + #include #else #include - #if defined(ANDROID) - #include - #endif #endif #else // Uglyness to fix redeclaration issues diff --git a/src/client/sound/Sound.h b/src/client/sound/Sound.h index 5665c2f..62f3db0 100755 --- a/src/client/sound/Sound.h +++ b/src/client/sound/Sound.h @@ -43,6 +43,7 @@ public: void destroy() const { if (isValid()) { + delete buffer; buffer = 0; } } @@ -60,7 +61,7 @@ private: mutable char* buffer; }; -#if !defined(PRE_ANDROID23) && !defined(__APPLE__) && !defined(RPI) +#if !defined(PRE_ANDROID23) && !defined(__APPLE__) && !defined(RPI) extern SoundDesc SA_cloth1; extern SoundDesc SA_cloth2; diff --git a/src/client/sound/SoundEngine.cpp b/src/client/sound/SoundEngine.cpp index e4bb471..b283d54 100755 --- a/src/client/sound/SoundEngine.cpp +++ b/src/client/sound/SoundEngine.cpp @@ -230,10 +230,13 @@ void SoundEngine::playUI(const std::string& name, float volume, float pitch) {} void SoundEngine::play(const std::string& name, float x, float y, float z, float volume, float pitch) { if ((volume *= options->sound) <= 0) return; - volume = Mth::clamp(volume, 0.0f, 1.0f); + volume = Mth::clamp( volume * _getVolumeMult(x, y, z), 0.0f, 1.0f); + if (/*!loaded || */options->sound == 0 || volume <= 0) return; SoundDesc sound; if (sounds.get(name, sound)) { + float dist = SOUND_DISTANCE; + if (volume > 1) dist *= volume; soundSystem.playAt(sound, x-_x, y-_y, z-_z, volume, pitch); } } diff --git a/src/client/sound/SoundEngine.h b/src/client/sound/SoundEngine.h index b09113b..42de601 100755 --- a/src/client/sound/SoundEngine.h +++ b/src/client/sound/SoundEngine.h @@ -2,9 +2,10 @@ #define NET_MINECRAFT_CLIENT_SOUND__SoundEngine_H__ //package net.minecraft.client.sound; + #if defined(ANDROID) && !defined(PRE_ANDROID23) #include "../../platform/audio/SoundSystemSL.h" -#elif (defined(__APPLE__) || defined (PLATFORM_DESKTOP)) && !defined(NO_SOUND) +#elif defined(__APPLE__) || defined(PLATFORM_DESKTOP) #include "../../platform/audio/SoundSystemAL.h" #else #include "../../platform/audio/SoundSystem.h" @@ -22,7 +23,7 @@ class SoundEngine #if defined(ANDROID) && !defined(PRE_ANDROID23) && !defined(RPI) SoundSystemSL soundSystem; - #elif (defined(__APPLE__) || defined (PLATFORM_DESKTOP)) && !defined(NO_SOUND) + #elif defined(__APPLE__) || defined(PLATFORM_DESKTOP) SoundSystemAL soundSystem; #else SoundSystem soundSystem; diff --git a/src/main_android.cpp b/src/main_android.cpp index db75547..7d9c2bb 100755 --- a/src/main_android.cpp +++ b/src/main_android.cpp @@ -1,5 +1,6 @@ #include "App.h" #include "AppPlatform_android.h" +#include // Horrible, I know. / A #ifndef MAIN_CLASS @@ -38,7 +39,15 @@ static void setupExternalPath(struct android_app* state, MAIN_CLASS* app) const char* str = env->GetStringUTFChars((jstring) pathString, NULL); app->externalStoragePath = str; app->externalCacheStoragePath = str; - LOGI(str); + LOGI("%s", str); + + // ensure the process working directory is set to a writable location + // on Android the default cwd may be '/' which isn't writable. By chdir'ing + // to the external storage path we make relative fopen calls (e.g. "options.txt") + // succeed and persist across launches, fixing the "options never save" bug. + if (chdir(str) != 0) { + LOGI("chdir to %s failed: %s", str, strerror(errno)); + } env->ReleaseStringUTFChars((jstring)pathString, str); diff --git a/src/main_android_java.cpp b/src/main_android_java.cpp index 2de0001..f2fde5c 100755 --- a/src/main_android_java.cpp +++ b/src/main_android_java.cpp @@ -3,6 +3,9 @@ //#include "main_android_java.h" #include "platform/input/Multitouch.h" +#include +#include +#define gettid() ((int)syscall(SYS_gettid)) // Horrible, I know. / A #ifndef MAIN_CLASS @@ -39,7 +42,12 @@ static void setupExternalPath(JNIEnv* env, MAIN_CLASS* app) const char* str = env->GetStringUTFChars((jstring) pathString, NULL); app->externalStoragePath = str; app->externalCacheStoragePath = str; - LOGI(str); + LOGI("%s", str); + + // same fix as the native entry point: make sure cwd is writable + if (chdir(str) != 0) { + LOGI("chdir to %s failed: %s", str, strerror(errno)); + } env->ReleaseStringUTFChars((jstring)pathString, str); } @@ -57,6 +65,7 @@ static void pointerMove(int pointerId, int x, int y) { static App* gApp = 0; static AppContext gContext; +static bool g_inNativeOnCreate = false; extern "C" { JNIEXPORT jint JNICALL @@ -81,34 +90,53 @@ Java_com_mojang_minecraftpe_MainActivity_nativeUnregisterThis(JNIEnv* env, jobje } JNIEXPORT void JNICALL -Java_com_mojang_minecraftpe_MainActivity_nativeOnCreate(JNIEnv* env) { - LOGI("@nativeOnCreate\n"); +Java_com_mojang_minecraftpe_MainActivity_nativeOnCreate(JNIEnv* env, jobject thiz, jint screenWidth, jint screenHeight) { + LOGI("@nativeOnCreate w=%d h=%d\n", (int)screenWidth, (int)screenHeight); + g_inNativeOnCreate = true; appPlatform.instance = g_pActivity; - appPlatform.initConsts(); + appPlatform.setScreenDimensions((int)screenWidth, (int)screenHeight); + LOGI("nativeOnCreate: screen set, no initConsts needed\n"); gContext.doRender = false; gContext.platform = &appPlatform; + LOGI("nativeOnCreate: creating gApp\n"); gApp = new MAIN_CLASS(); + LOGI("nativeOnCreate: gApp=%p\n", gApp); setupExternalPath(env, (MAIN_CLASS*)gApp); + if (env->ExceptionOccurred()) { + LOGI("nativeOnCreate: exception after setupExternalPath!\n"); + env->ExceptionDescribe(); + env->ExceptionClear(); + } + LOGI("nativeOnCreate: done\n"); + g_inNativeOnCreate = false; //gApp->init(gContext); } +static int s_surfaceCreatedCount = 0; + JNIEXPORT void JNICALL Java_com_mojang_minecraftpe_GLRenderer_nativeOnSurfaceCreated(JNIEnv* env) { - LOGI("@nativeOnSurfaceCreated\n"); + s_surfaceCreatedCount++; + if (g_inNativeOnCreate) { + // Skip re-entrant surface callbacks that fire during nativeOnCreate + return; + } + LOGI("@nativeOnSurfaceCreated #%d tid=%d\n", s_surfaceCreatedCount, (int)gettid()); if (gApp) { -// gApp->setSize( gContext.platform->getScreenWidth(), -// gContext.platform->getScreenHeight(), -// gContext.platform->isTouchscreen()); - // Don't call onGraphicsReset the first time - if (gApp->isInited()) + if (gApp->isInited()) { + LOGI("nativeOnSurfaceCreated: calling onGraphicsReset\n"); gApp->onGraphicsReset(gContext); + } - if (!gApp->isInited()) + if (!gApp->isInited()) { + LOGI("nativeOnSurfaceCreated: calling init\n"); gApp->init(gContext); + LOGI("nativeOnSurfaceCreated: init done, isInited=%d\n", (int)gApp->isInited()); + } } } @@ -159,6 +187,11 @@ Java_com_mojang_minecraftpe_MainActivity_nativeOnKeyDown(JNIEnv* env, jclass cls Keyboard::feed(keyCode, true); } JNIEXPORT void JNICALL +Java_com_mojang_minecraftpe_MainActivity_nativeTextChar(JNIEnv* env, jclass cls, jint unicodeChar) { + if (unicodeChar > 0 && unicodeChar < 128) + Keyboard::feedText((char)unicodeChar); +} +JNIEXPORT void JNICALL Java_com_mojang_minecraftpe_MainActivity_nativeOnKeyUp(JNIEnv* env, jclass cls, jint keyCode) { LOGI("@nativeOnKeyUp: %d\n", (int)keyCode); Keyboard::feed(keyCode, false); diff --git a/src/main_glfw.h b/src/main_glfw.h index 25ea2c5..9aa46a5 100755 --- a/src/main_glfw.h +++ b/src/main_glfw.h @@ -7,6 +7,8 @@ #include "SharedConstants.h" #include +#include +#include #include "platform/input/Keyboard.h" #include "platform/input/Mouse.h" #include "platform/input/Multitouch.h" @@ -32,6 +34,20 @@ int transformKey(int glfwkey) { void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { if(action == GLFW_REPEAT) return; + if (key == GLFW_KEY_F11 && action == GLFW_PRESS) { + GLFWmonitor* monitor = glfwGetWindowMonitor(window); + if (monitor) { + // Currently fullscreen → go windowed + glfwSetWindowMonitor(window, NULL, 80, 80, 854, 480, 0); + } else { + // Currently windowed → go fullscreen on primary monitor + GLFWmonitor* primary = glfwGetPrimaryMonitor(); + const GLFWvidmode* mode = glfwGetVideoMode(primary); + glfwSetWindowMonitor(window, primary, 0, 0, mode->width, mode->height, mode->refreshRate); + } + return; + } + Keyboard::feed(transformKey(key), action); } @@ -126,8 +142,9 @@ int main(void) { glfwSetWindowSizeCallback(window, window_size_callback); glfwMakeContextCurrent(window); - gladLoadGLES1Loader((GLADloadproc)glfwGetProcAddress); - glfwSwapInterval(1); + gladLoadGLES1Loader((GLADloadproc)winGLLoader); + glfwSwapInterval(0); + glPatchDesktopCompat(); #endif App* app = new MAIN_CLASS(); @@ -139,11 +156,23 @@ int main(void) { g_app->setSize(appContext.platform->getScreenWidth(), appContext.platform->getScreenHeight()); // Main event loop + using clock = std::chrono::steady_clock; while(!glfwWindowShouldClose(window) && !app->wantToQuit()) { + auto frameStart = clock::now(); + app->update(); glfwSwapBuffers(window); glfwPollEvents(); + + glfwSwapInterval(((MAIN_CLASS*)app)->options.vsync ? 1 : 0); + if(((MAIN_CLASS*)app)->options.limitFramerate) { + auto frameEnd = clock::now(); + auto elapsed = std::chrono::duration_cast(frameEnd - frameStart); + auto target = std::chrono::microseconds(33333); // ~30 fps + if(elapsed < target) + std::this_thread::sleep_for(target - elapsed); + } } delete app; diff --git a/src/network/ServerSideNetworkHandler.cpp b/src/network/ServerSideNetworkHandler.cpp index 986fdc9..26021ca 100755 --- a/src/network/ServerSideNetworkHandler.cpp +++ b/src/network/ServerSideNetworkHandler.cpp @@ -134,6 +134,11 @@ void ServerSideNetworkHandler::displayGameMessage(const std::string& message) raknetInstance->send(packet); } +void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& source, ChatPacket* packet) +{ + displayGameMessage(packet->message); +} + void ServerSideNetworkHandler::onNewClient(const RakNet::RakNetGUID& clientGuid) { LOGI("onNewClient, client guid: %s\n", clientGuid.ToString()); @@ -256,6 +261,11 @@ void ServerSideNetworkHandler::onReady_ClientGeneration(const RakNet::RakNetGUID SetTimePacket(level->getTime()).write(&bitStream); rakPeer->Send(&bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, source, false); + // send adventure settings (includes showNameTags, etc.) + bitStream.Reset(); + AdventureSettingsPacket(level->adventureSettings).write(&bitStream); + rakPeer->Send(&bitStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, source, false); + // send all pre-existing players to the new player const PlayerList& players = level->players; for (unsigned int i = 0; i < players.size(); i++) { diff --git a/src/network/ServerSideNetworkHandler.h b/src/network/ServerSideNetworkHandler.h index 574b2f2..2e2d8a4 100755 --- a/src/network/ServerSideNetworkHandler.h +++ b/src/network/ServerSideNetworkHandler.h @@ -57,18 +57,18 @@ public: virtual void handle(const RakNet::RakNetGUID& source, ContainerSetSlotPacket* packet); virtual void handle(const RakNet::RakNetGUID& source, ContainerClosePacket* packet); virtual void handle(const RakNet::RakNetGUID& source, SignUpdatePacket* packet); + virtual void handle(const RakNet::RakNetGUID& source, ChatPacket* packet); bool allowsIncomingConnections() { return _allowIncoming; } void allowIncomingConnections(bool doAllow); Player* popPendingPlayer(const RakNet::RakNetGUID& source); + + void displayGameMessage(const std::string& message); private: void redistributePacket(Packet* packet, const RakNet::RakNetGUID& fromPlayer); - void displayGameMessage(const std::string& message); - Player* getPlayer(const RakNet::RakNetGUID& source); -private: Minecraft* minecraft; Level* level; diff --git a/src/network/packet/SetTimePacket.h b/src/network/packet/SetTimePacket.h index 082d05e..e2fb484 100755 --- a/src/network/packet/SetTimePacket.h +++ b/src/network/packet/SetTimePacket.h @@ -4,6 +4,7 @@ //package net.minecraft.network.packet; #include "../Packet.h" +#include class SetTimePacket: public Packet { public: @@ -11,7 +12,7 @@ public: } SetTimePacket(long time) - : time(time) + : time((int32_t)time) {} void write(RakNet::BitStream* bitStream) @@ -30,7 +31,7 @@ public: callback->handle(source, (SetTimePacket*)this); } - long time; + int32_t time; }; #endif /*NET_MINECRAFT_NETWORK_PACKET__SetTimePacket_H__*/ diff --git a/src/network/packet/StartGamePacket.h b/src/network/packet/StartGamePacket.h index 31f6a78..24adead 100755 --- a/src/network/packet/StartGamePacket.h +++ b/src/network/packet/StartGamePacket.h @@ -2,11 +2,12 @@ #define NET_MINECRAFT_NETWORK_PACKET__StartGamePacket_H__ #include "../Packet.h" +#include class StartGamePacket : public Packet { public: - long levelSeed; + int32_t levelSeed; int levelGeneratorVersion; int gameType; @@ -18,7 +19,7 @@ public: } StartGamePacket(long seed, int levelGeneratorVersion, int gameType, int entityId, float x, float y, float z) - : levelSeed(seed), + : levelSeed((int32_t)seed), levelGeneratorVersion(levelGeneratorVersion), gameType(gameType), entityId(entityId), diff --git a/src/platform/CThread.cpp b/src/platform/CThread.cpp index 604dbdb..20e883e 100755 --- a/src/platform/CThread.cpp +++ b/src/platform/CThread.cpp @@ -64,7 +64,8 @@ TerminateThread(m_threadHandle, 0); #endif #if defined(LINUX) || defined(ANDROID) || defined(__APPLE__) || defined(POSIX) - pthread_join(m_thread, NULL); + // Thread was created detached; pthread_join on a detached thread is undefined + // and causes SIGABRT when the pthread_t is no longer valid. pthread_attr_destroy(&m_attributes); #endif } diff --git a/src/platform/audio/SoundSystemAL.cpp b/src/platform/audio/SoundSystemAL.cpp index 707ebe2..7ea7289 100755 --- a/src/platform/audio/SoundSystemAL.cpp +++ b/src/platform/audio/SoundSystemAL.cpp @@ -140,7 +140,7 @@ void SoundSystemAL::playAt( const SoundDesc& sound, float x, float y, float z, f { if (pitch < 0.01f) pitch = 1; - LOGI("playing sound '%s' with volume/pitch: %f, %f @ %f, %f, %f\n", sound.name.c_str(), volume, pitch, x, y, z); + //LOGI("playing sound '%s' with volume/pitch: %f, %f @ %f, %f, %f\n", sound.name.c_str(), volume, pitch, x, y, z); ALuint bufferID; if (!getBufferId(sound, &bufferID)) { @@ -151,7 +151,7 @@ void SoundSystemAL::playAt( const SoundDesc& sound, float x, float y, float z, f } errIdString = "Get buffer"; checkError(); - LOGI("playing sound %d - '%s' with volume/pitch: %f, %f @ %f, %f, %f\n", bufferID, sound.name.c_str(), volume, pitch, x, y, z); + //LOGI("playing sound %d - '%s' with volume/pitch: %f, %f @ %f, %f, %f\n", bufferID, sound.name.c_str(), volume, pitch, x, y, z); int sourceIndex; errIdString = "Get free index"; @@ -232,12 +232,12 @@ bool SoundSystemAL::getBufferId(const SoundDesc& sound, ALuint* buf) { : (sound.channels==2? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8); alBufferData(bufferID, format, sound.frames, sound.size, sound.frameRate); - LOGI("Creating %d (%p) from sound: '%s'\n", bufferID, sound.frames, sound.name.c_str()); + //LOGI("Creating %d (%p) from sound: '%s'\n", bufferID, sound.frames, sound.name.c_str()); errIdString = "Buffer data"; - LOGI("Creating buffer with data: %d (%d), %p, %d, %d\n", format, sound.byteWidth, sound.frames, sound.size, sound.frameRate); + //LOGI("Creating buffer with data: %d (%d), %p, %d, %d\n", format, sound.byteWidth, sound.frames, sound.size, sound.frameRate); checkError(); - LOGI("Sound ch: %d, fmt: %d, frames: %p, len: %f, fr: %d, sz: %d, numfr: %d\n", sound.channels, format, sound.frames, sound.length(), sound.frameRate, sound.size, sound.numFrames); + //LOGI("Sound ch: %d, fmt: %d, frames: %p, len: %f, fr: %d, sz: %d, numfr: %d\n", sound.channels, format, sound.frames, sound.length(), sound.frameRate, sound.size, sound.numFrames); Buffer buffer; @@ -248,6 +248,10 @@ bool SoundSystemAL::getBufferId(const SoundDesc& sound, ALuint* buf) { _buffers.push_back(buffer); // @huge @attn @note @fix: The original data is free'd + // On PLATFORM_DESKTOP the PCM data lives in static arrays (not heap), + // so calling delete[] on them causes a debug-heap __debugbreak crash. +#if !defined(PLATFORM_DESKTOP) sound.destroy(); +#endif return true; } diff --git a/src/platform/audio/SoundSystemSL.cpp b/src/platform/audio/SoundSystemSL.cpp index 0bc8629..15b4d7c 100755 --- a/src/platform/audio/SoundSystemSL.cpp +++ b/src/platform/audio/SoundSystemSL.cpp @@ -91,7 +91,7 @@ void SoundSystemSL::setListenerPos( float x, float y, float z ) return; } - SLVec3D pos = {1000.0f * x, 1000.0f * y, 1000.0f * z}; + SLVec3D pos = {(SLint32)(1000.0f * x), (SLint32)(1000.0f * y), (SLint32)(1000.0f * z)}; SLresult res = (*listener)->SetLocationCartesian(listener, &pos); checkErr(res); } @@ -115,10 +115,10 @@ void SoundSystemSL::playAt( const SoundDesc& sound, float x, float y, float z, f SLDataLocator_AndroidSimpleBufferQueue uri = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; SLDataFormat_PCM mime = { SL_DATAFORMAT_PCM, - sound.channels, - sound.frameRate * 1000, - sound.byteWidth << 3, - sound.byteWidth << 3, + (SLuint32)sound.channels, + (SLuint32)(sound.frameRate * 1000), + (SLuint32)(sound.byteWidth << 3), + (SLuint32)(sound.byteWidth << 3), sound.channels==1? SL_SPEAKER_FRONT_CENTER : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN diff --git a/src/world/level/Level.cpp b/src/world/level/Level.cpp index 6367dd9..96255eb 100755 --- a/src/world/level/Level.cpp +++ b/src/world/level/Level.cpp @@ -2236,6 +2236,6 @@ AdventureSettings::AdventureSettings() noPvM(false), noMvP(false), immutableWorld(false), - showNameTags(false) + showNameTags(true) { }