8 Commits

23 changed files with 244 additions and 32 deletions

View File

@@ -32,6 +32,12 @@ mkdir build && cd build
cmake .. -B . cmake .. -B .
make -j4 make -j4
``` ```
or
```
mkdir build && cd build
cmake --build . --config Release -j 10
```
## Visual Studio ## Visual Studio
1. Open the repository folder in **Visual Studio**. 1. Open the repository folder in **Visual Studio**.
@@ -40,17 +46,28 @@ make -j4
4. Press **Run** (or F5) to build and launch the game. 4. Press **Run** (or F5) to build and launch the game.
## Android ## Android
Download [r14b Android NDK](http://dl.google.com/android/repository/android-ndk-r14b-windows-x86_64.zip) and run `build.ps1`:
``` 1. Download **Android NDK r14b**:
http://dl.google.com/android/repository/android-ndk-r14b-windows-x86_64.zip
2. Extract it to the root of your `C:` drive so the path becomes:
```
C:\android-ndk-r14b
```
3. Run the build script:
```powershell
# Full build (NDK + Java + APK + install) # Full build (NDK + Java + APK + install)
.\build.ps1 .\build.ps1
# Skip NDK recompile (Java/assets changed only) # Skip C++ compilation (Java/assets changed only)
.\build.ps1 -NoJava
# Skip Java recompile (C++ changed only)
.\build.ps1 -NoCpp .\build.ps1 -NoCpp
# Only repackage + install (no recompile at all) # Skip Java compilation (C++ changed only)
.\build.ps1 -NoJava
# Only repackage + install (no compilation)
.\build.ps1 -NoBuild .\build.ps1 -NoBuild
``` ```

View File

@@ -78,6 +78,14 @@ void Screen::updateEvents()
void Screen::mouseEvent() void Screen::mouseEvent()
{ {
const MouseAction& e = Mouse::getEvent(); const MouseAction& e = Mouse::getEvent();
// forward wheel events to subclasses
if (e.action == MouseAction::ACTION_WHEEL) {
int xm = e.x * width / minecraft->width;
int ym = e.y * height / minecraft->height - 1;
mouseWheel(e.dx, e.dy, xm, ym);
return;
}
if (!e.isButton()) if (!e.isButton())
return; return;

View File

@@ -57,6 +57,9 @@ protected:
virtual void mouseClicked(int x, int y, int buttonNum); virtual void mouseClicked(int x, int y, int buttonNum);
virtual void mouseReleased(int x, int y, int buttonNum); virtual void mouseReleased(int x, int y, int buttonNum);
// mouse wheel movement (dx/dy are wheel deltas, xm/ym are GUI coords)
virtual void mouseWheel(int dx, int dy, int xm, int ym) {}
virtual void keyPressed(int eventKey); virtual void keyPressed(int eventKey);
virtual void keyboardNewChar(char inputChar) {} virtual void keyboardNewChar(char inputChar) {}
public: public:

View File

@@ -548,6 +548,14 @@ void ScrollingPane::stepThroughDecelerationAnimation(bool noAnimation) {
} }
} }
void ScrollingPane::scrollBy(float dx, float dy) {
// adjust the translation offsets fpx/fpy by the requested amount
float nfpx = fpx + dx;
float nfpy = fpy + dy;
// convert back to content offset (fpx = -contentOffset.x)
setContentOffset(Vec3(-nfpx, -nfpy, 0));
}
void ScrollingPane::setContentOffset(float x, float y) { void ScrollingPane::setContentOffset(float x, float y) {
this->setContentOffsetWithAnimation(Vec3(x, y, 0), false); this->setContentOffsetWithAnimation(Vec3(x, y, 0), false);
} }

View File

@@ -51,6 +51,10 @@ public:
void tick(); void tick();
void render(int xm, int ym, float alpha); void render(int xm, int ym, float alpha);
// scroll the content by the given amount (dx horizontal, dy vertical)
// positive values move content downward/rightward
void scrollBy(float dx, float dy);
bool getGridItemFor_slow(int itemIndex, GridItem& out); bool getGridItemFor_slow(int itemIndex, GridItem& out);
void setSelected(int id, bool selected); void setSelected(int id, bool selected);

