* Initial commit.

:)
This commit is contained in:
iProgramInCpp
2023-07-30 22:22:02 +03:00
commit 9642818a88
613 changed files with 151754 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

380
.gitignore vendored Normal file
View File

@@ -0,0 +1,380 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/minecraftpe/assets/font
/minecraftpe/assets/gui
/minecraftpe/assets/item
/minecraftpe/assets/mob
/minecraftpe/assets/particles.png
/minecraftpe/assets/terrain.png
/minecraftpe/games/com.mojang/*
/minecraftpe/DebugAsan
/minecraftpe/.old/DebugAsan
# Ignore sound data extracted from the game.
/sound_data
/windows_vs/games/com.mojang
/windows_vs/assets

76
README.md Normal file
View File

@@ -0,0 +1,76 @@
# Minecraft PE Reverse Engineering Project
This project is an attempt to recreate one of the first released builds of Minecraft: Pocket Edition -- mcpe01_canada.apk -- and port it to other platforms,
via binary reverse engineering.
An Android build will come soon.
* Note: The original mcpe01_canada.apk does not work on newer Android devices. A port of this likely will.
Eventually, I plan on creating a new repository, `mcpe01_canada`, which will include just the port of the Canada demo.
## WANT TO HELP?
Want to help this project? [Here's a list of things left to do.](TODO.md)
## Setup
Before opening the VS2022 project or trying to build, load the sound assets into the `sound_data/` folder in the root of the project
by running the following command:
* `tools/grabsounds.py /path/to/the/mcpe01_canada/lib/armeabi-v7a/libminecraftpe.so`.
## Have seams when playing?
I've had texture seams when playing Minecraft Classic, ClassiCube and this recreation of Minecraft PE. If seams bother you, and you are using an NVIDIA graphics card,
go to the NVIDIA Control Panel, then in "Manage 3D Settings", change "Antialiasing - Mode" to "Application Controlled".
## Notes on assets
The terrain.png and related textures appear to have stayed the same between the E3 demo and the final release for Xperia PLAY. It appears to have been fetched before
Java Edition Beta 1.4's release. This can be seen because the cookie's texture is missing. (it was added in Java Edition Beta 1.4)
## Screenshots
![Title screen](screenshots/title_screen.png)
![Generating world](screenshots/loading.png)
![In-game](screenshots/ingame.png)
![Inventory](screenshots/inventory.png)
![Pause menu](screenshots/pause_screen.png)
## Known bugs
### Patched bugs
1. Due to lack of initialization of memory, fire can potentially burn normally inflammable blocks. See ]https://www.youtube.com/watch?v=3hrz7KK2EJs](this video) for a demo.
2. Due to SHAPE_FIRE not being implemented, fire is invisible in this version. Fire rendering was backported from v0.7.1 and can be disabled with the ORIGINAL_CODE define
3. Due to `GL_BLEND` being disabled, the hotbar renders as fully opaque.
4. Memory is leaked when leaving a world with particles in it.
5. Stairs are invisible when held. To fix this, a fix has been backported from v0.1.1j
6. If the game is left on for approximately 1,242 days, the level will freeze, but the GUI will still be interactable.
The bugs can be "un-patched" by defining ORIGINAL_CODE. This allows compilation of an incomplete and mostly accurate recreation of MCPE for Xperia Play.
### Unpatched bugs
1. Particles with render texture 3 are invisible.
2. Potential freeze in `Entity::move`. If z_1 is between -0.04f and 0.04f, the game may freeze.
3. In `Particle::render`, the offset (`C_MAGIC_1`) is slightly larger than 1.0f / 16.0f. It's probably meant to be _smaller_ than 1.0f / 16.0f.
4. Ice doesn't properly cull faces, except on the y axis.
5. Fire is not extinguished on the server side.
6. Due to inaccuracies in level generation, some blocks may be different in multiplayer between a player on this version, and a player on the original MCPE.
## Enhancements
The original Minecraft: Pocket Edition had some missing details that make it feel like MCPE. Here are some enhancements that I've done (these are all turned off with the
ORIGINAL_CODE define)
* Make fire burn properly (see Patched bug #1)
* Allow fire to be rendered (see Patched bug #2)
* Allow instant breaking of blocks
* Make entity models have primitive shading
* Make in-hand items have shading
* Use a GUI scale of 2 instead of 3 (temporary)
* Allow enabling ambient occlusion (smooth lighting), using a hotkey (F4)
* Allow the hotbar to be transparent. Fixes Patched bug #3
* Use Minecraft Java Edition Beta 1.6's light ramp code instead of the original (Max brightness was only 0.8 in early versions of PE for whatever reason)
* Hide particles from a camera's view. This hides the smoke particles that the camera emits, creating a clearer picture
They can be turned on/off in `source/Base/Utils.hpp`. They're disabled by default.
### Known bugs with enhancements
1. The camera's white overlay is fully opaque in shaded entity mode.

101
TODO.md Normal file
View File

@@ -0,0 +1,101 @@
## What is there left to do?
### GUI stuff
* [DONE] `ChatScreen`
* [DONE] `ConfirmScreen`
* [DONE] `DeleteWorldScreen`
* [DONE] `InvalidLicenseScreen`
* [DONE] `SmallButton`
* [DONE] `ScrolledSelectionList`
* [DONE] `AvailableGamesList`
* [DONE] `JoinGameScreen`
* [DONE] `RenameMPLevelScreen`
### Save data
* [DONE] `PlayerData`
* `RegionFile`
### Multiplayer
* [DONE] `ServerSideNetworkHandler`
* [DONE] `ClientSideNetworkHandler`
* [DONE] `MinecraftPackets`
* [DONE] `NetEventCallback`
* [DONE] `Packet`
* [DONE] `AddPlayerPacket`
* [DONE] `ChunkDataPacket`
* [DONE] `LoginPacket`
* [DONE] `MessagePacket`
* [DONE] `MovePlayerPacket`
* [DONE] `PlaceBlockPacket`
* [DONE] `PlayerEquipmentPacket`
* [DONE] `RemoveBlockPacket`
* [DONE] `RemoveEntityPacket`
* [DONE] `RequestChunkPacket`
* [DONE] `StartGamePacket`
* [DONE] `UpdateBlockPacket`
### Xperia Play features
* [DONE] `Controller`
* [DONE] `ControllerTurnInput`
### World gen features
* `LargeFeature`
* `LargeCaveFeature` [unused]
### Sound system:
* Mutex [used for SoundSystemSL, most of it is inlined]
* [DONE] `SoundEngine`
* [DONE] `SoundRepository`
* [DONE] `SoundSystem`
* `SoundSystemSL`
* `SoundSystemWindows` (not original, but needed if you want sound on Windows)
### Unused entities and their renderers
* [DONE] `FallingTile`
* [DONE] `ItemEntity`
* `ItemSpriteRenderer`
### From the non-demo version
* `ExternalFileLevelStorage`
* `ExternalFileLevelStorageSource`
### Miscellanea
* `StopwatchHandler` -- `Performance::watches`. Unused?! Don't know.
## More generic things left to do:
* The level generator is inaccurate in terms of features. Not sure why yet.
The land and ice generation is accurate, though :)
* Untangle all the control flow and variables. Some functions have nonsense variable names like v3, x4,
and sometimes gotos are used (like label_3). It would be nice to untangle those.
* Instead of having a list of directories to include from, have only one and use relative paths instead.
* Add the assertions from v0.1.1j (unoptimized debug build)
* Attempt to recreate the project structure from Mojang. See the [Reconstructed project structure](#reconstructed-project-structure)
## Reconstructed project structure
(note: some info is present in v0.1.0demo, some in v0.1.1j. The latter will be marked as [J].)
Obviously, this is VERY incomplete. This is what we know:
* Root: `C:/dev/subversion/mojang/minecraftcpp/trunk/handheld`
```
project/
android_java/
jni/
Possibly: Android.mk, Application.mk
src/
raknet/
The RakNet source code resides here.[1]
world/ [J]
level/ [J]
storage/ [J]
RegionFile.cpp [J]
```
* [1] - In v0.1.1j, the RakNet source files are located at: `C:/dev/subversion/mojang/minecraftcpp/trunk/handheld/project/lib_projects//raknet/jni/RakNetSources/`.
In v0.1.0 demo, they're at `C:/dev/subversion/mojang/minecraftcpp/trunk/handheld/project/android/jni/../../../src/raknet/`.

368
compat/AKeyCodes.hpp Normal file
View File

@@ -0,0 +1,368 @@
#pragma once
#ifndef ORIGINAL_CODE
#ifdef _WIN32
#define NOMINMAX
#include "Utils.hpp"
enum
{
//fake keycodes for windows
AKEYCODE_UNKNOWN = 0,
AKEYCODE_MENU = VK_ESCAPE, // pause screen
AKEYCODE_SEARCH = VK_F5, // toggle third person mode
AKEYCODE_BACK = 'Y', // used to go left 1 slot
AKEYCODE_BUTTON_X = 'U', // used to go right 1 slot
AKEYCODE_BUTTON_Y = 'E', // show inventory
AKEYCODE_DPAD_UP = 'W',
AKEYCODE_DPAD_DOWN = 'S',
AKEYCODE_DPAD_LEFT = 'A',
AKEYCODE_DPAD_RIGHT = 'D',
AKEYCODE_DPAD_CENTER = ' ',
AKEYCODE_BUTTON_L1 = 'X',
AKEYCODE_BUTTON_R1 = 'C',
AKEYCODE_SHIFT_LEFT = VK_SHIFT,
AKEYCODE_SHIFT_RIGHT = VK_SHIFT,
AKEYCODE_DEL = VK_BACK,
AKEYCODE_FORWARD_DEL = VK_DELETE,
AKEYCODE_COMMA = VK_OEM_COMMA, // ',<'
AKEYCODE_PERIOD = VK_OEM_PERIOD,// '.>'
AKEYCODE_PLUS = VK_OEM_PLUS, // '=+'
AKEYCODE_MINUS = VK_OEM_MINUS, // '-_'
AKEYCODE_SEMICOLON = VK_OEM_1, // ';:'
AKEYCODE_SLASH = VK_OEM_2, // '/?'
AKEYCODE_GRAVE = VK_OEM_3, // '`~'
AKEYCODE_LEFT_BRACKET=VK_OEM_4, // '[{'
AKEYCODE_BACKSLASH = VK_OEM_5, // '\|'
AKEYCODE_RIGHT_BRACKET=VK_OEM_6,// ']}'
AKEYCODE_APOSTROPHE = VK_OEM_7, // ''"'
AKEYCODE_0 = '0',
//...
AKEYCODE_9 = '9',
AKEYCODE_A = 'A',
//...
AKEYCODE_Z = 'Z',
AKEYCODE_F4 = VK_F4,
};
// this sucks
#define AKEYCODE_ARROW_LEFT VK_LEFT
#define AKEYCODE_ARROW_RIGHT VK_RIGHT
#else
#error "Add AKEYCODEs for your platform!"
#endif
#else
enum
{
//the real deal android key codes
AKEYCODE_UNKNOWN = 0,
AKEYCODE_SOFT_LEFT = 1,
AKEYCODE_SOFT_RIGHT = 2,
AKEYCODE_HOME = 3,
AKEYCODE_BACK = 4,
AKEYCODE_CALL = 5,
AKEYCODE_ENDCALL = 6,
AKEYCODE_0 = 7,
AKEYCODE_1 = 8,
AKEYCODE_2 = 9,
AKEYCODE_3 = 10,
AKEYCODE_4 = 11,
AKEYCODE_5 = 12,
AKEYCODE_6 = 13,
AKEYCODE_7 = 14,
AKEYCODE_8 = 15,
AKEYCODE_9 = 16,
AKEYCODE_STAR = 17,
AKEYCODE_POUND = 18,
AKEYCODE_DPAD_UP = 19,
AKEYCODE_DPAD_DOWN = 20,
AKEYCODE_DPAD_LEFT = 21,
AKEYCODE_DPAD_RIGHT = 22,
AKEYCODE_DPAD_CENTER = 23,
AKEYCODE_VOLUME_UP = 24,
AKEYCODE_VOLUME_DOWN = 25,
AKEYCODE_POWER = 26,
AKEYCODE_CAMERA = 27,
AKEYCODE_CLEAR = 28,
AKEYCODE_A = 29,
AKEYCODE_B = 30,
AKEYCODE_C = 31,
AKEYCODE_D = 32,
AKEYCODE_E = 33,
AKEYCODE_F = 34,
AKEYCODE_G = 35,
AKEYCODE_H = 36,
AKEYCODE_I = 37,
AKEYCODE_J = 38,
AKEYCODE_K = 39,
AKEYCODE_L = 40,
AKEYCODE_M = 41,
AKEYCODE_N = 42,
AKEYCODE_O = 43,
AKEYCODE_P = 44,
AKEYCODE_Q = 45,
AKEYCODE_R = 46,
AKEYCODE_S = 47,
AKEYCODE_T = 48,
AKEYCODE_U = 49,
AKEYCODE_V = 50,
AKEYCODE_W = 51,
AKEYCODE_X = 52,
AKEYCODE_Y = 53,
AKEYCODE_Z = 54,
AKEYCODE_COMMA = 55,
AKEYCODE_PERIOD = 56,
AKEYCODE_ALT_LEFT = 57,
AKEYCODE_ALT_RIGHT = 58,
AKEYCODE_SHIFT_LEFT = 59,
AKEYCODE_SHIFT_RIGHT = 60,
AKEYCODE_TAB = 61,
AKEYCODE_SPACE = 62,
AKEYCODE_SYM = 63,
AKEYCODE_EXPLORER = 64,
AKEYCODE_ENVELOPE = 65,
AKEYCODE_ENTER = 66,
AKEYCODE_DEL = 67,
AKEYCODE_GRAVE = 68,
AKEYCODE_MINUS = 69,
AKEYCODE_EQUALS = 70,
AKEYCODE_LEFT_BRACKET = 71,
AKEYCODE_RIGHT_BRACKET = 72,
AKEYCODE_BACKSLASH = 73,
AKEYCODE_SEMICOLON = 74,
AKEYCODE_APOSTROPHE = 75,
AKEYCODE_SLASH = 76,
AKEYCODE_AT = 77,
AKEYCODE_NUM = 78,
AKEYCODE_HEADSETHOOK = 79,
AKEYCODE_FOCUS = 80,
AKEYCODE_PLUS = 81,
AKEYCODE_MENU = 82,
AKEYCODE_NOTIFICATION = 83,
AKEYCODE_SEARCH = 84,
AKEYCODE_MEDIA_PLAY_PAUSE = 85,
AKEYCODE_MEDIA_STOP = 86,
AKEYCODE_MEDIA_NEXT = 87,
AKEYCODE_MEDIA_PREVIOUS = 88,
AKEYCODE_MEDIA_REWIND = 89,
AKEYCODE_MEDIA_FAST_FORWARD = 90,
AKEYCODE_MUTE = 91,
AKEYCODE_PAGE_UP = 92,
AKEYCODE_PAGE_DOWN = 93,
AKEYCODE_PICTSYMBOLS = 94,
AKEYCODE_SWITCH_CHARSET = 95,
AKEYCODE_BUTTON_A = 96,
AKEYCODE_BUTTON_B = 97,
AKEYCODE_BUTTON_C = 98,
AKEYCODE_BUTTON_X = 99,
AKEYCODE_BUTTON_Y = 100,
AKEYCODE_BUTTON_Z = 101,
AKEYCODE_BUTTON_L1 = 102,
AKEYCODE_BUTTON_R1 = 103,
AKEYCODE_BUTTON_L2 = 104,
AKEYCODE_BUTTON_R2 = 105,
AKEYCODE_BUTTON_THUMBL = 106,
AKEYCODE_BUTTON_THUMBR = 107,
AKEYCODE_BUTTON_START = 108,
AKEYCODE_BUTTON_SELECT = 109,
AKEYCODE_BUTTON_MODE = 110,
AKEYCODE_ESCAPE = 111,
AKEYCODE_FORWARD_DEL = 112,
AKEYCODE_CTRL_LEFT = 113,
AKEYCODE_CTRL_RIGHT = 114,
AKEYCODE_CAPS_LOCK = 115,
AKEYCODE_SCROLL_LOCK = 116,
AKEYCODE_META_LEFT = 117,
AKEYCODE_META_RIGHT = 118,
AKEYCODE_FUNCTION = 119,
AKEYCODE_SYSRQ = 120,
AKEYCODE_BREAK = 121,
AKEYCODE_MOVE_HOME = 122,
AKEYCODE_MOVE_END = 123,
AKEYCODE_INSERT = 124,
AKEYCODE_FORWARD = 125,
AKEYCODE_MEDIA_PLAY = 126,
AKEYCODE_MEDIA_PAUSE = 127,
AKEYCODE_MEDIA_CLOSE = 128,
AKEYCODE_MEDIA_EJECT = 129,
AKEYCODE_MEDIA_RECORD = 130,
AKEYCODE_F1 = 131,
AKEYCODE_F2 = 132,
AKEYCODE_F3 = 133,
AKEYCODE_F4 = 134,
AKEYCODE_F5 = 135,
AKEYCODE_F6 = 136,
AKEYCODE_F7 = 137,
AKEYCODE_F8 = 138,
AKEYCODE_F9 = 139,
AKEYCODE_F10 = 140,
AKEYCODE_F11 = 141,
AKEYCODE_F12 = 142,
AKEYCODE_NUM_LOCK = 143,
AKEYCODE_NUMPAD_0 = 144,
AKEYCODE_NUMPAD_1 = 145,
AKEYCODE_NUMPAD_2 = 146,
AKEYCODE_NUMPAD_3 = 147,
AKEYCODE_NUMPAD_4 = 148,
AKEYCODE_NUMPAD_5 = 149,
AKEYCODE_NUMPAD_6 = 150,
AKEYCODE_NUMPAD_7 = 151,
AKEYCODE_NUMPAD_8 = 152,
AKEYCODE_NUMPAD_9 = 153,
AKEYCODE_NUMPAD_DIVIDE = 154,
AKEYCODE_NUMPAD_MULTIPLY = 155,
AKEYCODE_NUMPAD_SUBTRACT = 156,
AKEYCODE_NUMPAD_ADD = 157,
AKEYCODE_NUMPAD_DOT = 158,
AKEYCODE_NUMPAD_COMMA = 159,
AKEYCODE_NUMPAD_ENTER = 160,
AKEYCODE_NUMPAD_EQUALS = 161,
AKEYCODE_NUMPAD_LEFT_PAREN = 162,
AKEYCODE_NUMPAD_RIGHT_PAREN = 163,
AKEYCODE_VOLUME_MUTE = 164,
AKEYCODE_INFO = 165,
AKEYCODE_CHANNEL_UP = 166,
AKEYCODE_CHANNEL_DOWN = 167,
AKEYCODE_ZOOM_IN = 168,
AKEYCODE_ZOOM_OUT = 169,
AKEYCODE_TV = 170,
AKEYCODE_WINDOW = 171,
AKEYCODE_GUIDE = 172,
AKEYCODE_DVR = 173,
AKEYCODE_BOOKMARK = 174,
AKEYCODE_CAPTIONS = 175,
AKEYCODE_SETTINGS = 176,
AKEYCODE_TV_POWER = 177,
AKEYCODE_TV_INPUT = 178,
AKEYCODE_STB_POWER = 179,
AKEYCODE_STB_INPUT = 180,
AKEYCODE_AVR_POWER = 181,
AKEYCODE_AVR_INPUT = 182,
AKEYCODE_PROG_RED = 183,
AKEYCODE_PROG_GREEN = 184,
AKEYCODE_PROG_YELLOW = 185,
AKEYCODE_PROG_BLUE = 186,
AKEYCODE_APP_SWITCH = 187,
AKEYCODE_BUTTON_1 = 188,
AKEYCODE_BUTTON_2 = 189,
AKEYCODE_BUTTON_3 = 190,
AKEYCODE_BUTTON_4 = 191,
AKEYCODE_BUTTON_5 = 192,
AKEYCODE_BUTTON_6 = 193,
AKEYCODE_BUTTON_7 = 194,
AKEYCODE_BUTTON_8 = 195,
AKEYCODE_BUTTON_9 = 196,
AKEYCODE_BUTTON_10 = 197,
AKEYCODE_BUTTON_11 = 198,
AKEYCODE_BUTTON_12 = 199,
AKEYCODE_BUTTON_13 = 200,
AKEYCODE_BUTTON_14 = 201,
AKEYCODE_BUTTON_15 = 202,
AKEYCODE_BUTTON_16 = 203,
AKEYCODE_LANGUAGE_SWITCH = 204,
AKEYCODE_MANNER_MODE = 205,
AKEYCODE_3D_MODE = 206,
AKEYCODE_CONTACTS = 207,
AKEYCODE_CALENDAR = 208,
AKEYCODE_MUSIC = 209,
AKEYCODE_CALCULATOR = 210,
AKEYCODE_ZENKAKU_HANKAKU = 211,
AKEYCODE_EISU = 212,
AKEYCODE_MUHENKAN = 213,
AKEYCODE_HENKAN = 214,
AKEYCODE_KATAKANA_HIRAGANA = 215,
AKEYCODE_YEN = 216,
AKEYCODE_RO = 217,
AKEYCODE_KANA = 218,
AKEYCODE_ASSIST = 219,
AKEYCODE_BRIGHTNESS_DOWN = 220,
AKEYCODE_BRIGHTNESS_UP = 221,
AKEYCODE_MEDIA_AUDIO_TRACK = 222,
AKEYCODE_SLEEP = 223,
AKEYCODE_WAKEUP = 224,
AKEYCODE_PAIRING = 225,
AKEYCODE_MEDIA_TOP_MENU = 226,
AKEYCODE_11 = 227,
AKEYCODE_12 = 228,
AKEYCODE_LAST_CHANNEL = 229,
AKEYCODE_TV_DATA_SERVICE = 230,
AKEYCODE_VOICE_ASSIST = 231,
AKEYCODE_TV_RADIO_SERVICE = 232,
AKEYCODE_TV_TELETEXT = 233,
AKEYCODE_TV_NUMBER_ENTRY = 234,
AKEYCODE_TV_TERRESTRIAL_ANALOG = 235,
AKEYCODE_TV_TERRESTRIAL_DIGITAL = 236,
AKEYCODE_TV_SATELLITE = 237,
AKEYCODE_TV_SATELLITE_BS = 238,
AKEYCODE_TV_SATELLITE_CS = 239,
AKEYCODE_TV_SATELLITE_SERVICE = 240,
AKEYCODE_TV_NETWORK = 241,
AKEYCODE_TV_ANTENNA_CABLE = 242,
AKEYCODE_TV_INPUT_HDMI_1 = 243,
AKEYCODE_TV_INPUT_HDMI_2 = 244,
AKEYCODE_TV_INPUT_HDMI_3 = 245,
AKEYCODE_TV_INPUT_HDMI_4 = 246,
AKEYCODE_TV_INPUT_COMPOSITE_1 = 247,
AKEYCODE_TV_INPUT_COMPOSITE_2 = 248,
AKEYCODE_TV_INPUT_COMPONENT_1 = 249,
AKEYCODE_TV_INPUT_COMPONENT_2 = 250,
AKEYCODE_TV_INPUT_VGA_1 = 251,
AKEYCODE_TV_AUDIO_DESCRIPTION = 252,
AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253,
AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254,
AKEYCODE_TV_ZOOM_MODE = 255,
AKEYCODE_TV_CONTENTS_MENU = 256,
AKEYCODE_TV_MEDIA_CONTEXT_MENU = 257,
AKEYCODE_TV_TIMER_PROGRAMMING = 258,
AKEYCODE_HELP = 259,
AKEYCODE_NAVIGATE_PREVIOUS = 260,
AKEYCODE_NAVIGATE_NEXT = 261,
AKEYCODE_NAVIGATE_IN = 262,
AKEYCODE_NAVIGATE_OUT = 263,
AKEYCODE_STEM_PRIMARY = 264,
AKEYCODE_STEM_1 = 265,
AKEYCODE_STEM_2 = 266,
AKEYCODE_STEM_3 = 267,
AKEYCODE_DPAD_UP_LEFT = 268,
AKEYCODE_DPAD_DOWN_LEFT = 269,
AKEYCODE_DPAD_UP_RIGHT = 270,
AKEYCODE_DPAD_DOWN_RIGHT = 271,
AKEYCODE_MEDIA_SKIP_FORWARD = 272,
AKEYCODE_MEDIA_SKIP_BACKWARD = 273,
AKEYCODE_MEDIA_STEP_FORWARD = 274,
AKEYCODE_MEDIA_STEP_BACKWARD = 275,
AKEYCODE_SOFT_SLEEP = 276,
AKEYCODE_CUT = 277,
AKEYCODE_COPY = 278,
AKEYCODE_PASTE = 279,
AKEYCODE_SYSTEM_NAVIGATION_UP = 280,
AKEYCODE_SYSTEM_NAVIGATION_DOWN = 281,
AKEYCODE_SYSTEM_NAVIGATION_LEFT = 282,
AKEYCODE_SYSTEM_NAVIGATION_RIGHT = 283,
AKEYCODE_ALL_APPS = 284,
AKEYCODE_REFRESH = 285,
AKEYCODE_THUMBS_UP = 286,
AKEYCODE_THUMBS_DOWN = 287,
AKEYCODE_PROFILE_SWITCH = 288
};
// this sucks
#define AKEYCODE_ARROW_LEFT AKEYCODE_DPAD_LEFT
#define AKEYCODE_ARROW_RIGHT AKEYCODE_DPAD_RIGHT
#endif

30
compat/GL.hpp Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#ifdef USE_OPENGL_2
#define xglBindBuffer glBindBuffer
#define xglBufferData glBufferData
#define xglGenBuffers glGenBuffers
#else
#include "Utils.hpp"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glext.h> // it'll include from a different dir, namely thirdparty/GL/glext.h
void xglInit();
bool xglInitted();
void xglBindBuffer(GLenum target, GLuint buffer);
void xglBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
void xglGenBuffers(GLsizei num, GLuint* buffers);
void xglDeleteBuffers(GLsizei num, GLuint* buffers);
void xglOrthof(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat nearpl, GLfloat farpl);
void xglSwapIntervalEXT(int interval);
// @TODO: not the right place, but er, it's ok
void drawArrayVT(GLuint buffer, int count, int stride);
void drawArrayVTC(GLuint buffer, int count, int stride);
#endif

69
compat/GLExt.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include "GL.hpp"
HWND GetHWND();
extern LPCTSTR g_GameTitle;
// this sucks... F* you Microsoft for making me do this hacky ass bullsh*t.
PFNGLBINDBUFFERPROC p_glBindBuffer;
PFNGLBUFFERDATAPROC p_glBufferData;
PFNGLGENBUFFERSPROC p_glGenBuffers;
PFNGLDELETEBUFFERSPROC p_glDeleteBuffers;
// this is stupidly hacky
typedef BOOL(WINAPI* PFNWGLSWAPINTERVALEXTPROC) (int interval);
PFNWGLSWAPINTERVALEXTPROC p_wglSwapIntervalEXT;
bool xglInitted()
{
return p_glBindBuffer && p_glBufferData && p_glGenBuffers && p_glDeleteBuffers;
}
void xglInit()
{
p_glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
p_glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
p_glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
p_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)wglGetProcAddress("glDeleteBuffers");
p_wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
if (!xglInitted())
{
MessageBox(GetHWND(), TEXT("Error initializing GL extensions. Update your graphics drivers!"), g_GameTitle, MB_OK);
}
}
void xglBindBuffer(GLenum target, GLuint buffer)
{
p_glBindBuffer(target, buffer);
}
void xglBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)
{
p_glBufferData(target, size, data, usage);
}
void xglGenBuffers(GLsizei num, GLuint* buffers)
{
p_glGenBuffers(num, buffers);
}
void xglDeleteBuffers(GLsizei num, GLuint* buffers)
{
p_glDeleteBuffers(num, buffers);
}
void xglOrthof(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat nearpl, GLfloat farpl)
{
return glOrtho(GLdouble(left), GLdouble(right), GLdouble(bottom), GLfloat(top), GLdouble(nearpl), GLdouble(farpl));
}
void xglSwapIntervalEXT(int interval)
{
if (!p_wglSwapIntervalEXT)
return;
p_wglSwapIntervalEXT(interval);
}

1
compat/readme.txt Normal file
View File

@@ -0,0 +1 @@
This folder contains compatibility crap since Microsoft never updated from OpenGL V1.2.

BIN
screenshots/ingame.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

BIN
screenshots/inventory.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

BIN
screenshots/loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

61
source/App/App.cpp Normal file
View File

@@ -0,0 +1,61 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
App.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include "App.hpp"
void App::destroy()
{
}
void App::draw()
{
}
bool App::handleBack(bool b)
{
return false;
}
void App::init()
{
}
void App::loadState(void* a2, int a3)
{
}
AppPlatform* App::platform()
{
return m_pPlatform;
}
void App::quit()
{
m_bWantToQuit = true;
}
bool App::wantToQuit()
{
return m_bWantToQuit;
}
void App::saveState(void**, int)
{
}
void App::update()
{
}

40
source/App/App.hpp Normal file
View File

@@ -0,0 +1,40 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
App.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include "AppPlatform.hpp"
class App
{
public:
void destroy();
void draw();
virtual bool handleBack(bool);
virtual void init();
void loadState(void*, int);
AppPlatform* platform();
void quit();
void saveState(void**, int);
virtual void update();
bool wantToQuit();
public:
bool m_bWantToQuit = false;
// don't know what these are ...
int field_8;
int field_C;
int field_10;
AppPlatform* m_pPlatform = nullptr;
};

122
source/App/AppPlatform.cpp Normal file
View File

@@ -0,0 +1,122 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
AppPlatform.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include "AppPlatform.hpp"
#include "Utils.hpp"
void AppPlatform::_tick()
{
}
void AppPlatform::buyGame()
{
}
int AppPlatform::checkLicense()
{
return 0; // assume no license
}
void AppPlatform::createUserInput()
{
}
void AppPlatform::finish()
{
}
std::string AppPlatform::getDateString(int time)
{
return "";
}
// ??? AppPlatform::getOptionStrings()
int AppPlatform::getScreenWidth() const
{
return C_DEFAULT_SCREEN_WIDTH; // default rez of the XPERIA PLAY?
}
int AppPlatform::getScreenHeight() const
{
return C_DEFAULT_SCREEN_HEIGHT;
}
std::vector<std::string> AppPlatform::getUserInput()
{
return {};
}
int AppPlatform::getUserInputStatus()
{
return 0;
}
bool AppPlatform::hasBuyButtonWhenInvalidLicense()
{
return false;
}
// void AppPlatform::loadTexture(const std::string&, bool);
void AppPlatform::saveScreenshot(const std::string&, int, int)
{
}
void AppPlatform::showDialog(eDialogType type)
{
}
void AppPlatform::uploadPlatformDependentData(int, void*)
{
}
Texture AppPlatform::loadTexture(const std::string&, bool)
{
return Texture(0, 0, nullptr, 1, 0);
}
std::vector<std::string> AppPlatform::getOptionStrings()
{
return {};
}
void AppPlatform::recenterMouse()
{
}
void AppPlatform::setMouseGrabbed(bool b)
{
}
void AppPlatform::getMouseDiff(int& x, int& y)
{
x = y = 0;
}
void AppPlatform::clearDiff()
{
}
bool AppPlatform::shiftPressed()
{
return false;
}

View File

@@ -0,0 +1,60 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
AppPlatform.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include <string>
#include <vector>
#include "Texture.hpp"
class AppPlatform
{
public:
enum eDialogType
{
DLG_CREATE_WORLD = 1,
DLG_CHAT,
DLG_OPTIONS,
DLG_RENAME_MP_WORLD,
};
public:
virtual void buyGame();
virtual int checkLicense();
virtual void createUserInput();
virtual void finish();
virtual std::string getDateString(int);
virtual int getScreenWidth() const;
virtual int getScreenHeight() const;
virtual std::vector<std::string> getUserInput();
virtual int getUserInputStatus();
virtual bool hasBuyButtonWhenInvalidLicense();
virtual void saveScreenshot(const std::string&, int, int);
virtual void showDialog(eDialogType);
virtual void uploadPlatformDependentData(int, void*);
virtual Texture loadTexture(const std::string&, bool);
virtual std::vector<std::string> getOptionStrings();
#ifndef ORIGINAL_CODE
// Also add these to allow proper turning within the game.
virtual void recenterMouse();
virtual void setMouseGrabbed(bool b);
virtual void getMouseDiff(int& x, int& y);
virtual void clearDiff();
// Also add this to allow proper text input within the game.
virtual bool shiftPressed();
#endif
private:
virtual void _tick();
};

View File

@@ -0,0 +1,244 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
AppPlatform_windows.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include "AppPlatform_windows.hpp"
#include "Mouse.hpp"
#include "thirdparty/stb_image.h"
#include "thirdparty/stb_image_write.h"
extern LPCTSTR g_GameTitle;
void AppPlatform_windows::initConsts()
{
// just assume an 854x480 window for now:
m_ScreenWidth = C_DEFAULT_SCREEN_WIDTH;
m_ScreenHeight = C_DEFAULT_SCREEN_HEIGHT;
}
int AppPlatform_windows::checkLicense()
{
// we own the game!!
return 1;
}
void AppPlatform_windows::buyGame()
{
MessageBox(GetHWND(), TEXT("Buying the game!"), g_GameTitle, MB_OK | MB_ICONINFORMATION);
}
void AppPlatform_windows::saveScreenshot(const std::string& fileName, int width, int height)
{
int npixels = width * height;
uint32_t* pixels = new uint32_t[npixels];
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
stbi_flip_vertically_on_write(true);
stbi_write_png(fileName.c_str(), width, height, 4, pixels, width * 4);
delete[] pixels;
}
int AppPlatform_windows::getScreenWidth() const
{
return m_ScreenWidth;
}
int AppPlatform_windows::getScreenHeight() const
{
return m_ScreenHeight;
}
std::vector<std::string> AppPlatform_windows::getUserInput()
{
return m_UserInput;
}
int AppPlatform_windows::getUserInputStatus()
{
return m_UserInputStatus;
}
void AppPlatform_windows::createUserInput()
{
m_UserInput.clear();
m_UserInputStatus = -1;
}
void AppPlatform_windows::showDialog(eDialogType type)
{
// TODO
}
std::string AppPlatform_windows::getDateString(int time)
{
time_t tt = time;
struct tm t;
// using the _s variant. For a different platform there's gmtime_r. This is not directly portable however.
gmtime_s(&t, &tt);
//format it with strftime
char buf[2048];
strftime(buf, sizeof buf, "%b %d %Y %H:%M:%S", &t);
//strftime(buf, sizeof buf, "%a %b %d %H:%M:%S %Z %Y", &t);
return std::string(buf);
}
Texture AppPlatform_windows::loadTexture(const std::string& str, bool b)
{
std::string realPath = str;
if (realPath.size() && realPath[0] == '/')
// trim it off
realPath = realPath.substr(1);
realPath = "assets/" + realPath;
FILE* f = fopen(realPath.c_str(), "rb");
if (!f)
{
LogMsg("File %s couldn't be opened", realPath.c_str());
return Texture(0, 0, nullptr, 1, 0);
}
int width = 0, height = 0, channels = 0;
stbi_uc* img = stbi_load_from_file(f, &width, &height, &channels, STBI_rgb_alpha);
if (!img)
{
LogMsg("File %s couldn't be loaded via stb_image", realPath.c_str());
fclose(f);
return Texture(0, 0, nullptr, 1, 0);
}
fclose(f);
return Texture(width, height, (uint32_t*)img, 1, 0);
}
std::vector<std::string> AppPlatform_windows::getOptionStrings()
{
std::vector<std::string> o;
o.push_back("mp_username");
o.push_back("iProgramInCpp");
return o;
}
void AppPlatform_windows::setScreenSize(int width, int height)
{
m_ScreenWidth = width;
m_ScreenHeight = height;
}
void AppPlatform_windows::recenterMouse()
{
// only recenter the mouse if it's grabbed
if (!m_bGrabbedMouse)
return;
// If we aren't the foreground window, return
if (GetForegroundWindow() != GetHWND())
{
m_bWasUnfocused = true;
return;
}
POINT oldPos{ 0, 0 };
GetCursorPos(&oldPos);
RECT rect;
GetClientRect(GetHWND(), &rect);
POINT offs { m_ScreenWidth / 2, m_ScreenHeight / 2 };
ClientToScreen(GetHWND(), &offs);
SetCursorPos(offs.x, offs.y);
// Note. The only reason we do it this way instead of
// using the Mouse class is because, after SetCursorPos,
// we'll get an event on our window telling us "hey, the
// user has moved their cursor back to the center! Move
// the camera back as well", causing a camera that just
// refuses to move
// If we were unfocused last frame, ignore the diff data we have.
if (!m_bWasUnfocused)
{
m_MouseDiffX -= offs.x - oldPos.x;
m_MouseDiffY -= offs.y - oldPos.y;
}
m_bWasUnfocused = false;
}
void AppPlatform_windows::setMouseGrabbed(bool b)
{
// only if stuff has changed do we update
if (m_bGrabbedMouse == b)
return;
m_bGrabbedMouse = b;
if (!b)
{
//show the cursor
ShowCursor(TRUE);
//unconfine it
ClipCursor(NULL);
clearDiff();
}
else
{
//hide the cursor
ShowCursor(FALSE);
//confine it to our client area
RECT rect;
GetClientRect(GetHWND(), &rect);
POINT offs{ 0, 0 };
ClientToScreen(GetHWND(), &offs);
rect.left += offs.x;
rect.top += offs.y;
rect.right += offs.x;
rect.bottom += offs.y;
ClipCursor(&rect);
// set the cursor pos to the middle of the screen
recenterMouse();
clearDiff();
}
}
void AppPlatform_windows::getMouseDiff(int& x, int& y)
{
x = m_MouseDiffX;
y = m_MouseDiffY;
}
void AppPlatform_windows::clearDiff()
{
m_MouseDiffX = m_MouseDiffY = 0;
}
bool AppPlatform_windows::shiftPressed()
{
return m_bShiftPressed;
}
void AppPlatform_windows::setShiftPressed(bool b)
{
m_bShiftPressed = b;
}

View File

@@ -0,0 +1,67 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
AppPlatform_windows.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include "compat/GL.hpp"
#include <ctime>
#include "Utils.hpp"
#include "AppPlatform.hpp"
#ifdef ORIGINAL_CODE
#error "This isn't original code. You probably shouldn't try to compile this"
#endif
// note: probably won't add AppPlatform_android until it's time
// to build an Android app
class AppPlatform_windows : public AppPlatform
{
public:
void initConsts();
void buyGame() override;
void saveScreenshot(const std::string& fileName, int width, int height) override;
int checkLicense() override;
void createUserInput() override;
std::vector<std::string> getUserInput() override;
int getUserInputStatus() override;
int getScreenWidth() const override;
int getScreenHeight() const override;
void showDialog(eDialogType) override;
std::string getDateString(int time) override;
Texture loadTexture(const std::string& str, bool b) override;
std::vector<std::string> getOptionStrings() override;
// Also add these to allow proper turning within the game.
void recenterMouse() override;
void setMouseGrabbed(bool b) override;
void getMouseDiff(int& x, int& y) override;
void clearDiff() override;
// Also add these to allow proper text input within the game.
bool shiftPressed() override;
void setShiftPressed(bool b);
void setScreenSize(int width, int height);
private:
int m_ScreenWidth;
int m_ScreenHeight;
std::vector<std::string> m_UserInput;
int m_UserInputStatus = -1;
bool m_bGrabbedMouse = false;
bool m_bWasUnfocused = false;
bool m_bShiftPressed = false;
int m_MouseDiffX = 0, m_MouseDiffY = 0;
};

843
source/App/Minecraft.cpp Normal file
View File

@@ -0,0 +1,843 @@
#include "Minecraft.hpp"
#include "PauseScreen.hpp"
#include "StartMenuScreen.hpp"
#include "RenameMPLevelScreen.hpp"
#include "ServerSideNetworkHandler.hpp"
#include "ClientSideNetworkHandler.hpp"
#ifndef ORIGINAL_CODE
#include "MouseTurnInput.hpp"
#else
#include "ControllerTurnInput.hpp"
#endif
// note: Nothing changes these, so it'll think we're always running at 854x480 even if not
int Minecraft::width = C_DEFAULT_SCREEN_WIDTH;
int Minecraft::height = C_DEFAULT_SCREEN_HEIGHT;
bool Minecraft::useAmbientOcclusion = false;
int Minecraft::customDebugId = 0;
//@HUH: For the demo, this is defined as TRUE.
//@HUH: deadmau5 had camera cheats? That's interesting.
const bool Minecraft::DEADMAU5_CAMERA_CHEATS = true;
const char* Minecraft::progressMessages[] =
{
"Locating server",
"Building terrain",
"Preparing",
"Saving chunks",
};
Minecraft::Minecraft() : m_gui(this)
{
#ifndef ORIGINAL_CODE
m_pTurnInput = new MouseTurnInput(this);
#else
m_pTurnInput = new ControllerTurnInput;
#endif
m_pRakNetInstance = new RakNetInstance;
m_pSoundEngine = new SoundEngine;
m_pSoundEngine->init(&m_options);
}
int Minecraft::getLicenseId()
{
if (m_licenseID < 0)
m_licenseID = m_pPlatform->checkLicense();
return m_licenseID;
}
void Minecraft::releaseMouse()
{
if (!m_bGrabbedMouse)
return;
if (m_pLocalPlayer)
m_pLocalPlayer->m_pKeyboardInput->releaseAllKeys();
m_bGrabbedMouse = false;
platform()->setMouseGrabbed(false);
}
void Minecraft::grabMouse()
{
if (m_bGrabbedMouse)
return;
m_bGrabbedMouse = true;
field_D20 = 0.0f;
field_D24 = 0.0f;
setScreen(nullptr);
platform()->setMouseGrabbed(true);
}
void Minecraft::setScreen(Screen* pScreen)
{
if (field_DB0)
{
field_DB1 = 1;
m_pScreen = pScreen; //@BUG: potential memory leak?
}
else if (!pScreen || !pScreen->isErrorScreen())
{
if (field_D14)
{
field_D14->removed();
delete field_D14;
}
field_D14 = pScreen;
if (pScreen)
{
releaseMouse();
pScreen->init(this, int(width * Gui::InvGuiScale), int(height * Gui::InvGuiScale));
}
else
{
grabMouse();
}
}
//@BUG: memory leak?
#ifndef ORIGINAL_CODE
else
{
// @NOTE: Added this to not leak screens. A good idea unless you use the screen instance after calling setScreen()
delete pScreen;
}
#endif
}
void Minecraft::onGraphicsReset()
{
m_pTextures->clear();
m_pFont->onGraphicsReset();
if (m_pLevelRenderer)
m_pLevelRenderer->onGraphicsReset();
if (m_pGameRenderer)
m_pGameRenderer->onGraphicsReset();
EntityRenderDispatcher::getInstance()->onGraphicsReset();
}
void Minecraft::reloadOptions()
{
m_options.update(platform()->getOptionStrings());
}
bool Minecraft::isLevelGenerated()
{
if (m_pLevel)
return !m_bPreparingLevel;
return false;
}
bool Minecraft::isOnline()
{
return m_pNetEventCallback != nullptr;
}
bool Minecraft::isOnlineClient()
{
if (!m_pLevel)
return false;
return m_pLevel->field_11;
}
void Minecraft::handleMouseDown(int type, bool b)
{
if (!m_pGameMode->field_8 && (type != 1 || this->field_DA4 <= 0))
{
if (b && type == 1 && m_hitResult.m_hitType == HitResult::AABB && !m_hitResult.m_bUnk24)
{
m_pGameMode->continueDestroyBlock(m_hitResult.m_tileX, m_hitResult.m_tileY, m_hitResult.m_tileZ, m_hitResult.m_hitSide);
m_pParticleEngine->crack(m_hitResult.m_tileX, m_hitResult.m_tileY, m_hitResult.m_tileZ, m_hitResult.m_hitSide);
}
else
{
m_pGameMode->stopDestroyBlock();
}
}
}
void Minecraft::handleMouseClick(int type)
{
int a;
HitResult& hr = m_hitResult;
// @TODO: fix goto hell
if (type == 1)
{
if (field_DA4 > 0)
return;
m_pLocalPlayer->swing();
if (m_hitResult.m_hitType != HitResult::NONE)
goto label_3;
label_9:
if (type != 1)
goto label_5;
if (!m_pGameMode->isCreativeType())
field_DA4 = 10;
return;
}
if (m_hitResult.m_hitType == HitResult::NONE)
goto label_9;
label_3:
if (m_hitResult.m_hitType != HitResult::ENTITY)
{
if (m_hitResult.m_hitType != HitResult::AABB)
goto label_5;
// @NOTE: extra scope to avoid error
{
Tile* pTile = Tile::tiles[m_pLevel->getTile(hr.m_tileX, hr.m_tileY, hr.m_tileZ)];
if (type == 1)
{
if (pTile)
{
// @BUG: This is only done on the client side.
m_pLevel->extinguishFire(hr.m_tileX, hr.m_tileY, hr.m_tileZ, hr.m_hitSide);
if (pTile != Tile::unbreakable || m_pLocalPlayer->field_B94 > 99 && !hr.m_bUnk24)
{
m_pGameMode->startDestroyBlock(hr.m_tileX, hr.m_tileY, hr.m_tileZ, hr.m_hitSide);
}
}
return;
}
ItemInstance item(m_pLocalPlayer->m_pInventory->getSelectedItemId(), 999, 0);
if (m_pGameMode->useItemOn(m_pLocalPlayer, m_pLevel, item.m_itemID < 0 ? nullptr : &item, hr.m_tileX, hr.m_tileY, hr.m_tileZ, hr.m_hitSide))
{
m_pLocalPlayer->swing();
if (!isOnline())
return;
if (item.m_itemID > C_MAX_TILES || item.m_itemID < 0)
return;
int dx = hr.m_tileX, dz = hr.m_tileZ;
uint8_t dy = uint8_t(hr.m_tileY);
if (m_pLevel->getTile(hr.m_tileX, hr.m_tileY, hr.m_tileZ) != Tile::topSnow->m_ID)
{
switch (hr.m_hitSide)
{
case DIR_YNEG: dy--; break;
case DIR_YPOS: dy++; break;
case DIR_ZNEG: dz--; break;
case DIR_ZPOS: dz++; break;
case DIR_XNEG: dx--; break;
case DIR_XPOS: dx++; break;
}
}
m_pRakNetInstance->send(new PlaceBlockPacket(m_pLocalPlayer->m_EntityID, dx, dy, dz, uint8_t(item.m_itemID), uint8_t(hr.m_hitSide)));
return;
}
}
label_5:
if (type != 2)
return;
goto label_15;
}
if (type == 1)
{
m_pGameMode->attack(m_pLocalPlayer, hr.m_pEnt);
return;
}
if (type == 2)
{
a = hr.m_pEnt->interactPreventDefault();
m_pGameMode->interact(m_pLocalPlayer, hr.m_pEnt);
if (!a)
{
label_15:
int id = m_pLocalPlayer->m_pInventory->getSelectedItemId();
if (id >= 0)
{
ItemInstance item(m_pLocalPlayer->m_pInventory->getSelectedItemId(), 999, 0);
if (m_pGameMode->useItem(m_pLocalPlayer, m_pLevel, &item))
m_pGameRenderer->m_pItemInHandRenderer->itemUsed();
}
}
}
}
void Minecraft::tickInput()
{
if (field_D14)
{
if (!field_D14->field_10)
{
field_DB0 = 1;
field_D14->updateEvents();
field_DB0 = false;
if (field_DB1)
{
setScreen(m_pScreen);
m_pScreen = NULL;
field_DB1 = false;
}
return;
}
}
if (!m_pLocalPlayer)
return;
bool bIsInGUI = m_gui.isInside(Mouse::_x, Mouse::_y);
while (Mouse::_index + 1 < Mouse::_inputs.size())
{
Mouse::_index++;
if (getTimeMs() - field_2B4 > 200)
continue;
if (Mouse::_buttonStates[1])
m_gui.handleClick(1, Mouse::_x, Mouse::_y);
if (!bIsInGUI && m_options.field_19)
{
MouseInput& input = Mouse::_inputs[Mouse::_index];
if (input.field_0 == 1 && input.field_4 == 1)
{
handleMouseClick(1);
field_DAC = field_DA8;
}
if (input.field_0 == 2 && input.field_4 == 1)
{
handleMouseClick(2);
field_DAC = field_DA8;
}
}
}
while (Keyboard::_index + 1 < Keyboard::_inputs.size())
{
Keyboard::_index++;
Keyboard::Input& input = Keyboard::_inputs[Keyboard::_index];
int keyCode = input.field_4;
bool bPressed = input.field_0 == 1;
m_pLocalPlayer->m_pKeyboardInput->setKey(keyCode, bPressed);
if (bPressed)
{
m_gui.handleKeyPressed(keyCode);
int index = keyCode - '1';
if (index <= 8 && index >= 0)
{
m_pLocalPlayer->m_pInventory->selectSlot(index);
}
else if (keyCode == AKEYCODE_SEARCH)
{
m_options.field_23D ^= 1;
}
else if (keyCode == AKEYCODE_MENU)
{
pauseGame();
}
#ifdef ENH_ALLOW_AO
else if (keyCode == AKEYCODE_F4)
{
// Toggle ambient occlusion.
m_options.field_18 ^= 1;
Minecraft::useAmbientOcclusion = m_options.field_18;
m_pLevelRenderer->allChanged();
}
#endif
}
if (m_options.field_19)
continue;
if (getTimeMs() - field_2B4 <= 200)
{
if (m_options.m_keyBinds[Options::DESTROY].value == keyCode && bPressed)
handleMouseClick(1);
if (m_options.m_keyBinds[Options::PLACE].value == keyCode && bPressed)
handleMouseClick(2);
}
}
// @TODO: fix gotos
bool v12 = false;
if (m_options.field_19)
{
if (!Mouse::_buttonStates[1] || bIsInGUI)
goto label_12;
}
else if (Keyboard::_states[m_options.m_keyBinds[Options::DESTROY].value] != 1)
{
goto label_12;
}
if (!field_D14 && (field_DA8 - field_DAC) >= (m_timer.field_10 * 0.25f))
{
handleMouseClick(1);
field_DAC = field_DA8;
}
if (m_bGrabbedMouse)
{
v12 = true;
}
else
{
label_12:
v12 = false;
}
handleMouseDown(1, v12);
field_2B4 = getTimeMs();
Keyboard::_inputs.clear();
Keyboard::_index = -1;
Mouse::_inputs.clear();
Mouse::_index = -1;
#ifndef ORIGINAL_CODE
if (m_bGrabbedMouse)
{
platform()->recenterMouse();
}
#endif
}
void Minecraft::_levelGenerated()
{
if (m_pNetEventCallback)
m_pNetEventCallback->levelGenerated(m_pLevel);
}
void Minecraft::tick()
{
if (field_DA4 > 0)
field_DA4--;
tickInput();
m_gui.tick();
// if the level has been prepared, delete the prep thread
if (!m_bPreparingLevel)
{
if (m_pPrepThread)
{
delete m_pPrepThread;
m_pPrepThread = nullptr;
_levelGenerated();
}
if (m_pLevel && !field_288)
{
m_pGameRenderer->tick();
m_pLevelRenderer->tick();
m_pLevel->tickEntities();
m_pLevel->tick();
if (m_pLocalPlayer)
{
m_pLevel->animateTick(
Mth::floor(m_pLocalPlayer->m_pos.x),
Mth::floor(m_pLocalPlayer->m_pos.y),
Mth::floor(m_pLocalPlayer->m_pos.z));
}
}
m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);
if (!field_288)
{
m_pTextures->tick();
m_pParticleEngine->tick();
}
if (field_D14)
field_D14->tick();
}
}
void Minecraft::update()
{
if (field_288 && m_pLevel)
{
float x = m_timer.field_18;
m_timer.advanceTime();
m_timer.field_18 = x;
}
else
{
m_timer.advanceTime();
}
if (m_pRakNetInstance)
{
m_pRakNetInstance->runEvents(m_pNetEventCallback);
}
if (m_timer.field_14 > 0)
{
for (int i = 0; i < m_timer.field_14; i++)
{
tick();
field_DA8++;
}
}
if (m_pLevel && !m_bPreparingLevel)
{
m_pLevel->updateLights();
}
m_pGameRenderer->render(m_timer.field_18);
}
void Minecraft::init()
{
m_pTextures = new Textures(&m_options, platform());
m_pTextures->addDynamicTexture(new WaterTexture);
m_pTextures->addDynamicTexture(new WaterSideTexture);
m_pLevelRenderer = new LevelRenderer(this, m_pTextures);
m_pGameRenderer = new GameRenderer(this);
m_pParticleEngine = new ParticleEngine(m_pLevel, m_pTextures);
m_pUser = new User("TestUser", "");
m_pGameMode = new SurvivalMode(this);
reloadOptions();
m_pFont = new Font(&m_options, "font/default.png", m_pTextures);
}
Minecraft::~Minecraft()
{
SAFE_DELETE(m_pNetEventCallback);
SAFE_DELETE(m_pRakNetInstance);
SAFE_DELETE(m_pLevelRenderer);
SAFE_DELETE(m_pGameRenderer);
SAFE_DELETE(m_pParticleEngine);
SAFE_DELETE(m_pSoundEngine);
SAFE_DELETE(m_pGameMode);
SAFE_DELETE(m_pFont);
SAFE_DELETE(m_pTextures);
if (m_pLevel)
{
LevelStorage* pStor = m_pLevel->getLevelStorage();
if (pStor)
delete pStor;
if (m_pLevel)
delete m_pLevel;
}
SAFE_DELETE(m_pUser);
SAFE_DELETE(m_pLevelStorageSource);
SAFE_DELETE(m_pTurnInput);
//@BUG: potentially leaking a CThread instance if this is destroyed early?
}
void Minecraft::prepareLevel(const std::string& unused)
{
field_DA0 = 1;
float startTime = getTimeS();
Level* pLevel = m_pLevel;
if (!pLevel->field_B0C)
{
pLevel->setUpdateLights(0);
}
for (int i = 8, i2 = 0; i != 8 + C_MAX_CHUNKS_X * 16; i += 16)
{
for (int j = 8; j != 8 + C_MAX_CHUNKS_Z * 16; j += 16, i2 += 100)
{
// this looks like some kind of progress tracking
m_progressPercent = i2 / (C_MAX_CHUNKS_X * C_MAX_CHUNKS_Z);
float time1 = getTimeS();
// generating all the chunks at once
TileID unused = m_pLevel->getTile(i, (C_MAX_Y + C_MIN_Y) / 2, j);
if (time1 != -1.0f)
getTimeS();
float time2 = getTimeS();
if (m_pLevel->field_B0C)
{
while (m_pLevel->updateLights());
}
if (time2 != -1.0f)
getTimeS();
}
}
if (startTime != -1.0f)
getTimeS();
m_pLevel->setUpdateLights(1);
startTime = getTimeS();
for (int x = 0; x < C_MAX_CHUNKS_X; x++)
{
for (int z = 0; z < C_MAX_CHUNKS_Z; z++)
{
LevelChunk* pChunk = m_pLevel->getChunk(x, z);
if (!pChunk)
continue;
if (pChunk->field_237)
continue;
pChunk->m_bUnsaved = false;
pChunk->clearUpdateMap();
}
}
if (startTime != -1.0f)
getTimeS();
field_DA0 = 3;
if (m_pLevel->field_B0C)
{
m_pLevel->setInitialSpawn();
m_pLevel->saveLevelData();
m_pLevel->getChunkSource()->saveAll();
}
else
{
m_pLevel->saveLevelData();
}
m_progressPercent = -1;
field_DA0 = 2;
startTime = getTimeS();
m_pLevel->prepare();
if (startTime != -1.0f)
getTimeS();
// These strings are initialized and then removed quickly afterwards. Probably some debug leftover
// "Generate level:";
// " - light: ";
// " - getTl: ";
// " - clear: ";
// " - prepr: ";
}
void Minecraft::generateLevel(const std::string& unused, Level* pLevel)
{
float time = getTimeS(); //@UNUSED
prepareLevel(unused);
if (time != -1.0f)
getTimeS(); //@QUIRK: unused return value
#pragma warning(disable : 26444) // C26444: Don't try to declare a local variable with no name.
std::string("Level generated: "); //@QUIRK: unused string instance
LocalPlayer* pLocalPlayer = m_pLocalPlayer;
if (!pLocalPlayer)
{
pLocalPlayer = m_pGameMode->createPlayer(pLevel);
m_pLocalPlayer = pLocalPlayer;
}
if (pLocalPlayer)
{
pLocalPlayer->m_pKeyboardInput = new KeyboardInput(&m_options);
}
if (m_pLevelRenderer)
m_pLevelRenderer->setLevel(pLevel);
if (m_pParticleEngine)
m_pParticleEngine->setLevel(pLevel);
m_pGameMode->adjustPlayer(m_pLocalPlayer);
pLevel->validateSpawn();
pLevel->loadPlayer(m_pLocalPlayer);
if (m_pLocalPlayer)
{
m_pLocalPlayer->resetPos();
}
m_pMobPersp = m_pLocalPlayer;
m_pLevel = pLevel;
m_bPreparingLevel = false;
if (m_pRakNetInstance->m_bIsHost)
m_pRakNetInstance->announceServer(m_pUser->field_0);
}
void* Minecraft::prepareLevel_tspawn(void* ptr)
{
Minecraft* pMinecraft = (Minecraft*)ptr;
pMinecraft->generateLevel("Currently not used", pMinecraft->m_pLevel);
return nullptr;
}
void Minecraft::pauseGame()
{
if (field_D14) return;
m_pLevel->savePlayerData();
setScreen(new PauseScreen);
}
void Minecraft::setLevel(Level* pLevel, const std::string& text, LocalPlayer* pLocalPlayer)
{
m_pMobPersp = nullptr;
if (pLevel)
{
m_pGameMode->initLevel(pLevel);
if (pLocalPlayer && m_pLocalPlayer == nullptr)
{
m_pLocalPlayer = pLocalPlayer;
pLocalPlayer->resetPos();
}
else if (m_pLocalPlayer)
{
m_pLocalPlayer->resetPos();
pLevel->addEntity(m_pLocalPlayer);
}
m_pLevel = pLevel;
m_bPreparingLevel = true;
m_pPrepThread = new CThread(&Minecraft::prepareLevel_tspawn, this);
}
else
{
m_pLocalPlayer = nullptr;
}
}
void Minecraft::selectLevel(const std::string& a, const std::string& b, int c)
{
LevelStorage* pStor = m_pLevelStorageSource->selectLevel(a, false);
Dimension* pDim = Dimension::getNew(0);
m_pLevel = new Level(pStor, b, c, 1, pDim);
setLevel(m_pLevel, "Generating level", nullptr);
field_D9C = 1;
}
const char* Minecraft::getProgressMessage()
{
return progressMessages[field_DA0];
}
LevelStorageSource* Minecraft::getLevelSource()
{
return m_pLevelStorageSource;
}
void Minecraft::leaveGame(bool bCopyMap)
{
m_bPreparingLevel = false;
m_pRakNetInstance->disconnect();
m_pMobPersp = nullptr;
m_pLevelRenderer->setLevel(nullptr);
m_pParticleEngine->setLevel(nullptr);
#ifndef ORIGINAL_CODE
// @BUG: Deleting ServerSideNetworkHandler too late! This causes
// access to invalid memory in the destructor seeing as we already deleted the level.
delete m_pNetEventCallback;
#endif
if (m_pLevel)
{
LevelStorage* pStorage = m_pLevel->getLevelStorage();
SAFE_DELETE(pStorage);
SAFE_DELETE(m_pLevel);
m_pLevel = nullptr;
}
#ifdef ORIGINAL_CODE
delete m_pNetEventCallback;
#endif
m_pLocalPlayer = nullptr;
m_pNetEventCallback = nullptr;
field_D9C = 0;
if (bCopyMap)
setScreen(new RenameMPLevelScreen("_LastJoinedServer"));
else
setScreen(new StartMenuScreen);
}
void Minecraft::hostMultiplayer()
{
m_pRakNetInstance->host(m_pUser->field_0, C_DEFAULT_PORT, C_MAX_CONNECTIONS);
m_pNetEventCallback = new ServerSideNetworkHandler(this, m_pRakNetInstance);
}
void Minecraft::joinMultiplayer(const PingedCompatibleServer& serverInfo)
{
if (field_18 && m_pNetEventCallback)
{
field_18 = false;
m_pRakNetInstance->connect(serverInfo.m_address.ToString(), serverInfo.m_address.GetPort());
}
}
void Minecraft::cancelLocateMultiplayer()
{
field_18 = false;
m_pRakNetInstance->stopPingForHosts();
delete m_pNetEventCallback;
m_pNetEventCallback = nullptr;
}
void Minecraft::locateMultiplayer()
{
field_18 = true;
m_pRakNetInstance->pingForHosts(C_DEFAULT_PORT);
m_pNetEventCallback = new ClientSideNetworkHandler(this, m_pRakNetInstance);
}

114
source/App/Minecraft.hpp Normal file
View File

@@ -0,0 +1,114 @@
#pragma once
#include "App.hpp"
#include "Mth.hpp"
#include "Gui.hpp"
#include "Screen.hpp"
#include "Timer.hpp"
#include "GameRenderer.hpp"
#include "LevelRenderer.hpp"
#include "Level.hpp"
#include "CThread.hpp"
#include "LocalPlayer.hpp"
#include "SurvivalMode.hpp"
#include "ITurnInput.hpp"
#include "EntityRenderDispatcher.hpp"
#include "ParticleEngine.hpp"
#include "SoundEngine.hpp"
#include "RakNetInstance.hpp"
#include "NetEventCallback.hpp"
class Screen; // in case we're included from Screen.hpp
class Minecraft : public App
{
public:
Minecraft();
virtual ~Minecraft();
int getLicenseId();
void setScreen(Screen * pScreen);
void releaseMouse();
void grabMouse();
void tick();
void tickInput();
void reloadOptions();
void handleMouseClick(int type);
void handleMouseDown(int type, bool b);
bool isLevelGenerated();
void selectLevel(const std::string&, const std::string&, int);
void setLevel(Level*, const std::string&, LocalPlayer*);
void pauseGame();
void leaveGame(bool bCopyMap);
void hostMultiplayer();
void joinMultiplayer(const PingedCompatibleServer& serverInfo);
void cancelLocateMultiplayer();
void locateMultiplayer();
virtual void onGraphicsReset();
virtual void update() override;
virtual void init() override;
static void* prepareLevel_tspawn(void* pMinecraft);
void generateLevel(const std::string& unused, Level* pLevel);
void prepareLevel(const std::string& unused);
void _levelGenerated();
bool isOnline();
bool isOnlineClient();
const char* getProgressMessage();
LevelStorageSource* getLevelSource();
public:
static int width, height;
static bool useAmbientOcclusion;
static const char* progressMessages[];
static const bool DEADMAU5_CAMERA_CHEATS;
static int customDebugId;
public:
bool field_18 = false;
Options m_options;
bool field_288 = false;
LevelRenderer* m_pLevelRenderer = nullptr;
GameRenderer* m_pGameRenderer = nullptr;
ParticleEngine* m_pParticleEngine = nullptr;
SoundEngine* m_pSoundEngine = nullptr;
GameMode* m_pGameMode = nullptr;
Textures* m_pTextures = nullptr;
Font* m_pFont = nullptr;
RakNetInstance* m_pRakNetInstance = nullptr;
NetEventCallback* m_pNetEventCallback = nullptr;
int field_2B0 = 0;
int field_2B4;
int field_2B8;
User* m_pUser = nullptr;
Level* m_pLevel = nullptr;
LocalPlayer* m_pLocalPlayer = nullptr;
Mob* m_pMobPersp = nullptr; // why is there a duplicate?
Gui m_gui;
int field_D0C = 0;
CThread* m_pPrepThread = nullptr;
Screen* field_D14 = nullptr;
int field_D18 = 10;
ITurnInput* m_pTurnInput = nullptr;
float field_D20 = 0.0f;
float field_D24 = 0.0f;
bool m_bGrabbedMouse = true;
HitResult m_hitResult;
int m_progressPercent = 0;
std::string field_D58;
Timer m_timer;
bool m_bPreparingLevel = false;
LevelStorageSource* m_pLevelStorageSource = nullptr; // TODO
int field_D9C = 0;
int field_DA0 = 0;
int field_DA4 = 0;
int field_DA8 = 0;
int field_DAC = 0;
bool field_DB0 = 0;
bool field_DB1 = 0;
Screen* m_pScreen = nullptr;
int m_licenseID = -2;
};

118
source/App/NinecraftApp.cpp Normal file
View File

@@ -0,0 +1,118 @@
#include "NinecraftApp.hpp"
#include "StartMenuScreen.hpp"
#include "MemoryLevelStorageSource.hpp"
#include "Item.hpp"
bool NinecraftApp::_hasInitedStatics;
bool NinecraftApp::handleBack(bool b)
{
if (m_bPreparingLevel)
return true;
if (!m_pLevel)
{
if (!field_D14)
return false;
return field_D14->handleBackEvent(b);
}
if (b)
return 1;
if (!field_D14) return false;
if (field_D14->handleBackEvent(b))
return true;
setScreen(nullptr);
return true;
}
void NinecraftApp::initGLStates()
{
GL_TEXTURE_2D;
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.1f);
glCullFace(GL_NONE);
glEnable(GL_TEXTURE_2D);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
}
void NinecraftApp::init()
{
Mth::initMth();
if (!_hasInitedStatics)
{
_hasInitedStatics = true;
Material::initMaterials();
Tile::initTiles();
Item::initItems();
Biome::initBiomes();
}
initGLStates();
Tesselator::instance.init();
Minecraft::init();
m_pLevelStorageSource = new MemoryLevelStorageSource;
field_D9C = 0;
setScreen(new StartMenuScreen);
}
void NinecraftApp::onGraphicsReset()
{
initGLStates();
Tesselator::instance.init();
Minecraft::onGraphicsReset();
}
void NinecraftApp::teardown()
{
}
void NinecraftApp::update()
{
++m_fps;
Minecraft::update();
#ifdef ORIGINAL_CODE
eglSwapBuffers(field_8, field_10);
#endif
Mouse::_xOld = Mouse::_x;
Mouse::_yOld = Mouse::_y;
updateStats();
}
void NinecraftApp::updateStats()
{
int timeMs = getTimeMs();
if (timeMs > field_2B0 + 999)
{
if (m_pLocalPlayer)
{
Vec3 &pos = m_pLocalPlayer->m_pos;
printf("%d fps\t%3d chunk updates. (%.2f, %.2f, %.2f)\n", m_fps, Chunk::updates, pos.x, pos.y, pos.z);
printf("%s", m_pLevelRenderer->gatherStats1());
Chunk::updates = 0;
}
else
{
printf("%d fps\n", m_fps);
}
field_2B0 = timeMs;
m_fps = 0;
}
}
NinecraftApp::~NinecraftApp()
{
teardown();
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include "Minecraft.hpp"
#include "Level.hpp"
#include "Tile.hpp"
//@TYPO: This is probably meant to say "MinecraftApp". Still not fixed in V0.3.0 though so not sure
class NinecraftApp : public Minecraft
{
public:
virtual ~NinecraftApp();
bool handleBack(bool) override;
void init() override;
void update() override;
void onGraphicsReset() override;
void teardown();
void updateStats();
void initGLStates();
private:
int field_DBC = 0;
bool field_DC0 = 1;
int m_fps = 0;
static bool _hasInitedStatics;
};

200
source/Base/AABB.cpp Normal file
View File

@@ -0,0 +1,200 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
AABB.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include "AABB.hpp"
AABB::AABB()
{
}
AABB::AABB(Vec3 _min, Vec3 _max) :
min(_min), max(_max)
{
}
AABB::AABB(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) :
min(minX, minY, minZ), max(maxX, maxY, maxZ)
{
}
HitResult AABB::clip(const Vec3& vec1, const Vec3& vec2)
{
Vec3 clipMinX, clipMinY, clipMinZ;
Vec3 clipMaxX, clipMaxY, clipMaxZ;
bool bClipMinX, bClipMinY, bClipMinZ;
bool bClipMaxX, bClipMaxY, bClipMaxZ;
bClipMinX = vec1.clipX(vec2, min.x, clipMinX);
bClipMaxX = vec1.clipX(vec2, max.x, clipMaxX);
bClipMinY = vec1.clipX(vec2, min.y, clipMinY);
bClipMaxY = vec1.clipX(vec2, max.y, clipMaxY);
bClipMinZ = vec1.clipX(vec2, min.z, clipMinZ);
bClipMaxZ = vec1.clipX(vec2, max.z, clipMaxZ);
// <sigh>
if (bClipMinX)
{
if (clipMinX.y < this->min.y || clipMinX.y > this->max.y || clipMinX.z < this->min.z)
bClipMinX = 0;
else
bClipMinX = clipMinX.z <= this->max.z;
}
if (bClipMaxX)
{
if (clipMaxX.y < this->min.y || clipMaxX.y > this->max.y || clipMaxX.z < this->min.z)
bClipMaxX = 0;
else
bClipMaxX = clipMaxX.z <= this->max.z;
}
if (bClipMinY)
{
if (clipMinY.x < this->min.x || clipMinY.x > this->max.x || clipMinY.z < this->min.z)
bClipMinY = 0;
else
bClipMinY = clipMinY.z <= this->max.z;
}
if (bClipMaxY)
{
if (clipMaxY.x < this->min.x || clipMaxY.x > this->max.x || clipMaxY.z < this->min.z)
bClipMaxY = 0;
else
bClipMaxY = clipMaxY.z <= this->max.z;
}
if (bClipMinZ)
{
if (clipMinZ.x < this->min.x || clipMinZ.x > this->max.x || clipMinZ.y < this->min.y)
bClipMinZ = 0;
else
bClipMinZ = clipMinZ.y <= this->max.y;
}
if (bClipMaxZ)
{
if (clipMaxZ.x < this->min.x || clipMaxZ.x > this->max.x || clipMaxZ.y < this->min.y)
bClipMaxZ = 0;
else
bClipMaxZ = clipMaxZ.y <= this->max.y;
}
// the collided side of our AABB
HitResult::eHitSide collType = HitResult::NOHIT;
// the preferred vector for our collision
Vec3* pVec = nullptr;
if (bClipMinX)
pVec = &clipMinX, collType = HitResult::MINX;
if (bClipMaxX)
{
if (!pVec || clipMinZ.distanceToSqr(vec1) < pVec->distanceToSqr(vec1))
pVec = &clipMaxX, collType = HitResult::MAXX;
}
if (bClipMinY)
{
if (!pVec || clipMinY.distanceToSqr(vec1) < pVec->distanceToSqr(vec1))
pVec = &clipMinY, collType = HitResult::MINY;
}
if (bClipMaxY)
{
if (!pVec || clipMaxY.distanceToSqr(vec1) < pVec->distanceToSqr(vec1))
pVec = &clipMaxY, collType = HitResult::MAXY;
}
if (bClipMinZ)
{
if (!pVec || clipMinZ.distanceToSqr(vec1) < pVec->distanceToSqr(vec1))
pVec = &clipMinZ, collType = HitResult::MINZ;
}
if (bClipMaxZ)
{
if (!pVec || clipMinZ.distanceToSqr(vec1) < pVec->distanceToSqr(vec1))
pVec = &clipMaxZ, collType = HitResult::MAXZ;
}
if (!pVec)
{
// return a nothing burger
return HitResult();
}
return HitResult(0, 0, 0, collType, *pVec);
}
float AABB::clipXCollide(const AABB& bud, float f) const
{
if (bud.max.y > min.y && bud.min.y < max.y && bud.max.z > min.z && bud.min.z < max.z)
{
if (f > 0.0f)
{
if (bud.max.x <= min.x)
f = Mth::Min(min.x - bud.max.x, f);
}
if (f < 0.0f)
{
if (bud.min.x >= max.x)
f = Mth::Max(max.x - bud.min.x, f);
}
}
return f;
}
float AABB::clipYCollide(const AABB& bud, float f) const
{
if (bud.max.x > min.x && bud.min.x < max.x && bud.max.z > min.z && bud.min.z < max.z)
{
if (f > 0.0f)
{
if (bud.max.y <= min.y)
f = Mth::Min(min.y - bud.max.y, f);
}
if (f < 0.0f)
{
if (bud.min.y >= max.y)
f = Mth::Max(max.y - bud.min.y, f);
}
}
return f;
}
float AABB::clipZCollide(const AABB& bud, float f) const
{
if (bud.max.x > min.x && bud.min.x < max.x && bud.max.y > min.y && bud.min.y < max.y)
{
if (f > 0.0f)
{
if (bud.max.z <= min.z)
f = Mth::Min(min.z - bud.max.z, f);
}
if (f < 0.0f)
{
if (bud.min.z >= max.z)
f = Mth::Max(max.z - bud.min.z, f);
}
}
return f;
}
bool AABB::intersect(const AABB& other) const
{
return max.x > other.min.x
&& min.x < other.max.x
&& max.y > other.min.y
&& min.y < other.max.y
&& max.z > other.min.z
&& min.z < other.max.z;
}

69
source/Base/AABB.hpp Normal file
View File

@@ -0,0 +1,69 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
AABB.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include "Vec3.hpp"
#include "HitResult.hpp"
class AABB
{
public:
Vec3 min, max;
AABB();
AABB(Vec3 min, Vec3 max);
AABB(float minX, float minY, float minZ, float maxX, float maxY, float maxZ);
public:
HitResult clip(const Vec3&, const Vec3&);
float clipXCollide(const AABB& bud, float f) const;
float clipYCollide(const AABB& bud, float f) const;
float clipZCollide(const AABB& bud, float f) const;
bool intersect(const AABB& other) const;
// @NOTE: Names for `move`, `grow` and `expand` were taken from really early minecraft (rd-132211 to be exact).
void move(float x, float y, float z)
{
min += Vec3(x, y, z);
max += Vec3(x, y, z);
}
// same thing
void grow(float x, float y, float z)
{
min -= Vec3(x, y, z);
max += Vec3(x, y, z);
}
// same thing
void grow(float x)
{
min -= Vec3(x, x, x);
max += Vec3(x, x, x);
}
void expand(float x, float y, float z)
{
if (x < 0) min.x += x;
if (x > 0) max.x += x;
if (y < 0) min.y += y;
if (y > 0) max.y += y;
if (z < 0) min.z += z;
if (z > 0) max.z += z;
}
bool contains(const Vec3& v) const
{
return v.x > min.x && v.x < max.x && v.y > min.y && v.y < max.y && v.z > min.z && v.z < max.z;
}
};

41
source/Base/CThread.cpp Normal file
View File

@@ -0,0 +1,41 @@
#include "CThread.hpp"
#include "Utils.hpp"
#ifdef _WIN32
#include <Windows.h> // for Sleep()
#else
#include <unistd.h>
#endif
void CThread::sleep(uint32_t ms)
{
#ifdef _WIN32
Sleep(ms);
#else
usleep(1000 * ms);
#endif
}
CThread::CThread(CThreadFunction func, void* parm)
{
m_func = func;
#ifdef USE_CPP11_THREADS
std::thread thr(func, parm);
m_thrd.swap(thr);
#else
pthread_attr_init(&m_thrd_attr);
pthread_attr_setdetachstate(&m_thrd_attr, 1);
pthread_create(&m_thrd, &m_thrd_attr, m_func, parm);
#endif
}
CThread::~CThread()
{
#ifdef USE_CPP11_THREADS
m_thrd.join();
#else
pthread_join(m_thrd, 0);
pthread_attr_destroy(&m_thrd_attr);
#endif
}

33
source/Base/CThread.hpp Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
// CThread - Object oriented pthread wrapper
#define USE_CPP11_THREADS
// USE_CPP11_THREADS - Use a C++11 implementation of threads instead of using pthread
#ifdef USE_CPP11_THREADS
#include <thread>
#else
#include <pthread.h>
#endif
typedef void* (*CThreadFunction)(void*);
class CThread
{
public:
CThread(CThreadFunction, void* parm);
~CThread();
static void sleep(uint32_t ms);
private:
CThreadFunction m_func;
#ifdef USE_CPP11_THREADS
std::thread m_thrd;
#else
pthread_t m_thrd;
pthread_attr_t m_thrd_attr;
#endif
};

30
source/Base/HitResult.cpp Normal file
View File

@@ -0,0 +1,30 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
HitResult.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include "HitResult.hpp"
HitResult::HitResult() {}
HitResult::HitResult(int x, int y, int z, eHitSide hitSide, const Vec3& vec)
{
m_hitType = AABB;
m_hitSide = hitSide;
m_tileX = x;
m_tileY = y;
m_tileZ = z;
m_bUnk24 = false;
m_hitPos = vec;
}
HitResult::HitResult(Entity* pEnt)
{
m_hitType = ENTITY;
m_pEnt = pEnt;
}

59
source/Base/HitResult.hpp Normal file
View File

@@ -0,0 +1,59 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
HitResult.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include "Vec3.hpp"
class Entity;
class HitResult
{
public:
// looks ass backwards, but what can you do about it
enum eHitResultType
{
AABB,
ENTITY,
NONE,
};
enum eHitSide
{
NOHIT = -1,
MINY = 0,
MAXY, // 1
MINZ, // 2
MAXZ, // 3
MINX, // 4
MAXX, // 5
};
public:
HitResult();
HitResult(Entity*);
HitResult(int x, int y, int z, eHitSide hitSide, const Vec3&);
public:
eHitResultType m_hitType = NONE;
// block coords?
int m_tileX = 0;
int m_tileY = 0;
int m_tileZ = 0;
eHitSide m_hitSide = MINY;
// hit position
Vec3 m_hitPos;
Entity* m_pEnt = nullptr;
bool m_bUnk24 = 0;
};

View File

@@ -0,0 +1,206 @@
#include "ImprovedNoise.hpp"
#include "Mth.hpp"
ImprovedNoise::ImprovedNoise()
{
Random random(1);
init(&random);
}
ImprovedNoise::ImprovedNoise(Random* pRandom)
{
init(pRandom);
}
void ImprovedNoise::init(Random* pRandom)
{
m_offsetX = pRandom->nextFloat() * 256.0f;
m_offsetY = pRandom->nextFloat() * 256.0f;
m_offsetZ = pRandom->nextFloat() * 256.0f;
for (int i = 0; i < 256; i++)
m_permutation[i] = i;
for (int i = 0; i < 256; i++)
{
int x = pRandom->nextInt(256 - i) + i;
int t = m_permutation[i];
m_permutation[i] = m_permutation[x];
m_permutation[x] = t;
m_permutation[256 + i] = m_permutation[i];
}
}
float ImprovedNoise::getValue(float x, float y)
{
return getValue(x, y, 0.0f);
}
float ImprovedNoise::getValue(float x, float y, float z)
{
return noise(x, y, z);
}
float ImprovedNoise::lerp(float prog, float a, float b)
{
return a + (b - a) * prog;
}
float ImprovedNoise::grad(int hash, float x, float y, float z)
{
int h = hash & 0xF;
float u = h < 8 ? x : y;
float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
float ImprovedNoise::grad2(int hash, float x, float z)
{
return grad(hash, x, 0.0f, z);
}
float ImprovedNoise::fade(float x)
{
return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f);
}
float ImprovedNoise::noise(float x, float y, float z)
{
// couldn't figure out how to get it to work well enough so I just decided to port the original implementation from:
// https://cs.nyu.edu/~perlin/noise/
x += m_offsetX;
y += m_offsetY;
z += m_offsetZ;
int X = Mth::floor(x) & 255,
Y = Mth::floor(y) & 255,
Z = Mth::floor(z) & 255;
x -= Mth::floor(x);
y -= Mth::floor(y);
z -= Mth::floor(z);
float u = fade(x),
v = fade(y),
w = fade(z);
int* p = m_permutation;
int A = p[X ] + Y, AA = p[A] + Z, AB = p[A + 1] + Z,
B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;
return lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ),
grad(p[BA ], x-1, y , z )),
lerp(u, grad(p[AB ], x , y-1, z ),
grad(p[BB ], x-1, y-1, z ))),
lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ),
grad(p[BA+1], x-1, y , z-1 )),
lerp(u, grad(p[AB+1], x , y-1, z-1 ),
grad(p[BB+1], x-1, y-1, z-1 ))));
}
void ImprovedNoise::add(float* a2, float a3, float a4, float a5, int a6, int a7, int a8, float a9, float a10, float a11, float a12)
{
// @TODO: clean this up
if (a7 == 1)
{
for (int i = 0; i < a6; i++)
{
float x2 = m_offsetX + a9 * (i + a3);
int x3 = Mth::floor(x2);
float x4 = float(x3);
float x5 = x2 - x4;
int* x6 = &m_permutation[uint8_t(x3)];
int* x8 = &m_permutation[uint8_t(x3 + 1)];
float* x7 = &a2[a8 * i];
for (int j = 0; j < a8; j++)
{
float x9 = m_offsetZ + a11 * (j + a5);
int x10 = Mth::floor(x9);
float x11 = float(x10);
float x12 = x9 - x11;
int* x13 = &m_permutation[uint8_t(x10) + m_permutation[*x6]];
int* x15 = &m_permutation[uint8_t(x10) + m_permutation[*x8]];
int* x14 = x8;
float x16 = grad2(*x13, x5, x12);
float x17 = grad(*x15, x5 - 1, 0, x12);
float x18 = lerp(fade(x5), x16, x17);
float x19 = grad(x13[1], x5, 0, x12 - 1);
float x20 = grad(x15[1], x5 - 1, 0, x12 - 1);
float x21 = lerp(fade(x5), x19, x20);
*x7 += (1.0f / a12) * lerp(fade(x12), x18, x21);
x7++;
}
}
return;
}
float x30 = 0, x31 = 0, x32 = 0, x33 = 0;
int x34 = -1, x35 = 0;
for (int i = 0; i < a6; i++)
{
float x36 = m_offsetX + a9 * (i + a3);
int x37 = Mth::floor(x36);
float x38 = float(x37);
float x39 = x36 - x38;
float x40 = fade(x39);
if (a8 <= 0) continue;
int* x42 = &m_permutation[uint8_t(x37)];
int* x43 = &m_permutation[uint8_t(x37) + 1];
for (int j = 0; j < a8; j++)
{
float x44 = m_offsetZ + a11 * (j + a5);
int x45 = Mth::floor(x44);
float x46 = float(x45);
float x47 = x44 - x46;
uint8_t x48 = uint8_t(x45);
if (a7 <= 0) continue;
float* x49 = &a2[x35];
for (int k = 0; k < a7; k++)
{
float x50 = m_offsetY + a10 * (k + a4);
int x51 = Mth::floor(x50);
float x52 = float(x51);
float x53 = x50 - x52;
uint8_t bx51 = uint8_t(x51);
if (k == 0 || bx51 != x34)
{
int* x54 = &m_permutation[bx51 + *x42];
int x55 = x54[0] + x48;
int x56 = x54[1] + x48;
int* x57 = &m_permutation[bx51 + *x43];
int x58 = x57[1] + x48;
int* x59 = &m_permutation[*x57 + x48];
float x60 = grad(m_permutation[x55], x39, x53, x47);
float x61 = grad(*x59, x39 - 1, x53, x47);
x33 = lerp(x40, x60, x61);
float x62 = grad(m_permutation[x56], x39, x53 - 1, x47);
float x63 = grad(m_permutation[x58], x39 - 1, x53 - 1, x47);
x32 = lerp(x40, x62, x63);
float x64 = grad(m_permutation[x55 + 1], x39, x53, x47 - 1);
float x65 = grad(x59[1], x39 - 1, x53, x47 - 1);
x31 = lerp(x40, x64, x65);
float x66 = grad(m_permutation[x56 + 1], x39, x53 - 1, x47 - 1);
float x67 = grad(m_permutation[x58 + 1], x39 - 1, x53 - 1, x47 - 1);
x34 = bx51;
x30 = lerp(x40, x66, x67);
}
float x68 = lerp(fade(x53), x33, x32);
float x69 = lerp(fade(x53), x31, x30);
*x49 += (1.0f / a12) * lerp(fade(x47), x68, x69);
x49++;
}
x35 += a7;
}
}
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "Random.hpp"
#include "Synth.hpp"
// note: appears to maybe inherit from a class called Synth?
// the only purpose that it serves I guess is to provide
// a virtual getValue
class ImprovedNoise : public Synth
{
public:
float getValue(float, float) override;
ImprovedNoise();
ImprovedNoise(Random* pRandom);
void init(Random* pRandom);
float getValue(float, float, float);
float noise(float, float, float);
float grad(int, float, float, float);
float grad2(int, float, float);
float lerp(float prog, float a, float b);
float fade(float x); // inlined in the code
void add(float* a2, float a3, float a4, float a5, int a6, int a7, int a8, float a9, float a10, float a11, float a12);
public:
float m_offsetX;
float m_offsetY;
float m_offsetZ;
int m_permutation[512];
};

52
source/Base/Matrix.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "Matrix.hpp"
Matrix::Matrix()
{
memset(c, 0, sizeof c);
}
Matrix::Matrix(float x)
{
memset(c, 0, sizeof c);
c[0] = c[4 * 1 + 1] = c[4 * 2 + 2] = c[4 * 3 + 3] = x;
}
Matrix::Matrix(float* p)
{
memcpy(c, p, sizeof c);
}
Matrix::Matrix(float a, float b, float q, float d, float e, float f, float g, float h, float i, float j, float k, float l, float m, float n, float o, float p)
{
c[0] = a, c[1] = b, c[2] = q, c[3] = d, c[4] = e, c[5] = f, c[6] = g, c[7] = h, c[8] = i, c[9] = j, c[10] = k, c[11] = l, c[12] = m, c[13] = n, c[14] = o, c[15] = p;
}
void Matrix::fetchGL(GLenum pname)
{
glGetFloatv(pname, c);
}
Matrix operator*(const Matrix& a, const Matrix& b)
{
Matrix result;
//this is ugly
result.c[0] = a.c[0] * b.c[0] + a.c[1] * b.c[4] + a.c[2] * b.c[8] + a.c[3] * b.c[12];
result.c[1] = a.c[0] * b.c[1] + a.c[1] * b.c[5] + a.c[2] * b.c[9] + a.c[3] * b.c[13];
result.c[2] = a.c[0] * b.c[2] + a.c[1] * b.c[6] + a.c[2] * b.c[10] + a.c[3] * b.c[14];
result.c[3] = a.c[0] * b.c[3] + a.c[1] * b.c[7] + a.c[2] * b.c[11] + a.c[3] * b.c[15];
result.c[4] = a.c[4] * b.c[0] + a.c[5] * b.c[4] + a.c[6] * b.c[8] + a.c[7] * b.c[12];
result.c[5] = a.c[4] * b.c[1] + a.c[5] * b.c[5] + a.c[6] * b.c[9] + a.c[7] * b.c[13];
result.c[6] = a.c[4] * b.c[2] + a.c[5] * b.c[6] + a.c[6] * b.c[10] + a.c[7] * b.c[14];
result.c[7] = a.c[4] * b.c[3] + a.c[5] * b.c[7] + a.c[6] * b.c[11] + a.c[7] * b.c[15];
result.c[8] = a.c[8] * b.c[0] + a.c[9] * b.c[4] + a.c[10] * b.c[8] + a.c[11] * b.c[12];
result.c[9] = a.c[8] * b.c[1] + a.c[9] * b.c[5] + a.c[10] * b.c[9] + a.c[11] * b.c[13];
result.c[10] = a.c[8] * b.c[2] + a.c[9] * b.c[6] + a.c[10] * b.c[10] + a.c[11] * b.c[14];
result.c[11] = a.c[8] * b.c[3] + a.c[9] * b.c[7] + a.c[10] * b.c[11] + a.c[11] * b.c[15];
result.c[12] = a.c[12] * b.c[0] + a.c[13] * b.c[4] + a.c[14] * b.c[8] + a.c[15] * b.c[12];
result.c[13] = a.c[12] * b.c[1] + a.c[13] * b.c[5] + a.c[14] * b.c[9] + a.c[15] * b.c[13];
result.c[14] = a.c[12] * b.c[2] + a.c[13] * b.c[6] + a.c[14] * b.c[10] + a.c[15] * b.c[14];
result.c[15] = a.c[12] * b.c[3] + a.c[13] * b.c[7] + a.c[14] * b.c[11] + a.c[15] * b.c[15];
return result;
}

22
source/Base/Matrix.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "compat/GL.hpp"
#include "Mth.hpp"
class Matrix
{
public:
Matrix(); // create an empty matrix
Matrix(float a); // create an identity matrix
Matrix(float* p); // load matrix from memory
Matrix(float a, float b, float c, float d, float e, float f, float g, float h, float i, float j, float k, float l, float m, float n, float o, float p);
void fetchGL(GLenum pname);
friend Matrix operator*(const Matrix& a, const Matrix& b);
public:
float c[16] = { 0.0f };
};
Matrix operator*(const Matrix& a, const Matrix& b);

160
source/Base/Mth.cpp Normal file
View File

@@ -0,0 +1,160 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Mth.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include <cmath>
#include <cstdint>
#include "Mth.hpp"
#define C_SIN_TABLE_MULTIPLIER (10430.0f) // (3320.0f * 3.14156f)
#define ANG_TO_SIN_TABLE_INDEX(ang) ((int) ((ang) * C_SIN_TABLE_MULTIPLIER))
#define SIN_TABLE_INDEX_TO_ANG(ang) ((float)((ang) / C_SIN_TABLE_MULTIPLIER))
float g_SinTable[65536];
Random Mth::g_Random;
void Mth::initMth()
{
for (int i = 0; i < 65536; i++)
{
g_SinTable[i] = sinf(SIN_TABLE_INDEX_TO_ANG(i)); // value is 10430
}
}
int Mth::intFloorDiv(int a2, int a3)
{
if (a2 < 0)
return ~(~a2 / a3);
return a2 / a3;
}
float Mth::invSqrt(float number)
{
// It looks familiar. With IDA I get a convoluted mess. I'm going to assume
// they just stole it from Quake.
int32_t i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( int32_t * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}
float Mth::sin(float a2)
{
int angle = ANG_TO_SIN_TABLE_INDEX(a2) & 0xFFFF;
return g_SinTable[angle];
}
float Mth::cos(float a2)
{
int angle = (ANG_TO_SIN_TABLE_INDEX(a2) + 16384) & 0xFFFF;
return g_SinTable[angle];
}
float Mth::sqrt(float a2)
{
return sqrtf(a2);
}
int Mth::floor(float f)
{
int result = int(f);
if (result > f)
result--;
return result;
}
float Mth::atan(float f)
{
return atanf(f);
}
float Mth::atan2(float y, float x)
{
return atan2f(y, x);
}
float Mth::Min(float a, float b)
{
return a < b ? a : b;
}
int Mth::Min(int a, int b)
{
return a < b ? a : b;
}
float Mth::Max(float a, float b)
{
return a > b ? a : b;
}
int Mth::Max(int a, int b)
{
return a > b ? a : b;
}
float Mth::abs(float f)
{
if (f < 0)
f = -f;
return f;
}
int Mth::abs(int d)
{
return ::abs(d);
}
float Mth::absMax(float a2, float a3)
{
if (a2 < 0)
a2 = -a2;
if (a3 < 0)
a3 = -a3;
if (a2 <= a3)
a2 = a3;
return a2;
}
float Mth::absMaxSigned(float a2, float a3)
{
if (abs(a2) <= abs(a2))
a2 = a3;
return a2;
}
int Mth::random(int max)
{
return int(g_Random.nextInt(max));
}
float Mth::random()
{
return g_Random.genrand_int32() * (1.0f / 4294967295.0f);
// divided by 2^32-1
}

42
source/Base/Mth.hpp Normal file
View File

@@ -0,0 +1,42 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Mth.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include <cmath>
#include "Random.hpp"
class Mth
{
static Random g_Random;
public:
static float Max(float, float);
static int Max(int, int);
static float Min(float, float);
static int Min(int, int);
static float abs(float);
static int abs(int);
static float absMax(float, float);
static float absMaxSigned(float, float);
static float atan(float);
static float atan2(float y, float x);
static float cos(float);
static int floor(float);
static void initMth();
static int intFloorDiv(int, int);
static float invSqrt(float);
static int random(int);
static float random(void);
static float sin(float);
static float sqrt(float);
};

View File

@@ -0,0 +1,93 @@
#include "PerlinNoise.hpp"
#include "Utils.hpp"
PerlinNoise::PerlinNoise(int nOctaves)
{
m_pRandom = &m_random;
init(nOctaves);
}
PerlinNoise::PerlinNoise(Random* pRandom, int nOctaves)
{
m_pRandom = pRandom;
if (nOctaves == 10)
{
LogMsg("Ok");
}
init(nOctaves);
}
void PerlinNoise::init(int nOctaves)
{
m_nOctaves = nOctaves;
m_pImprovedNoise = new ImprovedNoise * [nOctaves];
for (int i = 0; i < nOctaves; i++)
{
m_pImprovedNoise[i] = new ImprovedNoise(m_pRandom);
}
}
PerlinNoise::~PerlinNoise()
{
for (int i = 0; i < m_nOctaves; i++)
delete[] m_pImprovedNoise[i];
delete[] m_pImprovedNoise;
}
float PerlinNoise::getValue(float x, float y)
{
if (m_nOctaves <= 0) return 0.0f;
float result = 0.0f, x1 = 1.0f;
for (int i = 0; i < m_nOctaves; i++)
{
result += m_pImprovedNoise[i]->getValue(x * x1, y * x1) / x1;
x1 /= 2.f;
}
return result;
}
float PerlinNoise::getValue(float x, float y, float z)
{
if (m_nOctaves <= 0) return 0.0f;
float result = 0.0f, x1 = 1.0f;
for (int i = 0; i < m_nOctaves; i++)
{
result += m_pImprovedNoise[i]->getValue(x * x1, y * x1, z * x1) / x1;
x1 /= 2.f;
}
return result;
}
float* PerlinNoise::getRegion(float* a2, int a3, int a4, int a5, int a6, float a7, float a8, float a9)
{
return getRegion(a2, float(a3), 10.0f, float(a4), a5, 1, a6, a7, 1.0f, a8);
}
float* PerlinNoise::getRegion(float* pMem, float a3, float a4, float a5, int a6, int a7, int a8, float a9, float a10, float a11)
{
int amt = a6 * a7 * a8;
if (!pMem)
pMem = new float[amt];
for (int i = 0; i < amt; i++)
pMem[i] = 0;
float x = 1.0f;
for (int i = 0; i < m_nOctaves; i++)
{
m_pImprovedNoise[i]->add(pMem, a3, a4, a5, a6, a7, a8, a9 * x, a10 * x, a11 * x, x);
x /= 2;
}
return pMem;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "ImprovedNoise.hpp"
class PerlinNoise : public Synth
{
public:
PerlinNoise(int nOctaves);
PerlinNoise(Random*, int nOctaves);
virtual ~PerlinNoise();
void init(int nOctaves);
float getValue(float, float) override;
float getValue(float, float, float);
float* getRegion(float*, int, int, int, int, float, float, float);
float* getRegion(float*, float, float , float, int, int, int, float, float, float);
private:
ImprovedNoise** m_pImprovedNoise;
int m_nOctaves;
Random m_random;
Random* m_pRandom; // random for seeding, I assume
};

110
source/Base/Random.cpp Normal file
View File

@@ -0,0 +1,110 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Random.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include "Utils.hpp"
#include "Random.hpp"
/* Period parameters */
#define N 624
#define M 397
#define MATRIX_A 0x9908b0dfUL /* constant vector a */
#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
#define LOWER_MASK 0x7fffffffUL /* least significant r bits */
Random::Random(long seed)
{
setSeed(seed);
}
void Random::setSeed(long seed)
{
rseed = seed;
mti = N + 1;
init_genrand(seed);
}
/* initializes mt[N] with a seed */
void Random::init_genrand(unsigned long s)
{
mt[0] = s & 0xffffffffUL;
for (mti = 1; mti < N; mti++) {
mt[mti] =
(1812433253UL * (mt[mti - 1] ^ (mt[mti - 1] >> 30)) + mti);
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
/* In the previous versions, MSBs of the seed affect */
/* only MSBs of the array mt[]. */
/* 2002/01/09 modified by Makoto Matsumoto */
mt[mti] &= 0xffffffffUL;
/* for >32 bit machines */
}
}
int Random::nextInt(int max)
{
return int(genrand_int32() % unsigned(max));
}
// Returns a number from 0 to n (excluding n)
unsigned int Random::genrand_int32()
{
unsigned long y;
static unsigned long mag01[2]={0x0UL, MATRIX_A};
/* mag01[x] = x * MATRIX_A for x=0,1 */
if (mti >= N) { /* generate N words at one time */
int kk;
if (mti == N+1) /* if init_genrand() has not been called, */
init_genrand(5489UL); /* a default initial seed is used */
for (kk=0;kk<N-M;kk++) {
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL];
}
for (;kk<N-1;kk++) {
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
}
y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];
mti = 0;
}
y = mt[mti++];
/* Tempering */
y ^= (y >> 11);
y ^= (y << 7) & 0x9d2c5680UL;
y ^= (y << 15) & 0xefc60000UL;
y ^= (y >> 18);
return y;
}
float Random::nextFloat()
{
return float(genrand_real2());
}
double Random::genrand_real2()
{
return double(genrand_int32()) * (1.0 / 4294967296.0);
}
long Random::nextLong()
{
return long(genrand_int32() >> 1);
}
int Random::nextInt()
{
return int(genrand_int32() >> 1);
}

