aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Minecraft.Client/ClientConnection.cpp104
-rw-r--r--Minecraft.Client/ClientConnection.h15
-rw-r--r--Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp2
-rw-r--r--Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp20
-rw-r--r--Minecraft.Client/Common/UI/UIComponent_TutorialPopup.cpp37
-rw-r--r--Minecraft.Client/Common/UI/UIScene.cpp21
-rw-r--r--Minecraft.Client/Common/UI/UIScene_FullscreenProgress.cpp3
-rw-r--r--Minecraft.Client/Common/UI/UIScene_HUD.cpp28
-rw-r--r--Minecraft.Client/Common/UI/UIScene_HUD.h2
-rw-r--r--Minecraft.Client/Gui.cpp402
-rw-r--r--Minecraft.Client/Minecraft.cpp6
-rw-r--r--Minecraft.Client/MultiPlayerLevel.h1
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp10
13 files changed, 369 insertions, 282 deletions
diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp
index ba40b43e..325e949b 100644
--- a/Minecraft.Client/ClientConnection.cpp
+++ b/Minecraft.Client/ClientConnection.cpp
@@ -149,8 +149,56 @@ bool ClientConnection::isPrimaryConnection() const
return g_NetworkManager.IsHost() || m_userIndex == ProfileManager.GetPrimaryPad();
}
+ClientConnection* ClientConnection::findPrimaryConnection() const
+{
+ if (level == nullptr) return nullptr;
+ int primaryPad = ProfileManager.GetPrimaryPad();
+ MultiPlayerLevel* mpLevel = (MultiPlayerLevel*)level;
+ for (ClientConnection* conn : mpLevel->connections)
+ {
+ if (conn != this && conn->m_userIndex == primaryPad)
+ return conn;
+ }
+ return nullptr;
+}
+
+bool ClientConnection::shouldProcessForEntity(int entityId) const
+{
+ if (g_NetworkManager.IsHost()) return true;
+ if (m_userIndex == ProfileManager.GetPrimaryPad()) return true;
+
+ ClientConnection* primary = findPrimaryConnection();
+ if (primary == nullptr) return true;
+ return !primary->isTrackingEntity(entityId);
+}
+
+bool ClientConnection::shouldProcessForPosition(int blockX, int blockZ) const
+{
+ if (g_NetworkManager.IsHost()) return true;
+ if (m_userIndex == ProfileManager.GetPrimaryPad()) return true;
+
+ ClientConnection* primary = findPrimaryConnection();
+ if (primary == nullptr) return true;
+ return !primary->m_visibleChunks.count(chunkKey(blockX >> 4, blockZ >> 4));
+}
+
+bool ClientConnection::anyOtherConnectionHasChunk(int x, int z) const
+{
+ if (level == nullptr) return false;
+ MultiPlayerLevel* mpLevel = (MultiPlayerLevel*)level;
+ int64_t key = chunkKey(x, z);
+ for (ClientConnection* conn : mpLevel->connections)
+ {
+ if (conn != this && conn->m_visibleChunks.count(key))
+ return true;
+ }
+ return false;
+}
+
ClientConnection::~ClientConnection()
{
+ m_trackedEntityIds.clear();
+ m_visibleChunks.clear();
delete connection;
delete random;
delete savedDataStorage;
@@ -664,6 +712,7 @@ void ClientConnection::handleAddEntity(shared_ptr<AddEntityPacket> packet)
}
e->entityId = packet->id;
level->putEntity(packet->id, e);
+ m_trackedEntityIds.insert(packet->id);
if (packet->data > -1) // 4J - changed "no data" value to be -1, we can have a valid entity id of 0
{
@@ -712,6 +761,7 @@ void ClientConnection::handleAddExperienceOrb(shared_ptr<AddExperienceOrbPacket>
e->xRot = 0;
e->entityId = packet->id;
level->putEntity(packet->id, e);
+ m_trackedEntityIds.insert(packet->id);
}
void ClientConnection::handleAddGlobalEntity(shared_ptr<AddGlobalEntityPacket> packet)
@@ -738,13 +788,13 @@ void ClientConnection::handleAddPainting(shared_ptr<AddPaintingPacket> packet)
{
shared_ptr<Painting> painting = std::make_shared<Painting>(level, packet->x, packet->y, packet->z, packet->dir, packet->motive);
level->putEntity(packet->id, painting);
+ m_trackedEntityIds.insert(packet->id);
}
void ClientConnection::handleSetEntityMotion(shared_ptr<SetEntityMotionPacket> packet)
{
- if (!isPrimaryConnection())
+ if (!shouldProcessForEntity(packet->id))
{
- // Secondary connection: only accept motion for our own local player (knockback)
if (minecraft->localplayers[m_userIndex] == NULL ||
packet->id != minecraft->localplayers[m_userIndex]->entityId)
return;
@@ -939,6 +989,7 @@ void ClientConnection::handleAddPlayer(shared_ptr<AddPlayerPacket> packet)
app.DebugPrintf("Custom cape for player %ls is %ls\n",player->name.c_str(),player->customTextureUrl2.c_str());
level->putEntity(packet->id, player);
+ m_trackedEntityIds.insert(packet->id);
vector<shared_ptr<SynchedEntityData::DataItem> > *unpackedData = packet->getUnpackedData();
if (unpackedData != nullptr)
@@ -979,7 +1030,7 @@ void ClientConnection::handleSetCarriedItem(shared_ptr<SetCarriedItemPacket> pac
void ClientConnection::handleMoveEntity(shared_ptr<MoveEntityPacket> packet)
{
- if (!isPrimaryConnection()) return;
+ if (!shouldProcessForEntity(packet->id)) return;
shared_ptr<Entity> e = getEntity(packet->id);
if (e == nullptr) return;
e->xp += packet->xa;
@@ -1009,7 +1060,7 @@ void ClientConnection::handleRotateMob(shared_ptr<RotateHeadPacket> packet)
void ClientConnection::handleMoveEntitySmall(shared_ptr<MoveEntityPacketSmall> packet)
{
- if (!isPrimaryConnection()) return;
+ if (!shouldProcessForEntity(packet->id)) return;
shared_ptr<Entity> e = getEntity(packet->id);
if (e == nullptr) return;
e->xp += packet->xa;
@@ -1068,6 +1119,7 @@ void ClientConnection::handleRemoveEntity(shared_ptr<RemoveEntitiesPacket> packe
#endif
for (int i = 0; i < packet->ids.length; i++)
{
+ m_trackedEntityIds.erase(packet->ids[i]);
level->removeEntity(packet->ids[i]);
}
}
@@ -1136,19 +1188,35 @@ void ClientConnection::handleChunkVisibilityArea(shared_ptr<ChunkVisibilityAreaP
{
if (level == NULL) return;
for(int z = packet->m_minZ; z <= packet->m_maxZ; ++z)
+ {
for(int x = packet->m_minX; x <= packet->m_maxX; ++x)
+ {
+ m_visibleChunks.insert(chunkKey(x, z));
level->setChunkVisible(x, z, true);
+ }
+ }
}
void ClientConnection::handleChunkVisibility(shared_ptr<ChunkVisibilityPacket> packet)
{
if (level == NULL) return;
- level->setChunkVisible(packet->x, packet->z, packet->visible);
+ if (packet->visible)
+ {
+ m_visibleChunks.insert(chunkKey(packet->x, packet->z));
+ level->setChunkVisible(packet->x, packet->z, true);
+ }
+ else
+ {
+ m_visibleChunks.erase(chunkKey(packet->x, packet->z));
+ if (!anyOtherConnectionHasChunk(packet->x, packet->z))
+ {
+ level->setChunkVisible(packet->x, packet->z, false);
+ }
+ }
}
void ClientConnection::handleChunkTilesUpdate(shared_ptr<ChunkTilesUpdatePacket> packet)
{
- if (!isPrimaryConnection()) return;
// 4J - changed to encode level in packet
MultiPlayerLevel *dimensionLevel = (MultiPlayerLevel *)minecraft->levels[packet->levelIdx];
if( dimensionLevel )
@@ -1218,7 +1286,6 @@ void ClientConnection::handleChunkTilesUpdate(shared_ptr<ChunkTilesUpdatePacket>
void ClientConnection::handleBlockRegionUpdate(shared_ptr<BlockRegionUpdatePacket> packet)
{
- if (!isPrimaryConnection()) return;
// 4J - changed to encode level in packet
MultiPlayerLevel *dimensionLevel = (MultiPlayerLevel *)minecraft->levels[packet->levelIdx];
if( dimensionLevel )
@@ -1279,7 +1346,6 @@ void ClientConnection::handleBlockRegionUpdate(shared_ptr<BlockRegionUpdatePacke
void ClientConnection::handleTileUpdate(shared_ptr<TileUpdatePacket> packet)
{
- if (!isPrimaryConnection()) return;
// 4J added - using a block of 255 to signify that this is a packet for destroying a tile, where we need to inform the level renderer that we are about to do so.
// This is used in creative mode as the point where a tile is first destroyed at the client end of things. Packets formed like this are potentially sent from
// ServerPlayerGameMode::destroyBlock
@@ -1394,7 +1460,7 @@ void ClientConnection::send(shared_ptr<Packet> packet)
void ClientConnection::handleTakeItemEntity(shared_ptr<TakeItemEntityPacket> packet)
{
- if (!isPrimaryConnection()) return;
+ if (!shouldProcessForEntity(packet->itemId)) return;
shared_ptr<Entity> from = getEntity(packet->itemId);
shared_ptr<LivingEntity> to = dynamic_pointer_cast<LivingEntity>(getEntity(packet->playerId));
@@ -2414,6 +2480,8 @@ void ClientConnection::close()
// If it's already done, then we don't need to do anything here. And in fact trying to do something could cause a crash
if(done) return;
done = true;
+ m_trackedEntityIds.clear();
+ m_visibleChunks.clear();
connection->flush();
connection->close(DisconnectPacket::eDisconnect_Closed);
}
@@ -2453,6 +2521,7 @@ void ClientConnection::handleAddMob(shared_ptr<AddMobPacket> packet)
mob->yd = packet->yd / 8000.0f;
mob->zd = packet->zd / 8000.0f;
level->putEntity(packet->id, mob);
+ m_trackedEntityIds.insert(packet->id);
vector<shared_ptr<SynchedEntityData::DataItem> > *unpackedData = packet->getUnpackedData();
if (unpackedData != nullptr)
@@ -2792,6 +2861,9 @@ void ClientConnection::handleRespawn(shared_ptr<RespawnPacket> packet)
// so it doesn't leak into the new dimension
level->playStreamingMusic(L"", 0, 0, 0);
+ m_trackedEntityIds.clear();
+ m_visibleChunks.clear();
+
// Remove client connection from this level
level->removeClientConnection(this, false);
@@ -2899,8 +2971,7 @@ void ClientConnection::handleRespawn(shared_ptr<RespawnPacket> packet)
void ClientConnection::handleExplosion(shared_ptr<ExplodePacket> packet)
{
- // World modification (block destruction) must only happen once
- if (isPrimaryConnection())
+ if (shouldProcessForPosition((int)packet->x, (int)packet->z))
{
if(!packet->m_bKnockbackOnly)
{
@@ -3244,7 +3315,6 @@ void ClientConnection::handleTileEditorOpen(shared_ptr<TileEditorOpenPacket> pac
void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
{
- if (!isPrimaryConnection()) return;
app.DebugPrintf("ClientConnection::handleSignUpdate - ");
if (minecraft->level->hasChunkAt(packet->x, packet->y, packet->z))
{
@@ -3278,7 +3348,6 @@ void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
void ClientConnection::handleTileEntityData(shared_ptr<TileEntityDataPacket> packet)
{
- if (!isPrimaryConnection()) return;
if (minecraft->level->hasChunkAt(packet->x, packet->y, packet->z))
{
shared_ptr<TileEntity> te = minecraft->level->getTileEntity(packet->x, packet->y, packet->z);
@@ -3331,7 +3400,6 @@ void ClientConnection::handleContainerClose(shared_ptr<ContainerClosePacket> pac
void ClientConnection::handleTileEvent(shared_ptr<TileEventPacket> packet)
{
- if (!isPrimaryConnection()) return;
PIXBeginNamedEvent(0,"Handle tile event\n");
minecraft->level->tileEvent(packet->x, packet->y, packet->z, packet->tile, packet->b0, packet->b1);
PIXEndNamedEvent();
@@ -3339,7 +3407,6 @@ void ClientConnection::handleTileEvent(shared_ptr<TileEventPacket> packet)
void ClientConnection::handleTileDestruction(shared_ptr<TileDestructionPacket> packet)
{
- if (!isPrimaryConnection()) return;
minecraft->level->destroyTileProgress(packet->getEntityId(), packet->getX(), packet->getY(), packet->getZ(), packet->getState());
}
@@ -3421,7 +3488,6 @@ void ClientConnection::handleGameEvent(shared_ptr<GameEventPacket> gameEventPack
void ClientConnection::handleComplexItemData(shared_ptr<ComplexItemDataPacket> packet)
{
- if (!isPrimaryConnection()) return;
if (packet->itemType == Item::map->id)
{
MapItem::getSavedData(packet->itemId, minecraft->level)->handleComplexItemData(packet->data);
@@ -3436,7 +3502,7 @@ void ClientConnection::handleComplexItemData(shared_ptr<ComplexItemDataPacket> p
void ClientConnection::handleLevelEvent(shared_ptr<LevelEventPacket> packet)
{
- if (!isPrimaryConnection()) return;
+ if (!shouldProcessForPosition(packet->x, packet->z)) return;
if (packet->type == LevelEvent::SOUND_DRAGON_DEATH)
{
for(unsigned int i = 0; i < XUSER_MAX_COUNT; ++i)
@@ -3456,8 +3522,6 @@ void ClientConnection::handleLevelEvent(shared_ptr<LevelEventPacket> packet)
{
minecraft->level->levelEvent(packet->type, packet->x, packet->y, packet->z, packet->data);
}
-
- minecraft->level->levelEvent(packet->type, packet->x, packet->y, packet->z, packet->data);
}
void ClientConnection::handleAwardStat(shared_ptr<AwardStatPacket> packet)
@@ -3660,7 +3724,6 @@ void ClientConnection::handlePlayerAbilities(shared_ptr<PlayerAbilitiesPacket> p
void ClientConnection::handleSoundEvent(shared_ptr<LevelSoundPacket> packet)
{
- if (!isPrimaryConnection()) return;
minecraft->level->playLocalSound(packet->getX(), packet->getY(), packet->getZ(), packet->getSound(), packet->getVolume(), packet->getPitch(), false);
}
@@ -3973,7 +4036,6 @@ void ClientConnection::handleSetPlayerTeamPacket(shared_ptr<SetPlayerTeamPacket>
void ClientConnection::handleParticleEvent(shared_ptr<LevelParticlesPacket> packet)
{
- if (!isPrimaryConnection()) return;
for (int i = 0; i < packet->getCount(); i++)
{
double xVarience = random->nextGaussian() * packet->getXDist();
diff --git a/Minecraft.Client/ClientConnection.h b/Minecraft.Client/ClientConnection.h
index f13b93e7..3448496d 100644
--- a/Minecraft.Client/ClientConnection.h
+++ b/Minecraft.Client/ClientConnection.h
@@ -1,4 +1,5 @@
#pragma once
+#include <unordered_set>
#include "..\Minecraft.World\net.minecraft.network.h"
class Minecraft;
class MultiPlayerLevel;
@@ -44,6 +45,20 @@ public:
private:
DWORD m_userIndex; // 4J Added
bool isPrimaryConnection() const;
+
+ std::unordered_set<int> m_trackedEntityIds;
+ std::unordered_set<int64_t> m_visibleChunks;
+
+ static int64_t chunkKey(int x, int z) { return ((int64_t)x << 32) | ((int64_t)z & 0xFFFFFFFF); }
+
+ ClientConnection* findPrimaryConnection() const;
+ bool shouldProcessForEntity(int entityId) const;
+ bool shouldProcessForPosition(int blockX, int blockZ) const;
+ bool anyOtherConnectionHasChunk(int x, int z) const;
+
+public:
+ bool isTrackingEntity(int entityId) const { return m_trackedEntityIds.count(entityId) > 0; }
+
public:
SavedDataStorage *savedDataStorage;
ClientConnection(Minecraft *minecraft, const wstring& ip, int port);
diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
index b32cc934..7340a7e0 100644
--- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
+++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
@@ -240,7 +240,7 @@ void CPlatformNetworkManagerStub::DoWork()
qnetPlayer->m_resolvedXuid = INVALID_XUID;
qnetPlayer->m_gamertag[0] = 0;
qnetPlayer->SetCustomDataValue(0);
- if (IQNet::s_playerCount > 1)
+ while (IQNet::s_playerCount > 1 && IQNet::m_player[IQNet::s_playerCount - 1].GetCustomDataValue() == 0)
IQNet::s_playerCount--;
}
// NOTE: Do NOT call PushFreeSmallId here. The old PlayerConnection's
diff --git a/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp b/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp
index 418546b7..4f60de5f 100644
--- a/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp
+++ b/Minecraft.Client/Common/UI/UIComponent_Tooltips.cpp
@@ -93,18 +93,22 @@ void UIComponent_Tooltips::updateSafeZone()
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
safeTop = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
- safeBottom = getSafeZoneHalfHeight();
+ safeTop = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
- safeLeft = getSafeZoneHalfWidth();
+ safeTop = getSafeZoneHalfHeight();
safeBottom = getSafeZoneHalfHeight();
+ safeLeft = getSafeZoneHalfWidth();
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
- safeRight = getSafeZoneHalfWidth();
+ safeTop = getSafeZoneHalfHeight();
safeBottom = getSafeZoneHalfHeight();
+
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
safeTop = getSafeZoneHalfHeight();
@@ -112,22 +116,22 @@ void UIComponent_Tooltips::updateSafeZone()
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
safeTop = getSafeZoneHalfHeight();
- safeRight = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
- safeBottom = getSafeZoneHalfHeight();
+ safeTop = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
- safeBottom = getSafeZoneHalfHeight();
- safeRight = getSafeZoneHalfWidth();
+ safeTop = getSafeZoneHalfHeight();
+
break;
case C4JRender::VIEWPORT_TYPE_FULLSCREEN:
default:
safeTop = getSafeZoneHalfHeight();
safeBottom = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
- safeRight = getSafeZoneHalfWidth();
+
break;
}
setSafeZone(safeTop, safeBottom, safeLeft, safeRight);
diff --git a/Minecraft.Client/Common/UI/UIComponent_TutorialPopup.cpp b/Minecraft.Client/Common/UI/UIComponent_TutorialPopup.cpp
index fcbd17f3..76d3babf 100644
--- a/Minecraft.Client/Common/UI/UIComponent_TutorialPopup.cpp
+++ b/Minecraft.Client/Common/UI/UIComponent_TutorialPopup.cpp
@@ -1,6 +1,7 @@
#include "stdafx.h"
#include "UI.h"
#include "UIComponent_TutorialPopup.h"
+#include "UISplitScreenHelpers.h"
#include "..\..\Common\Tutorial\Tutorial.h"
#include "..\..\..\Minecraft.World\StringHelpers.h"
#include "..\..\MultiplayerLocalPlayer.h"
@@ -474,27 +475,17 @@ void UIComponent_TutorialPopup::render(S32 width, S32 height, C4JRender::eViewpo
{
if(viewport != C4JRender::VIEWPORT_TYPE_FULLSCREEN)
{
- S32 xPos = 0;
- S32 yPos = 0;
- switch( viewport )
- {
- case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
- xPos = static_cast<S32>(ui.getScreenWidth() / 2);
- yPos = static_cast<S32>(ui.getScreenHeight() / 2);
- break;
- case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
- yPos = static_cast<S32>(ui.getScreenHeight() / 2);
- break;
- case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
- case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
- case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
- xPos = static_cast<S32>(ui.getScreenWidth() / 2);
- break;
- case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
- xPos = static_cast<S32>(ui.getScreenWidth() / 2);
- yPos = static_cast<S32>(ui.getScreenHeight() / 2);
- break;
- }
+ // Derive the viewport origin and fit a 16:9 box inside it (same as UIScene::render),
+ // then apply safezone nudges so the popup stays clear of screen edges.
+ F32 originX, originY, viewW, viewH;
+ GetViewportRect(ui.getScreenWidth(), ui.getScreenHeight(), viewport, originX, originY, viewW, viewH);
+
+ S32 fitW, fitH, offsetX, offsetY;
+ Fit16x9(viewW, viewH, fitW, fitH, offsetX, offsetY);
+
+ S32 xPos = static_cast<S32>(originX) + offsetX;
+ S32 yPos = static_cast<S32>(originY) + offsetY;
+
//Adjust for safezone
switch( viewport )
{
@@ -505,6 +496,7 @@ void UIComponent_TutorialPopup::render(S32 width, S32 height, C4JRender::eViewpo
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
yPos += getSafeZoneHalfHeight();
break;
+ default: break;
}
switch( viewport )
{
@@ -515,10 +507,11 @@ void UIComponent_TutorialPopup::render(S32 width, S32 height, C4JRender::eViewpo
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
xPos -= getSafeZoneHalfWidth();
break;
+ default: break;
}
ui.setupRenderPosition(xPos, yPos);
- IggyPlayerSetDisplaySize( getMovie(), width, height );
+ IggyPlayerSetDisplaySize( getMovie(), fitW, fitH );
IggyPlayerDraw( getMovie() );
}
else
diff --git a/Minecraft.Client/Common/UI/UIScene.cpp b/Minecraft.Client/Common/UI/UIScene.cpp
index 0088f43d..303897a7 100644
--- a/Minecraft.Client/Common/UI/UIScene.cpp
+++ b/Minecraft.Client/Common/UI/UIScene.cpp
@@ -172,15 +172,22 @@ void UIScene::updateSafeZone()
{
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
safeTop = getSafeZoneHalfHeight();
+ safeLeft = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
- safeBottom = getSafeZoneHalfHeight();
+ // safeTop mirrors SPLIT_TOP for visual symmetry. safeBottom omitted.
+ safeTop = getSafeZoneHalfHeight();
+ safeLeft = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
+ safeTop = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
- safeRight = getSafeZoneHalfWidth();
+ safeTop = getSafeZoneHalfHeight();
+
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
safeTop = getSafeZoneHalfHeight();
@@ -188,22 +195,22 @@ void UIScene::updateSafeZone()
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
safeTop = getSafeZoneHalfHeight();
- safeRight = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
- safeBottom = getSafeZoneHalfHeight();
+ safeTop = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
- safeBottom = getSafeZoneHalfHeight();
- safeRight = getSafeZoneHalfWidth();
+ safeTop = getSafeZoneHalfHeight();
+
break;
case C4JRender::VIEWPORT_TYPE_FULLSCREEN:
default:
safeTop = getSafeZoneHalfHeight();
safeBottom = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
- safeRight = getSafeZoneHalfWidth();
+
break;
}
setSafeZone(safeTop, safeBottom, safeLeft, safeRight);
diff --git a/Minecraft.Client/Common/UI/UIScene_FullscreenProgress.cpp b/Minecraft.Client/Common/UI/UIScene_FullscreenProgress.cpp
index e89c0626..6a4ea096 100644
--- a/Minecraft.Client/Common/UI/UIScene_FullscreenProgress.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_FullscreenProgress.cpp
@@ -278,7 +278,7 @@ void UIScene_FullscreenProgress::handleInput(int iPad, int key, bool repeat, boo
#ifdef __ORBIS__
case ACTION_MENU_TOUCHPAD_PRESS:
#endif
- if(pressed)
+ if(pressed && m_threadCompleted)
{
sendInputToMovie(key, repeat, pressed, released);
}
@@ -292,6 +292,7 @@ void UIScene_FullscreenProgress::handleInput(int iPad, int key, bool repeat, boo
}
break;
}
+ handled = true;
}
}
diff --git a/Minecraft.Client/Common/UI/UIScene_HUD.cpp b/Minecraft.Client/Common/UI/UIScene_HUD.cpp
index 0d8adcb2..213caa8d 100644
--- a/Minecraft.Client/Common/UI/UIScene_HUD.cpp
+++ b/Minecraft.Client/Common/UI/UIScene_HUD.cpp
@@ -65,22 +65,26 @@ void UIScene_HUD::updateSafeZone()
case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
safeTop = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
- safeRight = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
- safeBottom = getSafeZoneHalfHeight();
+ // safeTop mirrors SPLIT_TOP so both players have the same vertical inset
+ // from their viewport's top edge (split divider), keeping GUI symmetrical.
+ // safeBottom is intentionally omitted: it would shift m_Hud.y upward in
+ // ActionScript, placing the hotbar too high relative to SPLIT_TOP.
+ safeTop = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
- safeRight = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
- safeLeft = getSafeZoneHalfWidth();
safeTop = getSafeZoneHalfHeight();
safeBottom = getSafeZoneHalfHeight();
+ safeLeft = getSafeZoneHalfWidth();
break;
case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
- safeRight = getSafeZoneHalfWidth();
safeTop = getSafeZoneHalfHeight();
safeBottom = getSafeZoneHalfHeight();
+
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
safeTop = getSafeZoneHalfHeight();
@@ -88,22 +92,22 @@ void UIScene_HUD::updateSafeZone()
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
safeTop = getSafeZoneHalfHeight();
- safeRight = getSafeZoneHalfWidth();
+
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
- safeBottom = getSafeZoneHalfHeight();
+ safeTop = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
break;
case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
- safeBottom = getSafeZoneHalfHeight();
- safeRight = getSafeZoneHalfWidth();
+ safeTop = getSafeZoneHalfHeight();
+
break;
case C4JRender::VIEWPORT_TYPE_FULLSCREEN:
default:
safeTop = getSafeZoneHalfHeight();
safeBottom = getSafeZoneHalfHeight();
safeLeft = getSafeZoneHalfWidth();
- safeRight = getSafeZoneHalfWidth();
+
break;
}
setSafeZone(safeTop, safeBottom, safeLeft, safeRight);
@@ -734,7 +738,7 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
IggyPlayerSetDisplaySize( getMovie(), (S32)(m_movieWidth * scale), (S32)(m_movieHeight * scale) );
- repositionHud(tileWidth, tileHeight, scale);
+ repositionHud(tileWidth, tileHeight, scale, needsYTile);
m_renderWidth = tileWidth;
m_renderHeight = tileHeight;
@@ -805,7 +809,7 @@ void UIScene_HUD::handleTimerComplete(int id)
//setVisible(anyVisible);
}
-void UIScene_HUD::repositionHud(S32 tileWidth, S32 tileHeight, F32 scale)
+void UIScene_HUD::repositionHud(S32 tileWidth, S32 tileHeight, F32 scale, bool needsYTile)
{
if(!m_bSplitscreen) return;
diff --git a/Minecraft.Client/Common/UI/UIScene_HUD.h b/Minecraft.Client/Common/UI/UIScene_HUD.h
index 569b5234..04468c8e 100644
--- a/Minecraft.Client/Common/UI/UIScene_HUD.h
+++ b/Minecraft.Client/Common/UI/UIScene_HUD.h
@@ -176,5 +176,5 @@ protected:
#endif
private:
- void repositionHud(S32 tileWidth, S32 tileHeight, F32 scale);
+ void repositionHud(S32 tileWidth, S32 tileHeight, F32 scale, bool needsYTile);
};
diff --git a/Minecraft.Client/Gui.cpp b/Minecraft.Client/Gui.cpp
index 43b41998..f0d44319 100644
--- a/Minecraft.Client/Gui.cpp
+++ b/Minecraft.Client/Gui.cpp
@@ -443,7 +443,8 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
double maxHealth = minecraft->localplayers[iPad]->getAttribute(SharedMonsterAttributes.MAX_HEALTH);
double totalAbsorption = minecraft->localplayers[iPad]->getAbsorptionAmount();
- int numHealthRows = Mth.ceil((maxHealth + totalAbsorption) / 2 / (float) NUM_HEARTS_PER_ROW);
+ const double healthHalves = (maxHealth + totalAbsorption) / 2.0;
+ int numHealthRows = Mth.ceil(healthHalves / (float) NUM_HEARTS_PER_ROW);
int healthRowHeight = Math.max(10 - (numHealthRows - 2), 3);
int yLine2 = yLine1 - (numHealthRows - 1) * healthRowHeight - 10;
absorption = totalAbsorption;
@@ -469,7 +470,7 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
}
//minecraft.profiler.popPush("health");
- for (int i = Mth.ceil((maxHealth + totalAbsorption) / 2) - 1; i >= 0; i--)
+ for (int i = (int)Mth.ceil(healthHalves) - 1; i >= 0; i--)
{
int healthTexBaseX = 16;
if (minecraft.player.hasEffect(MobEffect.poison))
@@ -607,8 +608,11 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
// render air bubbles
if (minecraft->player->isUnderLiquid(Material::water))
{
- int count = (int) ceil((minecraft->player->getAirSupply() - 2) * 10.0f / Player::TOTAL_AIR_SUPPLY);
- int extra = (int) ceil((minecraft->player->getAirSupply()) * 10.0f / Player::TOTAL_AIR_SUPPLY) - count;
+ const int airSupply = minecraft->player->getAirSupply();
+ const float airScale = 10.0f / Player::TOTAL_AIR_SUPPLY;
+ const float airSupplyScaled = airSupply * airScale;
+ int count = (int) ceil((airSupply - 2) * airScale);
+ int extra = (int) ceil(airSupplyScaled) - count;
for (int i = 0; i < count + extra; i++)
{
// Air bubbles
@@ -725,7 +729,8 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
Lighting::turnOn();
glRotatef(-45 - 90, 0, 1, 0);
- glRotatef(-(float) atan(yd / 40.0f ) * 20, 1, 0, 0);
+ const float xRotAngle = -(float) atan(yd / 40.0f) * 20;
+ glRotatef(xRotAngle, 1, 0, 0);
float bodyRot = (minecraft->player->yBodyRotO + (minecraft->player->yBodyRot - minecraft->player->yBodyRotO));
// Fixed rotation angle of degrees, adjusted by bodyRot to negate the rotation that occurs in the renderer
// bodyRot in the rotation below is a simplification of "180 - (180 - bodyRot)" where the first 180 is EntityRenderDispatcher::instance->playerRotY that we set below
@@ -736,7 +741,7 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
// Set head rotation to body rotation to make head static
minecraft->player->yRot = bodyRot;
minecraft->player->yRotO = minecraft->player->yRot;
- minecraft->player->xRot = -(float) atan(yd / 40.0f) * 20;
+ minecraft->player->xRot = xRotAngle;
minecraft->player->onFire = 0;
minecraft->player->setSharedFlag(Entity::FLAG_ONFIRE, false);
@@ -849,207 +854,6 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
// font.draw(str, x + 1, y, 0xffffff);
// }
-#ifndef _FINAL_BUILD
- MemSect(31);
-
- // temporarily render overlay at all times so version is more obvious in bug reports
- // we can turn this off once things stabilize
- if (true)// minecraft->options->renderDebug && minecraft->player != nullptr && minecraft->level != nullptr)
- {
- const int debugLeft = 1;
- const int debugTop = 1;
- const float maxContentWidth = 1200.f;
- const float maxContentHeight = 420.f;
- float scale = static_cast<float>(screenWidth - debugLeft - 8) / maxContentWidth;
- float scaleV = static_cast<float>(screenHeight - debugTop - 80) / maxContentHeight;
- if (scaleV < scale) scale = scaleV;
- if (scale > 1.f) scale = 1.f;
- if (scale < 0.5f) scale = 0.5f;
- glPushMatrix();
- glTranslatef(static_cast<float>(debugLeft), static_cast<float>(debugTop), 0.f);
- glScalef(scale, scale, 1.f);
- glTranslatef(static_cast<float>(-debugLeft), static_cast<float>(-debugTop), 0.f);
-
- vector<wstring> lines;
-
- lines.push_back(ClientConstants::VERSION_STRING);
- lines.push_back(ClientConstants::BRANCH_STRING);
- if (minecraft->options->renderDebug && minecraft->player != nullptr && minecraft->level != nullptr)
- {
- lines.push_back(minecraft->fpsString);
- lines.push_back(L"E: " + std::to_wstring(minecraft->level->getAllEntities().size())); // Could maybe use entity::shouldRender to work out how many are rendered but thats like expensive
- // TODO Add server information with packet counts - once multiplayer is more stable
- int renderDistance = app.GetGameSettings(iPad, eGameSetting_RenderDistance);
- // Calculate the chunk sections using 16 * (2n + 1)^2
- lines.push_back(L"C: " + std::to_wstring(16 * (2 * renderDistance + 1) * (2 * renderDistance + 1)) + L" D: " + std::to_wstring(renderDistance));
- lines.push_back(minecraft->gatherStats4()); // Chunk Cache
-
- // Dimension
- wstring dimension = L"unknown";
- switch (minecraft->player->dimension)
- {
- case -1:
- dimension = L"minecraft:the_nether";
- break;
- case 0:
- dimension = L"minecraft:overworld";
- break;
- case 1:
- dimension = L"minecraft:the_end";
- break;
- }
- lines.push_back(dimension);
-
- lines.push_back(L""); // Spacer
-
- // Players block pos
- int xBlockPos = Mth::floor(minecraft->player->x);
- int yBlockPos = Mth::floor(minecraft->player->y);
- int zBlockPos = Mth::floor(minecraft->player->z);
-
- // Chunk player is in
- int xChunkPos = xBlockPos >> 4;
- int yChunkPos = yBlockPos >> 4;
- int zChunkPos = zBlockPos >> 4;
-
- // Players offset within the chunk
- int xChunkOffset = xBlockPos & 15;
- int yChunkOffset = yBlockPos & 15;
- int zChunkOffset = zBlockPos & 15;
-
- // Format the position like java with limited decumal places
- WCHAR posString[44]; // Allows upto 7 digit positions (+-9_999_999)
- swprintf(posString, 44, L"%.3f / %.5f / %.3f", minecraft->player->x, minecraft->player->y, minecraft->player->z);
-
- lines.push_back(L"XYZ: " + std::wstring(posString));
- lines.push_back(L"Block: " + std::to_wstring(static_cast<int>(xBlockPos)) + L" " + std::to_wstring(static_cast<int>(yBlockPos)) + L" " + std::to_wstring(static_cast<int>(zBlockPos)));
- lines.push_back(L"Chunk: " + std::to_wstring(xChunkOffset) + L" " + std::to_wstring(yChunkOffset) + L" " + std::to_wstring(zChunkOffset) + L" in " + std::to_wstring(xChunkPos) + L" " + std::to_wstring(yChunkPos) + L" " + std::to_wstring(zChunkPos));
-
- // Wrap the yRot to 360 then adjust to (-180 to 180) range to match java
- float yRotDisplay = fmod(minecraft->player->yRot, 360.0f);
- if (yRotDisplay > 180.0f)
- {
- yRotDisplay -= 360.0f;
- }
- if (yRotDisplay < -180.0f)
- {
- yRotDisplay += 360.0f;
- }
- // Generate the angle string in the format "yRot / xRot" with one decimal place, similar to java edition
- WCHAR angleString[16];
- swprintf(angleString, 16, L"%.1f / %.1f", yRotDisplay, minecraft->player->xRot);
-
- // Work out the named direction
- int direction = Mth::floor(minecraft->player->yRot * 4.0f / 360.0f + 0.5) & 0x3;
- wstring cardinalDirection;
- switch (direction)
- {
- case 0:
- cardinalDirection = L"south";
- break;
- case 1:
- cardinalDirection = L"west";
- break;
- case 2:
- cardinalDirection = L"north";
- break;
- case 3:
- cardinalDirection = L"east";
- break;
- }
-
- lines.push_back(L"Facing: " + cardinalDirection + L" (" + angleString + L")");
-
- // We have to limit y to 256 as we don't get any information past that
- if (minecraft->level != NULL && minecraft->level->hasChunkAt(xBlockPos, fmod(yBlockPos, 256), zBlockPos))
- {
- LevelChunk *chunkAt = minecraft->level->getChunkAt(xBlockPos, zBlockPos);
- if (chunkAt != NULL)
- {
- int skyLight = chunkAt->getBrightness(LightLayer::Sky, xChunkOffset, yChunkOffset, zChunkOffset);
- int blockLight = chunkAt->getBrightness(LightLayer::Block, xChunkOffset, yChunkOffset, zChunkOffset);
- int maxLight = fmax(skyLight, blockLight);
- lines.push_back(L"Light: " + std::to_wstring(maxLight) + L" (" + std::to_wstring(skyLight) + L" sky, " + std::to_wstring(blockLight) + L" block)");
-
- lines.push_back(L"CH S: " + std::to_wstring(chunkAt->getHeightmap(xChunkOffset, zChunkOffset)));
-
- Biome *biome = chunkAt->getBiome(xChunkOffset, zChunkOffset, minecraft->level->getBiomeSource());
- lines.push_back(L"Biome: " + biome->m_name + L" (" + std::to_wstring(biome->id) + L")");
-
- lines.push_back(L"Difficulty: " + std::to_wstring(minecraft->level->difficulty) + L" (Day " + std::to_wstring(minecraft->level->getGameTime() / Level::TICKS_PER_DAY) + L")");
- }
- }
-
- // This is all LCE only stuff, it was never on java
- lines.push_back(L""); // Spacer
- lines.push_back(L"Seed: " + std::to_wstring(minecraft->level->getLevelData()->getSeed()));
- lines.push_back(minecraft->gatherStats1()); // Time to autosave
- lines.push_back(minecraft->gatherStats2()); // Empty currently - CPlatformNetworkManagerStub::GatherStats()
- lines.push_back(minecraft->gatherStats3()); // RTT
- }
-
-#ifdef _DEBUG // Only show terrain features in debug builds not release
- // TERRAIN FEATURES
- if (minecraft->level->dimension->id == 0)
- {
- wstring wfeature[eTerrainFeature_Count];
-
- wfeature[eTerrainFeature_Stronghold] = L"Stronghold: ";
- wfeature[eTerrainFeature_Mineshaft] = L"Mineshaft: ";
- wfeature[eTerrainFeature_Village] = L"Village: ";
- wfeature[eTerrainFeature_Ravine] = L"Ravine: ";
-
- float maxW = static_cast<float>(screenWidth - debugLeft - 8) / scale;
- float maxWForContent = maxW - static_cast<float>(font->width(L"..."));
- bool truncated[eTerrainFeature_Count] = {};
-
- for (size_t i = 0; i < app.m_vTerrainFeatures.size(); i++)
- {
- FEATURE_DATA *pFeatureData = app.m_vTerrainFeatures[i];
- int type = pFeatureData->eTerrainFeature;
- if (type < eTerrainFeature_Stronghold || type > eTerrainFeature_Ravine)
- {
- continue;
- }
- if (truncated[type])
- {
- continue;
- }
-
- wstring itemInfo = L"[" + std::to_wstring(pFeatureData->x * 16) + L", " + std::to_wstring(pFeatureData->z * 16) + L"] ";
- if (font->width(wfeature[type] + itemInfo) <= maxWForContent)
- {
- wfeature[type] += itemInfo;
- }
- else
- {
- wfeature[type] += L"...";
- truncated[type] = true;
- }
- }
-
- lines.push_back(L""); // Add a spacer line
- for (int i = eTerrainFeature_Stronghold; i <= static_cast<int>(eTerrainFeature_Ravine); i++)
- {
- lines.push_back(wfeature[i]);
- }
- lines.push_back(L"");
- }
-#endif
-
- // Loop through the lines and draw them all on screen
- int yPos = debugTop;
- for (const auto &line : lines)
- {
- drawString(font, line, debugLeft, yPos, 0xffffff);
- yPos += 10;
- }
-
- glPopMatrix();
- }
- MemSect(0);
-#endif
-
lastTickA = a;
// 4J Stu - This is now displayed in a xui scene
#if 0
@@ -1203,6 +1007,190 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
glPopMatrix();
}
+#ifndef _FINAL_BUILD
+ MemSect(31);
+ if (true)
+ {
+ // Real window dimensions updated on every WM_SIZE — always current
+ extern int g_rScreenWidth;
+ extern int g_rScreenHeight;
+
+ // Set up a fresh projection using physical pixel coordinates so the debug
+ // text is never distorted regardless of aspect ratio, splitscreen layout,
+ // or menu state. 1 coordinate unit = 1 physical pixel.
+ // Compute the actual viewport dimensions for this player's screen section.
+ // glOrtho must match the viewport exactly for 1 unit = 1 physical pixel.
+ int vpW = g_rScreenWidth;
+ int vpH = g_rScreenHeight;
+ switch (minecraft->player->m_iScreenSection)
+ {
+ case C4JRender::VIEWPORT_TYPE_SPLIT_TOP:
+ case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM:
+ vpH /= 2;
+ break;
+ case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT:
+ case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT:
+ vpW /= 2;
+ break;
+ case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_LEFT:
+ case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT:
+ case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT:
+ case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT:
+ vpW /= 2;
+ vpH /= 2;
+ break;
+ default: // VIEWPORT_TYPE_FULLSCREEN
+ break;
+ }
+
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho(0, vpW, vpH, 0, 1000, 3000);
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+ glTranslatef(0, 0, -2000);
+
+ // Font was designed for guiScale px/unit; scale up so characters appear
+ // at the same physical size as the rest of the HUD at 0.5x.
+ const float fontScale = static_cast<float>(guiScale) * 1.0f;
+ const int debugLeft = 1;
+ const int debugTop = 1;
+
+ glTranslatef(static_cast<float>(debugLeft), static_cast<float>(debugTop), 0.f);
+ glScalef(fontScale, fontScale, 1.f);
+ glTranslatef(static_cast<float>(-debugLeft), static_cast<float>(-debugTop), 0.f);
+
+ vector<wstring> lines;
+
+ // Only show version/branch for player 0 to avoid cluttering each splitscreen viewport
+ if (iPad == 0)
+ {
+ lines.push_back(ClientConstants::VERSION_STRING);
+ lines.push_back(ClientConstants::BRANCH_STRING);
+ }
+ if (minecraft->options->renderDebug && minecraft->player != nullptr && minecraft->level != nullptr)
+ {
+ lines.push_back(minecraft->fpsString);
+ lines.push_back(L"E: " + std::to_wstring(minecraft->level->getAllEntities().size()));
+ int renderDistance = app.GetGameSettings(iPad, eGameSetting_RenderDistance);
+ lines.push_back(L"C: " + std::to_wstring(16 * (2 * renderDistance + 1) * (2 * renderDistance + 1)) + L" D: " + std::to_wstring(renderDistance));
+ lines.push_back(minecraft->gatherStats4());
+
+ wstring dimension = L"unknown";
+ switch (minecraft->player->dimension)
+ {
+ case -1: dimension = L"minecraft:the_nether"; break;
+ case 0: dimension = L"minecraft:overworld"; break;
+ case 1: dimension = L"minecraft:the_end"; break;
+ }
+ lines.push_back(dimension);
+ lines.push_back(L"");
+
+ int xBlockPos = Mth::floor(minecraft->player->x);
+ int yBlockPos = Mth::floor(minecraft->player->y);
+ int zBlockPos = Mth::floor(minecraft->player->z);
+ int xChunkPos = xBlockPos >> 4;
+ int yChunkPos = yBlockPos >> 4;
+ int zChunkPos = zBlockPos >> 4;
+ int xChunkOffset = xBlockPos & 15;
+ int yChunkOffset = yBlockPos & 15;
+ int zChunkOffset = zBlockPos & 15;
+
+ WCHAR posString[44];
+ swprintf(posString, 44, L"%.3f / %.5f / %.3f", minecraft->player->x, minecraft->player->y, minecraft->player->z);
+
+ lines.push_back(L"XYZ: " + std::wstring(posString));
+ lines.push_back(L"Block: " + std::to_wstring(xBlockPos) + L" " + std::to_wstring(yBlockPos) + L" " + std::to_wstring(zBlockPos));
+ lines.push_back(L"Chunk: " + std::to_wstring(xChunkOffset) + L" " + std::to_wstring(yChunkOffset) + L" " + std::to_wstring(zChunkOffset) + L" in " + std::to_wstring(xChunkPos) + L" " + std::to_wstring(yChunkPos) + L" " + std::to_wstring(zChunkPos));
+
+ float yRotDisplay = fmod(minecraft->player->yRot, 360.0f);
+ if (yRotDisplay > 180.0f) yRotDisplay -= 360.0f;
+ if (yRotDisplay < -180.0f) yRotDisplay += 360.0f;
+ WCHAR angleString[16];
+ swprintf(angleString, 16, L"%.1f / %.1f", yRotDisplay, minecraft->player->xRot);
+
+ int direction = Mth::floor(minecraft->player->yRot * 4.0f / 360.0f + 0.5) & 0x3;
+ const wchar_t* cardinals[] = { L"south", L"west", L"north", L"east" };
+ lines.push_back(L"Facing: " + std::wstring(cardinals[direction]) + L" (" + angleString + L")");
+
+ if (minecraft->level != NULL && minecraft->level->hasChunkAt(xBlockPos, fmod(yBlockPos, 256), zBlockPos))
+ {
+ LevelChunk *chunkAt = minecraft->level->getChunkAt(xBlockPos, zBlockPos);
+ if (chunkAt != NULL)
+ {
+ int skyLight = chunkAt->getBrightness(LightLayer::Sky, xChunkOffset, yChunkOffset, zChunkOffset);
+ int blockLight = chunkAt->getBrightness(LightLayer::Block, xChunkOffset, yChunkOffset, zChunkOffset);
+ int maxLight = fmax(skyLight, blockLight);
+ lines.push_back(L"Light: " + std::to_wstring(maxLight) + L" (" + std::to_wstring(skyLight) + L" sky, " + std::to_wstring(blockLight) + L" block)");
+ lines.push_back(L"CH S: " + std::to_wstring(chunkAt->getHeightmap(xChunkOffset, zChunkOffset)));
+ Biome *biome = chunkAt->getBiome(xChunkOffset, zChunkOffset, minecraft->level->getBiomeSource());
+ lines.push_back(L"Biome: " + biome->m_name + L" (" + std::to_wstring(biome->id) + L")");
+ lines.push_back(L"Difficulty: " + std::to_wstring(minecraft->level->difficulty) + L" (Day " + std::to_wstring(minecraft->level->getGameTime() / Level::TICKS_PER_DAY) + L")");
+ }
+ }
+
+ lines.push_back(L"");
+ lines.push_back(L"Seed: " + std::to_wstring(minecraft->level->getLevelData()->getSeed()));
+ lines.push_back(minecraft->gatherStats1());
+ lines.push_back(minecraft->gatherStats2());
+ lines.push_back(minecraft->gatherStats3());
+ }
+
+#ifdef _DEBUG
+ if (minecraft->options->renderDebug && minecraft->player != nullptr && minecraft->level != nullptr && minecraft->level->dimension->id == 0)
+ {
+ wstring wfeature[eTerrainFeature_Count];
+ wfeature[eTerrainFeature_Stronghold] = L"Stronghold: ";
+ wfeature[eTerrainFeature_Mineshaft] = L"Mineshaft: ";
+ wfeature[eTerrainFeature_Village] = L"Village: ";
+ wfeature[eTerrainFeature_Ravine] = L"Ravine: ";
+
+ // maxW in font units: physical width divided by font scale
+ float maxW = (static_cast<float>(g_rScreenWidth) - debugLeft - 8) / fontScale;
+ float maxWForContent = maxW - static_cast<float>(font->width(L"..."));
+ bool truncated[eTerrainFeature_Count] = {};
+
+ for (size_t i = 0; i < app.m_vTerrainFeatures.size(); i++)
+ {
+ FEATURE_DATA *pFeatureData = app.m_vTerrainFeatures[i];
+ int type = pFeatureData->eTerrainFeature;
+ if (type < eTerrainFeature_Stronghold || type > eTerrainFeature_Ravine) continue;
+ if (truncated[type]) continue;
+ wstring itemInfo = L"[" + std::to_wstring(pFeatureData->x * 16) + L", " + std::to_wstring(pFeatureData->z * 16) + L"] ";
+ if (font->width(wfeature[type] + itemInfo) <= maxWForContent)
+ wfeature[type] += itemInfo;
+ else
+ {
+ wfeature[type] += L"...";
+ truncated[type] = true;
+ }
+ }
+
+ lines.push_back(L"");
+ for (int i = eTerrainFeature_Stronghold; i <= static_cast<int>(eTerrainFeature_Ravine); i++)
+ lines.push_back(wfeature[i]);
+ lines.push_back(L"");
+ }
+#endif
+
+ int yPos = debugTop;
+ for (const auto &line : lines)
+ {
+ drawString(font, line, debugLeft, yPos, 0xffffff);
+ yPos += 10;
+ }
+
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
+ }
+ MemSect(0);
+#endif
+
glColor4f(1, 1, 1, 1);
glDisable(GL_BLEND);
glEnable(GL_ALPHA_TEST);
diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp
index aa8fa1fa..11fd81a0 100644
--- a/Minecraft.Client/Minecraft.cpp
+++ b/Minecraft.Client/Minecraft.cpp
@@ -1629,7 +1629,7 @@ void Minecraft::run_middle()
s_prevXButtons[i] = xCurButtons;
}
bool startJustPressed = s_startPressLatch[i] > 0;
- bool tryJoin = !pause && !ui.IsIgnorePlayerJoinMenuDisplayed(ProfileManager.GetPrimaryPad()) && g_NetworkManager.SessionHasSpace() && xCurButtons != 0;
+ bool tryJoin = !pause && !ui.IsIgnorePlayerJoinMenuDisplayed(ProfileManager.GetPrimaryPad()) && g_NetworkManager.SessionHasSpace() && xCurButtons != 0 && g_KBMInput.IsWindowFocused();
#else
bool tryJoin = !pause && !ui.IsIgnorePlayerJoinMenuDisplayed(ProfileManager.GetPrimaryPad()) && g_NetworkManager.SessionHasSpace() && RenderManager.IsHiDef() && InputManager.ButtonPressed(i);
#endif
@@ -3706,7 +3706,9 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures)
app.EnableDebugOverlay(options->renderDebug,iPad);
#else
// 4J Stu - The xbox uses a completely different way of navigating to this scene
- ui.NavigateToScene(0, eUIScene_DebugOverlay, nullptr, eUILayer_Debug);
+ // Always open in the fullscreen group so the overlay spans the full window
+ // regardless of split-screen viewport configuration.
+ ui.NavigateToScene(0, eUIScene_DebugOverlay, nullptr, eUILayer_Debug, eUIGroup_Fullscreen);
#endif
#endif
}
diff --git a/Minecraft.Client/MultiPlayerLevel.h b/Minecraft.Client/MultiPlayerLevel.h
index a552fc2b..b7f1640a 100644
--- a/Minecraft.Client/MultiPlayerLevel.h
+++ b/Minecraft.Client/MultiPlayerLevel.h
@@ -12,6 +12,7 @@ using namespace std;
class MultiPlayerLevel : public Level
{
+ friend class ClientConnection;
private:
static const int TICKS_BEFORE_RESET = 20 * 4;
diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
index 28d29504..9d73eda8 100644
--- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
+++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
@@ -392,6 +392,11 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
}
s_localSmallId = assignedSmallId;
+ // Save the host IP and port so JoinSplitScreen can connect to the same host
+ // regardless of how the connection was initiated (UI vs command line).
+ strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), ip, _TRUNCATE);
+ g_Win64MultiplayerPort = port;
+
app.DebugPrintf("Win64 LAN: Connected to %s:%d, assigned smallId=%d\n", ip, port, s_localSmallId);
s_active = true;
@@ -733,6 +738,11 @@ bool WinsockNetLayer::PopDisconnectedSmallId(BYTE* outSmallId)
void WinsockNetLayer::PushFreeSmallId(BYTE smallId)
{
+ // SmallIds 0..(XUSER_MAX_COUNT-1) are permanently reserved for the host's
+ // local pads and must never be recycled to remote clients.
+ if (smallId < (BYTE)XUSER_MAX_COUNT)
+ return;
+
EnterCriticalSection(&s_freeSmallIdLock);
// Guard against double-recycle: the reconnect path (queueSmallIdForRecycle) and
// the DoWork disconnect path can both push the same smallId. If we allow duplicates,