the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
at main 959 lines 29 kB view raw
1#include "stdafx.h" 2#include "MultiPlayerLevel.h" 3#include "MultiPlayerLocalPlayer.h" 4#include "ClientConnection.h" 5#include "MultiPlayerChunkCache.h" 6#include "..\Minecraft.World\net.minecraft.world.level.storage.h" 7#include "..\Minecraft.World\net.minecraft.world.level.dimension.h" 8#include "..\Minecraft.World\Pos.h" 9#include "MinecraftServer.h" 10#include "ServerLevel.h" 11#include "Minecraft.h" 12#include "FireworksParticles.h" 13#include "..\Minecraft.World\PrimedTnt.h" 14#include "..\Minecraft.World\Tile.h" 15#include "..\Minecraft.World\TileEntity.h" 16#include "..\Minecraft.World\JavaMath.h" 17 18MultiPlayerLevel::ResetInfo::ResetInfo(int x, int y, int z, int tile, int data) 19{ 20 this->x = x; 21 this->y = y; 22 this->z = z; 23 ticks = TICKS_BEFORE_RESET; 24 this->tile = tile; 25 this->data = data; 26} 27 28MultiPlayerLevel::MultiPlayerLevel(ClientConnection *connection, LevelSettings *levelSettings, int dimension, int difficulty) 29 : Level(shared_ptr<MockedLevelStorage >(new MockedLevelStorage()), L"MpServer", Dimension::getNew(dimension), levelSettings, false) 30{ 31 minecraft = Minecraft::GetInstance(); 32 33 // 4J - this this used to be called in parent ctor via a virtual fn 34 chunkSource = createChunkSource(); 35 // 4J - optimisation - keep direct reference of underlying cache here 36 chunkSourceCache = chunkSource->getCache(); 37 chunkSourceXZSize = chunkSource->m_XZSize; 38 39 // This also used to be called in parent ctor, but can't be called until chunkSource is created. Call now if required. 40 if (!levelData->isInitialized()) 41 { 42 initializeLevel(levelSettings); 43 levelData->setInitialized(true); 44 } 45 46 if(connection !=NULL) 47 { 48 this->connections.push_back( connection ); 49 } 50 this->difficulty = difficulty; 51 // Fix for #62566 - TU7: Content: Gameplay: Compass needle stops pointing towards the original spawn point, once the player has entered the Nether. 52 // 4J Stu - We should never be setting a specific spawn position for a multiplayer, this should only be set by receiving a packet from the server 53 // (which happens when a player logs in) 54 //setSpawnPos(new Pos(8, 64, 8)); 55 // The base ctor already has made some storage, so need to delete that 56 if( this->savedDataStorage ) delete savedDataStorage; 57 if(connection !=NULL) 58 { 59 savedDataStorage = connection->savedDataStorage; 60 } 61 unshareCheckX = 0; 62 unshareCheckZ = 0; 63 compressCheckX = 0; 64 compressCheckZ = 0; 65 66 // 4J Added, as there are some times when we don't want to add tile updates to the updatesToReset vector 67 m_bEnableResetChanges = true; 68} 69 70MultiPlayerLevel::~MultiPlayerLevel() 71{ 72 // Don't let the base class delete this, it comes from the connection for multiplayerlevels, and we'll delete there 73 this->savedDataStorage = NULL; 74} 75 76void MultiPlayerLevel::unshareChunkAt(int x, int z) 77{ 78 if( g_NetworkManager.IsHost() ) 79 { 80 Level::getChunkAt(x,z)->stopSharingTilesAndData(); 81 } 82} 83 84void MultiPlayerLevel::shareChunkAt(int x, int z) 85{ 86 if( g_NetworkManager.IsHost() ) 87 { 88 Level::getChunkAt(x,z)->startSharingTilesAndData(); 89 } 90} 91 92 93void MultiPlayerLevel::tick() 94{ 95 PIXBeginNamedEvent(0,"Sky color changing"); 96 setGameTime(getGameTime() + 1); 97 if (getGameRules()->getBoolean(GameRules::RULE_DAYLIGHT)) 98 { 99 // 4J: Debug setting added to keep it at day time 100#ifndef _FINAL_BUILD 101 bool freezeTime = app.DebugSettingsOn() && app.GetGameSettingsDebugMask(ProfileManager.GetPrimaryPad())&(1L<<eDebugSetting_FreezeTime); 102 if (!freezeTime) 103#endif 104 { 105 setDayTime(getDayTime() + 1); 106 } 107 } 108 /* 4J - change brought forward from 1.8.2 109 int newDark = this->getSkyDarken(1); 110 if (newDark != skyDarken) 111 { 112 skyDarken = newDark; 113 for (unsigned int i = 0; i < listeners.size(); i++) 114 { 115 listeners[i]->skyColorChanged(); 116 } 117 }*/ 118 PIXEndNamedEvent(); 119 120 PIXBeginNamedEvent(0,"Entity re-entry"); 121 EnterCriticalSection(&m_entitiesCS); 122 for (int i = 0; i < 10 && !reEntries.empty(); i++) 123 { 124 shared_ptr<Entity> e = *(reEntries.begin()); 125 126 if (find(entities.begin(), entities.end(), e) == entities.end() ) addEntity(e); 127 } 128 LeaveCriticalSection(&m_entitiesCS); 129 PIXEndNamedEvent(); 130 131 PIXBeginNamedEvent(0,"Connection ticking"); 132 // 4J HEG - Copy the connections vector to prevent crash when moving to Nether 133 vector<ClientConnection *> connectionsTemp = connections; 134 for(AUTO_VAR(connection, connectionsTemp.begin()); connection < connectionsTemp.end(); ++connection ) 135 { 136 (*connection)->tick(); 137 } 138 PIXEndNamedEvent(); 139 140 PIXBeginNamedEvent(0,"Updating resets"); 141 unsigned int lastIndexToRemove = 0; 142 bool eraseElements = false; 143 for (unsigned int i = 0; i < updatesToReset.size(); i++) 144 { 145 ResetInfo& r = updatesToReset[i]; 146 if (--r.ticks == 0) 147 { 148 Level::setTileAndData(r.x, r.y, r.z, r.tile, r.data, Tile::UPDATE_ALL); 149 Level::sendTileUpdated(r.x, r.y, r.z); 150 151 //updatesToReset.erase(updatesToReset.begin()+i); 152 eraseElements = true; 153 lastIndexToRemove = 0; 154 155 i--; 156 } 157 } 158 // 4J Stu - As elements in the updatesToReset vector are inserted with a fixed initial lifetime, the elements at the front should always be the oldest 159 // Therefore we can always remove from the first element 160 if(eraseElements) 161 { 162 updatesToReset.erase(updatesToReset.begin(), updatesToReset.begin()+lastIndexToRemove); 163 } 164 PIXEndNamedEvent(); 165 166 chunkCache->tick(); 167 tickTiles(); 168 169 // 4J - added this section. Each tick we'll check a different block, and force it to share data if it has been 170 // more than 2 minutes since we last wanted to unshare it. This shouldn't really ever happen, and is added 171 // here as a safe guard against accumulated memory leaks should a lot of chunks become unshared over time. 172 173 int ls = dimension->getXZSize(); 174 if( g_NetworkManager.IsHost() ) 175 { 176 if( Level::reallyHasChunk(unshareCheckX - ( ls / 2), unshareCheckZ - ( ls / 2 ) ) ) 177 { 178 LevelChunk *lc = Level::getChunk(unshareCheckX - ( ls / 2), unshareCheckZ - ( ls / 2 )); 179 if( g_NetworkManager.IsHost() ) 180 { 181 lc->startSharingTilesAndData(1000 * 60 * 2); 182 } 183 } 184 185 unshareCheckX++; 186 if( unshareCheckX >= ls ) 187 { 188 unshareCheckX = 0; 189 unshareCheckZ++; 190 if( unshareCheckZ >= ls ) 191 { 192 unshareCheckZ = 0; 193 } 194 } 195 } 196 197 // 4J added - also similar thing tosee if we can compress the lighting in any of these chunks. This is slightly different 198 // as it does try to make sure that at least one chunk has something done to it. 199 200 // At most loop round at least one row the chunks, so we should be able to at least find a non-empty chunk to do something with in 2.7 seconds of ticks, and process the whole thing in about 2.4 minutes. 201 for( int i = 0; i < ls; i++ ) 202 { 203 compressCheckX++; 204 if( compressCheckX >= ls ) 205 { 206 compressCheckX = 0; 207 compressCheckZ++; 208 if( compressCheckZ >= ls ) 209 { 210 compressCheckZ = 0; 211 } 212 } 213 214 if( Level::reallyHasChunk(compressCheckX - ( ls / 2), compressCheckZ - ( ls / 2 ) ) ) 215 { 216 LevelChunk *lc = Level::getChunk(compressCheckX - ( ls / 2), compressCheckZ - ( ls / 2 )); 217 lc->compressLighting(); 218 lc->compressBlocks(); 219 lc->compressData(); 220 break; 221 } 222 } 223 224#ifdef LIGHT_COMPRESSION_STATS 225 static int updateTick = 0; 226 227 if( ( updateTick % 60 ) == 0 ) 228 { 229 unsigned int totalBLu = 0; 230 unsigned int totalBLl = 0; 231 unsigned int totalSLu = 0; 232 unsigned int totalSLl = 0; 233 unsigned int totalChunks = 0; 234 235 for( int lcs_x = 0; lcs_x < ls; lcs_x++ ) 236 for( int lcs_z = 0; lcs_z < ls; lcs_z++ ) 237 { 238 if( Level::reallyHasChunk(lcs_x - ( ls / 2), lcs_z - ( ls / 2 ) ) ) 239 { 240 LevelChunk *lc = Level::getChunk(lcs_x - ( ls / 2), lcs_z - ( ls / 2 )); 241 totalChunks++; 242 totalBLu += lc->getBlockLightPlanesUpper(); 243 totalBLl += lc->getBlockLightPlanesLower(); 244 totalSLu += lc->getSkyLightPlanesUpper(); 245 totalSLl += lc->getSkyLightPlanesLower(); 246 } 247 } 248 if( totalChunks ) 249 { 250 MEMORYSTATUS memStat; 251 GlobalMemoryStatus(&memStat); 252 253 unsigned int totalBL = totalBLu + totalBLl; 254 unsigned int totalSL = totalSLu + totalSLl; 255 printf("%d: %d chunks, %d BL (%d + %d), %d SL (%d + %d ) (out of %d) - total %d %% (%dMB mem free)\n", 256 dimension->id, totalChunks, totalBL, totalBLu, totalBLl, totalSL, totalSLu, totalSLl, totalChunks * 256, ( 100 * (totalBL + totalSL) ) / ( totalChunks * 256 * 2),memStat.dwAvailPhys/(1024*1024) ); 257 } 258 } 259 updateTick++; 260 261#endif 262 263#ifdef DATA_COMPRESSION_STATS 264 static int updateTick = 0; 265 266 if( ( updateTick % 60 ) == 0 ) 267 { 268 unsigned int totalData = 0; 269 unsigned int totalChunks = 0; 270 271 for( int lcs_x = 0; lcs_x < ls; lcs_x++ ) 272 for( int lcs_z = 0; lcs_z < ls; lcs_z++ ) 273 { 274 if( Level::reallyHasChunk(lcs_x - ( ls / 2), lcs_z - ( ls / 2 ) ) ) 275 { 276 LevelChunk *lc = Level::getChunk(lcs_x - ( ls / 2), lcs_z - ( ls / 2 )); 277 totalChunks++; 278 totalData += lc->getDataPlanes(); 279 } 280 } 281 if( totalChunks ) 282 { 283 MEMORYSTATUS memStat; 284 GlobalMemoryStatus(&memStat); 285 286 printf("%d: %d chunks, %d data (out of %d) - total %d %% (%dMB mem free)\n", 287 dimension->id, totalChunks, totalData, totalChunks * 128, ( 100 * totalData)/ ( totalChunks * 128),memStat.dwAvailPhys/(1024*1024) ); 288 } 289 } 290 updateTick++; 291 292#endif 293 294#ifdef BLOCK_COMPRESSION_STATS 295 static int updateTick = 0; 296 297 if( ( updateTick % 60 ) == 0 ) 298 { 299 unsigned int total = 0; 300 unsigned int totalChunks = 0; 301 unsigned int total0 = 0, total1 = 0, total2 = 0, total4 = 0, total8 = 0; 302 303 printf("*****************************************************************************************************************************************\n"); 304 printf("TODO: Report upper chunk data as well\n"); 305 for( int lcs_x = 0; lcs_x < ls; lcs_x++ ) 306 for( int lcs_z = 0; lcs_z < ls; lcs_z++ ) 307 { 308 if( Level::reallyHasChunk(lcs_x - ( ls / 2), lcs_z - ( ls / 2 ) ) ) 309 { 310 LevelChunk *lc = Level::getChunk(lcs_x - ( ls / 2), lcs_z - ( ls / 2 )); 311 totalChunks++; 312 int i0, i1, i2, i4, i8; 313 int thisSize = lc->getBlocksAllocatedSize(&i0, &i1, &i2, &i4, &i8); 314 total0 += i0; 315 total1 += i1; 316 total2 += i2; 317 total4 += i4; 318 total8 += i8; 319 printf("%d ",thisSize); 320 thisSize = ( thisSize + 0xfff ) & 0xfffff000; // round to 4096k blocks for actual memory consumption 321 total += thisSize; 322 } 323 } 324 printf("\n*****************************************************************************************************************************************\n"); 325 if( totalChunks ) 326 { 327 printf("%d (0) %d (1) %d (2) %d (4) %d (8)\n",total0/totalChunks,total1/totalChunks,total2/totalChunks,total4/totalChunks,total8/totalChunks); 328 MEMORYSTATUS memStat; 329 GlobalMemoryStatus(&memStat); 330 331 printf("%d: %d chunks, %d KB (out of %dKB) : %d %% (%dMB mem free)\n", 332 dimension->id, totalChunks, total/1024, totalChunks * 32, ( ( total / 1024 ) * 100 ) / ( totalChunks * 32),memStat.dwAvailPhys/(1024*1024) ); 333 } 334 } 335 updateTick++; 336#endif 337 338 // super.tick(); 339 340} 341 342void MultiPlayerLevel::clearResetRegion(int x0, int y0, int z0, int x1, int y1, int z1) 343{ 344 for (unsigned int i = 0; i < updatesToReset.size(); i++) 345 { 346 ResetInfo& r = updatesToReset[i]; 347 if (r.x >= x0 && r.y >= y0 && r.z >= z0 && r.x <= x1 && r.y <= y1 && r.z <= z1) 348 { 349 updatesToReset.erase(updatesToReset.begin()+i); 350 i--; 351 } 352 } 353} 354 355ChunkSource *MultiPlayerLevel::createChunkSource() 356{ 357 chunkCache = new MultiPlayerChunkCache(this); 358 359 return chunkCache; 360} 361 362void MultiPlayerLevel::validateSpawn() 363{ 364 // Fix for #62566 - TU7: Content: Gameplay: Compass needle stops pointing towards the original spawn point, once the player has entered the Nether. 365 // 4J Stu - We should never be setting a specific spawn position for a multiplayer, this should only be set by receiving a packet from the server 366 // (which happens when a player logs in) 367 //setSpawnPos(new Pos(8, 64, 8)); 368} 369 370void MultiPlayerLevel::tickTiles() 371{ 372 chunksToPoll.clear(); // 4J - added or else we don't reset this set at all in a multiplayer level... think current java now resets in buildAndPrepareChunksToPoll rather than the calling functions 373 374 PIXBeginNamedEvent(0,"Ticking tiles (multiplayer)"); 375 PIXBeginNamedEvent(0,"buildAndPrepareChunksToPoll"); 376 Level::tickTiles(); 377 PIXEndNamedEvent(); 378 379 PIXBeginNamedEvent(0,"Ticking client side tiles"); 380#ifdef __PSVITA__ 381 // AP - see CustomSet.h for and explanation 382 for( int i = 0;i < chunksToPoll.end();i += 1 ) 383 { 384 ChunkPos cp = chunksToPoll.get(i); 385#else 386 AUTO_VAR(itEndCtp, chunksToPoll.end()); 387 for (AUTO_VAR(it, chunksToPoll.begin()); it != itEndCtp; it++) 388 { 389 ChunkPos cp = *it; 390#endif 391 int xo = cp.x * 16; 392 int zo = cp.z * 16; 393 394 LevelChunk *lc = getChunk(cp.x, cp.z); 395 396 tickClientSideTiles(xo, zo, lc); 397 } 398 PIXEndNamedEvent(); 399 PIXEndNamedEvent(); 400} 401 402void MultiPlayerLevel::setChunkVisible(int x, int z, bool visible) 403{ 404 if (visible) 405 { 406 chunkCache->create(x, z); 407 } 408 else 409 { 410 chunkCache->drop(x, z); 411 } 412 if (!visible) 413 { 414 setTilesDirty(x * 16, 0, z * 16, x * 16 + 15, Level::maxBuildHeight, z * 16 + 15); 415 } 416 417} 418 419bool MultiPlayerLevel::addEntity(shared_ptr<Entity> e) 420{ 421 bool ok = Level::addEntity(e); 422 forced.insert(e); 423 424 if (!ok) 425 { 426 reEntries.insert(e); 427 } 428 429 return ok; 430} 431 432void MultiPlayerLevel::removeEntity(shared_ptr<Entity> e) 433{ 434 // 4J Stu - Add this remove from the reEntries collection to stop us continually removing and re-adding things, 435 // in particular the MultiPlayerLocalPlayer when they die 436 AUTO_VAR(it, reEntries.find(e)); 437 if (it!=reEntries.end()) 438 { 439 reEntries.erase(it); 440 } 441 442 Level::removeEntity(e); 443 forced.erase(e); 444} 445 446void MultiPlayerLevel::entityAdded(shared_ptr<Entity> e) 447{ 448 Level::entityAdded(e); 449 AUTO_VAR(it, reEntries.find(e)); 450 if (it!=reEntries.end()) 451 { 452 reEntries.erase(it); 453 } 454} 455 456void MultiPlayerLevel::entityRemoved(shared_ptr<Entity> e) 457{ 458 Level::entityRemoved(e); 459 AUTO_VAR(it, forced.find(e)); 460 if (it!=forced.end()) 461 { 462 reEntries.insert(e); 463 } 464} 465 466void MultiPlayerLevel::putEntity(int id, shared_ptr<Entity> e) 467{ 468 shared_ptr<Entity> old = getEntity(id); 469 if (old != NULL) 470 { 471 removeEntity(old); 472 } 473 474 forced.insert(e); 475 e->entityId = id; 476 if (!addEntity(e)) 477 { 478 reEntries.insert(e); 479 } 480 entitiesById[id] = e; 481} 482 483shared_ptr<Entity> MultiPlayerLevel::getEntity(int id) 484{ 485 AUTO_VAR(it, entitiesById.find(id)); 486 if( it == entitiesById.end() ) return nullptr; 487 return it->second; 488} 489 490shared_ptr<Entity> MultiPlayerLevel::removeEntity(int id) 491{ 492 shared_ptr<Entity> e; 493 AUTO_VAR(it, entitiesById.find(id)); 494 if( it != entitiesById.end() ) 495 { 496 e = it->second; 497 entitiesById.erase(it); 498 forced.erase(e); 499 removeEntity(e); 500 } 501 else 502 { 503 } 504 return e; 505} 506 507// 4J Added to remove the entities from the forced list 508// This gets called when a chunk is unloaded, but we only do half an unload to remove entities slightly differently 509void MultiPlayerLevel::removeEntities(vector<shared_ptr<Entity> > *list) 510{ 511 for(AUTO_VAR(it, list->begin()); it < list->end(); ++it) 512 { 513 shared_ptr<Entity> e = *it; 514 515 AUTO_VAR(reIt, reEntries.find(e)); 516 if (reIt!=reEntries.end()) 517 { 518 reEntries.erase(reIt); 519 } 520 521 forced.erase(e); 522 } 523 Level::removeEntities(list); 524} 525 526bool MultiPlayerLevel::setData(int x, int y, int z, int data, int updateFlags, bool forceUpdate/*=false*/) // 4J added forceUpdate) 527{ 528 // First check if this isn't going to do anything, because if it isn't then the next stage (of unsharing data) is really quite 529 // expensive so far better to early out here 530 int d = getData(x, y, z); 531 532 if( ( d == data ) ) 533 { 534 // If we early-out, its important that we still do a checkLight here (which would otherwise have happened as part of Level::setTileAndDataNoUpdate) 535 // This is because since we are potentially sharing tile/data but not lighting data, it is possible that the server might tell a client 536 // of a lighting update that doesn't need actioned on the client just because the chunk's data was being shared with the server when it was set. However, 537 // the lighting data will potentially now be out of sync on the client. 538 checkLight(x,y,z); 539 return false; 540 } 541 // 4J - added - if this is the host, then stop sharing block data with the server at this point 542 unshareChunkAt(x,z); 543 544 if (Level::setData(x, y, z, data, updateFlags, forceUpdate)) 545 { 546 //if(m_bEnableResetChanges) updatesToReset.push_back(ResetInfo(x, y, z, t, d)); 547 return true; 548 } 549 // Didn't actually need to stop sharing 550 shareChunkAt(x,z); 551 return false; 552} 553 554bool MultiPlayerLevel::setTileAndData(int x, int y, int z, int tile, int data, int updateFlags) 555{ 556 // First check if this isn't going to do anything, because if it isn't then the next stage (of unsharing data) is really quite 557 // expensive so far better to early out here 558 int t = getTile(x, y, z); 559 int d = getData(x, y, z); 560 561 if( ( t == tile ) && ( d == data ) ) 562 { 563 // If we early-out, its important that we still do a checkLight here (which would otherwise have happened as part of Level::setTileAndDataNoUpdate) 564 // This is because since we are potentially sharing tile/data but not lighting data, it is possible that the server might tell a client 565 // of a lighting update that doesn't need actioned on the client just because the chunk's data was being shared with the server when it was set. However, 566 // the lighting data will potentially now be out of sync on the client. 567 checkLight(x,y,z); 568 return false; 569 } 570 // 4J - added - if this is the host, then stop sharing block data with the server at this point 571 unshareChunkAt(x,z); 572 573 if (Level::setTileAndData(x, y, z, tile, data, updateFlags)) 574 { 575 //if(m_bEnableResetChanges) updatesToReset.push_back(ResetInfo(x, y, z, t, d)); 576 return true; 577 } 578 // Didn't actually need to stop sharing 579 shareChunkAt(x,z); 580 return false; 581} 582 583 584bool MultiPlayerLevel::doSetTileAndData(int x, int y, int z, int tile, int data) 585{ 586 clearResetRegion(x, y, z, x, y, z); 587 588 // 4J - Don't bother setting this to dirty if it isn't going to visually change - we get a lot of 589 // water changing from static to dynamic for instance. Note that this is only called from a client connection, 590 // and so the thing being notified of any update through tileUpdated is the renderer 591 int prevTile = getTile(x, y, z); 592 bool visuallyImportant = (!( ( ( prevTile == Tile::water_Id ) && ( tile == Tile::calmWater_Id ) ) || 593 ( ( prevTile == Tile::calmWater_Id ) && ( tile == Tile::water_Id ) ) || 594 ( ( prevTile == Tile::lava_Id ) && ( tile == Tile::calmLava_Id ) ) || 595 ( ( prevTile == Tile::calmLava_Id ) && ( tile == Tile::calmLava_Id ) ) || 596 ( ( prevTile == Tile::calmLava_Id ) && ( tile == Tile::lava_Id ) ) ) ); 597 // If we're the host, need to tell the renderer for updates even if they don't change things as the host 598 // might have been sharing data and so set it already, but the renderer won't know to update 599 if( (Level::setTileAndData(x, y, z, tile, data, Tile::UPDATE_ALL) || g_NetworkManager.IsHost() ) ) 600 { 601 if( g_NetworkManager.IsHost() && visuallyImportant ) 602 { 603 // 4J Stu - This got removed from the tileUpdated function in TU14. Adding it back here as we need it 604 // to handle the cases where the chunk data is shared so the normal paths never call this 605 sendTileUpdated(x,y,z); 606 607 tileUpdated(x, y, z, tile); 608 } 609 return true; 610 } 611 return false; 612} 613 614void MultiPlayerLevel::disconnect(bool sendDisconnect /*= true*/) 615{ 616 if( sendDisconnect ) 617 { 618 for(AUTO_VAR(it, connections.begin()); it < connections.end(); ++it ) 619 { 620 (*it)->sendAndDisconnect( shared_ptr<DisconnectPacket>( new DisconnectPacket(DisconnectPacket::eDisconnect_Quitting) ) ); 621 } 622 } 623 else 624 { 625 for(AUTO_VAR(it, connections.begin()); it < connections.end(); ++it ) 626 { 627 (*it)->close(); 628 } 629 } 630} 631 632Tickable *MultiPlayerLevel::makeSoundUpdater(shared_ptr<Minecart> minecart) 633{ 634 return NULL; //new MinecartSoundUpdater(minecraft->soundEngine, minecart, minecraft->player); 635} 636 637void MultiPlayerLevel::tickWeather() 638{ 639 if (dimension->hasCeiling) return; 640 641 oRainLevel = rainLevel; 642 if (levelData->isRaining()) 643 { 644 rainLevel += 0.01; 645 } 646 else 647 { 648 rainLevel -= 0.01; 649 } 650 if (rainLevel < 0) rainLevel = 0; 651 if (rainLevel > 1) rainLevel = 1; 652 653 oThunderLevel = thunderLevel; 654 if (levelData->isThundering()) 655 { 656 thunderLevel += 0.01; 657 } 658 else 659 { 660 thunderLevel -= 0.01; 661 } 662 if (thunderLevel < 0) thunderLevel = 0; 663 if (thunderLevel > 1) thunderLevel = 1; 664 665} 666 667void MultiPlayerLevel::animateTick(int xt, int yt, int zt) 668{ 669 // Get 8x8x8 chunk (ie not like the renderer or game chunks... maybe we need another word here...) that the player is in 670 // We then want to add a 3x3 region of chunks into a set that we'll be ticking over. Set is stored as unsigned ints which encode 671 // this chunk position 672 int cx = xt >> 3; 673 int cy = yt >> 3; 674 int cz = zt >> 3; 675 676 for( int xx = -1; xx <= 1; xx++ ) 677 for( int yy = -1; yy <= 1; yy++ ) 678 for( int zz = -1; zz <= 1; zz++ ) 679 { 680 if( ( cy + yy ) < 0 ) continue; 681 if( ( cy + yy ) > 15 ) continue; 682 // Note - LEVEL_MAX_WIDTH is in game (16) tile chunks, and so our level goes from -LEVEL_MAX_WIDTH to LEVEL_MAX_WIDTH of our half-sized chunks 683 if( ( cx + xx ) >= LEVEL_MAX_WIDTH ) continue; 684 if( ( cx + xx ) < -LEVEL_MAX_WIDTH ) continue; 685 if( ( cz + zz ) >= LEVEL_MAX_WIDTH ) continue; 686 if( ( cz + zz ) < -LEVEL_MAX_WIDTH ) continue; 687 chunksToAnimate.insert( ( ( ( cx + xx ) & 0xff ) << 16 ) | ( ( ( cy + yy ) & 0xff ) << 8 ) | ( ( ( cz + zz ) & 0xff ) ) ); 688 } 689} 690 691// 4J - the game used to tick 1000 tiles in a random region +/- 16 units round the player. We've got a 3x3 region of 8x8x8 chunks round each 692// player. So the original game was ticking 1000 things in a 32x32x32 region ie had about a 1 in 32 chance of updating any one tile per tick. 693// We're not dealing with quite such a big region round each player (24x24x24) but potentially we've got 4 players. Ultimately, we could end 694// up ticking anywhere between 432 and 1728 tiles depending on how many players we've got, which seems like a good tradeoff from the original. 695void MultiPlayerLevel::animateTickDoWork() 696{ 697 const int ticksPerChunk = 16; // This ought to give us roughly the same 1000/32768 chance of a tile being animated as the original 698 699 // Horrible hack to communicate with the level renderer, which is just attached as a listener to this level. This let's the particle 700 // rendering know to use this level (rather than try to work it out from the current player), and to not bother distance clipping particles 701 // which would again be based on the current player. 702 Minecraft::GetInstance()->animateTickLevel = this; 703 704 MemSect(31); 705 Random *animateRandom = new Random(); 706 MemSect(0); 707 708 for( int i = 0; i < ticksPerChunk; i++ ) 709 { 710 for( AUTO_VAR(it, chunksToAnimate.begin()); it != chunksToAnimate.end(); it++ ) 711 { 712 int packed = *it; 713 int cx = ( packed << 8 ) >> 24; 714 int cy = ( packed << 16 ) >> 24; 715 int cz = ( packed << 24 ) >> 24; 716 cx <<= 3; 717 cy <<= 3; 718 cz <<= 3; 719 int x = cx + random->nextInt(8); 720 int y = cy + random->nextInt(8); 721 int z = cz + random->nextInt(8); 722 int t = getTile(x, y, z); 723 if (random->nextInt(8) > y && t == 0 && dimension->hasBedrockFog()) // 4J - test for bedrock fog brought forward from 1.2.3 724 { 725 addParticle(eParticleType_depthsuspend, x + random->nextFloat(), y + random->nextFloat(), z + random->nextFloat(), 0, 0, 0); 726 } 727 else if (t > 0) 728 { 729 Tile::tiles[t]->animateTick(this, x, y, z, animateRandom); 730 } 731 } 732 } 733 734 Minecraft::GetInstance()->animateTickLevel = NULL; 735 delete animateRandom; 736 737 chunksToAnimate.clear(); 738 739} 740 741void MultiPlayerLevel::playSound(shared_ptr<Entity> entity, int iSound, float volume, float pitch) 742{ 743 playLocalSound(entity->x, entity->y - entity->heightOffset, entity->z, iSound, volume, pitch); 744} 745 746void MultiPlayerLevel::playLocalSound(double x, double y, double z, int iSound, float volume, float pitch, bool distanceDelay/*= false */, float fClipSoundDist) 747{ 748 //float dd = 16; 749 if (volume > 1) fClipSoundDist *= volume; 750 751 // 4J - find min distance to any players rather than just the current one 752 float minDistSq = FLT_MAX; 753 for( int i = 0; i < XUSER_MAX_COUNT; i++ ) 754 { 755 if( minecraft->localplayers[i] ) 756 { 757 float distSq = minecraft->localplayers[i]->distanceToSqr(x, y, z ); 758 if( distSq < minDistSq ) 759 { 760 minDistSq = distSq; 761 } 762 } 763 } 764 765 if (minDistSq < fClipSoundDist * fClipSoundDist) 766 { 767 if (distanceDelay && minDistSq > 10 * 10) 768 { 769 // exhaggerate sound speed effect by making speed of sound ~= 770 // 40 m/s instead of 300 m/s 771 double delayInSeconds = sqrt(minDistSq) / 40.0; 772 minecraft->soundEngine->schedule(iSound, (float) x, (float) y, (float) z, volume, pitch, (int) Math::round(delayInSeconds * SharedConstants::TICKS_PER_SECOND)); 773 } 774 else 775 { 776 minecraft->soundEngine->play(iSound, (float) x, (float) y, (float) z, volume, pitch); 777 } 778 } 779} 780 781void MultiPlayerLevel::createFireworks(double x, double y, double z, double xd, double yd, double zd, CompoundTag *infoTag) 782{ 783 minecraft->particleEngine->add(shared_ptr<FireworksParticles::FireworksStarter>(new FireworksParticles::FireworksStarter(this, x, y, z, xd, yd, zd, minecraft->particleEngine, infoTag))); 784} 785 786void MultiPlayerLevel::setScoreboard(Scoreboard *scoreboard) 787{ 788 this->scoreboard = scoreboard; 789} 790 791void MultiPlayerLevel::setDayTime(__int64 newTime) 792{ 793 // 4J: We send daylight cycle rule with host options so don't need this 794 /*if (newTime < 0) 795 { 796 newTime = -newTime; 797 getGameRules()->set(GameRules::RULE_DAYLIGHT, L"false"); 798 } 799 else 800 { 801 getGameRules()->set(GameRules::RULE_DAYLIGHT, L"true"); 802 }*/ 803 804 Level::setDayTime(newTime); 805} 806 807void MultiPlayerLevel::removeAllPendingEntityRemovals() 808{ 809 //entities.removeAll(entitiesToRemove); 810 811 EnterCriticalSection(&m_entitiesCS); 812 for( AUTO_VAR(it, entities.begin()); it != entities.end(); ) 813 { 814 bool found = false; 815 for( AUTO_VAR(it2, entitiesToRemove.begin()); it2 != entitiesToRemove.end(); it2++ ) 816 { 817 if( (*it) == (*it2) ) 818 { 819 found = true; 820 break; 821 } 822 } 823 if( found ) 824 { 825 it = entities.erase(it); 826 } 827 else 828 { 829 it++; 830 } 831 } 832 LeaveCriticalSection(&m_entitiesCS); 833 834 AUTO_VAR(endIt, entitiesToRemove.end()); 835 for (AUTO_VAR(it, entitiesToRemove.begin()); it != endIt; it++) 836 { 837 shared_ptr<Entity> e = *it; 838 int xc = e->xChunk; 839 int zc = e->zChunk; 840 if (e->inChunk && hasChunk(xc, zc)) 841 { 842 getChunk(xc, zc)->removeEntity(e); 843 } 844 } 845 846 // 4J Stu - Is there a reason do this in a separate loop? Thats what the Java does... 847 endIt = entitiesToRemove.end(); 848 for (AUTO_VAR(it, entitiesToRemove.begin()); it != endIt; it++) 849 { 850 entityRemoved(*it); 851 } 852 entitiesToRemove.clear(); 853 854 //for (int i = 0; i < entities.size(); i++) 855 EnterCriticalSection(&m_entitiesCS); 856 vector<shared_ptr<Entity> >::iterator it = entities.begin(); 857 while( it != entities.end() ) 858 { 859 shared_ptr<Entity> e = *it;//entities.at(i); 860 861 if (e->riding != NULL) 862 { 863 if (e->riding->removed || e->riding->rider.lock() != e) 864 { 865 e->riding->rider = weak_ptr<Entity>(); 866 e->riding = nullptr; 867 } 868 else 869 { 870 ++it; 871 continue; 872 } 873 } 874 875 if (e->removed) 876 { 877 int xc = e->xChunk; 878 int zc = e->zChunk; 879 if (e->inChunk && hasChunk(xc, zc)) 880 { 881 getChunk(xc, zc)->removeEntity(e); 882 } 883 //entities.remove(i--); 884 885 it = entities.erase( it ); 886 entityRemoved(e); 887 } 888 else 889 { 890 it++; 891 } 892 } 893 LeaveCriticalSection(&m_entitiesCS); 894} 895 896void MultiPlayerLevel::removeClientConnection(ClientConnection *c, bool sendDisconnect) 897{ 898 if( sendDisconnect ) 899 { 900 c->sendAndDisconnect( shared_ptr<DisconnectPacket>( new DisconnectPacket(DisconnectPacket::eDisconnect_Quitting) ) ); 901 } 902 903 AUTO_VAR(it, find( connections.begin(), connections.end(), c )); 904 if( it != connections.end() ) 905 { 906 connections.erase( it ); 907 } 908} 909 910void MultiPlayerLevel::tickAllConnections() 911{ 912 PIXBeginNamedEvent(0,"Connection ticking"); 913 for(AUTO_VAR(it, connections.begin()); it < connections.end(); ++it ) 914 { 915 (*it)->tick(); 916 } 917 PIXEndNamedEvent(); 918} 919 920void MultiPlayerLevel::dataReceivedForChunk(int x, int z) 921{ 922 chunkCache->dataReceived(x, z); 923} 924 925// 4J added - removes all tile entities in the given region from both level & levelchunks 926void MultiPlayerLevel::removeUnusedTileEntitiesInRegion(int x0, int y0, int z0, int x1, int y1, int z1) 927{ 928 EnterCriticalSection(&m_tileEntityListCS); 929 930 for (unsigned int i = 0; i < tileEntityList.size();) 931 { 932 bool removed = false; 933 shared_ptr<TileEntity> te = tileEntityList[i]; 934 if (te->x >= x0 && te->y >= y0 && te->z >= z0 && te->x < x1 && te->y < y1 && te->z < z1) 935 { 936 LevelChunk *lc = getChunk(te->x >> 4, te->z >> 4); 937 if (lc != NULL) 938 { 939 // Only remove tile entities where this is no longer a tile entity 940 int tileId = lc->getTile(te->x & 15, te->y, te->z & 15 ); 941 if( Tile::tiles[tileId] == NULL || !Tile::tiles[tileId]->isEntityTile()) 942 { 943 tileEntityList[i] = tileEntityList.back(); 944 tileEntityList.pop_back(); 945 946 // 4J Stu - Chests can create new tile entities when being removed, so disable this 947 m_bDisableAddNewTileEntities = true; 948 lc->removeTileEntity(te->x & 15, te->y, te->z & 15); 949 m_bDisableAddNewTileEntities = false; 950 removed = true; 951 } 952 } 953 } 954 if( !removed ) i++; 955 } 956 957 LeaveCriticalSection(&m_tileEntityListCS); 958} 959