56
source/Base/Random.hpp Normal file
View File

@@ -0,0 +1,56 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Random.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include <cstdint>
// This appears to be VERY similar to https://github.com/SethRobinson/proton/blob/master/shared/util/CRandom.h#L10
// It turns out, RTsoft, Mojang, and the author of Game Coding Complete used the same reference implementation of
// the Mersenne Twister:
// http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/MT2002/CODES/mt19937ar.c
//A random generator based on the Mersenne Twister originally developed by Takuji Nishimura and Makoto Matsumoto.
// From the book GameCoding Complete by Mike McShaffry
/* Period parameters */
#define CMATH_N 624
#define CMATH_M 397
#define CMATH_MATRIX_A 0x9908b0df /* constant vector a */
#define CMATH_UPPER_MASK 0x80000000 /* most significant w-r bits */
#define CMATH_LOWER_MASK 0x7fffffff /* least significant r bits */
/* Tempering parameters */
#define CMATH_TEMPERING_MASK_B 0x9d2c5680
#define CMATH_TEMPERING_MASK_C 0xefc60000
#define CMATH_TEMPERING_SHIFT_U(y) (y >> 11)
#define CMATH_TEMPERING_SHIFT_S(y) (y << 7)
#define CMATH_TEMPERING_SHIFT_T(y) (y << 15)
#define CMATH_TEMPERING_SHIFT_L(y) (y >> 18)
int getTimeMs();
class Random
{
unsigned int rseed;
unsigned long mt[CMATH_N]; // the array for the state vector
int mti; // mti==N+1 means mt[N] is not initialized
public:
Random(long seed = getTimeMs());
void setSeed(long seed);
void init_genrand(unsigned long);
int nextInt(int max);
unsigned genrand_int32();
float nextFloat();
double genrand_real2();
long nextLong();
int nextInt();
};