View File

@@ -202,6 +202,25 @@ void IngameBlockSelectionScreen::keyPressed(int eventKey)
#endif #endif
} }
//------------------------------------------------------------------------------
// wheel support for creative inventory; scroll moves selection vertically
void IngameBlockSelectionScreen::mouseWheel(int dx, int dy, int xm, int ym)
{
if (dy == 0) return;
// just move selection up/down one row; desktop UI doesn't have a pane
int cols = InventoryCols;
int maxIndex = InventorySize - 1;
int idx = selectedItem;
if (dy > 0) {
// wheel up -> previous row
if (idx >= cols) idx -= cols;
} else {
// wheel down -> next row
if (idx + cols <= maxIndex) idx += cols;
}
selectedItem = idx;
}
int IngameBlockSelectionScreen::getSelectedSlot(int x, int y) int IngameBlockSelectionScreen::getSelectedSlot(int x, int y)
{ {
int left = width / 2 - InventoryCols * 10; int left = width / 2 - InventoryCols * 10;

View File

@@ -23,6 +23,9 @@ protected:
virtual void buttonClicked(Button* button); virtual void buttonClicked(Button* button);
// wheel input for creative inventory scrolling
virtual void mouseWheel(int dx, int dy, int xm, int ym) override;
virtual void keyPressed(int eventKey); virtual void keyPressed(int eventKey);
private: private:
void renderSlots(); void renderSlots();

View File

@@ -356,7 +356,7 @@ void SelectWorldScreen::render( int xm, int ym, float a )
worldsList->setComponentSelected(bWorldView.selected); worldsList->setComponentSelected(bWorldView.selected);
// #ifdef PLATFORM_DESKTOP // #ifdef PLATFORM_DESKTOP
// We should add scrolling with mouse wheel but currently i dont know how to implement it // desktop: render the list normally (mouse wheel handled separately below)
if (_mouseHasBeenUp) if (_mouseHasBeenUp)
worldsList->render(xm, ym, a); worldsList->render(xm, ym, a);
else { else {
@@ -412,6 +412,28 @@ std::string SelectWorldScreen::getUniqueLevelName( const std::string& level )
bool SelectWorldScreen::isInGameScreen() { return true; } bool SelectWorldScreen::isInGameScreen() { return true; }
void SelectWorldScreen::mouseWheel(int dx, int dy, int xm, int ym)
{
if (!worldsList)
return;
if (dy == 0)
return;
int num = worldsList->getNumberOfItems();
int idx = worldsList->selectedItem;
if (dy > 0) {
if (idx > 0) {
idx--;
worldsList->stepLeft();
}
} else {
if (idx < num - 1) {
idx++;
worldsList->stepRight();
}
}
worldsList->selectedItem = idx;
}
void SelectWorldScreen::keyPressed( int eventKey ) void SelectWorldScreen::keyPressed( int eventKey )
{ {
if (bWorldView.selected) { if (bWorldView.selected) {

View File

@@ -90,6 +90,9 @@ public:
void render(int xm, int ym, float a); void render(int xm, int ym, float a);
// mouse wheel scroll (new in desktop implementation)
virtual void mouseWheel(int dx, int dy, int xm, int ym);
bool isInGameScreen(); bool isInGameScreen();
private: private:
void loadLevelSource(); void loadLevelSource();

View File

@@ -32,6 +32,13 @@ SimpleChooseLevelScreen::~SimpleChooseLevelScreen()
void SimpleChooseLevelScreen::init() void SimpleChooseLevelScreen::init()
{ {
// make sure the base class loads the existing level list; the
// derived screen uses ChooseLevelScreen::getUniqueLevelName(), which
// depends on `levels` being populated. omitting this used to result
// in duplicate IDs ("creating the second world would load the
// first") when the name already existed.
ChooseLevelScreen::init();
tLevelName.text = "New world"; tLevelName.text = "New world";
// header + close button // header + close button

View File

@@ -6,6 +6,7 @@
#include "OptionsScreen.h" #include "OptionsScreen.h"
#include "PauseScreen.h" #include "PauseScreen.h"
#include "PrerenderTilesScreen.h" // test button #include "PrerenderTilesScreen.h" // test button
#include "../components/ImageButton.h"
#include "../../../util/Mth.h" #include "../../../util/Mth.h"
@@ -25,7 +26,8 @@
StartMenuScreen::StartMenuScreen() StartMenuScreen::StartMenuScreen()
: bHost( 2, 0, 0, 160, 24, "Start Game"), : bHost( 2, 0, 0, 160, 24, "Start Game"),
bJoin( 3, 0, 0, 160, 24, "Join Game"), bJoin( 3, 0, 0, 160, 24, "Join Game"),
bOptions( 4, 0, 0, 78, 22, "Options") bOptions( 4, 0, 0, 78, 22, "Options"),
bQuit( 5, "")
{ {
} }
@@ -54,10 +56,18 @@ void StartMenuScreen::init()
tabButtons.push_back(&bOptions); tabButtons.push_back(&bOptions);
#endif #endif
#ifdef DEMO_MODE // add quit button (top right X icon) match OptionsScreen style
buttons.push_back(&bBuy); {
tabButtons.push_back(&bBuy); ImageDef def;
#endif def.name = "gui/touchgui.png";
def.width = 34;
def.height = 26;
def.setSrc(IntRectangle(150, 0, (int)def.width, (int)def.height));
bQuit.setImageDef(def, true);
bQuit.scaleWhenPressed = false;
buttons.push_back(&bQuit);
// don't include in tab navigation
}
copyright = "\xffMojang AB";//. Do not distribute!"; copyright = "\xffMojang AB";//. Do not distribute!";
@@ -100,8 +110,9 @@ void StartMenuScreen::setupPositions() {
bJoin.x = (width - bJoin.width) / 2; bJoin.x = (width - bJoin.width) / 2;
bOptions.x = (width - bJoin.width) / 2; bOptions.x = (width - bJoin.width) / 2;
copyrightPosX = width - minecraft->font->width(copyright) - 1; // position quit icon at top-right (use image-defined size)
versionPosX = (width - minecraft->font->width(version)) / 2;// - minecraft->font->width(version) - 2; bQuit.x = width - bQuit.width;
bQuit.y = 0;
} }
void StartMenuScreen::tick() { void StartMenuScreen::tick() {
@@ -130,6 +141,10 @@ void StartMenuScreen::buttonClicked(Button* button) {
{ {
minecraft->setScreen(new OptionsScreen()); minecraft->setScreen(new OptionsScreen());
} }
if (button == &bQuit)
{
minecraft->quit();
}
} }
bool StartMenuScreen::isInGameScreen() { return false; } bool StartMenuScreen::isInGameScreen() { return false; }

View File

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

View File

@@ -33,15 +33,16 @@ void UsernameScreen::setupPositions()
int cx = width / 2; int cx = width / 2;
int cy = height / 2; int cy = height / 2;
_btnDone.width = 120; // Make the done button match the touch-style option tabs
_btnDone.height = 20; _btnDone.width = 66;
_btnDone.height = 26;
_btnDone.x = (width - _btnDone.width) / 2; _btnDone.x = (width - _btnDone.width) / 2;
_btnDone.y = height / 2 + 52; _btnDone.y = height / 2 + 52;
tUsername.x = _btnDone.x;
tUsername.y = _btnDone.y - 60;
tUsername.width = 120; tUsername.width = 120;
tUsername.height = 20; tUsername.height = 20;
tUsername.x = (width - tUsername.width) / 2;
tUsername.y = _btnDone.y - 60;
} }
void UsernameScreen::tick() void UsernameScreen::tick()
@@ -58,14 +59,20 @@ void UsernameScreen::keyPressed(int eventKey)
} }
// deliberately do NOT call super::keyPressed — that would close the screen on Escape // deliberately do NOT call super::keyPressed — that would close the screen on Escape
_btnDone.active = !tUsername.text.empty();
Screen::keyPressed(eventKey); Screen::keyPressed(eventKey);
// enable the Done button only when there is some text (and ensure it updates after backspace)
_btnDone.active = !tUsername.text.empty();
} }
void UsernameScreen::keyboardNewChar(char inputChar) void UsernameScreen::keyboardNewChar(char inputChar)
{ {
for (auto* tb : textBoxes) tb->handleChar(inputChar); // limit username length to 12 characters
if (tUsername.text.size() < 12) {
for (auto* tb : textBoxes) tb->handleChar(inputChar);
}
_btnDone.active = !tUsername.text.empty();
} }
void UsernameScreen::mouseClicked(int x, int y, int button) void UsernameScreen::mouseClicked(int x, int y, int button)

View File

@@ -30,7 +30,7 @@ protected:
virtual void buttonClicked(Button* button); virtual void buttonClicked(Button* button);
private: private:
Button _btnDone; Touch::TButton _btnDone;
TextBox tUsername; TextBox tUsername;
std::string _input; std::string _input;
int _cursorBlink; int _cursorBlink;

View File

@@ -153,6 +153,11 @@ int IngameBlockSelectionScreen::getSlotPosY(int slotY) {
return height - 16 - 3 - 22 * 2 - 22 * slotY; return height - 16 - 3 - 22 * 2 - 22 * slotY;
} }
int IngameBlockSelectionScreen::getSlotHeight() {
// same as non-touch implementation
return 22;
}
void IngameBlockSelectionScreen::mouseClicked(int x, int y, int buttonNum) { void IngameBlockSelectionScreen::mouseClicked(int x, int y, int buttonNum) {
_pendingClose = _blockList->_clickArea->isInside((float)x, (float)y); _pendingClose = _blockList->_clickArea->isInside((float)x, (float)y);
if (!_pendingClose) if (!_pendingClose)
@@ -166,6 +171,24 @@ void IngameBlockSelectionScreen::mouseReleased(int x, int y, int buttonNum) {
super::mouseReleased(x, y, buttonNum); super::mouseReleased(x, y, buttonNum);
} }
void IngameBlockSelectionScreen::mouseWheel(int dx, int dy, int xm, int ym)
{
if (dy == 0) return;
if (_blockList) {
float amount = -dy * getSlotHeight();
_blockList->scrollBy(0, amount);
}
int cols = InventoryColumns;
int maxIndex = InventorySize - 1;
int idx = selectedItem;
if (dy > 0) {
if (idx >= cols) idx -= cols;
} else {
if (idx + cols <= maxIndex) idx += cols;
}
selectedItem = idx;
}
bool IngameBlockSelectionScreen::addItem(const InventoryPane* pane, int itemId) bool IngameBlockSelectionScreen::addItem(const InventoryPane* pane, int itemId)
{ {
Inventory* inventory = minecraft->player->inventory; Inventory* inventory = minecraft->player->inventory;

View File

@@ -39,12 +39,16 @@ public:
protected: protected:
virtual void mouseClicked(int x, int y, int buttonNum); virtual void mouseClicked(int x, int y, int buttonNum);
virtual void mouseReleased(int x, int y, int buttonNum); virtual void mouseReleased(int x, int y, int buttonNum);
// also support wheel scrolling
virtual void mouseWheel(int dx, int dy, int xm, int ym) override;
private: private:
void renderDemoOverlay(); void renderDemoOverlay();
//int getLinearSlotId(int x, int y); //int getLinearSlotId(int x, int y);
int getSlotPosX(int slotX); int getSlotPosX(int slotX);
int getSlotPosY(int slotY); int getSlotPosY(int slotY);
int getSlotHeight();
private: private:
int selectedItem; int selectedItem;

View File

@@ -389,6 +389,26 @@ static char ILLEGAL_FILE_CHARACTERS[] = {
'/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':'
}; };
void SelectWorldScreen::mouseWheel(int dx, int dy, int xm, int ym)
{
if (!worldsList) return;
if (dy == 0) return;
int num = worldsList->getNumberOfItems();
int idx = worldsList->selectedItem;
if (dy > 0) {
if (idx > 0) {
idx--;
worldsList->stepLeft();
}
} else {
if (idx < num - 1) {
idx++;
worldsList->stepRight();
}
}
worldsList->selectedItem = idx;
}
void SelectWorldScreen::tick() void SelectWorldScreen::tick()
{ {
#if 0 #if 0

View File

@@ -97,6 +97,9 @@ public:
virtual void buttonClicked(Button* button); virtual void buttonClicked(Button* button);
virtual void keyPressed(int eventKey); virtual void keyPressed(int eventKey);
// support for mouse wheel when desktop code uses touch variant
virtual void mouseWheel(int dx, int dy, int xm, int ym) override;
bool isInGameScreen(); bool isInGameScreen();
private: private:
void loadLevelSource(); void loadLevelSource();

View File

@@ -30,7 +30,8 @@ namespace Touch {
StartMenuScreen::StartMenuScreen() StartMenuScreen::StartMenuScreen()
: bHost( 2, "Start Game"), : bHost( 2, "Start Game"),
bJoin( 3, "Join Game"), bJoin( 3, "Join Game"),
bOptions( 4, "Options") bOptions( 4, "Options"),
bQuit( 5, "")
{ {
ImageDef def; ImageDef def;
bJoin.width = 75; bJoin.width = 75;
@@ -59,7 +60,17 @@ void StartMenuScreen::init()
buttons.push_back(&bJoin); buttons.push_back(&bJoin);
buttons.push_back(&bOptions); buttons.push_back(&bOptions);
// add quit icon (same look as options header)
{
ImageDef def;
def.name = "gui/touchgui.png";
def.width = 34;
def.height = 26;
def.setSrc(IntRectangle(150, 0, (int)def.width, (int)def.height));
bQuit.setImageDef(def, true);
bQuit.scaleWhenPressed = false;
buttons.push_back(&bQuit);
}
tabButtons.push_back(&bHost); tabButtons.push_back(&bHost);
tabButtons.push_back(&bJoin); tabButtons.push_back(&bJoin);
@@ -108,6 +119,10 @@ void StartMenuScreen::setupPositions() {
bHost.x = 1*buttonWidth + (int)(2*spacing); bHost.x = 1*buttonWidth + (int)(2*spacing);
bOptions.x = 2*buttonWidth + (int)(3*spacing); bOptions.x = 2*buttonWidth + (int)(3*spacing);
// quit icon top-right (use size assigned in init)
bQuit.x = width - bQuit.width;
bQuit.y = 0;
copyrightPosX = width - minecraft->font->width(copyright) - 1; copyrightPosX = width - minecraft->font->width(copyright) - 1;
versionPosX = (width - minecraft->font->width(version)) / 2;// - minecraft->font->width(version) - 2; versionPosX = (width - minecraft->font->width(version)) / 2;// - minecraft->font->width(version) - 2;
} }
@@ -135,6 +150,10 @@ void StartMenuScreen::buttonClicked(::Button* button) {
{ {
minecraft->setScreen(new OptionsScreen()); minecraft->setScreen(new OptionsScreen());
} }
if (button == &bQuit)
{
minecraft->quit();
}
} }
bool StartMenuScreen::isInGameScreen() { return false; } bool StartMenuScreen::isInGameScreen() { return false; }

View File

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

View File

@@ -113,6 +113,14 @@ LRESULT WINAPI windowProc ( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
Multitouch::feed(0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); Multitouch::feed(0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
break; break;
} }
case WM_MOUSEWHEEL: {
// wheel delta is multiples of WHEEL_DELTA (120); convert to +/-1
int delta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
short x = GET_X_LPARAM(lParam);
short y = GET_Y_LPARAM(lParam);
Mouse::feed(MouseAction::ACTION_WHEEL, 0, x, y, 0, delta);
break;
}
default: default:
if (uMsg == WM_NCDESTROY) g_running = false; if (uMsg == WM_NCDESTROY) g_running = false;
else { else {

View File

@@ -165,7 +165,8 @@ void DoorTile::neighborChanged(Level* level, int x, int y, int z, int type) {
} }
if (spawn) { if (spawn) {
if (!level->isClientSide) { if (!level->isClientSide) {
spawnResources(level, x, y, z, data, 0); // use default chance (1.0) so the drop always occurs
spawnResources(level, x, y, z, data);
} }
} else { } else {
bool signal = level->hasNeighborSignal(x, y, z) || level->hasNeighborSignal(x, y + 1, z); bool signal = level->hasNeighborSignal(x, y, z) || level->hasNeighborSignal(x, y + 1, z);
@@ -174,13 +175,12 @@ void DoorTile::neighborChanged(Level* level, int x, int y, int z, int type) {
} }
} }
} else { } else {
// upper half: removal should not drop a second door. the
// lower half neighbour handler takes care of spawning the item
// whenever the door is broken from either end.
if (level->getTile(x, y - 1, z) != id) { if (level->getTile(x, y - 1, z) != id) {
level->setTile(x, y, z, 0); level->setTile(x, y, z, 0);
if(material == Material::metal) { // no resource spawn here
popResource(level, x, y, z, ItemInstance(Item::door_iron));
} else {
popResource(level, x, y, z, ItemInstance(Item::door_wood));
}
} }
if (type > 0 && type != id) { if (type > 0 && type != id) {
neighborChanged(level, x, y - 1, z, type); neighborChanged(level, x, y - 1, z, type);
@@ -189,7 +189,11 @@ void DoorTile::neighborChanged(Level* level, int x, int y, int z, int type) {
} }
int DoorTile::getResource(int data, Random* random) { int DoorTile::getResource(int data, Random* random) {
if ((data & 8) != 0) return 0; // only the lower half should return a resource ID; the upper half
// itself never drops anything and playerDestroy suppresses spawning
// from the top. This prevents duplicate drops if the bottom half is
// mined.
if ((data & UPPER_BIT) != 0) return 0;
if (material == Material::metal) return Item::door_iron->id; if (material == Material::metal) return Item::door_iron->id;
return Item::door_wood->id; return Item::door_wood->id;
} }
@@ -199,6 +203,14 @@ HitResult DoorTile::clip(Level* level, int xt, int yt, int zt, const Vec3& a, co
return super::clip(level, xt, yt, zt, a, b); return super::clip(level, xt, yt, zt, a, b);
} }
// override to prevent double-dropping when top half is directly mined
void DoorTile::playerDestroy(Level* level, Player* player, int x, int y, int z, int data) {
if ((data & UPPER_BIT) == 0) {
// only let the lower half handle the actual spawning
super::playerDestroy(level, player, x, y, z, data);
}
}
int DoorTile::getDir(LevelSource* level, int x, int y, int z) { int DoorTile::getDir(LevelSource* level, int x, int y, int z) {
return getCompositeData(level, x, y, z) & C_DIR_MASK; return getCompositeData(level, x, y, z) & C_DIR_MASK;
} }

View File

@@ -49,6 +49,9 @@ public:
int getResource(int data, Random* random); int getResource(int data, Random* random);
// override to avoid duplicate drops when upper half is mined directly
void playerDestroy(Level* level, Player* player, int x, int y, int z, int data) override;
HitResult clip(Level* level, int xt, int yt, int zt, const Vec3& a, const Vec3& b); HitResult clip(Level* level, int xt, int yt, int zt, const Vec3& a, const Vec3& b);
int getDir(LevelSource* level, int x, int y, int z); int getDir(LevelSource* level, int x, int y, int z);