diff --git a/Minecraft.Client/Common/Audio/SoundEngine.cpp b/Minecraft.Client/Common/Audio/SoundEngine.cpp index cf140c78..06bfdd83 100644 --- a/Minecraft.Client/Common/Audio/SoundEngine.cpp +++ b/Minecraft.Client/Common/Audio/SoundEngine.cpp @@ -468,65 +468,69 @@ void SoundEngine::play(int iSound, float x, float y, float z, float volume, floa sprintf_s(basePath, "Windows64Media/Sound/%s", (char*)szSoundName); char finalPath[256]; - sprintf_s(finalPath, "%s.wav", basePath); - const char* extensions[] = { ".ogg", ".wav", ".mp3" }; - size_t extCount = sizeof(extensions) / sizeof(extensions[0]); - bool found = false; - - for (size_t extIdx = 0; extIdx < extCount; extIdx++) + // Check path cache first to avoid expensive filesystem probing + auto cacheIt = m_soundPathCache.find(iSound); + if (cacheIt != m_soundPathCache.end()) { - char basePlusExt[256]; - sprintf_s(basePlusExt, "%s%s", basePath, extensions[extIdx]); - - DWORD attr = GetFileAttributesA(basePlusExt); - if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)) - { - sprintf_s(finalPath, "%s", basePlusExt); - found = true; - break; - } + const auto& paths = cacheIt->second; + if (paths.empty()) + return; // previously probed, no files found + const std::string& chosen = paths[rand() % paths.size()]; + sprintf_s(finalPath, "%s", chosen.c_str()); } - - if (!found) + else { - int count = 0; + // Cache miss — probe filesystem and store results + std::vector validPaths; + const char* extensions[] = { ".ogg", ".wav", ".mp3" }; + size_t extCount = sizeof(extensions) / sizeof(extensions[0]); + bool found = false; + // Check base name with each extension (non-numbered) for (size_t extIdx = 0; extIdx < extCount; extIdx++) { - for (size_t i = 1; i < 32; i++) + char basePlusExt[256]; + sprintf_s(basePlusExt, "%s%s", basePath, extensions[extIdx]); + + DWORD attr = GetFileAttributesA(basePlusExt); + if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)) { - char numberedPath[256]; - sprintf_s(numberedPath, "%s%d%s", basePath, i, extensions[extIdx]); - - DWORD attr = GetFileAttributesA(numberedPath); - if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)) + validPaths.push_back(basePlusExt); + found = true; + break; // non-numbered: only one base file needed + } + } + + if (!found) + { + // Check numbered variants (e.g. sound1.ogg, sound2.ogg, ...) + for (size_t extIdx = 0; extIdx < extCount; extIdx++) + { + for (int i = 1; i < 32; i++) { - count = i; + char numberedPath[256]; + sprintf_s(numberedPath, "%s%d%s", basePath, i, extensions[extIdx]); + + DWORD attr = GetFileAttributesA(numberedPath); + if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)) + { + validPaths.push_back(numberedPath); + } } } } - if (count > 0) + m_soundPathCache[iSound] = validPaths; + + if (validPaths.empty()) { - int chosen = (rand() % count) + 1; - for (size_t extIdx = 0; extIdx < extCount; extIdx++) - { - char numberedPath[256]; - sprintf_s(numberedPath, "%s%d%s", basePath, chosen, extensions[extIdx]); - - DWORD attr = GetFileAttributesA(numberedPath); - if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)) - { - sprintf_s(finalPath, "%s", numberedPath); - found = true; - break; - } - } - if (!found) - { - sprintf_s(finalPath, "%s%d.wav", basePath, chosen); - } + sprintf_s(finalPath, "%s.wav", basePath); // fallback for debug print + } + else + { + const std::string& chosen = validPaths[rand() % validPaths.size()]; + sprintf_s(finalPath, "%s", chosen.c_str()); } } @@ -546,7 +550,7 @@ void SoundEngine::play(int iSound, float x, float y, float z, float volume, floa if (ma_sound_init_from_file( &m_engine, finalPath, - MA_SOUND_FLAG_ASYNC, + MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, nullptr, nullptr, &s->sound) != MA_SUCCESS) @@ -602,24 +606,40 @@ void SoundEngine::playUI(int iSound, float volume, float pitch) sprintf_s(basePath, "Windows64Media/Sound/Minecraft/UI/%s", ConvertSoundPathToName(name)); char finalPath[256]; - sprintf_s(finalPath, "%s.wav", basePath); - const char* extensions[] = { ".ogg", ".wav", ".mp3" }; - size_t count = sizeof(extensions) / sizeof(extensions[0]); - bool found = false; - for (size_t i = 0; i < count; i++) + // Check UI sound path cache first + auto cacheIt = m_uiSoundPathCache.find(iSound); + if (cacheIt != m_uiSoundPathCache.end()) { - sprintf_s(finalPath, "%s%s", basePath, extensions[i]); - if (FileExists(finalPath)) + if (cacheIt->second.empty()) { - found = true; - break; + app.DebugPrintf("No sound file found for UI sound (cached): %s\n", basePath); + return; } + sprintf_s(finalPath, "%s", cacheIt->second.c_str()); } - if (!found) + else { - app.DebugPrintf("No sound file found for UI sound: %s\n", basePath); - return; + // Cache miss — probe filesystem and store result + const char* extensions[] = { ".ogg", ".wav", ".mp3" }; + size_t count = sizeof(extensions) / sizeof(extensions[0]); + bool found = false; + for (size_t i = 0; i < count; i++) + { + sprintf_s(finalPath, "%s%s", basePath, extensions[i]); + if (FileExists(finalPath)) + { + found = true; + break; + } + } + if (!found) + { + m_uiSoundPathCache[iSound] = ""; // cache negative result + app.DebugPrintf("No sound file found for UI sound: %s\n", basePath); + return; + } + m_uiSoundPathCache[iSound] = finalPath; } MiniAudioSound* s = new MiniAudioSound(); @@ -633,7 +653,7 @@ void SoundEngine::playUI(int iSound, float volume, float pitch) if (ma_sound_init_from_file( &m_engine, finalPath, - MA_SOUND_FLAG_ASYNC, + MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, nullptr, nullptr, &s->sound) != MA_SUCCESS) diff --git a/Minecraft.Client/Common/Audio/SoundEngine.h b/Minecraft.Client/Common/Audio/SoundEngine.h index 38d70d41..5bd131ec 100644 --- a/Minecraft.Client/Common/Audio/SoundEngine.h +++ b/Minecraft.Client/Common/Audio/SoundEngine.h @@ -5,6 +5,8 @@ using namespace std; #include "..\..\Minecraft.World\SoundTypes.h" #include "miniaudio.h" +#include +#include constexpr float SFX_3D_MIN_DISTANCE = 1.0f; constexpr float SFX_3D_MAX_DISTANCE = 16.0f; @@ -187,6 +189,9 @@ private: int m_iStream_CD_1; bool *m_bHeardTrackA; + std::unordered_map> m_soundPathCache; // play(): sound ID → all valid variant paths + std::unordered_map m_uiSoundPathCache; // playUI(): sound ID → resolved path + #ifdef __ORBIS__ int32_t m_hBGMAudio; #endif diff --git a/Minecraft.Client/GameRenderer.cpp b/Minecraft.Client/GameRenderer.cpp index 0b99a231..0b8f8541 100644 --- a/Minecraft.Client/GameRenderer.cpp +++ b/Minecraft.Client/GameRenderer.cpp @@ -1545,7 +1545,7 @@ void GameRenderer::renderLevel(float a, int64_t until) if (visibleWaterChunks > 0) { PIXBeginNamedEvent(0,"Fancy second pass - actual rendering"); - levelRenderer->render(cameraEntity, 1, a, updateChunks); // 4J - chanaged, used to be renderSameAsLast but we don't support that anymore + levelRenderer->renderChunksDirect(1, a); // Lightweight path — skips redundant allChanged/resortChunks checks PIXEndNamedEvent(); } diff --git a/Minecraft.Client/LevelRenderer.cpp b/Minecraft.Client/LevelRenderer.cpp index cf2937f4..079bb6a2 100644 --- a/Minecraft.Client/LevelRenderer.cpp +++ b/Minecraft.Client/LevelRenderer.cpp @@ -157,6 +157,11 @@ LevelRenderer::LevelRenderer(Minecraft *mc, Textures *textures) dirtyChunkPresent = false; lastDirtyChunkFound = 0; + visibleLists_layer0 = nullptr; + visibleLists_layer1 = nullptr; + visibleCount_layer0 = 0; + visibleCount_layer1 = 0; + this->mc = mc; this->textures = textures; @@ -455,6 +460,12 @@ void LevelRenderer::allChanged(int playerIndex) // delete sortedChunks[playerIndex]; // 4J - removed - not sorting our chunks anymore } + // Free old visible chunk lists + delete[] visibleLists_layer0; + delete[] visibleLists_layer1; + visibleLists_layer0 = nullptr; + visibleLists_layer1 = nullptr; + chunks[playerIndex] = ClipChunkArray(xChunks * yChunks * zChunks); // sortedChunks[playerIndex] = new vector(xChunks * yChunks * zChunks); // 4J - removed - not sorting our chunks anymore int id = 0; @@ -487,6 +498,13 @@ void LevelRenderer::allChanged(int playerIndex) } nonStackDirtyChunksAdded(); + // Allocate visible chunk lists (worst case: all chunks visible) + int totalChunkCount = xChunks * yChunks * zChunks; + visibleLists_layer0 = new int[totalChunkCount]; + visibleLists_layer1 = new int[totalChunkCount]; + visibleCount_layer0 = 0; + visibleCount_layer1 = 0; + if (level != nullptr) { shared_ptr player = mc->cameraTargetPlayer; @@ -705,42 +723,42 @@ int LevelRenderer::render(shared_ptr player, int layer, double alp { int playerIndex = mc->player->GetXboxPad(); - // 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require - if( lastPlayerCount[playerIndex] != activePlayers() ) - { - allChanged(); - } - else if (mc->options->viewDistance != lastViewDistance) - { - allChanged(); - } - + // Only check allChanged/resortChunks on layer 0 — they only need to run once per frame if (layer == 0) { + // 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require + if( lastPlayerCount[playerIndex] != activePlayers() ) + { + allChanged(); + } + else if (mc->options->viewDistance != lastViewDistance) + { + allChanged(); + } + totalChunks = 0; offscreenChunks = 0; occludedChunks = 0; renderedChunks = 0; emptyChunks = 0; + + double xd = player->x - xOld[playerIndex]; + double yd = player->y - yOld[playerIndex]; + double zd = player->z - zOld[playerIndex]; + + if (xd * xd + yd * yd + zd * zd > 4 * 4) + { + xOld[playerIndex] = player->x; + yOld[playerIndex] = player->y; + zOld[playerIndex] = player->z; + + resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z)); + } } double xOff = player->xOld + (player->x - player->xOld) * alpha; double yOff = player->yOld + (player->y - player->yOld) * alpha; double zOff = player->zOld + (player->z - player->zOld) * alpha; - - double xd = player->x - xOld[playerIndex]; - double yd = player->y - yOld[playerIndex]; - double zd = player->z - zOld[playerIndex]; - - if (xd * xd + yd * yd + zd * zd > 4 * 4) - { - xOld[playerIndex] = player->x; - yOld[playerIndex] = player->y; - zOld[playerIndex] = player->z; - - resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z)); - // sort(sortedChunks[playerIndex]->begin(),sortedChunks[playerIndex]->end(), DistanceChunkSorter(player)); // 4J - removed - not sorting our chunks anymore - } Lighting::turnOff(); int count = renderChunks(0, static_cast(chunks[playerIndex].length), layer, alpha); @@ -749,6 +767,38 @@ int LevelRenderer::render(shared_ptr player, int layer, double alp } +// Lightweight render path for the second layer 1 pass — skips allChanged/resortChunks checks +// and just does GL setup + visible list iteration + cleanup. +// Assumes Lighting::turnOff() was already called by the prior render() invocation. +void LevelRenderer::renderChunksDirect(int layer, double alpha) +{ + shared_ptr player = mc->cameraTargetPlayer; + if (player == nullptr) return; + + mc->gameRenderer->turnOnLightLayer(alpha); + double xOff = player->xOld + (player->x - player->xOld) * alpha; + double yOff = player->yOld + (player->y - player->yOld) * alpha; + double zOff = player->zOld + (player->z - player->zOld) * alpha; + + glPushMatrix(); + glTranslatef(static_cast(-xOff), static_cast(-yOff), static_cast(-zOff)); + + int *lists = (layer == 0) ? visibleLists_layer0 : visibleLists_layer1; + int numVisible = (layer == 0) ? visibleCount_layer0 : visibleCount_layer1; + bool first = true; + if (lists != nullptr) + { + for (int i = 0; i < numVisible; i++) + { + if (RenderManager.CBuffCall(lists[i], first)) + first = false; + } + } + + glPopMatrix(); + mc->gameRenderer->turnOffLightLayer(alpha); +} + #ifdef __PSVITA__ #include @@ -819,23 +869,35 @@ int LevelRenderer::renderChunks(int from, int to, int layer, double alpha) bool first = true; int count = 0; - ClipChunk *pClipChunk = chunks[playerIndex].data; - unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer; - for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ ) + + // Use compact visible lists built during cull() instead of iterating all chunks + int *lists = (layer == 0) ? visibleLists_layer0 : visibleLists_layer1; + int numVisible = (layer == 0) ? visibleCount_layer0 : visibleCount_layer1; + if (lists != nullptr) { - if( !pClipChunk->visible ) continue; // This will be set if the chunk isn't visible, or isn't compiled, or has both empty flags set - if( pClipChunk->globalIdx == -1 ) continue; // Not sure if we should ever encounter this... TODO check - if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue; // Check that this particular layer isn't empty - - // List can be calculated directly from the chunk's global idex - int list = pClipChunk->globalIdx * 2 + layer; - list += chunkLists; - - if(RenderManager.CBuffCall(list, first)) + for (int i = 0; i < numVisible; i++) { - first = false; + if (RenderManager.CBuffCall(lists[i], first)) + first = false; + count++; + } + } + else + { + // Fallback: iterate all chunks (before visible lists are allocated) + ClipChunk *pClipChunk = chunks[playerIndex].data; + unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer; + for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ ) + { + if( !pClipChunk->visible ) continue; + if( pClipChunk->globalIdx == -1 ) continue; + if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue; + int list = pClipChunk->globalIdx * 2 + layer; + list += chunkLists; + if(RenderManager.CBuffCall(list, first)) + first = false; + count++; } - count++; } #ifdef __PSVITA__ @@ -1968,6 +2030,9 @@ bool LevelRenderer::updateDirtyChunks() for( int y = 0; y < CHUNK_Y_COUNT; y++ ) { ClipChunk *pClipChunk = &chunks[p][(z * yChunks + y) * xChunks + x]; + // Early-out for non-dirty chunks - avoids distance calculation for the vast majority at steady state + if( !(globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_DIRTY) ) + continue; // Get distance to this chunk - deliberately not calling the chunk's method of doing this to avoid overheads (passing entitie, type conversion etc.) that this involves int xd = pClipChunk->xm - px; int yd = pClipChunk->ym - py; @@ -2163,7 +2228,9 @@ bool LevelRenderer::updateDirtyChunks() else { // Nothing to do - clear flags that there are things to process, unless it's been a while since we found any dirty chunks in which case force a check next time through - if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS ) + // Scale recheck period with render distance to reduce wasted full-scans at high distances + int recheckPeriod = (xChunks >= 60) ? 1000 : (xChunks >= 40) ? 500 : FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS; + if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > recheckPeriod ) { dirtyChunkPresent = true; } @@ -2564,32 +2631,81 @@ void LevelRenderer::cull(Culler *culler, float a) fdraw[i * 4 + 3] = static_cast(fd->m_Frustum[i][3] + (fx * -fc->xOff) + (fy * -fc->yOff) + (fz * -fc->zOff)); } - ClipChunk *pClipChunk = chunks[playerIndex].data; int vis = 0; int total = 0; int numWrong = 0; - for (unsigned int i = 0; i < chunks[playerIndex].length; i++) + + // Reset visible chunk lists for this frame + visibleCount_layer0 = 0; + visibleCount_layer1 = 0; + + // Column-level frustum culling: test one AABB per XZ column before testing individual Y chunks. + // At dist 64 this reduces ~278K clip() calls to ~17K column tests + per-chunk tests only for visible columns. + for (int x = 0; x < xChunks; x++) { - unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ]; + for (int z = 0; z < zChunks; z++) + { + // Build column AABB from bottom and top chunks in this column + ClipChunk *bottomChunk = &chunks[playerIndex][(z * yChunks + 0) * xChunks + x]; + ClipChunk *topChunk = &chunks[playerIndex][(z * yChunks + (yChunks - 1)) * xChunks + x]; + float columnAABB[6] = { + bottomChunk->aabb[0], bottomChunk->aabb[1], bottomChunk->aabb[2], // minX, minY, minZ + bottomChunk->aabb[3], topChunk->aabb[4], bottomChunk->aabb[5] // maxX, maxY(top), maxZ + }; - // Always perform frustum cull test - bool clipres = clip(pClipChunk->aabb, fdraw); + // Test entire column against frustum + if (!clip(columnAABB, fdraw)) + { + // Entire column outside frustum — mark all Y chunks invisible + for (int y = 0; y < yChunks; y++) + { + ClipChunk *pClipChunk = &chunks[playerIndex][(z * yChunks + y) * xChunks + x]; + pClipChunk->visible = false; + } + continue; + } - if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) ) - { - pClipChunk->visible = clipres; - if( pClipChunk->visible ) vis++; - total++; + // Column is (partially) in frustum — test individual chunks + for (int y = 0; y < yChunks; y++) + { + ClipChunk *pClipChunk = &chunks[playerIndex][(z * yChunks + y) * xChunks + x]; + unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ]; + + // Skip frustum test for confirmed-empty compiled chunks - they have nothing to render + if ((flags & CHUNK_FLAG_COMPILED) && (flags & CHUNK_FLAG_EMPTYBOTH) == CHUNK_FLAG_EMPTYBOTH) + { + pClipChunk->visible = false; + continue; + } + + bool clipres = clip(pClipChunk->aabb, fdraw); + + if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) ) + { + pClipChunk->visible = clipres; + if( pClipChunk->visible ) vis++; + total++; + } + else if (clipres) + { + pClipChunk->visible = true; + } + else + { + pClipChunk->visible = false; + } + + // Build compact visible chunk lists for renderChunks() + if (pClipChunk->visible && pClipChunk->globalIdx != -1 && visibleLists_layer0 != nullptr) + { + int list = pClipChunk->globalIdx * 2 + chunkLists; + if (!((flags & CHUNK_FLAG_EMPTY0) == CHUNK_FLAG_EMPTY0)) + visibleLists_layer0[visibleCount_layer0++] = list; + if (!((flags & CHUNK_FLAG_EMPTY1) == CHUNK_FLAG_EMPTY1)) + visibleLists_layer1[visibleCount_layer1++] = list + 1; + } + } } - else if (clipres) - { - pClipChunk->visible = true; - } - else - { - pClipChunk->visible = false; - } - pClipChunk++; } } diff --git a/Minecraft.Client/LevelRenderer.h b/Minecraft.Client/LevelRenderer.h index 37e0b813..4d24ed45 100644 --- a/Minecraft.Client/LevelRenderer.h +++ b/Minecraft.Client/LevelRenderer.h @@ -83,6 +83,7 @@ private: void resortChunks(int xc, int yc, int zc); public: int render(shared_ptr player, int layer, double alpha, bool updateChunks); + void renderChunksDirect(int layer, double alpha); private: int renderChunks(int from, int to, int layer, double alpha); public: @@ -270,6 +271,12 @@ public: XLockFreeStack dirtyChunksLockFreeStack; + // Visible chunk lists built by cull(), consumed by renderChunks() + int *visibleLists_layer0; + int *visibleLists_layer1; + int visibleCount_layer0; + int visibleCount_layer1; + bool dirtyChunkPresent; int64_t lastDirtyChunkFound; static const int FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS = 125; // decreased from 250 to 125 - updated by detectiveren diff --git a/Minecraft.Client/PlayerConnection.cpp b/Minecraft.Client/PlayerConnection.cpp index 054dcf71..a2ceba45 100644 --- a/Minecraft.Client/PlayerConnection.cpp +++ b/Minecraft.Client/PlayerConnection.cpp @@ -491,9 +491,9 @@ void PlayerConnection::handlePlayerAction(shared_ptr packet) } else if (packet->action == PlayerActionPacket::STOP_DESTROY_BLOCK) { - player->gameMode->stopDestroyBlock(x, y, z); + bool destroyed = player->gameMode->stopDestroyBlock(x, y, z); server->getPlayers()->prioritiseTileChanges(x, y, z, level->dimension->id); // 4J added - make sure that the update packets for this get prioritised over other general world updates - if (level->getTile(x, y, z) != 0) player->connection->send(std::make_shared(x, y, z, level)); + if (!destroyed && level->getTile(x, y, z) != 0) player->connection->send(std::make_shared(x, y, z, level)); } else if (packet->action == PlayerActionPacket::ABORT_DESTROY_BLOCK) { diff --git a/Minecraft.Client/ServerPlayerGameMode.cpp b/Minecraft.Client/ServerPlayerGameMode.cpp index 041487f5..fa91fad8 100644 --- a/Minecraft.Client/ServerPlayerGameMode.cpp +++ b/Minecraft.Client/ServerPlayerGameMode.cpp @@ -172,7 +172,7 @@ void ServerPlayerGameMode::startDestroyBlock(int x, int y, int z, int face) } } -void ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z) +bool ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z) { if (x == xDestroyBlock && y == yDestroyBlock && z == zDestroyBlock) { @@ -188,6 +188,7 @@ void ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z) isDestroyingBlock = false; level->destroyTileProgress(player->entityId, x, y, z, -1); destroyBlock(x, y, z); + return true; } else if (!hasDelayedDestroy) { @@ -198,9 +199,11 @@ void ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z) delayedDestroyY = y; delayedDestroyZ = z; delayedTickStart = destroyProgressStart; + return true; } } } + return false; } void ServerPlayerGameMode::abortDestroyBlock(int x, int y, int z) diff --git a/Minecraft.Client/ServerPlayerGameMode.h b/Minecraft.Client/ServerPlayerGameMode.h index 509e1676..6b18debc 100644 --- a/Minecraft.Client/ServerPlayerGameMode.h +++ b/Minecraft.Client/ServerPlayerGameMode.h @@ -45,7 +45,7 @@ public: void tick(); void startDestroyBlock(int x, int y, int z, int face); - void stopDestroyBlock(int x, int y, int z); + bool stopDestroyBlock(int x, int y, int z); void abortDestroyBlock(int x, int y, int z); private: diff --git a/Minecraft.World/Entity.cpp b/Minecraft.World/Entity.cpp index 924312e5..0b581527 100644 --- a/Minecraft.World/Entity.cpp +++ b/Minecraft.World/Entity.cpp @@ -705,6 +705,8 @@ void Entity::move(double xa, double ya, double za, bool noEntityCubes) // 4J - return; } + auto self = shared_from_this(); + ySlideOffset *= 0.4f; double xo = x; @@ -734,21 +736,21 @@ void Entity::move(double xa, double ya, double za, bool noEntityCubes) // 4J - if (isPlayerSneaking) { double d = 0.05; - while (xa != 0 && level->getCubes(shared_from_this(), bb->cloneMove(xa, -1.0, 0))->empty()) + while (xa != 0 && level->getCubes(self, bb->cloneMove(xa, -1.0, 0), true)->empty()) { if (xa < d && xa >= -d) xa = 0; else if (xa > 0) xa -= d; else xa += d; xaOrg = xa; } - while (za != 0 && level->getCubes(shared_from_this(), bb->cloneMove(0, -1.0, za))->empty()) + while (za != 0 && level->getCubes(self, bb->cloneMove(0, -1.0, za), true)->empty()) { if (za < d && za >= -d) za = 0; else if (za > 0) za -= d; else za += d; zaOrg = za; } - while (xa != 0 && za != 0 && level->getCubes(shared_from_this(), bb->cloneMove(xa, -1.0, za))->empty()) + while (xa != 0 && za != 0 && level->getCubes(self, bb->cloneMove(xa, -1.0, za), true)->empty()) { if (xa < d && xa >= -d) xa = 0; else if (xa > 0) xa -= d; @@ -761,7 +763,7 @@ void Entity::move(double xa, double ya, double za, bool noEntityCubes) // 4J - } } - AABBList *aABBs = level->getCubes(shared_from_this(), bb->expand(xa, ya, za), noEntityCubes, true); + AABBList *aABBs = level->getCubes(self, bb->expand(xa, ya, za), noEntityCubes, true); // 4J Stu - Particles (and possibly other entities) don't have xChunk and zChunk set, so calculate the chunk instead @@ -816,7 +818,7 @@ void Entity::move(double xa, double ya, double za, bool noEntityCubes) // 4J - bb->set(bbOrg); // 4J - added extra expand, as if we don't move up by footSize by hitting a block above us, then overall we could be trying to move as much as footSize downwards, // so we'd better include cubes under our feet in this list of things we might possibly collide with - aABBs = level->getCubes(shared_from_this(), bb->expand(xa, ya, za)->expand(0,-ya,0),false,true); + aABBs = level->getCubes(self, bb->expand(xa, ya, za)->expand(0,-ya,0),false,true); if(!level->isClientSide || level->reallyHasChunk(xc, zc)) { @@ -926,7 +928,7 @@ void Entity::move(double xa, double ya, double za, bool noEntityCubes) // 4J - playSound(eSoundType_LIQUID_SWIM, speed, 1 + (random->nextFloat() - random->nextFloat()) * 0.4f); } playStepSound(xt, yt, zt, t); - Tile::tiles[t]->stepOn(level, xt, yt, zt, shared_from_this()); + Tile::tiles[t]->stepOn(level, xt, yt, zt, self); } } @@ -969,6 +971,7 @@ void Entity::checkInsideTiles() if (level->hasChunksAt(x0, y0, z0, x1, y1, z1)) { + auto self = shared_from_this(); for (int x = x0; x <= x1; x++) for (int y = y0; y <= y1; y++) for (int z = z0; z <= z1; z++) @@ -976,7 +979,7 @@ void Entity::checkInsideTiles() int t = level->getTile(x, y, z); if (t > 0) { - Tile::tiles[t]->entityInside(level, x, y, z, shared_from_this()); + Tile::tiles[t]->entityInside(level, x, y, z, self); } } } diff --git a/Minecraft.World/Level.cpp b/Minecraft.World/Level.cpp index 161f4e56..237a7b48 100644 --- a/Minecraft.World/Level.cpp +++ b/Minecraft.World/Level.cpp @@ -932,6 +932,7 @@ bool Level::setTileAndData(int x, int y, int z, int tile, int data, int updateFl int old = c->getTile(x & 15, y, z & 15); int olddata = c->getData( x & 15, y, z & 15); #endif + int prevTile = c->getTile(x & 15, y, z & 15); result = c->setTileAndData(x & 15, y, z & 15, tile, data); if( updateFlags != Tile::UPDATE_INVISIBLE_NO_LIGHT) { @@ -939,7 +940,11 @@ bool Level::setTileAndData(int x, int y, int z, int tile, int data, int updateFl PIXBeginNamedEvent(0,"Checking light %d %d %d",x,y,z); PIXBeginNamedEvent(0,"was %d, %d now %d, %d",old,olddata,tile,data); #endif - checkLight(x, y, z); + if (Tile::lightBlock[tile & 0xff] != Tile::lightBlock[prevTile & 0xff] || + Tile::lightEmission[tile & 0xff] != Tile::lightEmission[prevTile & 0xff]) + { + checkLight(x, y, z); + } PIXEndNamedEvent(); PIXEndNamedEvent(); } @@ -3687,13 +3692,11 @@ vector > *Level::getEntities(shared_ptr except, AABB int zc0 = Mth::floor((bb->z0 - 2) / 16); int zc1 = Mth::floor((bb->z1 + 2) / 16); -#ifdef __PSVITA__ #ifdef _ENTITIES_RW_SECTION // AP - RW critical sections are expensive so enter it here so we only have to call it once instead of X times EnterCriticalRWSection(&LevelChunk::m_csEntities, false); #else EnterCriticalSection(&LevelChunk::m_csEntities); -#endif #endif for (int xc = xc0; xc <= xc1; xc++) @@ -3706,12 +3709,10 @@ vector > *Level::getEntities(shared_ptr except, AABB } MemSect(0); -#ifdef __PSVITA__ #ifdef _ENTITIES_RW_SECTION LeaveCriticalRWSection(&LevelChunk::m_csEntities, false); #else LeaveCriticalSection(&LevelChunk::m_csEntities); -#endif #endif return &es; @@ -3730,13 +3731,11 @@ vector > *Level::getEntitiesOfClass(const type_info& baseClas int zc1 = Mth::floor((bb->z1 + 2) / 16); vector > *es = new vector >(); -#ifdef __PSVITA__ #ifdef _ENTITIES_RW_SECTION // AP - RW critical sections are expensive so enter it here so we only have to call it once instead of X times EnterCriticalRWSection(&LevelChunk::m_csEntities, false); #else EnterCriticalSection(&LevelChunk::m_csEntities); -#endif #endif for (int xc = xc0; xc <= xc1; xc++) @@ -3750,12 +3749,10 @@ vector > *Level::getEntitiesOfClass(const type_info& baseClas } } -#ifdef __PSVITA__ #ifdef _ENTITIES_RW_SECTION LeaveCriticalRWSection(&LevelChunk::m_csEntities, false); #else LeaveCriticalSection(&LevelChunk::m_csEntities); -#endif #endif return es; diff --git a/Minecraft.World/LevelChunk.cpp b/Minecraft.World/LevelChunk.cpp index 584e3df1..a0749a2f 100644 --- a/Minecraft.World/LevelChunk.cpp +++ b/Minecraft.World/LevelChunk.cpp @@ -817,8 +817,9 @@ void LevelChunk::recalcHeight(int x, int yStart, int z) { minHeight = y; } - else + else if (yOld == minHeight) { + // Only rescan when the column that was at the minimum changed height int min = Level::maxBuildHeight - 1; for (int _x = 0; _x < 16; _x++) for (int _z = 0; _z < 16; _z++) @@ -886,11 +887,16 @@ void LevelChunk::recalcHeight(int x, int yStart, int z) if (!level->dimension->hasCeiling) { PIXBeginNamedEvent(0,"Light gaps"); - lightGap(xOffs - 1, zOffs, y1, y2); - lightGap(xOffs + 1, zOffs, y1, y2); - lightGap(xOffs, zOffs - 1, y1, y2); - lightGap(xOffs, zOffs + 1, y1, y2); - lightGap(xOffs, zOffs, y1, y2); + // Flag columns for gap rechecking — processed by recheckGaps() on next tick + auto flagGap = [&](int wx, int wz) { + LevelChunk *c = level->getChunkAt(wx, wz); + if (c && !c->isEmpty()) c->lightGaps(wx & 15, wz & 15); + }; + flagGap(xOffs - 1, zOffs); + flagGap(xOffs + 1, zOffs); + flagGap(xOffs, zOffs - 1); + flagGap(xOffs, zOffs + 1); + flagGap(xOffs, zOffs); PIXEndNamedEvent(); } @@ -1643,10 +1649,7 @@ void LevelChunk::getEntities(shared_ptr except, AABB *bb, vector= ENTITY_BLOCKS_LENGTH) yc1 = ENTITY_BLOCKS_LENGTH - 1; -#ifndef __PSVITA__ - // AP - RW critical sections are expensive so enter once in Level::getEntities - EnterCriticalSection(&m_csEntities); -#endif + // Lock is now always held by Level::getEntities() for (int yc = yc0; yc <= yc1; yc++) { vector > *entities = entityBlocks[yc]; @@ -1671,9 +1674,6 @@ void LevelChunk::getEntities(shared_ptr except, AABB *bb, vector > &es, const EntitySelector *selector) @@ -1698,10 +1698,7 @@ void LevelChunk::getEntitiesOfClass(const type_info& ec, AABB *bb, vector > *entities = entityBlocks[yc]; @@ -1729,9 +1726,6 @@ void LevelChunk::getEntitiesOfClass(const type_info& ec, AABB *bb, vectorinstanceof(eTYPE_PLAYER) ) + if (this->instanceof(eTYPE_PLAYER)) { thisPlayer = (Player*) this; } -#else - shared_ptr thisPlayer = dynamic_pointer_cast(shared_from_this()); -#endif if (isInWater() && !(thisPlayer && thisPlayer->abilities.flying) ) { double yo = y; @@ -1424,13 +1420,14 @@ void LivingEntity::travel(float xa, float ya) else { float friction = 0.91f; + int frictionTile = 0; if (onGround) { + frictionTile = level->getTile(Mth::floor(x), Mth::floor(bb->y0) - 1, Mth::floor(z)); friction = 0.6f * 0.91f; - int t = level->getTile(Mth::floor(x), Mth::floor(bb->y0) - 1, Mth::floor(z)); - if (t > 0) + if (frictionTile > 0) { - friction = Tile::tiles[t]->friction * 0.91f; + friction = Tile::tiles[frictionTile]->friction * 0.91f; } } @@ -1452,10 +1449,9 @@ void LivingEntity::travel(float xa, float ya) if (onGround) { friction = 0.6f * 0.91f; - int t = level->getTile( Mth::floor(x), Mth::floor(bb->y0) - 1, Mth::floor(z)); - if (t > 0) + if (frictionTile > 0) { - friction = Tile::tiles[t]->friction * 0.91f; + friction = Tile::tiles[frictionTile]->friction * 0.91f; } } if (onLadder())