1
source/Base/Synth.cpp Normal file
View File

@@ -0,0 +1 @@
#include "Synth.hpp"

8
source/Base/Synth.hpp Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
class Synth
{
public:
virtual float getValue(float, float) = 0;
};

39
source/Base/Timer.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "Timer.hpp"
#include "Utils.hpp"
void Timer::advanceTime()
{
int timeMs = getTimeMs();
if (timeMs - field_4 <= 1000)
{
if (timeMs - field_4 < 0)
{
field_4 = field_8 = timeMs;
}
}
else
{
int diff1 = timeMs - field_4;
int diff2 = timeMs - field_8;
field_C += ((float(diff1) / float(diff2)) - field_C) * 0.2f;
}
float diff = float(timeMs) / 1000.0f - field_0;
field_0 = float(timeMs) / 1000.0f;
float x1 = diff * field_C;
if (x1 > 1) x1 = 1;
if (x1 < 0) x1 = 0;
float x2 = field_20 + x1 * field_1C * field_10;
field_14 = int(x2);
field_20 = x2 - field_14;
field_18 = x2 - field_14;
if (field_14 > 10)
field_14 = 10;
}
Timer::Timer()
{
field_4 = field_8 = getTimeMs();
}

21
source/Base/Timer.hpp Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
class Timer
{
public:
Timer();
void advanceTime();
public:
float field_0 = 0;
int field_4 = 0;
int field_8 = 0;
float field_C = 1.0f;
float field_10 = 20.0f;
int field_14 = 0;
float field_18 = 0;
float field_1C = 1.0f;
float field_20 = 0;
};

83
source/Base/Util.cpp Normal file
View File

