the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
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