Fix Ender Dragon damage, End portal transition, and End Poem crash

Dragon melee damage: reassign sub-entity IDs to be sequential from
the parent entity ID in ServerLevel::entityAdded(), so the client's
offset-based ID calculation matches the server. Previously the server's
smallId pool allocated non-sequential IDs, causing melee attacks to
target entity IDs the server didn't recognize.

End portal transition: ensure the player entity is always added to the
new level when transitioning from The End, not just for non-End
dimensions. The addEntity call was previously gated behind a
lastDimension != 1 check that also excluded it from End exits.

End Poem crash: bounds-check the WIN_GAME event's player index before
accessing localplayers[], with a fallback to prevent null dereference
when the server sends an out-of-range index.
This commit is contained in:
itsRevela 2026-03-26 19:46:58 -05:00
parent 2e75441355
commit 35fbc7af17
6 changed files with 27 additions and 22 deletions

View file

@ -50,13 +50,18 @@ UIScene_EndPoem::UIScene_EndPoem(int iPad, void *initData, UILayer *parentLayer)
Minecraft *pMinecraft = Minecraft::GetInstance();
wstring playerName = L"";
if(pMinecraft->localplayers[ui.GetWinUserIndex()] != nullptr)
unsigned int winIdx = ui.GetWinUserIndex();
if(winIdx < XUSER_MAX_COUNT && pMinecraft->localplayers[winIdx] != nullptr)
{
playerName = escapeXML( pMinecraft->localplayers[ui.GetWinUserIndex()]->getDisplayName() );
playerName = escapeXML( pMinecraft->localplayers[winIdx]->getDisplayName() );
}
else if(pMinecraft->localplayers[ProfileManager.GetPrimaryPad()] != nullptr)
{
playerName = escapeXML( pMinecraft->localplayers[ProfileManager.GetPrimaryPad()]->getDisplayName() );
}
else
{
playerName = escapeXML( pMinecraft->localplayers[ProfileManager.GetPrimaryPad()]->getDisplayName() );
playerName = L"Player";
}
noNoiseString = replaceAll(noNoiseString,L"{*PLAYER*}",playerName);

View file

@ -825,9 +825,7 @@ void PlayerConnection::handleInteract(shared_ptr<InteractPacket> packet)
{
if ((target->GetType() == eTYPE_ITEMENTITY) || (target->GetType() == eTYPE_EXPERIENCEORB) || (target->GetType() == eTYPE_ARROW) || target == player)
{
//disconnect("Attempting to attack an invalid entity");
//server.warn("Player " + player.getName() + " tried to attack an invalid entity");
return;
return;
}
player->attack(target);
}

View file

@ -965,15 +965,16 @@ void PlayerList::repositionAcrossDimension(shared_ptr<Entity> entity, int lastDi
addPlayerToReceiving(player);
}
if (lastDimension != 1)
xt = static_cast<double>(Mth::clamp(static_cast<int>(xt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
zt = static_cast<double>(Mth::clamp(static_cast<int>(zt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
if (entity->isAlive())
{
xt = static_cast<double>(Mth::clamp(static_cast<int>(xt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
zt = static_cast<double>(Mth::clamp(static_cast<int>(zt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
if (entity->isAlive())
newLevel->addEntity(entity);
entity->moveTo(xt, entity->y, zt, entity->yRot, entity->xRot);
newLevel->tick(entity, false);
// Portal forcing only for non-End exits (End exits go to spawn, not a portal)
if (lastDimension != 1)
{
newLevel->addEntity(entity);
entity->moveTo(xt, entity->y, zt, entity->yRot, entity->xRot);
newLevel->tick(entity, false);
newLevel->cache->autoCreate = true;
newLevel->getPortalForcer()->force(entity, xOriginal, yOriginal, zOriginal, yRotOriginal);
newLevel->cache->autoCreate = false;

View file

@ -1052,9 +1052,14 @@ void ServerLevel::entityAdded(shared_ptr<Entity> e)
vector<shared_ptr<Entity> > *es = e->getSubEntities();
if (es)
{
// Reassign sub-entity IDs to be sequential from the parent's ID.
// The client assumes this layout when it applies an offset in handleAddMob.
int offset = 1;
for(auto& i : *es)
{
i->entityId = e->entityId + offset;
entitiesById.emplace(i->entityId, i);
offset++;
}
}
entityAddedExtra(e); // 4J added

View file

@ -1118,15 +1118,6 @@ bool EnderDragon::hurt(shared_ptr<MultiEntityMobPart> MultiEntityMobPart, Damage
damage = damage / 4 + 1;
}
//float rot1 = yRot * PI / 180;
//float ss1 = sin(rot1);
//float cc1 = cos(rot1);
//xTarget = x + ss1 * 5 + (random->nextFloat() - 0.5f) * 2;
//yTarget = y + random->nextFloat() * 3 + 1;
//zTarget = z - cc1 * 5 + (random->nextFloat() - 0.5f) * 2;
//attackTarget = nullptr;
if ( source->getEntity() != nullptr && source->getEntity()->instanceof(eTYPE_PLAYER) || source->isExplosion() )
{
int healthBefore = getHealth();

View file

@ -14,6 +14,11 @@ This project is based on source code of Minecraft Legacy Console Edition v1.6.05
## Latest:
End dimension fixes for dedicated servers:
- Fixed the Ender Dragon being immune to melee damage on dedicated servers. The server's entity ID allocator (smallId pool) assigned non-sequential IDs to the dragon's body parts, but the client assumed sequential offsets. Melee attacks targeted IDs the server didn't recognize, so hits were silently dropped. The server now reassigns sub-entity IDs to be sequential from the parent when an entity with parts is added to the level
- Fixed entering the End exit portal after defeating the dragon crashing the game. The player entity was never added to the Overworld level during the dimension transition, leaving the player as a ghost entity that caused a crash on the next interaction
- Fixed the End Poem crashing the client on dedicated servers due to an out-of-bounds player index lookup in the WIN_GAME event handler
Dedicated server player list fix:
- The Tab player list now correctly shows all connected players on dedicated servers. Previously only the local player was visible because remote players were never registered in the client's network player tracking when their `AddPlayerPacket` arrived
- The dedicated server's phantom host entry (slot 0, empty name) is now filtered from the list