@@ -0,0 +1,83 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Util.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include "Util.hpp"
std::string Util::stringTrim(const std::string& str, const std::string& filter, bool a4, bool a5)
{
if (str.empty() || filter.empty())
return "";
// don't know what the fuck this does. TODO: clean this crap up
if (!a4 && !a5)
return "";
int v12, strIndex = 0, v13, v17, strLength = int(str.size()), filterLength = int(filter.size());
if (a4)
{
v12 = strLength - 1;
if (strLength > 0)
{
v17 = strLength - 1;
do
{
const void* v14 = memchr(filter.c_str(), str[strIndex], filterLength);
if (!v14)
{
v13 = strIndex;
v12 = v17;
goto label_12;
}
++strIndex;
} while (strIndex != strLength);
v12 = strLength - 1;
v13 = strIndex;
}
else v13 = 0;
}
else
{
if (a5)
{
v12 = strLength - 1;
strIndex = 0;
v13 = 0;
goto label_19;
}
return "";
}
label_12:
if (a5)
{
v13 = strIndex;
label_19:
while (v12 >= strIndex)
{
const void* v15 = memchr(filter.c_str(), str[strIndex], filterLength);
if (!v15)
break;
--v12;
}
}
return std::string(str, v13, v12 - 1 + strIndex);
}
std::string Util::stringTrim(const std::string& str)
{
return stringTrim(str, " \t\n\r", true, true);
}

72
source/Base/Util.hpp Normal file
View File

@@ -0,0 +1,72 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Util.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include <string>
#include <vector>
class Util
{
public:
static std::string stringTrim(const std::string &, const std::string &, bool, bool);
static std::string stringTrim(const std::string &);
template<typename T>
static bool remove(std::vector<T>& vec, const T& t)
{
auto iter = std::find(vec.begin(), vec.end(), t);
if (iter == vec.end())
return false;
vec.erase(iter);
return true;
}
template<typename T>
static int removeAll(std::vector<T>& vec, const std::vector<T>& toRemove)
{
int removed = 0;
for (auto rem : toRemove)
{
if (remove(vec, rem))
removed++;
}
return removed;
}
// @TODO: reverse the actual thing? This is something different, but I'm lazy. It uses std::string::replace
static void stringReplace(std::string& in, const std::string& what, const std::string& with)
{
//snippet from Zahlman's post on gamedev: http://www.gamedev.net/community/forums/topic.asp?topic_id=372125
size_t pos = 0;
size_t whatLen = what.length();
size_t withLen = with.length();
while ((pos = in.find(what, pos)) != std::string::npos)
{
in.replace(pos, whatLen, with);
pos += withLen;
}
}
static long hashCode(const std::string& str)
{
long result = 0;
for (auto chr : str)
{
result = result * 31 + chr;
}
return result;
}
};

218
source/Base/Utils.cpp Normal file
View File

@@ -0,0 +1,218 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Utils.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
// note: not an official file name
#include "Utils.hpp"
#ifdef _WIN32
#include <Windows.h>
// Why are we not using GetTickCount64()? It's simple -- getTimeMs has the exact same problem as using regular old GetTickCount.
#pragma warning(disable : 28159)
#else
#include <sys/time.h>
#endif
#include "compat/GL.hpp"
int g_TimeSecondsOnInit = 0;
const char* GetTerrainName()
{
return "terrain.png";
}
const char* GetItemsName()
{
return "gui/items.png";
}
const char* GetGUIBlocksName()
{
return "gui/gui_blocks.png";
}
// In regular mode, getTimeMs depends on getTimeS.
// In Win32 mode, it's vice versa. Cool
int getTimeMs();
float getTimeS()
{
#ifdef _WIN32
return float(getTimeMs()) / 1000.0f;
#else
// variant implemented by Mojang. This does not work on MSVC
timeval tv;
gettimeofday(&tv, NULL);
if (g_TimeSecondsOnInit == 0)
g_TimeSecondsOnInit = tv.tv_sec;
return float(tv.tv_sec - g_TimeSecondsOnInit) + float(tv.tv_usec) / 1000000.0f;
#endif
}
int getTimeMs()
{
#ifdef _WIN32
// just return GetTickCount
int time = GetTickCount();
if (g_TimeSecondsOnInit == 0)
g_TimeSecondsOnInit = time;
return time - g_TimeSecondsOnInit;
#else
return int(getTimeS() * 1000.0f);
#endif
}
time_t getRawTimeS()
{
#ifdef _WIN32
return time_t(GetTickCount() / 1000);
#else
timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec;
#endif
}
time_t getEpochTimeS()
{
return time(0);
}
#ifdef _WIN32
HINSTANCE g_hInstance = NULL;
HWND g_hWnd = NULL;
void SetInstance(HINSTANCE hinst)
{
g_hInstance = hinst;
}
HINSTANCE GetInstance()
{
return g_hInstance;
}
void SetHWND(HWND hwnd)
{
g_hWnd = hwnd;
}
HWND GetHWND()
{
return g_hWnd;
}
void CenterWindow(HWND hWnd)
{
RECT r, desk;
GetWindowRect(hWnd, &r);
GetWindowRect(GetDesktopWindow(), &desk);
int wa, ha, wb, hb;
wa = (r.right - r.left) / 2;
ha = (r.bottom - r.top) / 2;
wb = (desk.right - desk.left) / 2;
hb = (desk.bottom - desk.top) / 2;
SetWindowPos(hWnd, NULL, wb - wa, hb - ha, r.right - r.left, r.bottom - r.top, 0);
}
void EnableOpenGL(HWND hwnd, HDC* hDC, HGLRC* hRC)
{
PIXELFORMATDESCRIPTOR pfd;
int iFormat;
/* get the device context (DC) */
*hDC = GetDC(hwnd);
/* set the pixel format for the DC */
ZeroMemory(&pfd, sizeof(pfd));
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 16;
pfd.iLayerType = PFD_MAIN_PLANE;
iFormat = ChoosePixelFormat(*hDC, &pfd);
SetPixelFormat(*hDC, iFormat, &pfd);
/* create and enable the render context (RC) */
*hRC = wglCreateContext(*hDC);
wglMakeCurrent(*hDC, *hRC);
}
void DisableOpenGL(HWND hwnd, HDC hDC, HGLRC hRC)
{
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);
ReleaseDC(hwnd, hDC);
}
void sleepMs(int ms)
{
#ifdef _WIN32
Sleep(ms);
#else
usleep(1000 * ms);
#endif
}
#endif
void drawArrayVT(GLuint buffer, int count, int stride)
{
xglBindBuffer(GL_ARRAY_BUFFER, buffer);
glTexCoordPointer(2, GL_FLOAT, stride, (void*)12);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(3, GL_FLOAT, stride, nullptr);
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_TRIANGLES, 0, count);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
void drawArrayVTC(GLuint buffer, int count, int stride)
{
xglBindBuffer(GL_ARRAY_BUFFER, buffer);
glVertexPointer(3, GL_FLOAT, stride, nullptr);
glTexCoordPointer(2, GL_FLOAT, stride, (void*)12);
glColorPointer(4, GL_UNSIGNED_BYTE, stride, (void*)20);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_TRIANGLES, 0, count);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
float Max(float a, float b)
{
if (a < b)
a = b;
return a;
}

568
source/Base/Utils.hpp Normal file
View File

@@ -0,0 +1,568 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Utils.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include <ctime>
#include <cstdio> // have to include this to avoid it being included again later from being a problem
#include <cstdint>
#include <cassert>
#ifdef _WIN32
// @HACK: Include WinSock2.h also
#include <WinSock2.h>
#include <Windows.h>
#include <WS2tcpip.h>
#endif
#include "compat/AKeyCodes.hpp"
#ifndef ORIGINAL_CODE
// Enhancements
//#define ENH_ENTITY_SHADING // Allows shading of entities
//#define ENH_SHADE_HELD_TILES // Allows shading of the item in hand
//#define ENH_FIX_INVIS_STAIRS // Fixes a bug wherein a 16x16x16 chunk in the world that contains only stairs is invisible
//#define ENH_ALLOW_AO // Allows using the F4 key to toggle ambient occlusion (buggy)
//#define ENH_TRANSPARENT_HOTBAR // Allows the hotbar to be transparent. Due to a bug in the code, it is not.
//#define ENH_INSTA_BREAK // Allows instant breaking of blocks. @TODO: Fix the mode without this
//#define ENH_CAMERA_NO_PARTICLES // Hide particles from the view of a camera, such as smoke, that would otherwise render the resulting image useless.
//#define ENH_USE_JAVA_LIGHT_RAMP // Use Java Beta 1.3 light ramp instead of flawed PE one
//#define ENH_RUN_DAY_NIGHT_CYCLE // Allow the day/night cycle to run.
//#define ENH_ENABLE_9TH_SLOT // Enable the 9th hotbar slot, instead of it being a "..." placeholder
//#define ENH_USE_OWN_AO // Use own ambient occlusion engine - looks pretty much the same except it fixes the corners
//#define ENH_ADD_OPTIONS_PAUSE // Add an 'options' button in the pause menu
//#define ENH_EXTRA_ITEMS_IN_INV // Add extra items in a new 5th row in the inventory.
//#define ENH_HIGHLIGHT_BY_HOVER // Highlight buttons by hovering them instead of the usual way.
//#define ENH_ALLOW_SAND_GRAVITY // Allow sand to fall.
//#define ENH_USE_GUI_SCALE_2 // Use a 2x GUI scale instead of 3x. Looks better on PC
// Mods
//#define MOD_USE_FLAT_WORLD // Use a flat world instead of the regular world generation
//#define MOD_USE_BIGGER_SCREEN_SIZE // Use a bigger screen size instead of 854x480
//#define MOD_DONT_COLOR_GRASS // Don't give the top of grass tiles a different color. (like Classic)
// Tests
//#define TEST_DROPPED_ITEMS // Allow dropped items to be dropped and collected.
// Toggle Demo Mode
#define DEMO
#else
#define DEMO
#endif
// don't know where to declare these:
#ifndef MOD_USE_BIGGER_SCREEN_SIZE
#define C_DEFAULT_SCREEN_WIDTH (854)
#define C_DEFAULT_SCREEN_HEIGHT (480)
#else
#define C_DEFAULT_SCREEN_WIDTH (1280)
#define C_DEFAULT_SCREEN_HEIGHT (720)
#endif
#define C_MAX_TILES (256)
#define C_DEFAULT_PORT (19132)
#define C_MAX_CONNECTIONS (4) // pitiful
constexpr int C_MIN_X = -32000000, C_MAX_X = 32000000;
constexpr int C_MIN_Z = -32000000, C_MAX_Z = 32000000;
constexpr int C_MIN_Y = 0, C_MAX_Y = 128;
const char* GetTerrainName();
const char* GetItemsName();
const char* GetGUIBlocksName();
#ifdef ORIGINAL_CODE
#define C_TERRAIN_NAME "terrain.png"
#define C_ITEMS_NAME "gui/items.png"
#define C_BLOCKS_NAME "gui/gui_blocks.png"
#else
#define C_TERRAIN_NAME GetTerrainName()
#define C_ITEMS_NAME "gui/items.png"
#define C_BLOCKS_NAME "gui/gui_blocks.png"
#endif
#define C_MAX_CHUNKS_X (16)
#define C_MAX_CHUNKS_Z (16)
// 9 chunks around a player things will tick
#define C_TICK_DISTANCE_CHKS (9)
enum eTileID
{
TILE_AIR,
TILE_STONE,
TILE_GRASS,
TILE_DIRT,
TILE_STONEBRICK,
TILE_WOOD,
TILE_BEDROCK = 7,
TILE_WATER = 8,
TILE_WATER_CALM,
TILE_LAVA,
TILE_LAVA_CALM,
TILE_SAND = 12,
TILE_GRAVEL,
TILE_ORE_GOLD,
TILE_ORE_IRON,
TILE_ORE_COAL,
TILE_TREE_TRUNK = 17,
TILE_LEAVES,
TILE_GLASS = 20,
TILE_ORE_LAPIS,
TILE_SANDSTONE = 24,
TILE_CLOTH = 35,
TILE_FLOWER = 37,
TILE_ROSE,
TILE_MUSHROOM_1,
TILE_MUSHROOM_2,
TILE_BLOCK_GOLD = 41,
TILE_BLOCK_IRON,
TILE_STONESLAB_FULL,
TILE_STONESLAB_HALF,
TILE_BRICKS,
TILE_TNT,
TILE_OBSIDIAN = 49,
TILE_TORCH,
TILE_FIRE,
TILE_STAIRS_WOOD = 53,
TILE_ORE_EMERALD = 56,
TILE_BLOCK_EMERALD,
TILE_FARMLAND = 60,
TILE_DOOR_WOOD = 64,
TILE_LADDER,
TILE_STAIRS_STONE = 67,
TILE_DOOR_IRON = 71,
TILE_ORE_REDSTONE = 73,
TILE_ORE_REDSTONE_LIT,
TILE_TOPSNOW = 78,
TILE_ICE,
TILE_CLAY = 82,
TILE_REEDS,
TILE_INVISIBLE = 95,
TILE_CLOTH_00 = 101,
TILE_CLOTH_10,
TILE_CLOTH_20,
TILE_CLOTH_30,
TILE_CLOTH_40,
TILE_CLOTH_50,
TILE_CLOTH_60,
TILE_CLOTH_70,
TILE_CLOTH_01,
TILE_CLOTH_11,
TILE_CLOTH_21,
TILE_CLOTH_31,
TILE_CLOTH_41,
TILE_CLOTH_51,
TILE_CLOTH_61,
ITEM_SHOVEL_IRON = 256,
ITEM_PICKAXE_IRON,
ITEM_HATCHET_IRON,
ITEM_FLINT_AND_STEEL,
ITEM_APPLE,
ITEM_BOW,
ITEM_ARROW,
ITEM_COAL,
ITEM_EMERALD,
ITEM_INGOT_IRON,
ITEM_INGOT_GOLD,
ITEM_SWORD_IRON,
ITEM_SWORD_WOOD,
ITEM_SHOVEL_WOOD,
ITEM_PICKAXE_WOOD,
ITEM_HATCHET_WOOD,
ITEM_SWORD_STONE,
ITEM_SHOVEL_STONE,
ITEM_PICKAXE_STONE,
ITEM_HATCHET_STONE,
ITEM_SWORD_EMERALD,
ITEM_SHOVEL_EMERALD,
ITEM_PICKAXE_EMERALD,
ITEM_HATCHET_EMERALD,
ITEM_STICK,
ITEM_BOWL,
ITEM_STEW_MUSHROOM,
ITEM_SWORD_GOLD,
ITEM_SHOVEL_GOLD,
ITEM_PICKAXE_GOLD,
ITEM_HATCHET_GOLD,
ITEM_STRING,
ITEM_FEATHER,
ITEM_SULPHUR,
ITEM_HOE_WOOD,
ITEM_HOE_STONE,
ITEM_HOE_IRON,
ITEM_HOE_EMERALD,
ITEM_HOE_GOLD,
ITEM_SEEDS,
ITEM_WHEAT,
ITEM_BREAD,
ITEM_HELMET_CLOTH,
ITEM_CHESTPLATE_CLOTH,
ITEM_LEGGINGS_CLOTH,
ITEM_BOOTS_CLOTH,
ITEM_HELMET_CHAIN,
ITEM_CHESTPLATE_CHAIN,
ITEM_LEGGINGS_CHAIN,
ITEM_BOOTS_CHAIN,
ITEM_HELMET_IRON,
ITEM_CHESTPLATE_IRON,
ITEM_LEGGINGS_IRON,
ITEM_BOOTS_IRON,
ITEM_HELMET_EMERALD,
ITEM_CHESTPLATE_EMERALD,
ITEM_LEGGINGS_EMERALD,
ITEM_BOOTS_EMERALD,
ITEM_HELMET_GOLD,
ITEM_CHESTPLATE_GOLD,
ITEM_LEGGINGS_GOLD,
ITEM_BOOTS_GOLD,
ITEM_FLINT,
ITEM_PORKCHOP_RAW,
ITEM_PORKCHOP_COOKED,
ITEM_PAINTING,
ITEM_APPLE_GOLD,
ITEM_SIGN,
ITEM_DOOR_WOOD,
ITEM_BUCKET,
ITEM_BUCKET_WATER,
ITEM_BUCKET_LAVA,
ITEM_MINECART,
ITEM_SADDLE,
ITEM_DOOR_IRON,
ITEM_REDSTONE,
ITEM_SNOWBALL,
ITEM_BOAT,
ITEM_LEATHER,
ITEM_BUCKET_MILK,
ITEM_BRICK,
ITEM_CLAY,
ITEM_REEDS,
ITEM_PAPER,
ITEM_BOOK,
ITEM_SLIME_BALL,
ITEM_MINECART_CHEST,
ITEM_MINECART_FURNACE,
ITEM_EGG,
ITEM_COMPASS,
ITEM_FISHING_ROD,
ITEM_CLOCK,
ITEM_YELLOW_DUST,
ITEM_FISH_RAW,
ITEM_FISH_COOKED,
ITEM_DYE_POWDER,
ITEM_BONE,
ITEM_SUGAR,
ITEM_CAKE,
ITEM_BED,
ITEM_DIODE,
ITEM_COOKIE,
ITEM_RECORD_01,
ITEM_RECORD_02,
ITEM_CAMERA = 456,
};
enum // Textures
{
TEXTURE_GRASS_TOP = 0,
TEXTURE_STONE,
TEXTURE_DIRT,
TEXTURE_GRASS_SIDE,
TEXTURE_PLANKS,
TEXTURE_STONE_SLAB_SIDE,
TEXTURE_STONE_SLAB_TOP,
TEXTURE_BRICKS,
TEXTURE_TNT_SIDE,
TEXTURE_TNT_TOP,
TEXTURE_TNT_BOTTOM,
TEXTURE_COBWEB,
TEXTURE_ROSE,
TEXTURE_FLOWER,
TEXTURE_WATER_STATIC,
TEXTURE_SAPLING,
TEXTURE_STONEBRICK,
TEXTURE_BEDROCK,
TEXTURE_SAND,
TEXTURE_GRAVEL,
TEXTURE_LOG_SIDE,
TEXTURE_LOG_TOP,
TEXTURE_IRON,
TEXTURE_GOLD,
TEXTURE_EMERALD,
TEXTURE_CHEST_ONE_TOP,
TEXTURE_CHEST_ONE_SIDE,
TEXTURE_CHEST_ONE_FRONT,
TEXTURE_MUSHROOM_RED,
TEXTURE_MUSHROOM_BROWN,
TEXTURE_NONE30,
TEXTURE_FIRE1,
TEXTURE_ORE_GOLD,
TEXTURE_ORE_IRON,
TEXTURE_ORE_COAL,
TEXTURE_BOOKSHELF,
TEXTURE_MOSSY_STONE,
TEXTURE_OBSIDIAN,
TEXTURE_OBSIDIAN_CRYING,
TEXTURE_NONE39,
TEXTURE_NONE40,
TEXTURE_CHEST_TWO_FRONT_LEFT,
TEXTURE_CHEST_TWO_FRONT_RIGHT,
TEXTURE_WORKBENCH_TOP,
TEXTURE_FURNACE_FRONT,
TEXTURE_FURNACE_SIDE,
TEXTURE_DISPENSER_SIDE,
TEXTURE_FIRE2,
TEXTURE_SPONGE,
TEXTURE_GLASS,
TEXTURE_ORE_EMERALD,
TEXTURE_ORE_RED_STONE,
TEXTURE_LEAVES_TRANSPARENT,
TEXTURE_LEAVES_OPAQUE,
TEXTURE_NONE54,
TEXTURE_NONE55,
TEXTURE_NONE56,
TEXTURE_CHEST_TWO_BACK_LEFT,
TEXTURE_CHEST_TWO_BACK_RIGHT,
TEXTURE_WORKBENCH_SIDE_1,
TEXTURE_WORKBENCH_SIDE_2,
TEXTURE_FURNACE_LIT,
TEXTURE_FURNACE_TOP,
TEXTURE_NONE63,
TEXTURE_CLOTH_64,
TEXTURE_SPAWNER,
TEXTURE_SNOW,
TEXTURE_ICE,
TEXTURE_GRASS_SIDE_SNOW,
TEXTURE_CACTUS_TOP,
TEXTURE_CACTUS_SIDE,
TEXTURE_CACTUS_BOTTOM,
TEXTURE_CLAY,
TEXTURE_REEDS,
TEXTURE_JUKEBOX_SIDE,
TEXTURE_JUKEBOX_TOP,
TEXTURE_NONE76,
TEXTURE_NONE77,
TEXTURE_NONE78,
TEXTURE_NONE79,
TEXTURE_TORCH_LIT,
TEXTURE_DOOR_TOP,
TEXTURE_DOOR_IRON_TOP,
TEXTURE_LADDER,
TEXTURE_NONE84,
TEXTURE_NONE85,
TEXTURE_FARMLAND,
TEXTURE_FARMLAND_DRY,
TEXTURE_WHEAT_0,
TEXTURE_WHEAT_1,
TEXTURE_WHEAT_2,
TEXTURE_WHEAT_3,
TEXTURE_WHEAT_4,
TEXTURE_WHEAT_5,
TEXTURE_WHEAT_6,
TEXTURE_WHEAT_7,
TEXTURE_LEVER,
TEXTURE_DOOR_BOTTOM,
TEXTURE_DOOR_IRON_BOTTOM,
TEXTURE_TORCH_RED_STONE,
TEXTURE_NONE100,
TEXTURE_NONE101,
TEXTURE_PUMPKIN_TOP,
TEXTURE_BLOODSTONE,
TEXTURE_SOULSAND,
TEXTURE_GLOWSTONE,
TEXTURE_NONE106,
TEXTURE_NONE107,
TEXTURE_NONE108,
TEXTURE_NONE109,
TEXTURE_NONE110,
TEXTURE_NONE111,
TEXTURE_RAIL_CURVED,
TEXTURE_CLOTH_112,
TEXTURE_CLOTH_113,
TEXTURE_TORCH_RED_STONE_OFF,
TEXTURE_LOG_SPRUCE,
TEXTURE_LOG_BIRCH,
TEXTURE_PUMPKIN_SIDE,
TEXTURE_PUMPKIN_FACE,
TEXTURE_PUMPKIN_FACE_LIT,
TEXTURE_CAKE_TOP,
TEXTURE_CAKE_SIDE,
TEXTURE_CAKE_SIDE_BIT,
TEXTURE_CAKE_BOTTOM,
TEXTURE_NONE125,
TEXTURE_NONE126,
TEXTURE_NONE127,
TEXTURE_ORE_LAPIS = 160,
TEXTURE_SANDSTONE_TOP = 176,
TEXTURE_SANDSTONE_SIDE = 192,
TEXTURE_WATER = 205,
TEXTURE_SANDSTONE_BOTTOM = 208,
TEXTURE_LAVA = 237,
TEXTURE_LAVA_PLACEHOLDER = 255,
};
enum eRenderShape
{
SHAPE_NONE = -1,
SHAPE_SOLID,
SHAPE_CROSS,
SHAPE_TORCH,
SHAPE_FIRE,
SHAPE_WATER,
SHAPE_UNK5,
SHAPE_UNK6,
SHAPE_DOOR,
SHAPE_LADDER,
SHAPE_UNK9,
SHAPE_STAIRS,
};
enum eRenderLayer
{
LAYER_OPAQUE,
LAYER_ALPHA
};
enum eDirection
{
DIR_YNEG,
DIR_YPOS,
DIR_ZNEG, // North
DIR_ZPOS, // South
DIR_XNEG, // West
DIR_XPOS, // East
};
struct ChunkPos
{
int x = 0, z = 0;
ChunkPos() {}
ChunkPos(int _x, int _z) : x(_x), z(_z) {}
bool operator<(const ChunkPos& b) const
{
if (x != b.x)
return x < b.x;
return z < b.z;
}
};
struct Pos
{
int x = 0, y = 0, z = 0;
Pos() {}
Pos(int _x, int _y, int _z) : x(_x), y(_y), z(_z) {}
};
// @NOTE: Duplicate?
struct TilePos
{
int x = 0, y = 0, z = 0;
TilePos() {}
TilePos(int _x, int _y, int _z) : x(_x), y(_y), z(_z) {}
bool operator<(const TilePos& b) const
{
if (x != b.x)
return x < b.x;
if (y != b.y)
return y < b.y;
return z < b.z;
}
};
#define SAFE_DELETE(ptr) do { if (ptr) delete ptr; } while (0)
#define SAFE_DELETE_ARRAY(ptr) do { if (ptr) delete[] ptr; } while (0)
typedef uint8_t TileID;
// functions from Mojang
time_t getEpochTimeS();
time_t getRawTimeS();
float getTimeS();
int getTimeMs();
float Max(float a, float b);
void sleepMs(int ms);
// @NOTE: This is inlined.
constexpr float Lerp(float a, float b, float progress)
{
return a + progress * (b - a);
}
// things that we added:
#ifndef ORIGINAL_CODE
void LogMsg(const char* fmt, ...); // not part of actual Minecraft (they use printf), but useful
void LogMsgNoCR(const char* fmt, ...); // not part of actual Minecraft (they use printf), but useful
#ifdef printf
#error "printf is defined. Why?"
#endif
#define printf LogMsgNoCR
#define puts(str) LogMsg("%s", str);
#ifdef _WIN32
HINSTANCE GetInstance();
HWND GetHWND();
void CenterWindow(HWND hWnd);
void EnableOpenGL(HWND hwnd, HDC*, HGLRC*);
void DisableOpenGL(HWND, HDC, HGLRC);
void SetInstance(HINSTANCE hinst);
void SetHWND(HWND hwnd);
#endif
#else
#define LogMsg(...)
#define LogMsgNoCR(...)
#endif

91
source/Base/Vec3.cpp Normal file
View File

@@ -0,0 +1,91 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Vec3.cpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#include "Vec3.hpp"
Vec3::Vec3() {}
Vec3::Vec3(float x, float y, float z)
{
this->x = x;
this->y = y;
this->z = z;
}
Vec3 Vec3::normalize()
{
float dist = Mth::sqrt(x * x + y * y + z * z);
if (dist < 0.0001f)
return Vec3(0, 0, 0);
return Vec3(x / dist, y / dist, z / dist);
}
bool Vec3::clipX(const Vec3& a2, float a3, Vec3& a4) const
{
float crap = a2.x - this->x;
if (crap * crap < 0.000001f)
return false;
float crap2 = (a3 - this->x) / crap;
if (crap2 < 0.0f || crap2 > 1.0f)
return false;
a4.x = x + (a2.x - x) * crap2;
a4.y = y + (a2.y - y) * crap2;
a4.z = z + (a2.z - z) * crap2;
return true;
}
bool Vec3::clipY(const Vec3& a2, float a3, Vec3& a4) const
{
float crap = a2.y - this->y;
if (crap * crap < 0.000001f)
return false;
float crap2 = (a3 - this->y) / crap;
if (crap2 < 0.0f || crap2 > 1.0f)
return false;
a4.x = x + (a2.x - x) * crap2;
a4.y = y + (a2.y - y) * crap2;
a4.z = z + (a2.z - z) * crap2;
return true;
}
bool Vec3::clipZ(const Vec3& a2, float a3, Vec3& a4) const
{
float crap = a2.z - this->z;
if (crap * crap < 0.000001f)
return false;
float crap2 = (a3 - this->z) / crap;
if (crap2 < 0.0f || crap2 > 1.0f)
return false;
a4.x = x + (a2.x - x) * crap2;
a4.y = y + (a2.y - y) * crap2;
a4.z = z + (a2.z - z) * crap2;
return true;
}
float Vec3::distanceToSqr(const Vec3& b) const
{
float xd = (x-b.x);
float yd = (y-b.y);
float zd = (z-b.z);
return xd * xd + yd * yd + zd * zd;
}
float Vec3::distanceTo(const Vec3& b) const
{
return Mth::sqrt(distanceToSqr(b));
}

81
source/Base/Vec3.hpp Normal file
View File

@@ -0,0 +1,81 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
Vec3.hpp
The following code is licensed under the following license:
< no license yet :( >
********************************************************************/
#pragma once
#include "Mth.hpp"
class Vec3
{
public:
float x = 0;
float y = 0;
float z = 0;
public:
bool clipX(const Vec3& a2, float a3, Vec3& a4) const;
bool clipY(const Vec3& a2, float a3, Vec3& a4) const;
bool clipZ(const Vec3& a2, float a3, Vec3& a4) const;
Vec3 normalize();
// this constructor is nice to have, but it's probably inlined
Vec3();
Vec3(float x, float y, float z);
// these are likely inlined
float distanceTo(const Vec3& b) const;
float distanceToSqr(const Vec3& b) const;
// these are also likely inlined, but I'll declare them in the header
Vec3 operator+(const Vec3& b)
{
return Vec3(x + b.x, y + b.y, z + b.z);
}
Vec3 operator-(const Vec3& b)
{
return Vec3(x - b.x, y - b.y, z - b.z);
}
void operator+=(const Vec3& b)
{
x += b.x;
y += b.y;
z += b.z;
}
void operator-=(const Vec3& b)
{
(*this) += -b;
}
void operator*=(float f)
{
x *= f;
y *= f;
z *= f;
}
Vec3 operator-() const
{
return Vec3(-x, -y, -z);
}
Vec3 operator*(float f) const
{
return Vec3(f * x, f * y, f * z);
}
Vec3 translate(float tx, float ty, float tz) const
{
return Vec3(x + tx, y + ty, z + tz);
}
};

View File

@@ -0,0 +1,31 @@
#include "AvailableGamesList.hpp"
AvailableGamesList::AvailableGamesList(Minecraft* a, int b, int c, int d, int e, int f) :
ScrolledSelectionList(a, b, c, d, e, f)
{
}
int AvailableGamesList::getNumberOfItems()
{
return int(m_games.size());
}
bool AvailableGamesList::isSelectedItem(int i)
{
return m_selectedIndex == i;
}
void AvailableGamesList::renderBackground()
{
}
void AvailableGamesList::renderItem(int idx, int x, int y, int width, Tesselator& t)
{
drawString(m_pMinecraft->m_pFont, std::string(m_games[idx].m_name.C_String()), x, y + 2, 0xFFFFA0);
drawString(m_pMinecraft->m_pFont, std::string(m_games[idx].m_address.ToString()), x, y + 16, 0xFFFFA0);
}
void AvailableGamesList::selectItem(int index, bool b)
{
m_selectedIndex = index;
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include "ScrolledSelectionList.hpp"
#include "PingedCompatibleServer.hpp"
class AvailableGamesList : public ScrolledSelectionList
{
public:
AvailableGamesList(Minecraft*, int, int, int, int, int);
int getNumberOfItems() override;
bool isSelectedItem(int i) override;
void renderBackground() override;
void renderItem(int, int, int, int, Tesselator& t) override;
void selectItem(int, bool) override;
public:
int m_selectedIndex;
std::vector<PingedCompatibleServer> m_games;
};

89
source/GUI/Button.cpp Normal file
View File

@@ -0,0 +1,89 @@
#include "Button.hpp"
Button::Button(int a2, int xPos, int yPos, int btnWidth, int btnHeight, const std::string& text)
{
field_30 = a2;
m_xPos = xPos;
m_yPos = yPos;
field_18 = text;
m_width = btnWidth;
m_height = btnHeight;
}
Button::Button(int a2, int xPos, int yPos, const std::string& text)
{
field_30 = a2;
m_xPos = xPos;
m_yPos = yPos;
field_18 = text;
m_width = 200;
m_height = 24;
}
Button::Button(int a2, const std::string& text)
{
field_30 = a2;
field_18 = text;
m_width = 200;
m_height = 24;
}
bool Button::clicked(Minecraft* pMinecraft, int xPos, int yPos)
{
if (!m_bEnabled) return false;
if (xPos < m_xPos) return false;
if (yPos < m_yPos) return false;
if (xPos >= m_xPos + m_width) return false;
if (yPos >= m_yPos + m_height) return false;
return true;
}
int Button::getYImage(bool bHovered)
{
if (!m_bEnabled) return 0;
if (bHovered) return 2;
return 1;
}
void Button::released(int xPos, int yPos)
{
}
void Button::renderBg(Minecraft*, int, int)
{
}
void Button::render(Minecraft* pMinecraft, int xPos, int yPos)
{
if (!m_bVisible) return;
#ifdef ENH_HIGHLIGHT_BY_HOVER
field_36 = clicked(pMinecraft, xPos, yPos);
#endif
Font* pFont = pMinecraft->m_pFont;
Textures* pTexs = pMinecraft->m_pTextures;
pTexs->loadAndBindTexture("gui/gui.png");
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
int iYPos = 20 * getYImage(field_36) + 46;
blit(m_xPos, m_yPos, 0, iYPos, m_width / 2, m_height, 0, 20);
blit(m_xPos + m_width / 2, m_yPos, 200 - m_width / 2, iYPos, m_width / 2, m_height, 0, 20);
renderBg(pMinecraft, xPos, yPos);
int textColor;
if (!m_bEnabled)
textColor = int(0xFFA0A0A0U);
else if (field_36)
textColor = int(0xFFFFA0U);
else
textColor = int(0xE0E0E0U);
drawCenteredString(pFont, field_18, m_xPos + m_width / 2, m_yPos + (m_height - 8) / 2, textColor);
}

38
source/GUI/Button.hpp Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include "GuiComponent.hpp"
#include "Minecraft.hpp"
class Screen;
class Button : public GuiComponent
{
public:
Button(int, int x, int y, int width, int height, const std::string&);
Button(int, int x, int y, const std::string&);
Button(int, const std::string&);
// I can't possibly explain why Minecraft is referenced here
bool clicked(Minecraft*, int xPos, int yPos);
int getYImage(bool bHovered);
void released(int xPos, int yPos);
void renderBg(Minecraft*, int, int);
void render(Minecraft*, int xPos, int yPos);
public:
int m_width = 0;
int m_height = 0;
int m_xPos = 0;
int m_yPos = 0;
std::string field_18 = "";
int field_30;
bool m_bEnabled = true;
bool m_bVisible = true;
bool field_36 = false;
#ifndef ORIGINAL_CODE
int m_lastX = 0;
int m_lastY = 0;
#endif
};

402
source/GUI/Gui.cpp Normal file
View File

@@ -0,0 +1,402 @@
#include "Minecraft.hpp"
#include "IngameBlockSelectionScreen.hpp"
#include "ItemRenderer.hpp"
#ifdef _WIN32
#pragma warning(disable : 4244)
#endif
#ifdef ENH_USE_GUI_SCALE_2
float Gui::InvGuiScale = 0.5f;
#else
float Gui::InvGuiScale = 0.333333f;
#endif
Gui::Gui(Minecraft* pMinecraft)
{
m_pMinecraft = pMinecraft;
xglGenBuffers(1, &m_renderChunk.field_0);
}
void Gui::addMessage(const std::string& s)
{
std::string str = s;
while (m_pMinecraft->m_pFont->width(str) > 320)
{
int i = 2;
for (; i < int(str.size()); i++)
{
std::string sstr = str.substr(0, i);
// this sucks
if (m_pMinecraft->m_pFont->width(sstr) > 320)
break;
}
std::string sstr = str.substr(0, i - 1);
addMessage(sstr);
str = str.substr(i - 1);
}
if (m_guiMessages.size() > 50)
{
m_guiMessages.erase(m_guiMessages.end());
}
m_guiMessages.insert(m_guiMessages.begin(), GuiMessage(str, 0));
}
void Gui::setNowPlaying(const std::string& str)
{
field_A00 = "Now playing: " + str;
field_A18 = 60;
field_A1C = true;
}
void Gui::renderVignette(float a2, int a3, int a4)
{
a2 = 1.0f - a2;
if (a2 > 1.0f)
a2 = 1.0f;
if (a2 < 0.0f)
a2 = 0.0f;
field_A20 += ((a2 - field_A20) * 0.01f);
glDisable(GL_DEPTH_TEST);
glDepthMask(false);
glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
glColor4f(field_A20, field_A20, field_A20, 1.0f);
//! @BUG: No misc/vignette.png to be found in the original.
//! This function is unused anyways
m_pMinecraft->m_pTextures->loadAndBindTexture("misc/vignette.png");
Tesselator& t = Tesselator::instance;
t.begin();
t.vertexUV(0.0f, a4, -90.0f, 0.0f, 1.0f);
t.vertexUV(a3, a4, -90.0f, 1.0f, 1.0f);
t.vertexUV(a3, 0.0f, -90.0f, 1.0f, 0.0f);
t.vertexUV(0.0f, 0.0f, -90.0f, 0.0f, 0.0f);
t.draw();
glDepthMask(true);
glEnable(GL_DEPTH_TEST);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void Gui::inventoryUpdated()
{
field_A3C = true;
}
void Gui::render(float f, bool bHaveScreen, int mouseX, int mouseY)
{
Minecraft* m = m_pMinecraft;
m->m_pGameRenderer->setupGuiScreen();
if (!m->m_pLevel || !m->m_pLocalPlayer)
return;
field_4 = -90.0f;
#ifndef ENH_TRANSPARENT_HOTBAR
glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
#else
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
#endif
m->m_pTextures->loadAndBindTexture("gui/gui.png");
Inventory* pInventory = m->m_pLocalPlayer->m_pInventory;
field_4 = -90.0f;
int width = Minecraft::width * InvGuiScale,
height = Minecraft::height * InvGuiScale;
#ifdef ENH_TRANSPARENT_HOTBAR
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#endif
// hotbar
int cenX = width / 2;
blit(cenX - 182 / 2, height - 22, 0, 0, 182, 22, 0, 0);
// selection mark
blit(cenX - 92 + 20 * pInventory->m_SelectedHotbarSlot, height - 23, 0, 22, 24, 22, 0, 0);
m->m_pTextures->loadAndBindTexture("gui/icons.png");
#ifndef ENH_TRANSPARENT_HOTBAR
glEnable(GL_BLEND);
#endif
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
// crosshair
blit(cenX - 8, height / 2 - 8, 0, 0, 16, 16, 0, 0);
glDisable(GL_BLEND);
if (m_pMinecraft->m_pGameMode->canHurtPlayer())
{
LocalPlayer* pLP = m_pMinecraft->m_pLocalPlayer;
// why??
m_random.init_genrand(312871 * field_9FC);
int emptyHeartX = 16;
bool b1 = false;
if (pLP->field_B8 < 10)
{
b1 = pLP->field_B8 / 3 % 2;
emptyHeartX += 9 * b1;
}
// @NOTE: At the default scale, this would go off screen.
int heartX = cenX - 191; // why?
int heartYStart = height - 10;
//@NOTE: Alpha-style health UI. I'll probably remove this on release.
#ifndef ORIGINAL_CODE
heartX = cenX - 91;
heartYStart = height - 32;
#endif
int playerHealth = pLP->m_health;
for (int healthNo = 1; healthNo <= C_MAX_MOB_HEALTH; healthNo += 2)
{
int heartY = heartYStart;
if (playerHealth <= 4 && m_random.genrand_int32() % 2)
heartY++;
blit(heartX, heartY, emptyHeartX, 0, 9, 9, 0, 0);
if (b1)
{
if (healthNo < pLP->field_100)
blit(heartX, heartY, 70, 0, 9, 9, 0, 0);
else if (healthNo == pLP->field_100)
blit(heartX, heartY, 79, 0, 9, 9, 0, 0);
}
if (healthNo < playerHealth)
blit(heartX, heartY, 52, 0, 9, 9, 0, 0);
else if (healthNo == playerHealth)
blit(heartX, heartY, 61, 0, 9, 9, 0, 0);
heartX += 8;
}
if (m->m_pLocalPlayer->isUnderLiquid(Material::water))
{
int breathRaw = m->m_pLocalPlayer->field_BC;
int breathFull = int(ceilf((float(breathRaw - 2) * 10.0f) / 300.0f));
int breathMeter = int(ceilf((float(breathRaw) * 10.0f) / 300.0f)) - breathFull;
int bubbleX = cenX - 191;
int bubbleY = height - 19;
#ifndef ORIGINAL_CODE
bubbleX = cenX - 91;
bubbleY = height - 41;
#endif
//@NOTE: Not sure this works as it should
for (int bubbleNo = 0; bubbleNo < breathFull + breathMeter; bubbleNo++)
{
if (bubbleNo < breathFull)
blit(bubbleX, bubbleY, 16, 18, 9, 9, 0, 0);
else
blit(bubbleX, bubbleY, 25, 18, 9, 9, 0, 0);
bubbleX += 8;
}
}
}
m->m_pTextures->loadAndBindTexture("gui/gui_blocks.png");
Tesselator& t = Tesselator::instance;
t.begin();
// if we aren't trying to build the real deal, don't do this. It only does worse
#ifdef ORIGINAL_CODE
t.voidBeginAndEndCalls(true); // to ensure that Gui::renderSlot doesn't end() our tesselator right away?
#endif
int slotX = cenX - 88;
#ifdef ENH_ENABLE_9TH_SLOT
#define HOTBAR_DIFF 0
#else
#define HOTBAR_DIFF 1
#endif
for (int i = 0; i < C_MAX_HOTBAR_ITEMS - HOTBAR_DIFF; i++)
{
renderSlot(i, slotX, height - 19, f);
slotX += 20;
}
#undef HOTBAR_DIFF
field_A3C = false;
#ifdef ORIGINAL_CODE
t.voidBeginAndEndCalls(false);
#endif
t.draw();
// blit the "more items" button
#ifndef ENH_ENABLE_9TH_SLOT
m->m_pTextures->loadAndBindTexture(C_TERRAIN_NAME);
blit(cenX + 72, height - 19, 208, 208, 16, 16, 0, 0);
#endif
// render messages
int topEdge = height - 49;
for (auto& msg : m_guiMessages)
{
if (msg.field_18 > 199)
continue;
int bkgdColor = 0x7F000000, textColor = 0xFFFFFFFF;
float fade = 10.0f * (1.0f - (float(msg.field_18) / 200.0f));
if (fade <= 0.0f)
continue;
if (fade < 1.0f)
{
int x = int(fade * fade * 255.0f);
if (x == 0)
continue;
bkgdColor = (x / 2) << 24;
textColor = (x << 24) + 0xFFFFFF;
}
fill(2, topEdge, 322, topEdge + 9, bkgdColor);
glEnable(GL_BLEND);
m->m_pFont->drawShadow(msg.msg, 2, topEdge + 1, textColor);
topEdge -= 9;
}
glDisable(GL_BLEND);
}
void Gui::tick()
{
for (auto& msg : m_guiMessages)
{
msg.field_18++;
}
}
void Gui::renderSlot(int slot, int x, int y, float f)
{
int itemID = m_pMinecraft->m_pLocalPlayer->m_pInventory->getSelectionSlotItemId(slot);
if (itemID < 0)
return;
ItemInstance inst(Item::items[itemID], 1, 0);
ItemRenderer::renderGuiItem(m_pMinecraft->m_pFont, m_pMinecraft->m_pTextures, &inst, x, y, true);
}
int Gui::getSlotIdAt(int mouseX, int mouseY)
{
int scaledY = int(InvGuiScale * mouseY);
int scaledHeight = int(InvGuiScale * Minecraft::height);
if (scaledY >= scaledHeight)
return -1;
if (scaledY < scaledHeight-19)
return -1;
int slotX = (int(InvGuiScale * mouseX) - int(InvGuiScale * Minecraft::width) / 2 + 88 + 20) / 20;
//@NOTE: Why not just -88?
if (slotX >= 0)
slotX--;
if (slotX > 8)
slotX = -1;
return slotX;
}
bool Gui::isInside(int mouseX, int mouseY)
{
return getSlotIdAt(mouseX, mouseY) != -1;
}
void Gui::handleClick(int clickID, int mouseX, int mouseY)
{
if (clickID != 1)
return;
int slot = getSlotIdAt(mouseX, mouseY);
if (slot == -1)
return;
#ifndef ENH_ENABLE_9TH_SLOT
if (slot == 8)
{
m_pMinecraft->setScreen(new IngameBlockSelectionScreen);
}
else
#endif
{
m_pMinecraft->m_pLocalPlayer->m_pInventory->selectSlot(slot);
}
}
void Gui::handleKeyPressed(int keyCode)
{
switch (keyCode)
{
case AKEYCODE_BUTTON_Y:
{
m_pMinecraft->setScreen(new IngameBlockSelectionScreen);
break;
}
case AKEYCODE_BACK:
{
int* slot = &m_pMinecraft->m_pLocalPlayer->m_pInventory->m_SelectedHotbarSlot;
#ifdef ENH_ENABLE_9TH_SLOT
#define MAX_ITEMS (C_MAX_HOTBAR_ITEMS - 2)
#else
#define MAX_ITEMS (C_MAX_HOTBAR_ITEMS - 3)
#endif
//@HUH: for whatever reason, it ignores the 7th item
if (*slot <= MAX_ITEMS)
(*slot)++;
break;
}
case AKEYCODE_BUTTON_X:
{
int* slot = &m_pMinecraft->m_pLocalPlayer->m_pInventory->m_SelectedHotbarSlot;
if (*slot > 0)
(*slot)--;
break;
}
}
}

55
source/GUI/Gui.hpp Normal file
View File

@@ -0,0 +1,55 @@
#pragma once
#include "GuiComponent.hpp"
#include "Minecraft.hpp"
#include "Random.hpp"
#include "Utils.hpp"
class Minecraft; // in case we're included from Minecraft.hpp
struct GuiMessage
{
std::string msg;
int field_18;
GuiMessage(const std::string& x, int a) : msg(x), field_18(a) {}
};
class Gui : public GuiComponent
{
public:
Gui(Minecraft* pMinecraft);
void addMessage(const std::string& str);
void inventoryUpdated();
void renderVignette(float, int, int);
void setNowPlaying(const std::string& str);
void render(float f, bool bHaveScreen, int mouseX, int mouseY);
void tick();
void renderSlot(int slot, int x, int y, float f);
int getSlotIdAt(int mx, int my);
bool isInside(int mx, int my);
void handleClick(int id, int mx, int my);
void handleKeyPressed(int keyCode);
public:
static float InvGuiScale;
public:
float field_8 = 0;
std::string field_C = "";
std::vector<GuiMessage> m_guiMessages;
int field_24 = 0;
int field_28 = 0;
int field_2C = 0;
Random m_random;
Minecraft* m_pMinecraft;
int field_9FC = 0;
std::string field_A00 = "";
int field_A18 = 0;
bool field_A1C = false;
float field_A20 = 1.0f;
RenderChunk m_renderChunk;
bool field_A3C = true;
};

View File

@@ -0,0 +1,82 @@
#include "GuiComponent.hpp"
#ifdef _WIN32
#pragma warning (disable : 4244)
#endif
void GuiComponent::blit(int dx, int dy, int sx, int sy, int tw, int th, int sw, int sh)
{
auto& t = Tesselator::instance;
if (!sh) sh = th;
if (!sw) sw = tw;
int width = sw, height = sh;
t.begin();
t.vertexUV(dx, dy + th, field_4, float(sx) / 256.0f, float(sy + sh) / 256.0f);
t.vertexUV(dx + tw, dy + th, field_4, float(sx + sw) / 256.0f, float(sy + sh) / 256.0f);
t.vertexUV(dx + tw, dy, field_4, float(sx + sw) / 256.0f, float(sy) / 256.0f);
t.vertexUV(dx, dy, field_4, float(sx) / 256.0f, float(sy) / 256.0f);
t.draw();
}
void GuiComponent::drawCenteredString(Font* pFont, const std::string& str, int cx, int cy, int color)
{
int width = pFont->width(str);
int height = pFont->height(str);
pFont->drawShadow(str, cx - width / 2, cy - height / 2, color);
}
void GuiComponent::drawString(Font* pFont, const std::string& str, int cx, int cy, int color)
{
pFont->drawShadow(str, cx, cy, color);
}
void GuiComponent::fill(int a2, int a3, int a4, int a5, int a6)
{
glEnable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(float(GET_RED(a6)) / 255.0f, float(GET_GREEN(a6)) / 255.0f, float(GET_BLUE(a6)) / 255.0f, float(GET_ALPHA(a6)) / 255.0f);
Tesselator& t = Tesselator::instance;
t.begin();
t.vertex(a2, a5, 0.0f);
t.vertex(a4, a5, 0.0f);
t.vertex(a4, a3, 0.0f);
t.vertex(a2, a3, 0.0f);
t.draw();
glEnable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
}
void GuiComponent::fillGradient(int a2, int a3, int a4, int a5, int a6, int a7)
{
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glDisable(GL_ALPHA_TEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glShadeModel(GL_SMOOTH);
Tesselator& t = Tesselator::instance;
t.begin();
// note: for some stupid reason OG uses the float overload.
t.color(float(GET_RED(a6)) / 255.0f, float(GET_GREEN(a6)) / 255.0f, float(GET_BLUE(a6)) / 255.0f, float(GET_ALPHA(a6)) / 255.0f);
t.vertex(a2, a5, 0.0f);
t.vertex(a4, a5, 0.0f);
t.color(float(GET_RED(a7)) / 255.0f, float(GET_GREEN(a7)) / 255.0f, float(GET_BLUE(a7)) / 255.0f, float(GET_ALPHA(a7)) / 255.0f);
t.vertex(a4, a3, 0.0f);
t.vertex(a2, a3, 0.0f);
t.draw();
glShadeModel(GL_FLAT);
glDisable(GL_BLEND);
glEnable(GL_ALPHA_TEST);
glEnable(GL_TEXTURE_2D);
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "Tesselator.hpp"
#include "Font.hpp"
class GuiComponent
{
public:
void blit(int dstX, int dstY, int srcX, int srcY, int dstWidth, int dstHeight, int srcWidth, int srcHeight);
void drawCenteredString(Font*, const std::string&, int cx, int cy, int color);
void drawString(Font*, const std::string&, int cx, int cy, int color);
void fill(int left, int top, int right, int bottom, int color);
void fillGradient(int left, int top, int right, int bottom, int colorUp, int colorDown);
public:
float field_4;
};

View File

@@ -0,0 +1,319 @@
#include "RolledSelectionList.hpp"
static float g_RolledSelectionListUnk, g_RolledSelectionListUnk2;
RolledSelectionList::RolledSelectionList(Minecraft* minecraft, int a, int b, int c, int d, int e, int f, int g)
{
m_pMinecraft = minecraft;
m_itemWidth = g;
field_18 = a;
field_C = float(c);
field_10 = float(d);
field_1C = b;
field_20 = float(e);
field_24 = float(f);
g_RolledSelectionListUnk = field_30 = field_34 = float(g - a) / 2.0f;
}
int RolledSelectionList::getItemAtXPositionRaw(int x)
{
if (x < 0)
return -1;
// @NOTE: redundant check
int idx = x / m_itemWidth;
if (idx < 0)
return -1;
if (idx >= getNumberOfItems())
return -1;
return idx;
}
int RolledSelectionList::getItemAtPosition(int x, int y)
{
if (float(y) < field_20)
return -1;
if (float(y) > field_24)
return -1;
return getItemAtXPositionRaw(transformX(x));
}
bool RolledSelectionList::capXPosition()
{
float f1 = float(m_itemWidth - field_18) / 2.0f;
int i1 = getNumberOfItems();
if (field_30 < f1)
{
field_30 = f1;
field_38 = 0.0f;
return true;
}
f1 += float(m_itemWidth * (i1 - 1));
if (field_30 > f1)
{
field_30 = f1;
field_38 = 0.0f;
return true;
}
return false;
}
void RolledSelectionList::tick()
{
float diff = g_RolledSelectionListUnk - field_34;
g_RolledSelectionListUnk = field_34;
g_RolledSelectionListUnk2 = diff;
field_34 = field_30 - field_38;
}
void RolledSelectionList::render(int mouseX, int mouseY, float f)
{
renderBackground();
int nItems = getNumberOfItems();
// @TODO: fix gotos.
if (!Mouse::_buttonStates[1])
{
if (field_28 < 0)
{
_crap:
field_28 = -1;
field_30 = getPos(f);
goto _done;
}
if (g_RolledSelectionListUnk2 < 0.0f)
field_38 = Mth::Max(-20.0f, g_RolledSelectionListUnk2);
else
field_38 = Mth::Min(20.0f, g_RolledSelectionListUnk2);
if (fabsf(field_38) < 2.0f)
{
field_38 = 0.0f;
_continue:
if (getTimeMs() - field_4C < 300)
{
int idx = transformX(mouseX) / m_itemWidth;
if (idx >= 0)
{
if (field_50 == idx && abs(field_3C - mouseX) <= 9)
selectItem(field_50, 0);
}
goto _crap;
}
goto _crap;
}
if (fabsf(field_38) > 10.0f)
{
goto _crap;
}
goto _continue;
}
else
{
touched();
if (float(mouseY) >= field_20 && float(mouseY) <= field_24)
{
if (field_28 == -1)
{
field_4C = getTimeMs();
field_3C = mouseX;
field_50 = getItemAtPosition(mouseX, field_1C / 2);
}
else if (field_28 >= 0)
{
field_34 = field_30 = field_30 - (float(mouseX) - field_2C);
}
field_28 = 0;
}
}
_done:
field_2C = float(mouseX);
capXPosition();
glDisable(GL_LIGHTING);
glDisable(GL_FOG);
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/background.png");
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
Tesselator& t = Tesselator::instance;
t.begin();
t.color(0x202020);
t.vertexUV(field_C, field_24, 0.0f, (field_C + float(int(field_30))) / 32.0f, field_24 / 32.0f);
t.vertexUV(field_10, field_24, 0.0f, (field_10 + float(int(field_30))) / 32.0f, field_24 / 32.0f);
t.vertexUV(field_10, field_20, 0.0f, (field_10 + float(int(field_30))) / 32.0f, field_20 / 32.0f);
t.vertexUV(field_C, field_20, 0.0f, (field_C + float(int(field_30))) / 32.0f, field_20 / 32.0f);
t.draw();
if (!getNumberOfItems())
field_30 = 0.0f;
if (field_48)
renderHeader(int(field_C + 4.0f - float(int(field_30))), field_1C / 2 - 40, t);
for (int i = 0; i < nItems; i++)
{
float itemX = float(field_44 + float(int(field_C + 4.0f - float(field_30))) + m_itemWidth * i);
if (field_10 < itemX) continue;
float width = m_itemWidth - 4.0f;
if (itemX + width < field_C) continue;
if (m_bRenderSelection && isSelectedItem(i))
{
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glDisable(GL_TEXTURE_2D);
t.begin();
t.color(m_bComponentSelected ? 0x7F89BF : 0x808080);
float right = itemX + width;
float up = float(field_1C) / 2.0f - 48.0f - 4.0f;
float dn = float(field_1C) / 2.0f + 48.0f - 4.0f;
t.vertexUV(itemX - 2, up, 0.0f, 0.0f, 0.0f);
t.vertexUV(itemX - 2, dn, 0.0f, 1.0f, 0.0f);
t.vertexUV(right + 2, dn, 0.0f, 1.0f, 1.0f);
t.vertexUV(right + 2, up, 0.0f, 0.0f, 1.0f);
t.color(0x000000);
t.vertexUV(itemX - 1, up + 1.0f, 0.0f, 0.0f, 0.0f);
t.vertexUV(itemX - 1, dn - 1.0f, 0.0f, 1.0f, 0.0f);
t.vertexUV(right + 1, dn - 1.0f, 0.0f, 1.0f, 1.0f);
t.vertexUV(right + 1, up + 1.0f, 0.0f, 0.0f, 1.0f);
t.draw();
glEnable(GL_TEXTURE_2D);
}
renderItem(i, int(itemX), field_1C / 2 - 40, int(width), t);
}
glDisable(GL_DEPTH_TEST);
renderHoleBackground(0.0f, field_20, 255, 255);
renderHoleBackground(field_24, float(field_1C), 255, 255);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_ALPHA_TEST);
glShadeModel(GL_SMOOTH);
glDisable(GL_TEXTURE_2D);
// @BUG: The X and Y coordinates have been swapped. This causes the gradient to not render
// in the right place.
#ifdef ORIGINAL_CODE
t.begin();
t.color(0, 0);
t.vertexUV(field_20, field_C + 4.0f, 0.0f, 0.0f, 1.0f);
t.vertexUV(field_24, field_C + 4.0f, 0.0f, 1.0f, 1.0f);
t.color(0, 255);
t.vertexUV(field_24, field_C, 0.0f, 1.0f, 0.0f);
t.vertexUV(field_20, field_C, 0.0f, 0.0f, 0.0f);
t.draw();
t.begin();
t.color(0, 255);
t.vertexUV(field_20, field_10, 0.0f, 0.0f, 1.0f);
t.vertexUV(field_24, field_10, 0.0f, 1.0f, 1.0f);
t.color(0, 0);
t.vertexUV(field_24, field_10 - 4.0f, 0.0f, 1.0f, 0.0f);
t.vertexUV(field_20, field_10 - 4.0f, 0.0f, 0.0f, 0.0f);
t.draw();
#else
t.begin();
t.color(0, 0);
t.vertexUV(field_C + 4.0f, field_20, 0.0f, 0.0f, 1.0f);
t.vertexUV(field_C + 4.0f, field_24, 0.0f, 1.0f, 1.0f);
t.color(0, 255);
t.vertexUV(field_C, field_24, 0.0f, 1.0f, 0.0f);
t.vertexUV(field_C, field_20, 0.0f, 0.0f, 0.0f);
t.draw();
t.begin();
t.color(0, 255);
t.vertexUV(field_10, field_20, 0.0f, 0.0f, 1.0f);
t.vertexUV(field_10, field_24, 0.0f, 1.0f, 1.0f);
t.color(0, 0);
t.vertexUV(field_10 - 4.0f, field_24, 0.0f, 1.0f, 0.0f);
t.vertexUV(field_10 - 4.0f, field_20, 0.0f, 0.0f, 0.0f);
t.draw();
#endif
renderDecorations(mouseX, mouseY);
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
glEnable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
}
void RolledSelectionList::renderHoleBackground(float y1, float y2, int a, int b)
{
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/background.png");
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
Tesselator& t = Tesselator::instance;
t.begin();
t.color(0x505050, b);
t.vertexUV(0.0f, y2, 0.0f, 0.0f, y2 / 32.0f);
t.vertexUV(float(field_18), y2, 0.0f, float(field_18) / 32.0f, y2 / 32.0f);
t.color(0x505050, a);
t.vertexUV(float(field_18), y1, 0.0f, float(field_18) / 32.0f, y1 / 32.0f);
t.vertexUV(0.0f, y1, 0.0f, 0.0f, y1 / 32.0f);
t.draw();
}
void RolledSelectionList::setRenderSelection(bool b)
{
m_bRenderSelection = b;
}
void RolledSelectionList::setComponentSelected(bool b)
{
m_bComponentSelected = b;
}
int RolledSelectionList::getMaxPosition()
{
return field_44 + m_itemWidth * getNumberOfItems();
}
float RolledSelectionList::getPos(float f)
{
return field_34 - field_38 * f;
}
void RolledSelectionList::touched()
{
}
void RolledSelectionList::renderHeader(int a, int b, Tesselator& t)
{
}
void RolledSelectionList::renderDecorations(int x, int y)
{
}
void RolledSelectionList::clickedHeader(int x, int y)
{
}

View File

@@ -0,0 +1,59 @@
#pragma once
#include "GuiComponent.hpp"
#include "Minecraft.hpp"
class RolledSelectionList : public GuiComponent
{
public:
RolledSelectionList(Minecraft*, int, int, int, int, int, int, int);
virtual int getItemAtPosition(int, int);
virtual bool capXPosition();
virtual void tick();
virtual void render(int mouseX, int mouseY, float);
virtual void renderHoleBackground(float y1, float y2, int a, int b);
virtual void setRenderSelection(bool);
virtual void setComponentSelected(bool);
virtual int getNumberOfItems() = 0;
virtual void selectItem(int, bool) = 0;
virtual bool isSelectedItem(int) = 0;
virtual int getMaxPosition();
virtual float getPos(float f);
virtual void touched();
virtual void renderItem(int, int, int, int, Tesselator&) = 0;
virtual void renderHeader(int, int, Tesselator&);
virtual void renderBackground() = 0;
virtual void renderDecorations(int x, int y);
virtual void clickedHeader(int, int);
int getItemAtXPositionRaw(int x);
// @NOTE: This is inlined.
inline int transformX(int x)
{
return int(x - field_C - float(field_44) + float(int(field_30)) - 4.0f);
}
public:
Minecraft* m_pMinecraft;
float field_C;
float field_10;
int m_itemWidth;
int field_18;
int field_1C;
float field_20;
float field_24;
int field_28 = -2;
float field_2C = 0.0f;
float field_30;
float field_34;
float field_38 = 0.0f;
int field_3C = -1;
bool m_bRenderSelection = true;
bool m_bComponentSelected = false;
int field_44 = 0;
bool field_48 = false;
int field_4C = 0;
int field_50 = -1;
};

View File

@@ -0,0 +1,32 @@
#include "ChatScreen.hpp"
// @NOTE: This is unused.
void ChatScreen::buttonClicked(Button* pButton)
{
}
void ChatScreen::init()
{
m_pMinecraft->platform()->showDialog(AppPlatform::DLG_CHAT);
m_pMinecraft->platform()->createUserInput();
}
void ChatScreen::render(int mouseX, int mouseY, float f)
{
int userInputStatus = m_pMinecraft->platform()->getUserInputStatus();
if (userInputStatus < 0)
return;
if (userInputStatus == 1)
{
std::vector<std::string> userInput = m_pMinecraft->platform()->getUserInput();
if (userInput.size() >= 1)
{
// @NOTE: No sending multiplayer chats. Weird
m_pMinecraft->m_gui.addMessage(userInput[0]);
}
}
m_pMinecraft->setScreen(nullptr);
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "Screen.hpp"
class ChatScreen : public Screen
{
public:
void buttonClicked(Button*) override;
void init() override;
void render(int mouseX, int mouseY, float f) override;
};

View File

@@ -0,0 +1,67 @@
#include "ConfirmScreen.hpp"
ConfirmScreen::ConfirmScreen(Screen* pScreen, const std::string& line1, const std::string& line2, int x) :
m_btnOK (0, 0, 0, "Ok"),
m_btnCancel(1, 0, 0, "Cancel"),
m_textLine1(line1),
m_textLine2(line2),
m_pScreen(pScreen),
field_40(x)
{
}
ConfirmScreen::ConfirmScreen(Screen* pScreen, const std::string& line1, const std::string& line2, const std::string& ok, const std::string& cancel, int x) :
m_btnOK (0, 0, 0, ok),
m_btnCancel(1, 0, 0, cancel),
m_textLine1(line1),
m_textLine2(line2),
m_pScreen(pScreen),
field_40(x)
{
}
// @NOTE: potential memory leak if pScreen is set and not destroyed!
void ConfirmScreen::buttonClicked(Button* pButton)
{
postResult(pButton->field_30 == 0);
}
bool ConfirmScreen::handleBackEvent(bool b)
{
if (!b)
postResult(false);
return true;
}
void ConfirmScreen::init()
{
m_btnOK.m_xPos = m_width / 2 - 4 - 120;
m_btnCancel.m_xPos = m_width / 2 + 4;
m_btnCancel.m_yPos = m_btnOK.m_yPos = m_height / 6 + 72;
m_btnOK.m_width = m_btnCancel.m_width = 120;
m_btnOK.m_height = m_btnCancel.m_height = 24;
m_buttons.push_back(&m_btnOK);
m_buttons.push_back(&m_btnCancel);
m_buttonTabList.push_back(&m_btnOK);
m_buttonTabList.push_back(&m_btnCancel);
}
void ConfirmScreen::render(int mouseX, int mouseY, float f)
{
renderBackground();
drawCenteredString(m_pFont, m_textLine1, m_width / 2, 50, 0xFFFFFF);
drawCenteredString(m_pFont, m_textLine2, m_width / 2, 70, 0xFFFFFF);
Screen::render(mouseX, mouseY, f);
}
void ConfirmScreen::postResult(bool b)
{
m_pScreen->confirmResult(b, field_40);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include "Screen.hpp"
#include "SmallButton.hpp"
class ConfirmScreen : public Screen
{
public:
ConfirmScreen(Screen* pScreen, const std::string& line1, const std::string& line2, int x);
ConfirmScreen(Screen* pScreen, const std::string& line1, const std::string& line2, const std::string& ok, const std::string& cancel, int x);
void buttonClicked(Button* pButton) override;
bool handleBackEvent(bool b) override;
void init() override;
void render(int mouseX, int mouseY, float f) override;
virtual void postResult(bool b);
private:
Screen* m_pScreen = nullptr;
int field_40 = 0;
std::string m_textLine1;
std::string m_textLine2;
SmallButton m_btnOK;
SmallButton m_btnCancel;
};

View File

@@ -0,0 +1,23 @@
#include "DeleteWorldScreen.hpp"
#include "SelectWorldScreen.hpp"
DeleteWorldScreen::DeleteWorldScreen(const LevelSummary& level) :
ConfirmScreen(nullptr,
"Are you sure you want to delete this world?",
"'" + level.field_18 + "' will be lost forever!",
"Delete", "Cancel", 0),
m_level(level)
{
// highlight the cancel button so the user will have to do 1 extra action to delete their world
m_tabButtonIndex = 1;
}
void DeleteWorldScreen::postResult(bool b)
{
if (b)
{
m_pMinecraft->getLevelSource()->deleteLevel(m_level.field_0);
}
m_pMinecraft->setScreen(new SelectWorldScreen);
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include "ConfirmScreen.hpp"
class DeleteWorldScreen : public ConfirmScreen
{
public:
DeleteWorldScreen(const LevelSummary& level);
void postResult(bool b) override;
private:
LevelSummary m_level;
};

View File

@@ -0,0 +1,186 @@
#include "IngameBlockSelectionScreen.hpp"
#include "ItemRenderer.hpp"
#define C_SLOTS_HEIGHT ((C_MAX_INVENTORY_ITEMS + 8) / 9)
std::string g_sNotAvailableInDemoVersion = "Not available in the demo version";
int IngameBlockSelectionScreen::getSelectedSlot(int x, int y)
{
int bottom = m_height - 151;
int left = m_width / 2 - 87;
if (y < bottom)
return -1;
if (x < left)
return -1;
int idx = (x - left) / 20;
if (idx > 8)
return -1;
return idx + 36 - 9 * ((y - bottom) / 22);
}
int IngameBlockSelectionScreen::getSlotPosX(int x)
{
return m_width / 2 - 88 + 20 * x;
}
int IngameBlockSelectionScreen::getSlotPosY(int y)
{
return m_height - 63 - 22 * y;
}
bool IngameBlockSelectionScreen::isAllowed(int slot)
{
if (slot < 0 || slot > C_MAX_INVENTORY_ITEMS-1)
return false;
#ifdef DEMO
return slot > 17;
#endif
return true;
}
void IngameBlockSelectionScreen::init()
{
Inventory* pInv = m_pMinecraft->m_pLocalPlayer->m_pInventory;
for (int i = 9; i < C_MAX_HOTBAR_ITEMS + C_MAX_INVENTORY_ITEMS; i++)
{
if (pInv->getSelectionSlotItemId(i) == pInv->getSelectedItemId())
{
m_selectedSlot = i - 9;
break;
}
}
if (!isAllowed(m_selectedSlot))
m_selectedSlot = 27;
}
void IngameBlockSelectionScreen::renderSlot(int index, int x, int y, float f)
{
int item = m_pMinecraft->m_pLocalPlayer->m_pInventory->getSelectionSlotItemId(index);
if (item < 0)
return;
ItemInstance inst(item, 2, 0);
ItemRenderer::renderGuiItem(m_pMinecraft->m_pFont, m_pMinecraft->m_pTextures, &inst, x, y, item);
}
void IngameBlockSelectionScreen::renderSlots()
{
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/gui.png");
for (int y = 0; y != -22 * C_SLOTS_HEIGHT; y -= 22)
blit(m_width / 2 - 182 / 2, m_height - 66 + y, 0, 0, 182, 22, 0, 0);
if (m_selectedSlot >= 0)
blit(m_width / 2 - 92 + 20 * (m_selectedSlot % 9), m_height - 67 - 22 * (m_selectedSlot / 9), 0, 22, 24, 22, 0, 0);
for (int y = 0, index = 9; y < C_SLOTS_HEIGHT; y++)
{
int posY = getSlotPosY(y);
for (int x = 0; x < 9; x++)
{
int posX = getSlotPosX(x);
renderSlot(index++, posX, posY, 0.0f);
}
}
}
void IngameBlockSelectionScreen::renderDemoOverlay()
{
fill(getSlotPosX(0) - 3, getSlotPosY(1) - 3, getSlotPosX(9), getSlotPosY(-1), 0xA0000000);
int x = (getSlotPosX(4) + getSlotPosX(5)) / 2;
int y = (getSlotPosY(0) + getSlotPosY(1)) / 2 + 5;
drawCenteredString(m_pMinecraft->m_pFont, g_sNotAvailableInDemoVersion, x, y, 0xFFFFFFFF);
}
void IngameBlockSelectionScreen::render(int x, int y, float f)
{
Screen::render(x, y, f);
glDisable(GL_DEPTH_TEST);
fill(0, 0, m_width, m_height, 0x80000000);
glEnable(GL_BLEND);
renderSlots();
#ifdef DEMO
renderDemoOverlay();
#endif
glEnable(GL_DEPTH_TEST);
}
void IngameBlockSelectionScreen::mouseClicked(int x, int y, int type)
{
// not a left click
if (type != 1)
return;
int slot = getSelectedSlot(x, y);
if (isAllowed(slot))
m_selectedSlot = slot;
}
void IngameBlockSelectionScreen::mouseReleased(int x, int y, int type)
{
// not a left click
if (type != 1)
return;
int slot = getSelectedSlot(x, y);
if (isAllowed(slot) && slot == m_selectedSlot)
selectSlotAndClose();
}
void IngameBlockSelectionScreen::selectSlotAndClose()
{
Inventory* pInv = m_pMinecraft->m_pLocalPlayer->m_pInventory;
int item = pInv->getSelectionSlotItemId(m_selectedSlot + 9);
int idx = 0;
// @TODO: Fix gotos
#ifdef ENH_ENABLE_9TH_SLOT
#define MAX_ITEMS (C_MAX_HOTBAR_ITEMS - 1)
#else
#define MAX_ITEMS (C_MAX_HOTBAR_ITEMS - 2)
#endif
if (item == pInv->getSelectionSlotItemId(0))
{
label_4:
if (!idx)
goto label_5;
}
else while (++idx != MAX_ITEMS)
{
if (item == pInv->getSelectionSlotItemId(idx))
goto label_4;
}
while (true)
{
int item = pInv->getSelectionSlotItemId(idx - 1);
pInv->setSelectionSlotItemId(idx, item);
if (idx == 1)
break;
--idx;
}
label_5:
pInv->setSelectionSlotItemId(0, item);
pInv->selectSlot(0);
// @TODO: SoundEngine
m_pMinecraft->setScreen(nullptr);
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Screen.hpp"
class IngameBlockSelectionScreen : public Screen
{
public:
int getSelectedSlot(int x, int y);
int getSlotPosX(int x);
int getSlotPosY(int y);
bool isAllowed(int slot);
void renderSlots();
void renderDemoOverlay();
void renderSlot(int index, int x, int y, float f);
void selectSlotAndClose();
virtual void init() override;
virtual void render(int x, int y, float f) override;
virtual void mouseClicked(int x, int y, int type) override;
virtual void mouseReleased(int x, int y, int type) override;
private:
int m_selectedSlot = 0;
};

View File

@@ -0,0 +1,61 @@
#include "InvalidLicenseScreen.hpp"
InvalidLicenseScreen::InvalidLicenseScreen(int error, bool bHasQuitButton) :
m_error(error),
m_btnOk (1, "Ok"),
m_btnBuy(2, "Buy"),
m_bHasQuitButton(bHasQuitButton)
{
if (bHasQuitButton)
m_btnOk.field_18 = "Quit";
}
void InvalidLicenseScreen::buttonClicked(Button* pButton)
{
if (pButton->field_30 == m_btnOk.field_30)
{
m_pMinecraft->quit();
}
if (pButton->field_30 == m_btnBuy.field_30)
{
m_pMinecraft->platform()->buyGame();
}
}
void InvalidLicenseScreen::init()
{
field_E4 = m_height / 3;
if (m_bHasQuitButton)
field_E4 -= 24;
if (m_error > 1)
{
char str[20] = { 0 };
sprintf(str, "%d", m_error);
m_textLine1 = "License verification failed (error " + std::string(str) + ")";
m_textLine2 = "Try again later.";
}
m_btnOk.m_xPos = m_btnBuy.m_xPos = (m_width - m_btnOk.m_width) / 2;
m_btnOk.m_yPos = field_E4 + 60;
m_btnBuy.m_yPos = field_E4 + 88;
m_buttons.push_back(&m_btnOk);
m_buttons.push_back(&m_btnBuy);
m_buttonTabList.push_back(&m_btnOk);
m_buttonTabList.push_back(&m_btnBuy);
}
void InvalidLicenseScreen::tick()
{
}
void InvalidLicenseScreen::render(int mouseX, int mouseY, float f)
{
renderDirtBackground(0);
drawCenteredString(m_pMinecraft->m_pFont, m_textLine1, m_width / 2, field_E4, 0xFFFFFF);
drawCenteredString(m_pMinecraft->m_pFont, m_textLine2, m_width / 2, field_E4 + 24, 0xFFFFFF);
Screen::render(mouseX, mouseY, f);
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "Screen.hpp"
class InvalidLicenseScreen : public Screen
{
public:
InvalidLicenseScreen(int error, bool bHasQuitButton);
void buttonClicked(Button* pButton) override;
void init() override;
void tick() override;
void render(int mouseX, int mouseY, float f) override;
private:
int m_error;
std::string m_textLine1;
std::string m_textLine2;
Button m_btnOk;
Button m_btnBuy;
bool m_bHasQuitButton;
int field_E4 = 0;
};

View File

@@ -0,0 +1,155 @@
#include "JoinGameScreen.hpp"
#include "ProgressScreen.hpp"
#include "StartMenuScreen.hpp"
JoinGameScreen::JoinGameScreen() :
m_btnJoin(2, "Join Game"),
m_btnBack(3, "Back")
{
}
JoinGameScreen::~JoinGameScreen()
{
if (m_pAvailableGamesList)
delete m_pAvailableGamesList;
}
void JoinGameScreen::buttonClicked(Button* pButton)
{
if (pButton->field_30 == m_btnJoin.field_30)
{
if (isIndexValid(m_pAvailableGamesList->m_selectedIndex))
{
m_pMinecraft->joinMultiplayer(m_pAvailableGamesList->m_games[m_pAvailableGamesList->m_selectedIndex]);
m_pMinecraft->setScreen(new ProgressScreen);
m_btnJoin.m_bEnabled = false;
m_btnBack.m_bEnabled = false;
}
}
if (pButton->field_30 == m_btnBack.field_30)
{
m_pMinecraft->setScreen(new StartMenuScreen);
}
}
bool JoinGameScreen::handleBackEvent(bool b)
{
if (!b)
{
m_pMinecraft->cancelLocateMultiplayer();
m_pMinecraft->setScreen(new StartMenuScreen);
}
return true;
}
void JoinGameScreen::init()
{
m_btnBack.m_yPos = m_btnJoin.m_yPos = m_height - 26;
m_btnBack.m_width = m_btnJoin.m_width = 120;
m_btnJoin.m_xPos = m_width / 2 - 124;
m_btnBack.m_xPos = m_width / 2 + 4;
m_buttons.push_back(&m_btnJoin);
m_buttons.push_back(&m_btnBack);
m_pMinecraft->m_pRakNetInstance->clearServerList();
m_pAvailableGamesList = new AvailableGamesList(m_pMinecraft, m_width, m_height, 24, m_height - 30, 28);
m_buttonTabList.push_back(&m_btnJoin);
m_buttonTabList.push_back(&m_btnBack);
}
bool JoinGameScreen::isInGameScreen()
{
return false;
}
void JoinGameScreen::render(int mouseX, int mouseY, float f)
{
renderBackground();
m_pAvailableGamesList->render(mouseX, mouseY, f);
Screen::render(mouseX, mouseY, f);
drawCenteredString(m_pMinecraft->m_pFont, "Scanning for Games...", m_width / 2, 8, 0xFFFFFFFF);
}
void JoinGameScreen::tick()
{
std::vector<PingedCompatibleServer> *serverList, serverListFiltered;
serverList = m_pMinecraft->m_pRakNetInstance->getServerList();
for (const PingedCompatibleServer& pcs : *serverList)
{
if (pcs.m_name.GetLength())
serverListFiltered.push_back(pcs);
}
if (serverListFiltered.size() != m_pAvailableGamesList->m_games.size())
{
PingedCompatibleServer selectedItem;
if (isIndexValid(m_pAvailableGamesList->m_selectedIndex))
{
selectedItem = m_pAvailableGamesList->m_games[m_pAvailableGamesList->m_selectedIndex];
m_pAvailableGamesList->m_games = serverListFiltered;
m_pAvailableGamesList->selectItem(-1, false);
// relocate the new list item, if possible
for (int i = 0; i < int(serverListFiltered.size()); i++)
{
if (serverListFiltered[i].m_address == selectedItem.m_address)
{
m_pAvailableGamesList->selectItem(i, false);
break;
}
}
}
else
{
m_pAvailableGamesList->m_games = serverListFiltered;
m_pAvailableGamesList->selectItem(-1, false);
}
}
else if (!serverListFiltered.empty())
{
// Update the names of the items already in the available games list.
// @BUG: What would happen if the filtered server list removes an IP and adds another all in the same tick?
std::vector<PingedCompatibleServer>* pGames = &m_pAvailableGamesList->m_games;
for (int i = int(pGames->size() - 1); i >= 0; i--)
{
int j = 0;
for (; j < int(serverListFiltered.size()); j++)
{
if (serverListFiltered[j].m_address == (*pGames)[i].m_address)
break;
}
if (j == serverListFiltered.size())
continue;
(*pGames)[i].m_name = serverListFiltered[j].m_name;
}
}
m_btnJoin.m_bEnabled = isIndexValid(m_pAvailableGamesList->m_selectedIndex);
}
bool JoinGameScreen::isIndexValid(int idx)
{
if (!m_pAvailableGamesList)
return false;
if (idx < 0)
return false;
if (idx >= m_pAvailableGamesList->getNumberOfItems())
return false;
return true;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Screen.hpp"
#include "AvailableGamesList.hpp"
class JoinGameScreen : public Screen
{
public:
JoinGameScreen();
~JoinGameScreen();
void buttonClicked(Button* pButton) override;
bool handleBackEvent(bool b) override;
void init() override;
bool isInGameScreen() override;
void render(int mouseX, int mouseY, float f) override;
void tick() override;
virtual bool isIndexValid(int idx);
public:
Button m_btnJoin;
Button m_btnBack;
AvailableGamesList* m_pAvailableGamesList = nullptr;
};

View File

@@ -0,0 +1,200 @@
#include "OptionsScreen.hpp"
#include "StartMenuScreen.hpp"
#include "PauseScreen.hpp"
enum eOptionsButton
{
OB_BACK = 1,
OB_AO,
OB_SRV_VIS,
OB_FANCY_GFX,
OB_INVERT_Y,
OB_ANAGLYPHS,
OB_VIEW_BOB,
OB_VIEW_DIST,
OB_FLY_HAX,
};
OptionsScreen::OptionsScreen()
#ifndef ORIGINAL_CODE
:m_BackButton (1, 0, 0, 200, 20, "Done"),
m_AOButton (2, 0, 0, 150, 20, ""),
m_srvVisButton (3, 0, 0, 150, 20, ""),
m_fancyGfxButton (4, 0, 0, 150, 20, ""),
m_invertYButton (5, 0, 0, 150, 20, ""),
m_anaglyphsButton(6, 0, 0, 150, 20, ""),
m_viewBobButton (7, 0, 0, 150, 20, ""),
m_viewDistButton (8, 0, 0, 150, 20, ""),
m_flightHaxButton(9, 0, 0, 150, 20, "")
#endif
{
}
#ifndef ORIGINAL_CODE
static std::string BoolOptionStr(bool b)
{
return b ? "ON" : "OFF";
}
static std::string ViewDistanceStr(int dist)
{
switch (dist)
{
case 0: return "EXTREME";
case 1: return "FAR";
case 2: return "NORMAL";
case 3: return "SHORT";
case 4: return "TINY";
default:
{
std::stringstream ss;
ss << dist;
return ss.str();
}
}
}
void OptionsScreen::UpdateTexts()
{
Options& o = m_pMinecraft->m_options;
m_AOButton.field_18 = "Smooth lighting: " + BoolOptionStr(o.field_18);
m_invertYButton.field_18 = "Invert Y-axis: " + BoolOptionStr(o.m_bInvertMouse);
m_viewBobButton.field_18 = "View bobbing: " + BoolOptionStr(o.field_14);
m_anaglyphsButton.field_18 = "3d Anaglyphs: " + BoolOptionStr(o.m_bAnaglyphs);
m_fancyGfxButton.field_18 = "Fancy graphics: " + BoolOptionStr(o.m_bFancyGraphics);
m_flightHaxButton.field_18 = "Flight hax: " + BoolOptionStr(o.m_bFlyCheat);
m_viewDistButton.field_18 = "View distance: " + ViewDistanceStr(o.field_10);
m_srvVisButton.field_18 = "Server " + std::string(o.m_bServerVisibleDefault ? "visible" : "invisible") + " by default";
}
#endif
void OptionsScreen::init()
{
m_pMinecraft->platform()->showDialog(AppPlatform::DLG_OPTIONS);
m_pMinecraft->platform()->createUserInput();
#ifndef ORIGINAL_CODE
m_BackButton.m_xPos = m_width / 2 - m_BackButton.m_width / 2;
m_BackButton.m_yPos = m_height - 33;
m_BackButton.m_height = 20;
m_buttons.push_back(&m_BackButton);
m_AOButton.m_xPos =
m_srvVisButton.m_xPos =
m_fancyGfxButton.m_xPos =
m_viewDistButton.m_xPos = m_width / 2 - m_AOButton.m_width - 5;
m_invertYButton.m_xPos =
m_anaglyphsButton.m_xPos =
m_viewBobButton.m_xPos =
m_flightHaxButton.m_xPos = m_width / 2 + 5;
int yPos = 40;
m_AOButton.m_yPos = m_invertYButton.m_yPos = yPos; yPos += 25;
m_srvVisButton.m_yPos = m_anaglyphsButton.m_yPos = yPos; yPos += 25;
m_fancyGfxButton.m_yPos = m_viewBobButton.m_yPos = yPos; yPos += 25;
m_viewDistButton.m_yPos = m_flightHaxButton.m_yPos = yPos; yPos += 25;
m_buttons.push_back(&m_AOButton);
m_buttons.push_back(&m_srvVisButton);
m_buttons.push_back(&m_fancyGfxButton);
m_buttons.push_back(&m_invertYButton);
m_buttons.push_back(&m_anaglyphsButton);
m_buttons.push_back(&m_viewBobButton);
m_buttons.push_back(&m_viewDistButton);
m_buttons.push_back(&m_flightHaxButton);
m_buttonTabList.push_back(&m_AOButton);
m_buttonTabList.push_back(&m_srvVisButton);
m_buttonTabList.push_back(&m_fancyGfxButton);
m_buttonTabList.push_back(&m_viewDistButton);
m_buttonTabList.push_back(&m_invertYButton);
m_buttonTabList.push_back(&m_anaglyphsButton);
m_buttonTabList.push_back(&m_viewBobButton);
m_buttonTabList.push_back(&m_flightHaxButton);
m_buttonTabList.push_back(&m_BackButton);
UpdateTexts();
#endif
}
void OptionsScreen::render(int a, int b, float c)
{
renderBackground();
if (m_pMinecraft->m_pPlatform->getUserInputStatus() >= 0)
{
m_pMinecraft->setScreen(new StartMenuScreen);
}
#ifndef ORIGINAL_CODE
drawCenteredString(m_pFont, "Options", m_width / 2, 20, 0xFFFFFF);
Screen::render(a, b, c);
#endif
}
void OptionsScreen::removed()
{
m_pMinecraft->reloadOptions();
}
#ifndef ORIGINAL_CODE
void OptionsScreen::buttonClicked(Button* pButton)
{
Options& o = m_pMinecraft->m_options;
bool* pOption = nullptr;
switch (pButton->field_30)
{
case OB_BACK:
if (m_pMinecraft->isLevelGenerated())
m_pMinecraft->setScreen(new PauseScreen);
else
m_pMinecraft->setScreen(new StartMenuScreen);
return;
case OB_AO:
o.field_18 ^= 1;
Minecraft::useAmbientOcclusion = o.field_18;
m_pMinecraft->m_pLevelRenderer->allChanged();
UpdateTexts();
return;
case OB_FANCY_GFX:
o.m_bFancyGraphics ^= 1;
m_pMinecraft->m_pLevelRenderer->allChanged();
UpdateTexts();
return;
case OB_VIEW_DIST:
// @TODO: fix the 'extreme' render distance
o.field_10 = (o.field_10 + 1) % 4;
UpdateTexts();
return;
case OB_ANAGLYPHS:
pOption = &o.m_bAnaglyphs;
break;
case OB_INVERT_Y:
pOption = &o.m_bInvertMouse;
break;
case OB_SRV_VIS:
pOption = &o.m_bServerVisibleDefault;
break;
case OB_VIEW_BOB:
pOption = &o.field_14;
break;
case OB_FLY_HAX:
pOption = &o.m_bFlyCheat;
break;
}
if (!pOption)
return;
*pOption ^= 1;
UpdateTexts();
}
#endif

View File

@@ -0,0 +1,30 @@
#pragma once
#include "Screen.hpp"
class OptionsScreen : public Screen
{
public:
OptionsScreen();
void init() override;
void render(int, int, float) override;
void removed() override;
#ifndef ORIGINAL_CODE
void buttonClicked(Button* pButton) override;
void UpdateTexts();
private:
Button m_BackButton;
Button m_AOButton;
Button m_srvVisButton;
Button m_fancyGfxButton;
Button m_invertYButton;
Button m_anaglyphsButton;
Button m_viewBobButton;
Button m_viewDistButton;
Button m_flightHaxButton;
#endif
};

View File

@@ -0,0 +1,112 @@
#include "PauseScreen.hpp"
#include "OptionsScreen.hpp"
#include "ServerSideNetworkHandler.hpp"
PauseScreen::PauseScreen() :
m_btnBack(1, "Back to game"),
m_btnQuit(2, "Quit to title"),
m_btnQuitAndCopy(3, "Quit and copy map"),
m_btnVisible(4, "")
#ifdef ENH_ADD_OPTIONS_PAUSE
, m_btnOptions(999, "Options")
#endif
{
}
void PauseScreen::init()
{
m_btnQuit.m_width = 160;
m_btnBack.m_width = 160;
m_btnVisible.m_width = 160;
m_btnQuitAndCopy.m_width = 160;
m_btnBack.m_yPos = 48;
m_btnQuit.m_yPos = 80;
m_btnBack.m_xPos = (m_width - 160) / 2;
m_btnQuit.m_xPos = (m_width - 160) / 2;
m_btnVisible.m_xPos = (m_width - 160) / 2;
m_btnQuitAndCopy.m_xPos = (m_width - 160) / 2;
m_btnVisible.m_yPos = 112;
m_btnQuitAndCopy.m_yPos = 112;
#ifdef ENH_ADD_OPTIONS_PAUSE
// TODO: when visible or quit&copy are on, lower this
m_btnOptions.m_width = 160;
m_btnOptions.m_yPos = 144;
m_btnOptions.m_xPos = m_btnBack.m_xPos;
#endif
// add the buttons to the screen:
m_buttons.push_back(&m_btnBack);
m_buttons.push_back(&m_btnQuit);
#ifdef ENH_ADD_OPTIONS_PAUSE
m_buttons.push_back(&m_btnOptions);
#endif
//m_buttons.push_back(&m_btnQuitAndCopy);
if (m_pMinecraft->m_pRakNetInstance)
{
updateServerVisibilityText();
m_buttons.push_back(&m_btnVisible);
}
for (auto thing : m_buttons)
m_buttonTabList.push_back(thing);
}
void PauseScreen::updateServerVisibilityText()
{
if (!m_pMinecraft->m_pRakNetInstance) return;
if (!m_pMinecraft->m_pRakNetInstance->m_bIsHost) return;
ServerSideNetworkHandler* pSSNH = (ServerSideNetworkHandler*)m_pMinecraft->m_pNetEventCallback;
if (pSSNH->m_bAllowIncoming)
m_btnVisible.field_18 = "Server is visible";
else
m_btnVisible.field_18 = "Server is invisible";
}
void PauseScreen::tick()
{
field_40++;
}
void PauseScreen::render(int a, int b, float c)
{
renderBackground();
drawCenteredString(m_pFont, "Game menu", m_width / 2, 24, 0xFFFFFF);
Screen::render(a, b, c);
}
void PauseScreen::buttonClicked(Button* pButton)
{
if (pButton->field_30 == m_btnBack.field_30)
m_pMinecraft->setScreen(nullptr);
if (pButton->field_30 == m_btnQuit.field_30)
m_pMinecraft->leaveGame(false);
if (pButton->field_30 == m_btnQuitAndCopy.field_30)
m_pMinecraft->leaveGame(true);
if (pButton->field_30 == m_btnVisible.field_30)
{
if (m_pMinecraft->m_pRakNetInstance && m_pMinecraft->m_pRakNetInstance->m_bIsHost)
{
ServerSideNetworkHandler* pSSNH = (ServerSideNetworkHandler*)m_pMinecraft->m_pNetEventCallback;
pSSNH->allowIncomingConnections(!pSSNH->m_bAllowIncoming);
updateServerVisibilityText();
}
}
#ifdef ENH_ADD_OPTIONS_PAUSE
if (pButton->field_30 == m_btnOptions.field_30)
m_pMinecraft->setScreen(new OptionsScreen);
#endif
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "Screen.hpp"
class PauseScreen : public Screen
{
public:
PauseScreen();
virtual void init() override;
virtual void tick() override;
virtual void render(int a, int b, float c) override;
virtual void buttonClicked(Button*) override;
void updateServerVisibilityText();
private:
int field_3C = 0;
int field_40 = 0;
Button m_btnBack;
Button m_btnQuit;
Button m_btnQuitAndCopy;
Button m_btnVisible;
#ifdef ENH_ADD_OPTIONS_PAUSE
Button m_btnOptions;
#endif
};

View File

@@ -0,0 +1,78 @@
#include "ProgressScreen.hpp"
bool ProgressScreen::isInGameScreen()
{
return false;
}
void ProgressScreen::render(int a, int b, float c)
{
if (m_pMinecraft->isLevelGenerated())
{
m_pMinecraft->setScreen(nullptr);
return;
}
renderBackground();
// render the dirt background
// for some reason, this was manually written:
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/background.png");
//! why not use the screen stuff
int x_width = int(Minecraft::width * Gui::InvGuiScale);
int x_height = int(Minecraft::height * Gui::InvGuiScale);
Tesselator& t = Tesselator::instance;
t.begin();
t.color(0x404040);
t.vertexUV(0.0f, float(x_height), 0, 0, float(x_height) / 32.0f);
t.vertexUV(float(x_width), float(x_height), 0, float(x_width) / 32.0f, float(x_height) / 32.0f);
t.vertexUV(float(x_width), 0, 0, float(x_width) / 32.0f, 0);
t.vertexUV(0.0f, 0, 0, 0, 0);
t.draw();
int yPos = x_height / 2;
if (m_pMinecraft->m_progressPercent >= 0)
{
float lX = float(x_width) / 2 - 50, rX = float(x_width) / 2 + 50;
float prog = float(m_pMinecraft->m_progressPercent);
// disable the texturing
glDisable(GL_TEXTURE_2D);
t.begin();
t.color(0x808080); // gray background
t.vertex(lX, float(yPos + 16), 0);
t.vertex(lX, float(yPos + 18), 0);
t.vertex(rX, float(yPos + 18), 0);
t.vertex(rX, float(yPos + 16), 0);
t.color(0x80FF80); // the green stuff
t.vertex(lX, float(yPos + 16), 0);
t.vertex(lX, float(yPos + 18), 0);
t.vertex(lX + prog, float(yPos + 18), 0);
t.vertex(lX + prog, float(yPos + 16), 0);
t.draw();
// restore old state
glEnable(GL_TEXTURE_2D);
}
//! Using m_pMinecraft->m_pFont instead of m_pFont.
Font* pFont = m_pMinecraft->m_pFont;
int width = pFont->width("Generating world");
pFont->drawShadow("Generating world", (x_width - width) / 2, yPos - 20, 0xFFFFFF);
width = pFont->width(m_pMinecraft->getProgressMessage());
pFont->drawShadow(m_pMinecraft->getProgressMessage(), (x_width - width) / 2, yPos + 4, 0xFFFFFF);
#ifdef ORIGINAL_CODE
sleepMs(50);
#endif
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "Screen.hpp"
class ProgressScreen : public Screen
{
public:
void render(int, int, float) override;
bool isInGameScreen() override;
};

View File

@@ -0,0 +1,30 @@
#include "RenameMPLevelScreen.hpp"
#include "StartMenuScreen.hpp"
RenameMPLevelScreen::RenameMPLevelScreen(const std::string& levelName) : m_levelName(levelName)
{
}
void RenameMPLevelScreen::init()
{
m_pMinecraft->platform()->showDialog(AppPlatform::DLG_RENAME_MP_WORLD);
m_pMinecraft->platform()->createUserInput();
}
void RenameMPLevelScreen::render(int mouseX, int mouseY, float f)
{
int userInputStatus = m_pMinecraft->platform()->getUserInputStatus();
if (userInputStatus < 0)
return;
if (userInputStatus == 1)
{
std::vector<std::string> input = m_pMinecraft->platform()->getUserInput();
if (input.size() > 0 && !input[0].empty())
{
m_pMinecraft->getLevelSource()->renameLevel(m_levelName, input[0]);
}
}
m_pMinecraft->setScreen(new StartMenuScreen);
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include "Screen.hpp"
class RenameMPLevelScreen : public Screen
{
public:
RenameMPLevelScreen(const std::string& levelName);
void init() override;
void render(int mouseX, int mouseY, float f) override;
private:
std::string m_levelName;
};

View File

@@ -0,0 +1,252 @@
#include "Screen.hpp"
Screen::Screen()
{
}
Screen::~Screen()
{
m_pClickedButton = nullptr;
m_buttons.clear();
}
void Screen::init(Minecraft* pMinecraft, int a3, int a4)
{
m_width = a3;
m_height = a4;
m_pMinecraft = pMinecraft;
m_pFont = pMinecraft->m_pFont;
init();
updateTabButtonSelection();
}
void Screen::init()
{
}
void Screen::buttonClicked(Button* pButton)
{
}
void Screen::confirmResult(bool b, int i)
{
}
bool Screen::handleBackEvent(bool b)
{
return false;
}
bool Screen::isPauseScreen()
{
return true;
}
bool Screen::isErrorScreen()
{
return false;
}
bool Screen::isInGameScreen()
{
return true;
}
void Screen::keyPressed(int key)
{
if (key == '\x1B')//escape
{
m_pMinecraft->setScreen(nullptr);
}
if (m_buttonTabList.size())
{
#ifndef ENH_HIGHLIGHT_BY_HOVER
if (m_pMinecraft->m_options.m_keyBinds[Options::MENU_NEXT].value == key)
{
m_tabButtonIndex++;
if (m_tabButtonIndex == int(m_buttonTabList.size()))
m_tabButtonIndex = 0;
}
if (m_pMinecraft->m_options.m_keyBinds[Options::MENU_PREVIOUS].value == key)
{
m_tabButtonIndex--;
if (m_tabButtonIndex == -1)
m_tabButtonIndex = int(m_buttonTabList.size() - 1);
}
if (m_pMinecraft->m_options.m_keyBinds[Options::MENU_OK].value == key)
{
if (m_buttonTabList[m_tabButtonIndex]->m_bEnabled)
{
m_pMinecraft->m_pSoundEngine->play("random.click");
buttonClicked(m_buttonTabList[m_tabButtonIndex]);
}
}
updateTabButtonSelection();
#endif
}
#ifndef ORIGINAL_CODE
for (auto textInput : m_textInputs)
{
textInput->keyPressed(m_pMinecraft, key);
}
#endif
}
void Screen::mouseClicked(int xPos, int yPos, int d) // d = clicked?
{
if (!d) return;
for (auto button : m_buttons)
{
if (button->clicked(m_pMinecraft, xPos, yPos))
{
m_pClickedButton = button;
m_pMinecraft->m_pSoundEngine->play("random.click");
buttonClicked(button);
}
}
#ifndef ORIGINAL_CODE
for (auto textInput : m_textInputs)
{
textInput->onClick(xPos, yPos);
}
#endif
}
void Screen::mouseReleased(int xPos, int yPos, int d)
{
if (!d) return;
if (m_pClickedButton)
{
m_pClickedButton->released(xPos, yPos);
m_pClickedButton = nullptr;
}
}
void Screen::render(int xPos, int yPos, float unused)
{
for (auto button : m_buttons)
{
button->render(m_pMinecraft, xPos, yPos);
}
#ifndef ORIGINAL_CODE
for (auto textInput : m_textInputs)
{
textInput->tick();
textInput->render();
}
#endif
}
void Screen::tick()
{
}
void Screen::removed()
{
}
void Screen::setSize(int width, int height)
{
m_width = width;
m_height = height;
}
void Screen::updateEvents()
{
if (field_10) return;
for (int i = Mouse::_index + 1; i<int(Mouse::_inputs.size()); i++)
{
Mouse::_index = i;
mouseEvent();
}
for (int i = Keyboard::_index + 1; i<int(Keyboard::_inputs.size()); i++)
{
Keyboard::_index = i;
keyboardEvent();
}
}
void Screen::keyboardEvent()
{
// @UB: This probably behaves in an unexpected way if _inputs is empty
#ifndef ORIGINAL_CODE
if (Keyboard::_inputs.empty() || Keyboard::_index < 0)
return;
#endif
if (Keyboard::_inputs[Keyboard::_index].field_0)
keyPressed(Keyboard::_inputs[Keyboard::_index].field_4);
}
void Screen::mouseEvent()
{
MouseInput& inp = Mouse::_inputs[Mouse::_index];
if (1 <= inp.field_0 && inp.field_0 <= 2)
{
if (inp.field_4 == 1)
mouseClicked(m_width * Mouse::_x / Minecraft::width, m_height * Mouse::_y / Minecraft::height - 1, inp.field_0);
else
mouseReleased(m_width * Mouse::_x / Minecraft::width, m_height * Mouse::_y / Minecraft::height - 1, inp.field_0);
}
}
void Screen::renderBackground(int unk)
{
if (m_pMinecraft->isLevelGenerated())
{
fillGradient(0, 0, m_width, m_height, 0xC0101010, 0xD0101010);
}
else
{
renderDirtBackground(unk);
}
}
void Screen::renderBackground()
{
renderBackground(0);
}
void Screen::renderDirtBackground(int unk)
{
glDisable(GL_FOG);
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/background.png");
glColor4f(1, 1, 1, 1);
Tesselator& t = Tesselator::instance;
t.begin();
t.color(0x404040);
t.vertexUV(0.0f, float(m_height), 0, 0, float(unk) + float(m_height) / 32.0f);
t.vertexUV(float(m_width), float(m_height), 0, float(unk) + float(m_width) / 32.0f, float(unk) + float(m_height) / 32.0f);
t.vertexUV(float(m_width), 0, 0, float(unk) + float(m_width) / 32.0f, 0);
t.vertexUV(0.0f, 0, 0, 0, 0);
t.draw();
}
void Screen::updateTabButtonSelection()
{
#ifndef ENH_HIGHLIGHT_BY_HOVER
for (int i = 0; i < int(m_buttonTabList.size()); i++)
{
m_buttonTabList[i]->field_36 = m_tabButtonIndex == i;
}
#endif
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include "Mouse.hpp"
#include "Keyboard.hpp"
#include "Button.hpp"
#include "TextInputBox.hpp"
class Button;
class Screen : public GuiComponent
{
public:
Screen();
virtual ~Screen();
void init(Minecraft*, int, int);
void updateTabButtonSelection();
void setSize(int width, int height);
virtual void render(int, int, float);
virtual void init();
virtual void updateEvents();
virtual void mouseEvent();
virtual void keyboardEvent();
virtual bool handleBackEvent(bool);
virtual void tick();
virtual void removed();
virtual void renderBackground(int);
virtual void renderBackground();
virtual void renderDirtBackground(int);
virtual bool isPauseScreen();
virtual bool isErrorScreen();
virtual bool isInGameScreen();
virtual void confirmResult(bool, int);
virtual void buttonClicked(Button*);
virtual void mouseClicked(int, int, int);
virtual void mouseReleased(int, int, int);
virtual void keyPressed(int);
public:
int m_width = 1;
int m_height = 1;
bool field_10 = false;
Minecraft* m_pMinecraft;
std::vector<Button*> m_buttons;
std::vector<Button*> m_buttonTabList;
int m_tabButtonIndex = 0;
Font* m_pFont;
Button* m_pClickedButton = 0;
#ifndef ORIGINAL_CODE
std::vector<TextInputBox*> m_textInputs;
#endif
};

View File

@@ -0,0 +1,246 @@
#include "SelectWorldScreen.hpp"
#include "DeleteWorldScreen.hpp"
#include "ProgressScreen.hpp"
#include "StartMenuScreen.hpp"
#include "Util.hpp"
SelectWorldScreen::SelectWorldScreen() :
m_btnDelete (1, "Delete"),
m_btnCreateNew(2, "Create new"),
m_btnBack (3, "Back"),
m_btnUnknown (4, "")
{
m_btnDelete.m_bEnabled = false;
}
void SelectWorldScreen::init()
{
m_pWorldSelectionList = new WorldSelectionList(m_pMinecraft, m_width, m_height);
loadLevelSource();
m_pWorldSelectionList->commit();
m_btnDelete.m_yPos = m_btnBack.m_yPos = m_btnCreateNew.m_yPos = m_height - 28;
m_btnDelete.m_width = m_btnBack.m_width = m_btnCreateNew.m_width = 84;
m_btnDelete.m_height = m_btnBack.m_height = m_btnCreateNew.m_height = 24;
m_btnDelete.m_xPos = m_width / 2 - 130;
m_btnCreateNew.m_xPos = m_width / 2 - 42;
m_btnBack.m_xPos = m_width / 2 + 46;
m_buttons.push_back(&m_btnCreateNew);
m_buttons.push_back(&m_btnBack);
m_buttons.push_back(&m_btnDelete);
field_12C = Mouse::_buttonStates[1] == 0;
m_buttonTabList.push_back(&m_btnUnknown);
m_buttonTabList.push_back(&m_btnDelete);
m_buttonTabList.push_back(&m_btnCreateNew);
m_buttonTabList.push_back(&m_btnBack);
}
bool SelectWorldScreen::isInGameScreen()
{
return true;
}
void SelectWorldScreen::keyPressed(int code)
{
#ifndef ORIGINAL_CODE
if (m_pMinecraft->m_options.m_keyBinds[Options::MENU_OK].value == code)
m_pWorldSelectionList->selectItem(m_pWorldSelectionList->getItemAtPosition(m_width / 2, m_height / 2), false);
m_btnUnknown.field_36 = true;
#endif
if (m_btnUnknown.field_36)
{
if (m_pMinecraft->m_options.m_keyBinds[Options::LEFT].value == code)
m_pWorldSelectionList->stepLeft();
if (m_pMinecraft->m_options.m_keyBinds[Options::RIGHT].value == code)
m_pWorldSelectionList->stepRight();
}
Screen::keyPressed(code);
}
static char g_SelectWorldFilterArray[] = { '/','\n','\r','\x09','\0','\xC','`','?','*','\\','<','>','|','"',':'};
void SelectWorldScreen::tick()
{
#ifndef ORIGINAL_CODE
m_btnUnknown.field_36 = true;
#endif
if (field_130 == 1)
{
// poll the user status to get details about the world name and seed
int userInputStatus = m_pMinecraft->platform()->getUserInputStatus();
if (userInputStatus < 0)
return;
if (userInputStatus != 1)
{
field_130 = 0;
return;
}
std::vector<std::string> userInput = m_pMinecraft->platform()->getUserInput();
std::string levelNickname = Util::stringTrim(userInput[0]);
std::string levelUniqueName = levelNickname;
for (int i = 0; i < sizeof(g_SelectWorldFilterArray); i++)
{
std::string str;
str.push_back(g_SelectWorldFilterArray[i]);
Util::stringReplace(levelUniqueName, str, "");
}
levelUniqueName = getUniqueLevelName(levelUniqueName);
int seed = int(getEpochTimeS());
if (userInput.size() > 1)
{
std::string seedThing = Util::stringTrim(userInput[1]);
if (!seedThing.empty())
{
int num;
if (sscanf(seedThing.c_str(), "%d", &num) > 0)
seed = num;
else
seed = Util::hashCode(seedThing);
}
}
m_pMinecraft->selectLevel(levelUniqueName, levelNickname, seed);
m_pMinecraft->hostMultiplayer();
m_pMinecraft->setScreen(new ProgressScreen);
field_130 = 0;
return;
}
m_pWorldSelectionList->tick();
if (m_pWorldSelectionList->field_90)
{
LevelSummary& ls = m_pWorldSelectionList->m_levelSummary;
m_pMinecraft->selectLevel(ls.field_0, ls.field_18, 0);
m_pMinecraft->hostMultiplayer();
m_pMinecraft->setScreen(new ProgressScreen);
return;
}
// the level summary stuff is unused.
LevelSummary ls;
if (isIndexValid(m_pWorldSelectionList->m_selectedIndex))
ls = m_pWorldSelectionList->m_items[m_pWorldSelectionList->m_selectedIndex];
m_btnDelete.m_bEnabled = isIndexValid(m_pWorldSelectionList->m_selectedIndex);
}
void SelectWorldScreen::render(int mouseX, int mouseY, float f)
{
renderBackground();
#ifndef ORIGINAL_CODE
m_btnUnknown.field_36 = true;
#endif
m_pWorldSelectionList->setComponentSelected(m_btnUnknown.field_36);
if (field_12C)
{
m_pWorldSelectionList->render(mouseX, mouseY, f);
}
else
{
m_pWorldSelectionList->render(0, 0, f);
field_12C = Mouse::_buttonStates[1] == 0;
}
Screen::render(mouseX, mouseY, f);
drawCenteredString(m_pMinecraft->m_pFont, "Select world", m_width / 2, 8, 0xFFFFFFFF);
}
bool SelectWorldScreen::handleBackEvent(bool b)
{
if (b)
return true;
// @TODO: m_pMinecraft->cancelLocateMultiplayer();
m_pMinecraft->setScreen(new StartMenuScreen);
return true;
}
void SelectWorldScreen::buttonClicked(Button* pButton)
{
if (pButton->field_30 == m_btnCreateNew.field_30)
{
m_pMinecraft->platform()->showDialog(AppPlatform::DLG_CREATE_WORLD);
m_pMinecraft->platform()->createUserInput();
field_130 = true;
}
if (pButton->field_30 == m_btnDelete.field_30)
{
LevelSummary ls(m_pWorldSelectionList->m_items[m_pWorldSelectionList->m_selectedIndex]);
m_pMinecraft->setScreen(new DeleteWorldScreen(ls));
}
if (pButton->field_30 == m_btnBack.field_30)
{
// @TODO: m_pMinecraft->cancelLocateMultiplayer();
m_pMinecraft->setScreen(new StartMenuScreen);
}
if (pButton->field_30 == m_btnUnknown.field_30)
{
m_pWorldSelectionList->selectItem(m_pWorldSelectionList->getItemAtPosition(m_width / 2, m_height / 2), false);
}
}
bool SelectWorldScreen::isIndexValid(int idx)
{
if (!m_pWorldSelectionList)
return false;
if (idx < 0)
return false;
if (idx >= m_pWorldSelectionList->getNumberOfItems())
return false;
return true;
}
std::string SelectWorldScreen::getUniqueLevelName(const std::string& in)
{
std::set<std::string> maps;
for (const auto& ls : m_levels)
{
maps.insert(ls.field_0);
}
std::string out = in;
while (maps.find(out) != maps.end())
out += "-";
return out;
}
void SelectWorldScreen::loadLevelSource()
{
m_pMinecraft->getLevelSource()->getLevelList(m_levels);
std::sort(m_levels.begin(), m_levels.end());
for (const auto& level : m_levels)
{
if (level.field_0 == "_LastJoinedServer")
continue;
m_pWorldSelectionList->m_items.push_back(level);
}
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include "Screen.hpp"
#include "WorldSelectionList.hpp"
class SelectWorldScreen : public Screen
{
public:
SelectWorldScreen();
void init() override;
bool isInGameScreen() override;
void keyPressed(int code) override;
void tick() override;
void render(int mouseX, int mouseY, float f) override;
bool handleBackEvent(bool b) override;
void buttonClicked(Button* pButton) override;
bool isIndexValid(int);
std::string getUniqueLevelName(const std::string& in);
void loadLevelSource();
public:
Button m_btnDelete;
Button m_btnCreateNew;
Button m_btnBack;
Button m_btnUnknown;
WorldSelectionList* m_pWorldSelectionList = nullptr;
std::vector<LevelSummary> m_levels;
bool field_12C;
int field_130 = 0;
};

View File

@@ -0,0 +1,171 @@
#include "StartMenuScreen.hpp"
#include "InvalidLicenseScreen.hpp"
#include "OptionsScreen.hpp"
#include "ProgressScreen.hpp"
#include "SelectWorldScreen.hpp"
#include "JoinGameScreen.hpp"
StartMenuScreen::StartMenuScreen() :
m_startButton (2, 0, 0, 160, 24, "Start Game"),
m_joinButton (3, 0, 0, 160, 24, "Join Game"),
m_optionsButton(4, 0, 0, 78, 22, "Options"),
m_testButton (999, 0, 0, 78, 22, "Test"),
m_buyButton (5, 0, 0, 78, 22, "Buy")
//, m_testBox(1, 10, 10, 200, 16, "Insert some text...")
{
}
void StartMenuScreen::_updateLicense()
{
int licenseID = m_pMinecraft->getLicenseId();
if (licenseID < 0)
{
m_optionsButton.m_bEnabled = false;
m_startButton.m_bEnabled = false;
m_joinButton.m_bEnabled = false;
}
else if (licenseID <= 1)
{
m_optionsButton.m_bEnabled = true;
m_startButton.m_bEnabled = true;
m_joinButton.m_bEnabled = true;
}
else
{
m_pMinecraft->setScreen(new InvalidLicenseScreen(licenseID, m_pMinecraft->platform()->hasBuyButtonWhenInvalidLicense()));
}
}
void StartMenuScreen::buttonClicked(Button* pButton)
{
if (pButton->field_30 == m_startButton.field_30)
{
#if defined(DEMO) || !defined(ORIGINAL_CODE)
# ifdef DEMO
# define DEMO_SEED int(getEpochTimeS())
# else
// 1942892620 = long(12345678901324)
# define DEMO_SEED 123456
# endif
m_pMinecraft->selectLevel("_DemoLevel", "_DemoLevel", DEMO_SEED);
m_pMinecraft->hostMultiplayer();
m_pMinecraft->setScreen(new ProgressScreen);
#else
m_pMinecraft->setScreen(new SelectWorldScreen);
#endif
}
else if (pButton->field_30 == m_joinButton.field_30)
{
m_pMinecraft->locateMultiplayer();
m_pMinecraft->setScreen(new JoinGameScreen);
}
else if (pButton->field_30 == m_buyButton.field_30)
{
m_pMinecraft->platform()->buyGame();
}
else if (pButton->field_30 == m_optionsButton.field_30)
{
m_pMinecraft->setScreen(new OptionsScreen);
}
}
void StartMenuScreen::init()
{
int yPos = m_height / 2;
m_joinButton.m_yPos = yPos + 25;
m_startButton.m_yPos = yPos - 3;
yPos += 55;
m_optionsButton.m_yPos = yPos;
m_testButton.m_yPos = yPos;
m_buyButton.m_yPos = yPos;
m_startButton.m_xPos = (m_width - m_startButton.m_width) / 2;
int x1 = m_width - m_joinButton.m_width;
m_joinButton.m_xPos = x1 / 2;
m_optionsButton.m_xPos = x1 / 2;
m_buyButton.m_xPos = x1 / 2 + m_optionsButton.m_width + 4;
m_testButton.m_xPos = x1 / 2 + m_optionsButton.m_width + 4;
// add the buttons to the screen:
m_buttons.push_back(&m_startButton);
m_buttonTabList.push_back(&m_startButton);
m_buttons.push_back(&m_joinButton);
m_buttonTabList.push_back(&m_joinButton);
m_buttons.push_back(&m_optionsButton);
m_buttonTabList.push_back(&m_optionsButton);
#ifdef DEMO
m_buttons.push_back(&m_buyButton);
m_buttonTabList.push_back(&m_buyButton);
#endif
field_154 = "\xFFMojang AB";
field_16C = m_width - 1 - m_pFont->width(field_154);
field_170 = "v0.1.0 alpha"
#ifdef DEMO
" (Demo)"
#endif
;
field_188 = (m_width - m_pFont->width(field_170)) / 2;
//m_testBox.init(m_pFont);
//m_textInputs.push_back(&m_testBox);
_updateLicense();
}
bool StartMenuScreen::isInGameScreen()
{
return false;
}
void StartMenuScreen::render(int a, int b, float c)
{
renderBackground();
Textures* tx = m_pMinecraft->m_pTextures;
int id = tx->loadTexture("gui/title.png", true);
Texture *pTex = tx->getTemporaryTextureData(id);
if (pTex)
{
if (id != tx->m_currBoundTex)
{
glBindTexture(GL_TEXTURE_2D, id);
tx->m_currBoundTex = id;
}
int left = (m_width - pTex->m_width) / 2;
int width = pTex->m_width;
int height = pTex->m_height;
Tesselator& t = Tesselator::instance;
glColor4f(1, 1, 1, 1);
t.begin();
t.vertexUV(float(left), float(height + 4), field_4, 0.0f, 1.0f);
t.vertexUV(float(left + width), float(height + 4), field_4, 1.0f, 1.0f);
t.vertexUV(float(left + width), 4, field_4, 1.0f, 0.0f);
t.vertexUV(float(left), 4, field_4, 0.0f, 0.0f);
t.draw();
}
drawString(m_pFont, field_170, field_188, 62, 0xFFCCCCCC);
drawString(m_pFont, field_154, field_16C, m_height - 10, 0x00FFFFFF);
Screen::render(a, b, c);
}
void StartMenuScreen::tick()
{
_updateLicense();
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "Screen.hpp"
class StartMenuScreen : public Screen
{
public:
StartMenuScreen();
void _updateLicense();
void init() override;
void buttonClicked(Button*) override;
bool isInGameScreen() override;
void render(int, int, float) override;
void tick() override;
private:
Button m_startButton;
Button m_joinButton;
Button m_optionsButton;
Button m_testButton;
Button m_buyButton;
std::string field_154;
int field_16C;
std::string field_170;
int field_188;
//TextInputBox m_testBox;
};

View File

@@ -0,0 +1,233 @@
#include "ScrolledSelectionList.hpp"
#define C_ITEM_WIDTH (220)
ScrolledSelectionList::ScrolledSelectionList(Minecraft* minecraft, int a3, int a4, int a5, int a6, int a7) :
m_pMinecraft(minecraft),
field_C(float(a5)),
field_10(float(a6)),
m_itemHeight(a7),
field_18(a3),
field_1C(a4),
field_20(float(a3))
{
}
void ScrolledSelectionList::setRenderSelection(bool b)
{
m_bRenderSelection = b;
}
int ScrolledSelectionList::getMaxPosition()
{
return field_48 + m_itemHeight * getNumberOfItems();
}
void ScrolledSelectionList::renderHeader(int a, int b, Tesselator& t)
{
}
void ScrolledSelectionList::renderDecorations(int a, int b)
{
}
void ScrolledSelectionList::clickedHeader(int x, int y)
{
}
int ScrolledSelectionList::getItemAtPosition(int x, int y)
{
if (x < field_18 / 2 - C_ITEM_WIDTH / 2)
return -1;
if (x > field_18 / 2 + C_ITEM_WIDTH / 2)
return -1;
return getItemAtYPositionRaw(transformY(y));
}
void ScrolledSelectionList::capYPosition()
{
float maxY = float(getMaxPosition()) - (float(field_10 - field_C) - 4.0f);
if (maxY < 0.0f)
maxY *= 0.5f;
if (field_34 < 0.0f)
field_34 = 0.0f;
if (field_34 > maxY)
field_34 = maxY;
}
void ScrolledSelectionList::render(int mouseX, int mouseY, float f)
{
renderBackground();
int nItems = getNumberOfItems();
if (Mouse::_buttonStates[1])
{
if (float(mouseY) >= field_C && float(mouseY) <= field_10 && mouseY != field_28)
{
int field_2C_old = field_2C;
if (field_2C == -1)
{
field_2C = 1;
}
else if (field_2C == 1)
{
field_3C = mouseY;
field_40 = getTimeMs();
}
else if (field_2C == 0)
{
float diff = float(mouseY) - field_30;
field_34 -= diff;
field_38 += diff;
}
if (field_2C_old >= 0)
field_2C = 0;
field_28 = -1;
}
}
else
{
if (field_2C >= 0)
{
if (fabsf(field_38) < 2.0f)
field_38 = 0.0f;
if (getTimeMs() - field_40 < 300)
{
if (transformY(mouseY) / m_itemHeight >= 0 && m_itemHeight > abs(field_3C - mouseY))
{
selectItem(transformY(mouseY) / m_itemHeight, false);
field_38 = 0.0f;
}
}
}
field_2C = -1;
field_34 -= field_38;
}
field_30 = float(mouseY);
field_38 *= 0.75f;
capYPosition();
glDisable(GL_LIGHTING);
glDisable(GL_FOG);
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/background.png");
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
Tesselator& t = Tesselator::instance;
t.begin();
t.color(0x202020);
t.vertexUV(field_24, field_10, 0.0f, field_24 / 32.0f, (field_10 + float(int(field_34))) / 32.0f);
t.vertexUV(field_20, field_10, 0.0f, field_20 / 32.0f, (field_10 + float(int(field_34))) / 32.0f);
t.vertexUV(field_20, field_C, 0.0f, field_20 / 32.0f, (field_C + float(int(field_34))) / 32.0f);
t.vertexUV(field_24, field_C, 0.0f, field_24 / 32.0f, (field_C + float(int(field_34))) / 32.0f);
t.draw();
int itemX = field_18 / 2 - (C_ITEM_WIDTH - 4) / 2;
int scrollY = int(field_C + 4 - float(int(field_34)));
if (field_45)
renderHeader(itemX, scrollY, t);
for (int i = 0; i < nItems; i++)
{
float itemY = float(field_48 + scrollY + i * m_itemHeight);
if (field_10 < itemY)
continue;
float lowerY = itemY + m_itemHeight - 4;
if (lowerY < field_C)
continue;
if (m_bRenderSelection && isSelectedItem(i))
{
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glDisable(GL_TEXTURE_2D);
t.begin();
t.color(0x808080);
t.vertexUV(float(field_18) / 2.0f - C_ITEM_WIDTH / 2.0f, lowerY + 2.0f, 0.0f, 0.0f, 1.0f);
t.vertexUV(float(field_18) / 2.0f + C_ITEM_WIDTH / 2.0f, lowerY + 2.0f, 0.0f, 1.0f, 1.0f);
t.vertexUV(float(field_18) / 2.0f + C_ITEM_WIDTH / 2.0f, itemY - 2.0f, 0.0f, 1.0f, 0.0f);
t.vertexUV(float(field_18) / 2.0f - C_ITEM_WIDTH / 2.0f, itemY - 2.0f, 0.0f, 0.0f, 0.0f);
t.color(0x000000);
t.vertexUV(float(field_18) / 2.0f - C_ITEM_WIDTH / 2.0f + 1, lowerY + 1.0f, 0.0f, 0.0f, 1.0f);
t.vertexUV(float(field_18) / 2.0f + C_ITEM_WIDTH / 2.0f - 1, lowerY + 1.0f, 0.0f, 1.0f, 1.0f);
t.vertexUV(float(field_18) / 2.0f + C_ITEM_WIDTH / 2.0f - 1, itemY - 1.0f, 0.0f, 1.0f, 0.0f);
t.vertexUV(float(field_18) / 2.0f - C_ITEM_WIDTH / 2.0f + 1, itemY - 1.0f, 0.0f, 0.0f, 0.0f);
t.draw();
glEnable(GL_TEXTURE_2D);
}
renderItem(i, itemX, int(itemY), int(m_itemHeight - 4.0f), t);
}
glDisable(GL_DEPTH_TEST);
renderHoleBackground(0.0f, field_C, 255, 255);
renderHoleBackground(field_10, float(field_1C), 255, 255);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_ALPHA_TEST);
glShadeModel(GL_SMOOTH);
glDisable(GL_TEXTURE_2D);
t.begin();
t.color(0, 0);
t.vertexUV(field_24, field_C + 4.0f, 0.0f, 0.0f, 1.0f);
t.vertexUV(field_20, field_C + 4.0f, 0.0f, 1.0f, 1.0f);
t.color(0, 255);
t.vertexUV(field_20, field_C, 0.0f, 1.0f, 0.0f);
t.vertexUV(field_24, field_C, 0.0f, 0.0f, 0.0f);
t.draw();
t.begin();
t.color(0, 255);
t.vertexUV(field_24, field_10, 0.0f, 0.0f, 1.0f);
t.vertexUV(field_20, field_10, 0.0f, 1.0f, 1.0f);
t.color(0, 0);
t.vertexUV(field_20, field_10 - 4.0f, 0.0f, 1.0f, 0.0f);
t.vertexUV(field_24, field_10 - 4.0f, 0.0f, 0.0f, 0.0f);
t.draw();
renderDecorations(mouseX, mouseY);
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
glEnable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
}
void ScrolledSelectionList::renderHoleBackground(float a, float b, int c, int d)
{
m_pMinecraft->m_pTextures->loadAndBindTexture("gui/background.png");
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
Tesselator& t = Tesselator::instance;
t.begin();
t.color(0x505050, d);
t.vertexUV(0.0f, b, 0.0f, 0.0f, b / 32.0f);
t.vertexUV(float(field_18), b, 0.0f, field_18 / 32.0f, b / 32.0f);
t.color(0x505050, c);
t.vertexUV(float(field_18), a, 0.0f, field_18 / 32.0f, a / 32.0f);
t.vertexUV(0.0f, a, 0.0f, 0.0f, a / 32.0f);
t.draw();
}
void ScrolledSelectionList::setRenderHeader(bool b, int i)
{
field_45 = b;
if (!b)
i = 0;
field_48 = i;
}

View File

@@ -0,0 +1,71 @@
#pragma once
#include "GuiComponent.hpp"
#include "Minecraft.hpp"
class ScrolledSelectionList : public GuiComponent
{
public:
ScrolledSelectionList(Minecraft*, int, int, int, int, int);
virtual void setRenderSelection(bool);
virtual int getNumberOfItems() = 0;
virtual void selectItem(int, bool) = 0;
virtual bool isSelectedItem(int) = 0;
virtual int getMaxPosition();
virtual void renderItem(int, int, int, int, Tesselator&) = 0;
virtual void renderHeader(int, int, Tesselator&);
virtual void renderBackground() = 0;
virtual void renderDecorations(int, int);
virtual void clickedHeader(int x, int y);
virtual int getItemAtPosition(int x, int y);
virtual void capYPosition();
virtual void render(int mouseX, int mouseY, float f);
virtual void renderHoleBackground(float, float, int, int);
void setRenderHeader(bool, int);
// @NOTE: This is inlined.
inline int getItemAtYPositionRaw(int y)
{
if (y < 0)
return -1;
// @NOTE: redundant check
int idx = y / m_itemHeight;
if (idx < 0)
return -1;
if (idx >= getNumberOfItems())
return -1;
return idx;
}
// @NOTE: This is also inlined.
inline int transformY(int y)
{
return int(y - field_C - field_48 + field_34 - 4.0f);
}
public:
Minecraft* m_pMinecraft;
float field_C;
float field_10;
int m_itemHeight;
int field_18;
int field_1C;
float field_20;
float field_24 = 0.0f;
int field_28;
int field_2C = -2;
float field_30 = 0.0f;
float field_34 = 0.0f;
float field_38 = 0.0f;
int field_3C = -1;
int field_40 = 0;
bool m_bRenderSelection = true;
bool field_45 = false;
int field_48 = 0;
};

View File

@@ -0,0 +1,25 @@
#include "SmallButton.hpp"
// @NOTE: Used in the ConfirmScreen.
// I reckon this was used in the OptionsScreen as well, since the button sizes are the same.
SmallButton::SmallButton(int id, int x, int y, const std::string& str) :
Button(id, x, y, 150, 20, str)
{
}
SmallButton::SmallButton(int id, int x, int y, int width, int height, const std::string& str) :
Button(id, x, y, width, height, str)
{
}
SmallButton::SmallButton(int id, int x, int y, Options::Option* pOption, const std::string& str) :
Button(id, x, y, 150, 20, str),
m_pOption(pOption)
{
}
Options::Option* SmallButton::getOption()
{
return m_pOption;
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Button.hpp"
class SmallButton : public Button
{
public:
SmallButton(int id, int x, int y, const std::string& str);
SmallButton(int id, int x, int y, int width, int height, const std::string& str);
SmallButton(int id, int x, int y, Options::Option* pOption, const std::string& str);
Options::Option* getOption();
private:
Options::Option* m_pOption = nullptr;
};

258
source/GUI/TextInputBox.cpp Normal file
View File

@@ -0,0 +1,258 @@
#include "TextInputBox.hpp"
#include "Minecraft.hpp"
#ifndef ORIGINAL_CODE
TextInputBox::TextInputBox(int id, int x, int y) :
TextInputBox(id, x, y, 200, 12, "", "")
{
}
TextInputBox::TextInputBox(int id, int x, int y, int width, int height) :
TextInputBox(id, x, y, width, height, "", "")
{
}
TextInputBox::TextInputBox(int id, int x, int y, int width, int height, const std::string& placeholder) :
TextInputBox(id, x, y, width, height, placeholder, "")
{
}
TextInputBox::TextInputBox(int id, int x, int y, int width, int height, const std::string& placeholder, const std::string& text) :
m_ID(id),
m_xPos(x),
m_yPos(y),
m_width(width),
m_height(height),
m_placeholder(placeholder),
m_text(text)
{
}
void TextInputBox::init(Font* pFont)
{
m_pFont = pFont;
}
void TextInputBox::setEnabled(bool bEnabled)
{
m_bEnabled = true;
}
void TextInputBox::keyPressed(Minecraft* minecraft, int key)
{
if (!m_bFocused)
return;
bool bShiftPressed = minecraft->platform()->shiftPressed();
char chr = '\0';
if (key >= AKEYCODE_A && key <= AKEYCODE_Z)
{
chr = char((key - AKEYCODE_A) + (bShiftPressed ? 'A' : 'a'));
}
if (key >= AKEYCODE_0 && key <= AKEYCODE_9)
{
static const char shiftmap[] = { ')', '!', '@', '#', '$', '%', '^', '&', '*', '(' };
chr = char(bShiftPressed ? shiftmap[key - AKEYCODE_0] : (key - AKEYCODE_0 + '0'));
}
switch (key)
{
case AKEYCODE_DEL:
chr = '\b';
break;
case AKEYCODE_FORWARD_DEL:
chr = '\001';
break;
case AKEYCODE_ARROW_LEFT:
chr = '\002';
break;
case AKEYCODE_ARROW_RIGHT:
chr = '\003';
break;
case AKEYCODE_COMMA:
chr = bShiftPressed ? '<' : ',';
break;
case AKEYCODE_PERIOD:
chr = bShiftPressed ? '>' : ':';
break;
case AKEYCODE_PLUS:
chr = bShiftPressed ? '+' : '=';
break;
case AKEYCODE_MINUS:
chr = bShiftPressed ? '_' : '-';
break;
case AKEYCODE_SEMICOLON:
chr = bShiftPressed ? ':' : ';';
break;
case AKEYCODE_SLASH:
chr = bShiftPressed ? '?' : '/';
break;
case AKEYCODE_GRAVE:
chr = bShiftPressed ? '~' : '`';
break;
case AKEYCODE_BACKSLASH:
chr = bShiftPressed ? '|' : '\\';
break;
case AKEYCODE_APOSTROPHE:
chr = bShiftPressed ? '"' : '\'';
break;
case AKEYCODE_LEFT_BRACKET:
chr = bShiftPressed ? '{' : '[';
break;
case AKEYCODE_RIGHT_BRACKET:
chr = bShiftPressed ? '}' : ']';
break;
}
if (chr)
charPressed(chr);
}
void TextInputBox::tick()
{
if (!m_lastFlashed)
m_lastFlashed = getTimeMs();
if (m_bFocused)
{
if (getTimeMs() > m_lastFlashed + 500)
{
m_lastFlashed += 500;
m_bCursorOn ^= 1;
}
}
else
{
m_bCursorOn = false;
}
}
void TextInputBox::setFocused(bool b)
{
if (m_bFocused == b)
return;
m_bFocused = b;
if (b)
{
m_lastFlashed = getTimeMs();
m_bCursorOn = true;
}
}
void TextInputBox::onClick(int x, int y)
{
setFocused(clicked(x, y));
}
void TextInputBox::charPressed(int k)
{
if (!m_bFocused)
return;
if (k == '\b')
{
if (m_text.empty())
return;
if (m_insertHead <= 0)
return;
if (m_insertHead > int(m_text.size()))
m_insertHead = int(m_text.size());
m_text.erase(m_text.begin() + m_insertHead - 1, m_text.begin() + m_insertHead);
m_insertHead--;
return;
}
if (k == '\001') // delete
{
if (m_text.empty())
return;
if (m_insertHead < 0)
return;
if (m_insertHead >= int(m_text.size()))
return;
m_text.erase(m_text.begin() + m_insertHead, m_text.begin() + m_insertHead + 1);
return;
}
if (k == '\002') // left
{
m_insertHead--;
if (m_insertHead < 0)
m_insertHead = 0;
}
if (k == '\003') // right
{
m_insertHead++;
if (!m_text.empty())
{
if (m_insertHead > int(m_text.size()))
m_insertHead = int(m_text.size());
}
else
{
m_insertHead = 0;
}
}
if (k == '\n' || k == '\r')
{
m_bFocused = false;
return;
}
// other unrenderable character?
if (k < ' ' || k > '~')
return;
// note: the width will increase by the same amount no matter where K is appended
int width = m_pFont->width(m_text + char(k));
if (width < m_width - 2)
{
m_text.insert(m_text.begin() + m_insertHead, k);
m_insertHead++;
}
}
void TextInputBox::render()
{
fill(m_xPos, m_yPos, m_xPos + m_width, m_yPos + m_height, 0xFF000000);
int textYPos = (m_height - 8) / 2;
if (m_text.empty())
drawString(m_pFont, m_placeholder, m_xPos + 1, m_xPos + 1, 0x404040);
else
drawString(m_pFont, m_text, m_xPos + 1, m_xPos + textYPos, 0xFFFFFF);
if (m_bCursorOn)
{
int xPos = 1;
std::string substr = m_text.substr(0, m_insertHead);
xPos += m_pFont->width(substr);
drawString(m_pFont, "_", m_xPos + xPos, m_xPos + textYPos + 1, 0xFFFFFF);
}
}
bool TextInputBox::clicked(int xPos, int yPos)
{
if (!m_bEnabled) return false;
if (xPos < m_xPos) return false;
if (yPos < m_yPos) return false;
if (xPos >= m_xPos + m_width) return false;
if (yPos >= m_yPos + m_height) return false;
return true;
}
#endif

View File

@@ -0,0 +1,46 @@
#pragma once
#include "Utils.hpp"
#include "GuiComponent.hpp"
class Minecraft;
// @NOTE: This is NOT original Mojang code.
#ifndef ORIGINAL_CODE
class TextInputBox : public GuiComponent
{
public:
TextInputBox(int id, int x, int y);
TextInputBox(int id, int x, int y, int width, int height);
TextInputBox(int id, int x, int y, int width, int height, const std::string& placeholder);
TextInputBox(int id, int x, int y, int width, int height, const std::string& placeholder, const std::string& text);
void init(Font* pFont);
void setEnabled(bool bEnabled);
void keyPressed(Minecraft*, int key);
void charPressed(int chr);
void render();
void tick();
void setFocused(bool b);
void onClick(int x, int y);
bool clicked(int x, int y);
private:
int m_ID = 0;
int m_xPos = 0;
int m_yPos = 0;
int m_width = 0;
int m_height = 0;
std::string m_placeholder;
std::string m_text;
bool m_bFocused = false;
bool m_bEnabled = true;
bool m_bCursorOn = true;
int m_insertHead = 0;
int m_lastFlashed = 0;
Font* m_pFont = nullptr;
};
#endif

View File

@@ -0,0 +1,209 @@
#include "WorldSelectionList.hpp"
#include "Utils.hpp"
static float WorldSelectionList_Static1(float a, float b, float c, float d)
{
float x1 = 2 * (a / b), x2;
if (x1 < 1.0f)
x2 = c + ((d - c) * 0.5f) * x1 * x1;
else
x2 = c + ((d - c) * -0.5f) * ((x1 - 1.0f) * (x1 - 3.0f) - 1.0f);
return x2;
}
WorldSelectionList::WorldSelectionList(Minecraft* minecraft, int a, int b) :
RolledSelectionList(minecraft, a, b, 0, a, 26, b - 32, 120)
{
field_68 = b;
}
bool WorldSelectionList::capXPosition()
{
if (RolledSelectionList::capXPosition())
{
field_D8 = 0;
return true;
}
return false;
}
void WorldSelectionList::tick()
{
RolledSelectionList::tick();
field_D0++;
if (Mouse::_buttonStates[1] || !field_28)
return;
m_selectedIndex = -1;
if (field_D8 == 1)
{
field_54 += 1.0f;
if (field_54 != field_58)
{
tweenInited();
return;
}
field_D8 = 0;
field_38 = 0.0f;
field_34 = field_30 = field_60;
m_selectedIndex = getItemAtPosition(field_18 / 2, field_1C / 2);
return;
}
float abs_field38 = Mth::abs(field_38);
if (abs_field38 >= 5.0f)
{
field_38 *= 0.9f;
return;
}
field_38 *= 0.8f;
if (abs_field38 < 1.0f && field_28 < 0)
{
float x1 = float((field_18 - m_itemWidth) / 2) + field_30;
int x2 = getItemAtXPositionRaw(int(x1 - 10.0f * field_38));
float x3 = float(m_itemWidth * x2) - x1;
if (x3 < -float(m_itemWidth / 2))
x3 += float(m_itemWidth);
if (Mth::abs(x3) > 1.0f || abs_field38 >= 0.1f)
{
field_5C = field_30;
field_60 = field_30 + x3;
field_54 = 0.0f;
field_D8 = 1;
field_58 = float(Mth::Min(7, int(float(0.25f * Mth::abs(x3))) + 1));
tweenInited();
return;
}
m_selectedIndex = getItemAtPosition(field_18 / 2, field_1C / 2);
}
}
int WorldSelectionList::getNumberOfItems()
{
return int(m_items.size());
}
void WorldSelectionList::selectItem(int index, bool b)
{
if (m_selectedIndex >= 0 && m_selectedIndex == index && !field_90)
{
field_90 = 1;
m_levelSummary = m_items[index];
}
}
bool WorldSelectionList::isSelectedItem(int index)
{
return m_selectedIndex == index;
}
float WorldSelectionList::getPos(float f)
{
if (field_D8 != 1)
return RolledSelectionList::getPos(f);
return Lerp(WorldSelectionList_Static1(field_54, field_58, field_5C, field_60),
WorldSelectionList_Static1(field_54 + 1.0f, field_58, field_5C, field_60),
f);
}
void WorldSelectionList::touched()
{
field_D8 = false;
}
void WorldSelectionList::renderItem(int index, int xPos, int yPos, int width, Tesselator& t)
{
int xCenter = xPos + m_itemWidth / 2;
float mult = Max(1.1f - 0.0055f * float(abs(field_18 / 2 - xCenter)), 0.2f);
if (mult > 1.0f)
mult = 1.0f;
int color1 = 0x010101 * int(mult * 255.0f);
int color2 = 0x010101 * int(mult * 140.0f);
std::vector<std::string> details = m_vvs[index];
drawString(m_pMinecraft->m_pFont, details[0], xCenter + 5 - m_itemWidth / 2, yPos + 50, color1);
drawString(m_pMinecraft->m_pFont, details[1], xCenter + 5 - m_itemWidth / 2, yPos + 60, color2);
drawString(m_pMinecraft->m_pFont, details[2], xCenter + 5 - m_itemWidth / 2, yPos + 70, color2);
m_pMinecraft->m_pTextures->loadAndBindTexture(m_previewImages[index]);
// @NOTE: useless assignment of color
t.color(0.3f, 1.0f, 0.2f);
t.begin();
t.color(color1);
float y = float(yPos) - 6.0f;
t.vertexUV(float(xCenter - 32), y, this->field_4, 0.0f, 0.0f);
t.vertexUV(float(xCenter - 32), y + 48.0f, this->field_4, 0.0f, 1.0f);
t.vertexUV(float(xCenter + 32), y + 48.0f, this->field_4, 1.0f, 1.0f);
t.vertexUV(float(xCenter + 32), y, this->field_4, 1.0f, 0.0f);
t.draw();
}
void WorldSelectionList::renderBackground()
{
}
void WorldSelectionList::commit()
{
for (const auto& item : m_items)
{
// @NOTE: this string stream crap is unused.
// Weirdly Java Edition Beta 1.3 did not have world previews, so its interesting to see PE try
std::stringstream ss;
ss << item.field_18 << "/preview.png";
m_previewImages.push_back("gui/default_world.png");
std::vector<std::string> vs;
vs.push_back(item.field_18);
vs.push_back(m_pMinecraft->platform()->getDateString(item.field_30));
vs.push_back(item.field_0);
m_vvs.push_back(vs);
}
}
void WorldSelectionList::stepLeft()
{
if (m_selectedIndex <= 0)
return;
field_5C = field_30;
field_D8 = 1;
field_60 = field_5C - float(m_itemWidth);
field_54 = 0.0f;
field_58 = 8.0f;
tweenInited();
}
void WorldSelectionList::stepRight()
{
if (m_selectedIndex < 0 || m_selectedIndex >= getNumberOfItems() - 1)
return;
field_5C = field_30;
field_D8 = 1;
field_60 = field_5C + float(m_itemWidth);
field_54 = 0.0f;
field_58 = 8.0f;
tweenInited();
}
void WorldSelectionList::tweenInited()
{
field_38 =
WorldSelectionList_Static1(field_54, field_58, field_5C, field_60) -
WorldSelectionList_Static1(field_54 + 1.0f, field_58, field_5C, field_60);
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include "RolledSelectionList.hpp"
class WorldSelectionList : public RolledSelectionList
{
public:
WorldSelectionList(Minecraft*, int, int);
bool capXPosition() override;
void tick() override;
int getNumberOfItems() override;
void selectItem(int, bool) override;
bool isSelectedItem(int) override;
float getPos(float) override;
void touched() override;
void renderItem(int, int, int, int, Tesselator&) override;
void renderBackground() override;
void commit();
void stepLeft();
void stepRight();
void tweenInited();
public:
float field_54;
float field_58;
float field_5C;
float field_60;
int m_selectedIndex;
int field_68;
std::vector<LevelSummary> m_items;
std::vector<std::vector<std::string> > m_vvs;
std::vector<std::string> m_previewImages;
bool field_90 = false;
LevelSummary m_levelSummary;
int field_CC = -1;
int field_D0 = 0;
int field_D4;
int field_D8 = 0;
};

View File

@@ -0,0 +1,110 @@
#include "GameMode.hpp"
#include "Minecraft.hpp"
GameMode::GameMode(Minecraft* pMinecraft)
{
m_pMinecraft = pMinecraft;
}
GameMode::~GameMode()
{
}
void GameMode::initLevel(Level* pLevel)
{
}
void GameMode::startDestroyBlock(int x, int y, int z, int i)
{
destroyBlock(x, y, z, i);
}
bool GameMode::destroyBlock(int x, int y, int z, int i)
{
Level* pLevel = m_pMinecraft->m_pLevel;
Tile* pTile = Tile::tiles[pLevel->getTile(x, y, z)];
int tileData = pLevel->getData(x, y, z);
bool bChanged = pLevel->setTile(x, y, z, 0);
if (pTile && bChanged)
{
m_pMinecraft->m_pSoundEngine->play("step." + pTile->m_pSound->m_name,
float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f,
0.5f * (1.0f + pTile->m_pSound->field_18), 0.8f * pTile->m_pSound->field_1C);
pTile->destroy(pLevel, x, y, z, tileData);
return true;
}
return false;
}
void GameMode::continueDestroyBlock(int x, int y, int z, int t)
{
}
void GameMode::stopDestroyBlock()
{
}
void GameMode::tick()
{
}
void GameMode::render(float f)
{
}
float GameMode::getPickRange()
{
return 7.5f;
}
LocalPlayer* GameMode::createPlayer(Level* pLevel)
{
return new LocalPlayer(m_pMinecraft, pLevel, m_pMinecraft->m_pUser, pLevel->m_pDimension->field_50);
}
void GameMode::initPlayer(Player* pPlayer)
{
}
void GameMode::adjustPlayer(Player* pPlayer)
{
}
bool GameMode::canHurtPlayer()
{
return false;
}
void GameMode::interact(Player* player, Entity* entity)
{
player->interact(entity);
}
void GameMode::attack(Player* player, Entity* entity)
{
player->attack(entity);
}
int GameMode::handleInventoryMouseClick(int a, int b, int c, Player* player)
{
return 0;
}
void GameMode::handleCloseInventory(int a, Player* player)
{
}
bool GameMode::isCreativeType()
{
return true;
}
bool GameMode::isSurvivalType()
{
return false;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include "Level.hpp"
#include "ItemInstance.hpp"
class Minecraft;
class GameMode
{
public:
GameMode(Minecraft* pMinecraft);
virtual ~GameMode();
virtual void initLevel(Level*);
virtual void startDestroyBlock(int, int, int, int);
virtual bool destroyBlock(int, int, int, int);
virtual void continueDestroyBlock(int, int, int, int);
virtual void stopDestroyBlock();
virtual void tick();
virtual void render(float f);
virtual float getPickRange();
virtual bool useItem(Player*, Level*, ItemInstance*);
virtual bool useItemOn(Player*, Level*, ItemInstance*, int, int, int, int);
virtual LocalPlayer* createPlayer(Level*);
virtual void initPlayer(Player*);
virtual void adjustPlayer(Player*);
virtual bool canHurtPlayer();
virtual void interact(Player*, Entity*);
virtual void attack(Player*, Entity*);
virtual int handleInventoryMouseClick(int, int, int, Player*);
virtual void handleCloseInventory(int, Player*);
virtual bool isCreativeType();
virtual bool isSurvivalType();
public:
Minecraft* m_pMinecraft;
uint8_t field_8 = 0;
};

View File

@@ -0,0 +1,171 @@
#include "SurvivalMode.hpp"
#include "Minecraft.hpp"
SurvivalMode::SurvivalMode(Minecraft* pMC) : GameMode(pMC)
{
}
void SurvivalMode::initPlayer(Player* p)
{
p->m_yaw = -180.0;
}
void SurvivalMode::startDestroyBlock(int x, int y, int z, int i)
{
TileID tile = m_pMinecraft->m_pLevel->getTile(x, y, z);
if (tile <= 0)
return;
#ifdef ENH_INSTA_BREAK
destroyBlock(x, y, z, i);
return;
#endif
if (field_18 == 0.0f)
{
Tile::tiles[tile]->attack(m_pMinecraft->m_pLevel, x, y, z, m_pMinecraft->m_pLocalPlayer);
}
if (Tile::tiles[tile]->getDestroyProgress(m_pMinecraft->m_pLocalPlayer) >= 1.0f)
{
destroyBlock(x, y, z, i);
}
}
bool SurvivalMode::destroyBlock(int x, int y, int z, int i)
{
m_pMinecraft->m_pParticleEngine->destroy(x, y, z);
TileID tile = m_pMinecraft->m_pLevel->getTile(x, y, z);
int data = m_pMinecraft->m_pLevel->getData(x, y, z);
if (!GameMode::destroyBlock(x, y, z, i))
return false;
//@HUH: check too late?
bool bCanDestroy = m_pMinecraft->m_pLocalPlayer->canDestroy(Tile::tiles[tile]);
if (bCanDestroy)
{
Tile::tiles[tile]->playerDestroy(m_pMinecraft->m_pLevel, m_pMinecraft->m_pLocalPlayer, x, y, z, data);
if (m_pMinecraft->isOnline())
{
m_pMinecraft->m_pRakNetInstance->send(new RemoveBlockPacket(m_pMinecraft->m_pLocalPlayer->m_EntityID, x, y, z));
}
}
return true;
}
void SurvivalMode::continueDestroyBlock(int x, int y, int z, int i)
{
if (field_24 > 0)
{
field_24--;
return;
}
if (m_destroyingX != x || m_destroyingY != y || m_destroyingZ != z)
{
field_18 = 0.0f;
field_1C = 0.0f;
field_20 = 0;
m_destroyingX = x;
m_destroyingY = y;
m_destroyingZ = z;
return;
}
TileID tile = m_pMinecraft->m_pLevel->getTile(m_destroyingX, m_destroyingY, m_destroyingZ);
if (!tile)
return;
Tile* pTile = Tile::tiles[tile];
float destroyProgress = pTile->getDestroyProgress(m_pMinecraft->m_pLocalPlayer);
field_18 += 16.0f * destroyProgress;
field_20++;
if ((field_20 & 3) == 1)
{
m_pMinecraft->m_pSoundEngine->play("step." + pTile->m_pSound->m_name,
float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f,
0.5f * (1.0f + pTile->m_pSound->field_18), 0.8f * pTile->m_pSound->field_1C);
}
if (field_18 >= 1.0f)
{
destroyBlock(m_destroyingX, m_destroyingY, m_destroyingZ, i);
field_20 = 0;
field_24 = 5;
field_18 = 0.0f;
field_1C = 0.0f;
}
}
void SurvivalMode::stopDestroyBlock()
{
field_18 = 0.0f;
field_24 = 0;
}
void SurvivalMode::tick()
{
field_1C = field_18;
}
void SurvivalMode::render(float f)
{
if (field_18 <= 0.0f)
{
m_pMinecraft->m_gui.field_8 = 0.0f;
m_pMinecraft->m_pLevelRenderer->field_10 = 0.0f;
}
else
{
float x = field_1C + (field_18 - field_1C) * f;
m_pMinecraft->m_gui.field_8 = x;
m_pMinecraft->m_pLevelRenderer->field_10 = x;
}
}
float SurvivalMode::getPickRange()
{
return 5.0f;
}
bool SurvivalMode::isCreativeType()
{
return false;
}
bool SurvivalMode::isSurvivalType()
{
return true;
}
bool GameMode::useItem(Player* player, Level* level, ItemInstance* instance)
{
int oldAmount = instance->m_amount;
if (instance == instance->use(level, player))
return instance->m_amount != oldAmount;
return true;
}
bool GameMode::useItemOn(Player* player, Level* level, ItemInstance* instance, int x, int y, int z, int d)
{
TileID tile = level->getTile(x, y, z);
if (tile > 0 && Tile::tiles[tile]->use(level, x, y, z, player))
return true;
if (instance)
return instance->useOn(player, level, x, y, z, d);
return false;
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "GameMode.hpp"
class SurvivalMode : public GameMode
{
public:
SurvivalMode(Minecraft*);
virtual void startDestroyBlock(int x, int y, int z, int i) override;
virtual bool destroyBlock(int x, int y, int z, int i);
virtual void continueDestroyBlock(int x, int y, int z, int i);
virtual void stopDestroyBlock() override;
virtual void tick() override;
virtual void render(float f) override;
virtual float getPickRange() override;
virtual bool isCreativeType() override;
virtual bool isSurvivalType() override;
virtual void initPlayer(Player*) override;
public:
int m_destroyingX = -1;
int m_destroyingY = -1;
int m_destroyingZ = -1;
float field_18 = 0.0f;
float field_1C = 0.0f;
int field_20 = 0;
int field_24 = 0;
};

198
source/Inventory.cpp Normal file
View File

@@ -0,0 +1,198 @@
#include "Inventory.hpp"
#include "Item.hpp"
#ifdef DEMO
static void MoveItemToSlot(int* pItems, int item, int index)
{
if (index < 0 || index >= C_MAX_INVENTORY_ITEMS)
return;
if (pItems[index] == item)
return;
// search for the item, if it doesn't exist, return
int i = 0;
for (; i < C_MAX_INVENTORY_ITEMS; i++)
{
if (pItems[i] == item)
break;
}
if (i == C_MAX_INVENTORY_ITEMS)
return;
// swap the slot where our `item` was, and the slot at the index
int oldItem = pItems[index];
pItems[index] = pItems[i];
pItems[i] = oldItem;
#ifndef ORIGINAL_CODE
if (item > 0)
#endif
printf("adding item: %s to %d\n", Tile::tiles[item]->getDescriptionId().c_str(), index);
}
static void ShuffleInventoryForDemo(int* pHotbar, int* pItems)
{
pHotbar[0] = Tile::wood->m_ID;
pHotbar[1] = Tile::stoneBrick->m_ID;
pHotbar[2] = Tile::sandStone->m_ID;
pHotbar[3] = Tile::dirt->m_ID;
pHotbar[4] = Tile::redBrick->m_ID;
pHotbar[5] = Tile::rock->m_ID;
pHotbar[6] = Tile::torch->m_ID;
pHotbar[7] = Tile::ladder->m_ID;
#ifdef ENH_ENABLE_9TH_SLOT
pHotbar[8] = Tile::rose->m_ID;
#endif
MoveItemToSlot(pItems, pHotbar[0], 27);
MoveItemToSlot(pItems, pHotbar[1], 28);
MoveItemToSlot(pItems, pHotbar[2], 29);
MoveItemToSlot(pItems, pHotbar[3], 30);
MoveItemToSlot(pItems, pHotbar[4], 31);
MoveItemToSlot(pItems, pHotbar[5], 32);
MoveItemToSlot(pItems, pHotbar[6], 33);
MoveItemToSlot(pItems, pHotbar[7], 34);
MoveItemToSlot(pItems, Tile::flower->m_ID, 35);
MoveItemToSlot(pItems, Tile::cloth_10->m_ID, 18);
MoveItemToSlot(pItems, Tile::cloth_20->m_ID, 19);
MoveItemToSlot(pItems, Tile::cloth_30->m_ID, 20);
MoveItemToSlot(pItems, Tile::cloth_40->m_ID, 21);
MoveItemToSlot(pItems, Tile::cloth_50->m_ID, 22);
MoveItemToSlot(pItems, Tile::cloth_60->m_ID, 23);
MoveItemToSlot(pItems, Tile::cloth_70->m_ID, 24);
MoveItemToSlot(pItems, Tile::sand->m_ID, 25);
MoveItemToSlot(pItems, Tile::glass->m_ID, 26);
MoveItemToSlot(pItems, Tile::mushroom1->m_ID, 1);
MoveItemToSlot(pItems, Tile::obsidian->m_ID, 8);
#ifndef ORIGINAL_CODE
// @NOTE: For Testing
//pHotbar[1] = Item::camera->m_itemID;
//pHotbar[2] = Tile::tnt->m_ID;
//pHotbar[3] = Tile::water->m_ID;
//pHotbar[4] = Tile::lava->m_ID;
#endif
}
#endif
Inventory::Inventory(Player* pPlayer)
{
m_pPlayer = pPlayer;
m_SelectedHotbarSlot = 0;
for (int i = 0; i < C_MAX_HOTBAR_ITEMS; i++)
m_hotbar[i] = -1;
for (int i = 0; i < C_MAX_INVENTORY_ITEMS; i++)
m_items[i] = -1;
// @NOTE: This layout of the hotbar and inventory can be seen in the following video,
// titled "Minecraft - Pocket Edition on Xperia Play".
// https://www.youtube.com/watch?v=jO-y5wzmK4E
m_hotbar[0] = Tile::wood->m_ID;
m_hotbar[1] = Tile::cloth_10->m_ID;
m_hotbar[2] = Tile::cloth_20->m_ID;
m_hotbar[3] = Tile::cloth_30->m_ID;
m_hotbar[4] = Tile::cloth_40->m_ID;
m_hotbar[5] = Tile::cloth_50->m_ID;
m_hotbar[6] = Tile::cloth_60->m_ID;
m_hotbar[7] = Tile::ladder->m_ID;
// slot 8 missing. I assume that's the "..." button
m_items[0] = Tile::rock->m_ID;
m_items[1] = Tile::stoneBrick->m_ID;
m_items[2] = Tile::sandStone->m_ID;
m_items[3] = Tile::wood->m_ID;
m_items[4] = Tile::treeTrunk->m_ID;
m_items[5] = Tile::goldBlock->m_ID;
m_items[6] = Tile::ironBlock->m_ID;
m_items[7] = Tile::emeraldBlock->m_ID;
m_items[8] = Tile::redBrick->m_ID;
m_items[9] = Tile::leaves->m_ID;
m_items[10] = Tile::cloth_10->m_ID;
m_items[11] = Tile::cloth_20->m_ID;
m_items[12] = Tile::cloth_30->m_ID;
m_items[13] = Tile::cloth_40->m_ID;
m_items[14] = Tile::cloth_50->m_ID;
m_items[15] = Tile::cloth_60->m_ID;
m_items[16] = Tile::cloth_70->m_ID;
m_items[17] = Tile::glass->m_ID;
m_items[18] = Tile::cloth_01->m_ID;
m_items[19] = Tile::cloth_11->m_ID;
m_items[20] = Tile::cloth_21->m_ID;
m_items[21] = Tile::cloth_31->m_ID;
m_items[22] = Tile::cloth_41->m_ID;
m_items[23] = Tile::stairs_wood->m_ID;
m_items[24] = Tile::stairs_stone->m_ID;
m_items[25] = Tile::stoneSlabHalf->m_ID;
m_items[26] = Tile::sand->m_ID;
m_items[27] = Tile::ladder->m_ID;
m_items[28] = Tile::torch->m_ID;
m_items[29] = Tile::flower->m_ID;
m_items[30] = Tile::rose->m_ID;
m_items[31] = Tile::mushroom1->m_ID;
m_items[32] = Tile::mushroom2->m_ID;
m_items[33] = Tile::reeds->m_ID;
m_items[34] = Tile::obsidian->m_ID;
m_items[35] = Tile::dirt->m_ID;
#ifdef DEMO
ShuffleInventoryForDemo(m_hotbar, m_items);
#endif
#ifdef ENH_EXTRA_ITEMS_IN_INV
// populate the 5th row now with items that might be of interest
m_items[36] = Tile::tnt->m_ID;
m_items[37] = Item::camera->m_itemID;
m_items[38] = Item::door_wood->m_itemID;
m_items[39] = Tile::gravel->m_ID;
m_items[40] = Tile::water->m_ID;
#endif
}
#ifdef ENH_ENABLE_9TH_SLOT
#define HOTBAR_DIFF 0
#else
#define HOTBAR_DIFF 1
#endif
int Inventory::getSelectionSize()
{
return C_MAX_HOTBAR_ITEMS;
}
int Inventory::getSelectionSlotItemId(int slot)
{
if (slot >= 0 && slot < C_MAX_HOTBAR_ITEMS - HOTBAR_DIFF)
return m_hotbar[slot];
if (slot > C_MAX_HOTBAR_ITEMS + C_MAX_INVENTORY_ITEMS - 1 || slot < 0)
return -1;
return m_items[slot - C_MAX_HOTBAR_ITEMS];
}
void Inventory::setSelectionSlotItemId(int slotNo, int item)
{
if (slotNo >= 0 && slotNo < C_MAX_HOTBAR_ITEMS - HOTBAR_DIFF)
m_hotbar[slotNo] = item;
}
int Inventory::getSelectedItemId()
{
return getSelectionSlotItemId(m_SelectedHotbarSlot);
}
void Inventory::selectSlot(int slotNo)
{
if (slotNo < 0 || slotNo >= C_MAX_HOTBAR_ITEMS - HOTBAR_DIFF)
return;
m_SelectedHotbarSlot = slotNo;
}

33
source/Inventory.hpp Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include "Player.hpp"
class Player; // in case we're included from Player.hpp
#define C_MAX_HOTBAR_ITEMS (9)
#ifdef ENH_EXTRA_ITEMS_IN_INV
#define C_MAX_INVENTORY_ITEMS (36+9)
#else
#define C_MAX_INVENTORY_ITEMS (36)
#endif
class Inventory
{
public:
Inventory(Player*);
int getSelectionSize();
int getSelectionSlotItemId(int slotNo);
int getSelectedItemId();
void selectSlot(int slotNo);
void setSelectionSlotItemId(int slotNo, int item);
public:
int m_SelectedHotbarSlot;
Player* m_pPlayer;
int m_hotbar[C_MAX_HOTBAR_ITEMS];
int m_items [C_MAX_INVENTORY_ITEMS];
};

View File

@@ -0,0 +1,357 @@
#include <RakPeer.h>
#include "ClientSideNetworkHandler.hpp"
// This lets you make the client shut up and not log events in the debug console.
#define VERBOSE_CLIENT
#if defined(ORIGINAL_CODE) || defined(VERBOSE_CLIENT)
#define puts_ignorable(str) puts(str)
#define printf_ignorable(str, ...) printf(str, __VA_ARGS__)
#else
#define puts_ignorable(str)
#define printf_ignorable(str, ...)
#endif
ClientSideNetworkHandler::ClientSideNetworkHandler(Minecraft* pMinecraft, RakNetInstance* pRakNetInstance)
{
m_pMinecraft = pMinecraft;
m_pRakNetInstance = pRakNetInstance;
m_pServerPeer = m_pRakNetInstance->getPeer();
}
void ClientSideNetworkHandler::levelGenerated(Level* level)
{
m_pLevel = level;
requestNextChunk();
}
void ClientSideNetworkHandler::onConnect(const RakNet::RakNetGUID& rakGuid) // server guid
{
RakNet::RakNetGUID localGuid = ((RakNet::RakPeer*)m_pServerPeer)->GetMyGUID();
printf_ignorable("onConnect, server guid: %s, local guid: %s\n", rakGuid.ToString(), localGuid.ToString());
m_serverGUID = rakGuid;
LoginPacket* pLoginPkt = new LoginPacket;
pLoginPkt->m_str = RakNet::RakString(m_pMinecraft->m_pUser->field_0.c_str());
m_pRakNetInstance->send(pLoginPkt);
}
void ClientSideNetworkHandler::onUnableToConnect()
{
puts_ignorable("onUnableToConnect");
}
void ClientSideNetworkHandler::onDisconnect(const RakNet::RakNetGUID& rakGuid)
{
puts_ignorable("onDisconnect");
if (m_pLevel)
m_pLevel->field_11 = false;
m_pMinecraft->m_gui.addMessage("Disconnected from server");
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MessagePacket* pMsgPkt)
{
puts_ignorable("MessagePacket");
m_pMinecraft->m_gui.addMessage(pMsgPkt->m_str.C_String());
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, StartGamePacket* pStartGamePkt)
{
puts_ignorable("StartGamePacket");
m_pMinecraft->getLevelSource()->deleteLevel("_LastJoinedServer");
m_pLevel = new Level(
m_pMinecraft->getLevelSource()->selectLevel("_LastJoinedServer", true),
"temp",
pStartGamePkt->field_4,
pStartGamePkt->field_8);
m_pLevel->field_11 = true;
auto pLocalPlayer = new LocalPlayer(m_pMinecraft, m_pLevel, m_pMinecraft->m_pUser, m_pLevel->m_pDimension->field_50);
pLocalPlayer->m_guid = ((RakNet::RakPeer*)m_pServerPeer)->GetMyGUID();
pLocalPlayer->m_EntityID = pStartGamePkt->field_C;
pLocalPlayer->moveTo(
pStartGamePkt->field_10,
pStartGamePkt->field_14,
pStartGamePkt->field_18,
pLocalPlayer->m_yaw,
pLocalPlayer->m_pitch);
m_pMinecraft->setLevel(m_pLevel, "ClientSideNetworkHandler -> setLevel", pLocalPlayer);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, AddPlayerPacket* pAddPlayerPkt)
{
puts_ignorable("AddPlayerPacket");
if (!m_pLevel) return;
Player* pPlayer = new Player(m_pLevel);
pPlayer->m_EntityID = pAddPlayerPkt->m_id;
m_pLevel->addEntity(pPlayer);
pPlayer->moveTo(
pAddPlayerPkt->m_x,
pAddPlayerPkt->m_y,
pAddPlayerPkt->m_z,
pPlayer->m_yaw,
pPlayer->m_pitch);
pPlayer->m_name = pAddPlayerPkt->m_name;
pPlayer->m_guid = pAddPlayerPkt->m_guid;
m_pMinecraft->m_gui.addMessage(pPlayer->m_name + " joined the game");
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, RemoveEntityPacket* pRemoveEntityPkt)
{
if (!m_pLevel) return;
Entity* pEnt = m_pLevel->getEntity(pRemoveEntityPkt->m_id);
#if defined(ORIGINAL_CODE) || UINTPTR_MAX == UINT32_MAX
// @NOTE: On x64 systems, this won't print the right things.
printf_ignorable("RemoveEntityPacket %d %d", pEnt, m_pMinecraft->m_pLocalPlayer);
#else
printf_ignorable("RemoveEntityPacket %p %p", pEnt, m_pMinecraft->m_pLocalPlayer);
#endif
if (pEnt)
m_pLevel->removeEntity(pEnt);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, MovePlayerPacket* packet)
{
if (!m_pLevel) return;
Entity* pEntity = m_pLevel->getEntity(packet->m_id);
if (!pEntity)
{
LogMsg("No player with id %d", packet->m_id);
return;
}
pEntity->lerpTo(packet->m_x, packet->m_y, packet->m_z, packet->m_yaw, packet->m_pitch, 3);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlaceBlockPacket* pPlaceBlockPkt)
{
puts_ignorable("PlaceBlockPacket");
Player* pPlayer = (Player*)m_pLevel->getEntity(pPlaceBlockPkt->m_playerID);
if (!pPlayer)
{
LogMsg("No player with id %d", pPlaceBlockPkt->m_playerID);
return;
}
pPlayer->swing();
// @BUG: Not buffering the update.
if (!areAllChunksLoaded())
return;
int x = pPlaceBlockPkt->m_x;
int y = pPlaceBlockPkt->m_y;
int z = pPlaceBlockPkt->m_z;
int tile = pPlaceBlockPkt->m_tile;
int face = pPlaceBlockPkt->m_face;
if (!m_pLevel->mayPlace(tile, x, y, z, true))
return;
Tile* pTile = Tile::tiles[tile];
if (!m_pLevel->setTile(x, y, z, tile))
return;
Tile::tiles[tile]->setPlacedOnFace(m_pLevel, x, y, z, face);
Tile::tiles[tile]->setPlacedBy(m_pLevel, x, y, z, pPlayer);
const Tile::SoundType* pSound = pTile->m_pSound;
m_pLevel->playSound(float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f, "step." + pSound->m_name, 0.5f * (1.0f + pSound->field_18), 0.8f * pSound->field_1C);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, RemoveBlockPacket* pRemoveBlockPkt)
{
puts_ignorable("RemoveBlockPacket");
Player* pPlayer = (Player*)m_pLevel->getEntity(pRemoveBlockPkt->m_playerID);
if (!pPlayer)
{
LogMsg("No player with id %d", pRemoveBlockPkt->m_playerID);
return;
}
pPlayer->swing();
// @BUG: Not buffering the update.
if (!areAllChunksLoaded())
return;
int x = pRemoveBlockPkt->m_x;
int y = pRemoveBlockPkt->m_y;
int z = pRemoveBlockPkt->m_z;
Tile* pTile = Tile::tiles[m_pLevel->getTile(x, y, z)];
int data = m_pLevel->getData(x, y, z);
bool setTileResult = m_pLevel->setTile(x, y, z, TILE_AIR);
if (pTile && setTileResult)
{
const Tile::SoundType* pSound = pTile->m_pSound;
m_pLevel->playSound(float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f, "step." + pSound->m_name, 0.5f * (1.0f + pSound->field_18), 0.8f * pSound->field_1C);
pTile->destroy(m_pLevel, x, y, z, data);
}
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, UpdateBlockPacket* pkt)
{
if (!areAllChunksLoaded())
{
m_bufferedBlockUpdates.push_back(SBufferedBlockUpdate(pkt->m_x, pkt->m_y, pkt->m_z, pkt->m_tile, pkt->m_data));
return;
}
m_pLevel->setTileAndData(pkt->m_x, pkt->m_y, pkt->m_z, pkt->m_tile, pkt->m_data);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, ChunkDataPacket* pChunkDataPkt)
{
if (!m_pLevel)
{
puts("Level @ handle ChunkDataPacket is 0");
return;
}
LevelChunk* pChunk = m_pLevel->getChunkSource()->create(pChunkDataPkt->m_x, pChunkDataPkt->m_z);
if (!pChunk || pChunk->isEmpty())
{
puts("Failed to find write-able chunk");
// @BUG: Not trying again.
return;
}
int x16 = 16 * pChunkDataPkt->m_x;
int z16 = 16 * pChunkDataPkt->m_z;
bool updated = false;
int minY = 128, maxY = 0;
int minX = 16, minZ = 16;
int maxX = 0, maxZ = 0;
for (int k = 0; k < 256; k++)
{
uint8_t updMap;
pChunkDataPkt->m_data.Read(updMap);
if (!updMap)
continue;
for (int j = 0; j < 8; j++)
{
if ((updMap >> j) & 1)
{
int yPos = j * 16;
TileID tiles[16];
uint8_t datas[16 / 2];
pChunkDataPkt->m_data.Read((char*)tiles, 16 * sizeof(TileID));
pChunkDataPkt->m_data.Read((char*)datas, 16 / 2);
for (int i = 0; i < 16; i++)
{
m_pLevel->setTileNoUpdate(x16 + (k & 0xF), yPos + i, z16 + (k >> 4), tiles[i]);
}
int idx = ((k & 0xF) << 11) | ((k >> 4) << 7) + yPos;
memcpy(&pChunk->m_tileData[idx >> 1], datas, sizeof datas);
}
int ymin = 16 * (1 << j);
if (minY >= ymin)
minY = ymin;
if (maxY < ymin + 15)
maxY = ymin + 15;
}
if (minX >= (k & 0xF))
minX = k & 0xF;
if (minZ >= (k >> 4))
minZ = k >> 4;
if (maxX <= (k & 0xF))
maxX = k & 0xF;
if (maxZ <= (k >> 4))
maxZ = k >> 4;
updated = true;
}
if (updated)
m_pLevel->setTilesDirty(minX + x16, minY, minZ, maxX + x16, maxY, maxZ + z16);
pChunk->m_bUnsaved = true;
if (areAllChunksLoaded())
flushAllBufferedUpdates();
else
requestNextChunk();
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlayerEquipmentPacket* pPlayerEquipmentPkt)
{
Player* pPlayer = (Player*)m_pLevel->getEntity(pPlayerEquipmentPkt->m_playerID);
if (!pPlayer)
return;
#ifndef ORIGINAL_CODE
if (!Item::items[pPlayerEquipmentPkt->m_itemID])
{
LogMsg("That item %d doesn't actually exist!", pPlayerEquipmentPkt->m_itemID);
return;
}
#endif
if (pPlayer->m_guid == m_pServerPeer->GetMyGUID())
{
puts("Attempted to modify local player's inventory");
return;
}
pPlayer->m_pInventory->setSelectionSlotItemId(0, pPlayerEquipmentPkt->m_itemID);
pPlayer->m_pInventory->selectSlot(0);
}
bool ClientSideNetworkHandler::areAllChunksLoaded()
{
return m_chunksRequested > 255;
}
void ClientSideNetworkHandler::requestNextChunk()
{
if (areAllChunksLoaded())
return;
// @BUG: The return value of areAllChunksLoaded() is actually true even before the
// 256th chunk is loaded.
m_pRakNetInstance->send(new RequestChunkPacket(m_chunksRequested % 16, m_chunksRequested / 16));
m_chunksRequested++;
}
void ClientSideNetworkHandler::flushAllBufferedUpdates()
{
for (const SBufferedBlockUpdate& u : m_bufferedBlockUpdates)
{
m_pLevel->setTileAndData(u.x, u.y, u.z, u.tile, u.data);
}
}

Some files were not shown because too many files have changed in this diff Show More