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 "PlayerChunkMap.h"
3#include "PlayerConnection.h"
4#include "ServerLevel.h"
5#include "ServerChunkCache.h"
6#include "ServerPlayer.h"
7#include "MinecraftServer.h"
8#include "..\Minecraft.World\net.minecraft.network.packet.h"
9#include "..\Minecraft.World\net.minecraft.world.level.h"
10#include "..\Minecraft.World\net.minecraft.world.level.chunk.h"
11#include "..\Minecraft.World\net.minecraft.world.level.tile.h"
12#include "..\Minecraft.World\ArrayWithLength.h"
13#include "..\Minecraft.World\System.h"
14#include "PlayerList.h"
15
16PlayerChunkMap::PlayerChunk::PlayerChunk(int x, int z, PlayerChunkMap *pcm) : pos(x,z)
17{
18 // 4J - added initialisers
19 changes = 0;
20 changedTiles = shortArray(MAX_CHANGES_BEFORE_RESEND);
21 xChangeMin = xChangeMax = 0;
22 yChangeMin = yChangeMax = 0;
23 zChangeMin = zChangeMax = 0;
24 parent = pcm; // 4J added
25 ticksToNextRegionUpdate = 0; // 4J added
26 prioritised = false; // 4J added
27 firstInhabitedTime = 0;
28
29 parent->getLevel()->cache->create(x, z);
30}
31
32PlayerChunkMap::PlayerChunk::~PlayerChunk()
33{
34 delete changedTiles.data;
35}
36
37// 4J added - construct an an array of flags that indicate which entities are still waiting to have network packets sent out to say that they have been removed
38// If there aren't any entities to be flagged, this function does nothing. If there *are* entities to be added, uses the removedFound as an input to
39// determine if the flag array has already been initialised at all - if it has been, then just adds flags to it; if it hasn't, then memsets the output
40// flag array and adds to it for this ServerPlayer.
41void PlayerChunkMap::flagEntitiesToBeRemoved(unsigned int *flags, bool *flagToBeRemoved)
42{
43 for(AUTO_VAR(it,players.begin()); it != players.end(); it++)
44 {
45 shared_ptr<ServerPlayer> serverPlayer = *it;
46 serverPlayer->flagEntitiesToBeRemoved(flags, flagToBeRemoved);
47 }
48}
49
50void PlayerChunkMap::PlayerChunk::add(shared_ptr<ServerPlayer> player, bool sendPacket /*= true*/)
51{
52 //app.DebugPrintf("--- Adding player to chunk x=%d\tz=%d\n",x, z);
53 if (find(players.begin(),players.end(),player) != players.end())
54 {
55 // 4J-PB - At the start of the game, lots of chunks are added, and we can then move into an area that is outside the diameter of our starting area,
56 // but is inside the area loaded at the start.
57 app.DebugPrintf("--- Adding player to chunk x=%d\t z=%d, but they are already in there!\n",pos.x, pos.z);
58 return;
59
60 //assert(false);
61// 4J - was throw new IllegalStateException("Failed to add player. " + player + " already is in chunk " + x + ", " + z);
62 }
63
64 player->seenChunks.insert(pos);
65
66 // 4J Added the sendPacket check. See PlayerChunkMap::add for the usage
67 if( sendPacket ) player->connection->send( shared_ptr<ChunkVisibilityPacket>( new ChunkVisibilityPacket(pos.x, pos.z, true) ) );
68
69 if (players.empty())
70 {
71 firstInhabitedTime = parent->level->getGameTime();
72 }
73
74 players.push_back(player);
75
76 player->chunksToSend.push_back(pos);
77
78#ifdef _LARGE_WORLDS
79 parent->getLevel()->cache->dontDrop(pos.x, pos.z); // 4J Added;
80#endif
81}
82
83void PlayerChunkMap::PlayerChunk::remove(shared_ptr<ServerPlayer> player)
84{
85 PlayerChunkMap::PlayerChunk *toDelete = NULL;
86
87 //app.DebugPrintf("--- PlayerChunkMap::PlayerChunk::remove x=%d\tz=%d\n",x,z);
88 AUTO_VAR(it, find(players.begin(),players.end(),player));
89 if ( it == players.end())
90 {
91 app.DebugPrintf("--- INFO - Removing player from chunk x=%d\t z=%d, but they are not in that chunk!\n",pos.x, pos.z);
92
93 return;
94 }
95
96 players.erase(it);
97 if (players.size() == 0)
98 {
99 {
100 LevelChunk *chunk = parent->level->getChunk(pos.x, pos.z);
101 updateInhabitedTime(chunk);
102 AUTO_VAR(it, find(parent->knownChunks.begin(), parent->knownChunks.end(),this));
103 if(it != parent->knownChunks.end()) parent->knownChunks.erase(it);
104 }
105 __int64 id = (pos.x + 0x7fffffffLL) | ((pos.z + 0x7fffffffLL) << 32);
106 AUTO_VAR(it, parent->chunks.find(id));
107 if( it != parent->chunks.end() )
108 {
109 toDelete = it->second; // Don't delete until the end of the function, as this might be this instance
110 parent->chunks.erase(it);
111 }
112 if (changes > 0)
113 {
114 AUTO_VAR(it, find(parent->changedChunks.begin(),parent->changedChunks.end(),this));
115 parent->changedChunks.erase(it);
116 }
117 parent->getLevel()->cache->drop(pos.x, pos.z);
118 }
119
120 player->chunksToSend.remove(pos);
121 // 4J - I don't think there's any point sending these anymore, as we don't need to unload chunks with fixed sized maps
122 // 4J - We do need to send these to unload entities in chunks when players are dead. If we do not and the entity is removed
123 // while they are dead, that entity will remain in the clients world
124 if (player->connection != NULL && player->seenChunks.find(pos) != player->seenChunks.end())
125 {
126 INetworkPlayer *thisNetPlayer = player->connection->getNetworkPlayer();
127 bool noOtherPlayersFound = true;
128
129 if( thisNetPlayer != NULL )
130 {
131 for( AUTO_VAR(it, players.begin()); it < players.end(); ++it )
132 {
133 shared_ptr<ServerPlayer> currPlayer = *it;
134 INetworkPlayer *currNetPlayer = currPlayer->connection->getNetworkPlayer();
135 if( currNetPlayer != NULL && currNetPlayer->IsSameSystem( thisNetPlayer ) && currPlayer->seenChunks.find(pos) != currPlayer->seenChunks.end() )
136 {
137 noOtherPlayersFound = false;
138 break;
139 }
140 }
141 if(noOtherPlayersFound)
142 {
143 //wprintf(L"Sending ChunkVisiblity packet false for chunk (%d,%d) to player %ls\n", x, z, player->name.c_str() );
144 player->connection->send( shared_ptr<ChunkVisibilityPacket>( new ChunkVisibilityPacket(pos.x, pos.z, false) ) );
145 }
146 }
147 else
148 {
149 //app.DebugPrintf("PlayerChunkMap::PlayerChunk::remove - QNetPlayer is NULL\n");
150 }
151 }
152
153 delete toDelete;
154}
155
156void PlayerChunkMap::PlayerChunk::updateInhabitedTime()
157{
158 updateInhabitedTime(parent->level->getChunk(pos.x, pos.z));
159}
160
161void PlayerChunkMap::PlayerChunk::updateInhabitedTime(LevelChunk *chunk)
162{
163 chunk->inhabitedTime += parent->level->getGameTime() - firstInhabitedTime;
164
165 firstInhabitedTime = parent->level->getGameTime();
166}
167
168void PlayerChunkMap::PlayerChunk::tileChanged(int x, int y, int z)
169{
170 if (changes == 0)
171 {
172 parent->changedChunks.push_back(this);
173 xChangeMin = xChangeMax = x;
174 yChangeMin = yChangeMax = y;
175 zChangeMin = zChangeMax = z;
176 }
177 if (xChangeMin > x) xChangeMin = x;
178 if (xChangeMax < x) xChangeMax = x;
179
180 if (yChangeMin > y) yChangeMin = y;
181 if (yChangeMax < y) yChangeMax = y;
182
183 if (zChangeMin > z) zChangeMin = z;
184 if (zChangeMax < z) zChangeMax = z;
185
186 if (changes < MAX_CHANGES_BEFORE_RESEND)
187 {
188 short id = (short) ((x << 12) | (z << 8) | (y));
189
190 for (int i = 0; i < changes; i++)
191 {
192 if (changedTiles[i] == id) return;
193 }
194
195 changedTiles[changes++] = id;
196 }
197}
198
199// 4J added - make sure that any tile updates for the chunk at this location get prioritised for sending
200void PlayerChunkMap::PlayerChunk::prioritiseTileChanges()
201{
202 prioritised = true;
203}
204
205void PlayerChunkMap::PlayerChunk::broadcast(shared_ptr<Packet> packet)
206{
207 vector< shared_ptr<ServerPlayer> > sentTo;
208 for (unsigned int i = 0; i < players.size(); i++)
209 {
210 shared_ptr<ServerPlayer> player = players[i];
211
212 // 4J - don't send to a player we've already sent this data to that shares the same machine. TileUpdatePacket,
213 // ChunkTilesUpdatePacket and SignUpdatePacket all used to limit themselves to sending once to each machine
214 // by only sending to the primary player on each machine. This was causing trouble for split screen
215 // as updates were only coming in for the region round this one player. Now these packets can be sent to any
216 // player, but we try to restrict the network impact this has by not resending to the one machine
217 bool dontSend = false;
218 if( sentTo.size() )
219 {
220 INetworkPlayer *thisPlayer = player->connection->getNetworkPlayer();
221 if( thisPlayer == NULL )
222 {
223 dontSend = true;
224 }
225 else
226 {
227 for(unsigned int j = 0; j < sentTo.size(); j++ )
228 {
229 shared_ptr<ServerPlayer> player2 = sentTo[j];
230 INetworkPlayer *otherPlayer = player2->connection->getNetworkPlayer();
231 if( otherPlayer != NULL && thisPlayer->IsSameSystem(otherPlayer) )
232 {
233 dontSend = true;
234 }
235 }
236 }
237 }
238 if( dontSend )
239 {
240 continue;
241 }
242
243 // 4J Changed to get the flag index for the player before we send a packet. This flag is updated when we queue
244 // for send the first BlockRegionUpdatePacket for this chunk to that player/players system. Therefore there is no need to
245 // send tile updates or other updates until that has been sent
246 int flagIndex = ServerPlayer::getFlagIndexForChunk(pos, parent->dimension);
247 if (player->seenChunks.find(pos) != player->seenChunks.end() && (player->connection->isLocal() || g_NetworkManager.SystemFlagGet(player->connection->getNetworkPlayer(),flagIndex) ))
248 {
249 player->connection->send(packet);
250 sentTo.push_back(player);
251 }
252 }
253 // Now also check round all the players that are involved in this game. We also want to send the packet
254 // to them if their system hasn't received it already, but they have received the first BlockRegionUpdatePacket for this
255 // chunk
256
257 // Make sure we are only doing this for BlockRegionUpdatePacket, ChunkTilesUpdatePacket and TileUpdatePacket.
258 // We'll be potentially sending to players who aren't on the same level as this packet is intended for,
259 // and only these 3 packets have so far been updated to be able to encode the level so they are robust
260 // enough to cope with this
261 if(!( ( packet->getId() == 51 ) || ( packet->getId() == 52 ) || ( packet->getId() == 53 ) ) )
262 {
263 return;
264 }
265
266 for( int i = 0; i < parent->level->getServer()->getPlayers()->players.size(); i++ )
267 {
268 shared_ptr<ServerPlayer> player = parent->level->getServer()->getPlayers()->players[i];
269 // Don't worry about local players, they get all their updates through sharing level with the server anyway
270 if ( player->connection == NULL ) continue;
271 if( player->connection->isLocal() ) continue;
272
273 // Don't worry about this player if they haven't had this chunk yet (this flag will be the
274 // same for all players on the same system)
275 int flagIndex = ServerPlayer::getFlagIndexForChunk(pos,parent->dimension);
276 if(!g_NetworkManager.SystemFlagGet(player->connection->getNetworkPlayer(),flagIndex)) continue;
277
278 // From here on the same rules as in the loop above - don't send it if we've already sent to the same system
279 bool dontSend = false;
280 if( sentTo.size() )
281 {
282 INetworkPlayer *thisPlayer = player->connection->getNetworkPlayer();
283 if( thisPlayer == NULL )
284 {
285 dontSend = true;
286 }
287 else
288 {
289 for(unsigned int j = 0; j < sentTo.size(); j++ )
290 {
291 shared_ptr<ServerPlayer> player2 = sentTo[j];
292 INetworkPlayer *otherPlayer = player2->connection->getNetworkPlayer();
293 if( otherPlayer != NULL && thisPlayer->IsSameSystem(otherPlayer) )
294 {
295 dontSend = true;
296 }
297 }
298 }
299 }
300 if( !dontSend )
301 {
302 player->connection->send(packet);
303 sentTo.push_back(player);
304 }
305 }
306}
307
308bool PlayerChunkMap::PlayerChunk::broadcastChanges(bool allowRegionUpdate)
309{
310 bool didRegionUpdate = false;
311 ServerLevel *level = parent->getLevel();
312 if( ticksToNextRegionUpdate > 0 ) ticksToNextRegionUpdate--;
313 if (changes == 0)
314 {
315 prioritised = false;
316 return false;
317 }
318 if (changes == 1)
319 {
320 int x = pos.x * 16 + xChangeMin;
321 int y = yChangeMin;
322 int z = pos.z * 16 + zChangeMin;
323 broadcast( shared_ptr<TileUpdatePacket>( new TileUpdatePacket(x, y, z, level) ) );
324 if (level->isEntityTile(x, y, z))
325 {
326 broadcast(level->getTileEntity(x, y, z));
327 }
328 }
329 else if (changes == MAX_CHANGES_BEFORE_RESEND)
330 {
331 // 4J added, to allow limiting of region update packets created
332 if( !prioritised )
333 {
334 if( !allowRegionUpdate || ( ticksToNextRegionUpdate > 0 ) )
335 {
336 return false;
337 }
338 }
339
340 yChangeMin = yChangeMin / 2 * 2;
341 yChangeMax = (yChangeMax / 2 + 1) * 2;
342 int xp = xChangeMin + pos.x * 16;
343 int yp = yChangeMin;
344 int zp = zChangeMin + pos.z * 16;
345 int xs = xChangeMax - xChangeMin + 1;
346 int ys = yChangeMax - yChangeMin + 2;
347 int zs = zChangeMax - zChangeMin + 1;
348
349 // Fix for buf #95007 : TCR #001 BAS Game Stability: TU12: Code: Compliance: More than 192 dropped items causes game to freeze or crash.
350 // Block region update packets can only encode ys in a range of 1 - 256
351 if( ys > 256 ) ys = 256;
352
353 broadcast( shared_ptr<BlockRegionUpdatePacket>( new BlockRegionUpdatePacket(xp, yp, zp, xs, ys, zs, level) ) );
354 vector<shared_ptr<TileEntity> > *tes = level->getTileEntitiesInRegion(xp, yp, zp, xp + xs, yp + ys, zp + zs);
355 for (unsigned int i = 0; i < tes->size(); i++)
356 {
357 broadcast(tes->at(i));
358 }
359 delete tes;
360 ticksToNextRegionUpdate = MIN_TICKS_BETWEEN_REGION_UPDATE;
361 didRegionUpdate = true;
362 }
363 else
364 {
365 // 4J As we only get here if changes is less than MAX_CHANGES_BEFORE_RESEND (10) we only need to send a byte value in the packet
366 broadcast( shared_ptr<ChunkTilesUpdatePacket>( new ChunkTilesUpdatePacket(pos.x, pos.z, changedTiles, (byte)changes, level) ) );
367 for (int i = 0; i < changes; i++)
368 {
369 int x = pos.x * 16 + ((changedTiles[i] >> 12) & 15);
370 int y = ((changedTiles[i]) & 255);
371 int z = pos.z * 16 + ((changedTiles[i] >> 8) & 15);
372
373 if (level->isEntityTile(x, y, z))
374 {
375// System.out.println("Sending!");
376 broadcast(level->getTileEntity(x, y, z));
377 }
378 }
379 }
380 changes = 0;
381 prioritised = false;
382 return didRegionUpdate;
383}
384
385void PlayerChunkMap::PlayerChunk::broadcast(shared_ptr<TileEntity> te)
386{
387 if (te != NULL)
388 {
389 shared_ptr<Packet> p = te->getUpdatePacket();
390 if (p != NULL)
391 {
392 broadcast(p);
393 }
394 }
395}
396
397PlayerChunkMap::PlayerChunkMap(ServerLevel *level, int dimension, int radius)
398{
399 assert(radius <= MAX_VIEW_DISTANCE);
400 assert(radius >= MIN_VIEW_DISTANCE);
401 this->radius = radius;
402 this->level = level;
403 this->dimension = dimension;
404 lastInhabitedUpdate = 0;
405}
406
407PlayerChunkMap::~PlayerChunkMap()
408{
409 for( AUTO_VAR(it, chunks.begin()); it != chunks.end(); it++ )
410 {
411 delete it->second;
412 }
413}
414
415ServerLevel *PlayerChunkMap::getLevel()
416{
417 return level;
418}
419
420void PlayerChunkMap::tick()
421{
422 __int64 time = level->getGameTime();
423
424 if (time - lastInhabitedUpdate > Level::TICKS_PER_DAY / 3)
425 {
426 lastInhabitedUpdate = time;
427
428 for (int i = 0; i < knownChunks.size(); i++)
429 {
430 PlayerChunk *chunk = knownChunks.at(i);
431
432 // 4J Stu - Going to let our changeChunks handler below deal with this
433 //chunk.broadcastChanges();
434
435 chunk->updateInhabitedTime();
436 }
437 }
438
439 // 4J - some changes here so that we only send one region update per tick. The chunks themselves also
440 // limit their resend rate to once every MIN_TICKS_BETWEEN_REGION_UPDATE ticks
441 bool regionUpdateSent = false;
442 for (unsigned int i = 0; i < changedChunks.size();)
443 {
444 regionUpdateSent |= changedChunks[i]->broadcastChanges(!regionUpdateSent);
445 // Changes will be 0 if the chunk actually sent something, in which case we can delete it from this array
446 if( changedChunks[i]->changes == 0 )
447 {
448 changedChunks[i] = changedChunks.back();
449 changedChunks.pop_back();
450 }
451 else
452 {
453 // Limiting of some kind means we didn't send this chunk so move onto the next
454 i++;
455 }
456 }
457
458 for( unsigned int i = 0; i < players.size(); i++ )
459 {
460 tickAddRequests(players[i]);
461 }
462
463 // 4J Stu - Added 1.1 but not relevant to us as we never no 0 players anyway, and don't think we should be dropping stuff
464 //if (players.isEmpty()) {
465 // ServerLevel level = server.getLevel(this.dimension);
466 // Dimension dimension = level.dimension;
467 // if (!dimension.mayRespawn()) {
468 // level.cache.dropAll();
469 // }
470 //}
471}
472
473bool PlayerChunkMap::hasChunk(int x, int z)
474{
475 __int64 id = (x + 0x7fffffffLL) | ((z + 0x7fffffffLL) << 32);
476 return chunks.find(id) != chunks.end();
477}
478
479PlayerChunkMap::PlayerChunk *PlayerChunkMap::getChunk(int x, int z, bool create)
480{
481 __int64 id = (x + 0x7fffffffLL) | ((z + 0x7fffffffLL) << 32);
482 AUTO_VAR(it, chunks.find(id));
483
484 PlayerChunk *chunk = NULL;
485 if( it != chunks.end() )
486 {
487 chunk = it->second;
488 }
489 else if ( create)
490 {
491 chunk = new PlayerChunk(x, z, this);
492 chunks[id] = chunk;
493 knownChunks.push_back(chunk);
494 }
495
496 return chunk;
497}
498
499// 4J - added. If a chunk exists, add a player to it straight away. If it doesn't exist,
500// queue a request for it to be created.
501void PlayerChunkMap::getChunkAndAddPlayer(int x, int z, shared_ptr<ServerPlayer> player)
502{
503 __int64 id = (x + 0x7fffffffLL) | ((z + 0x7fffffffLL) << 32);
504 AUTO_VAR(it, chunks.find(id));
505
506 if( it != chunks.end() )
507 {
508 it->second->add(player);
509 }
510 else
511 {
512 addRequests.push_back(PlayerChunkAddRequest(x,z,player));
513 }
514}
515
516// 4J - added. If the chunk and player are in the queue to be added, remove from there. Otherwise
517// attempt to remove from main chunk map.
518void PlayerChunkMap::getChunkAndRemovePlayer(int x, int z, shared_ptr<ServerPlayer> player)
519{
520 for( AUTO_VAR(it, addRequests.begin()); it != addRequests.end(); it++ )
521 {
522 if( ( it->x == x ) &&
523 ( it->z == z ) &&
524 ( it->player == player ) )
525 {
526 addRequests.erase(it);
527 return;
528 }
529 }
530 __int64 id = (x + 0x7fffffffLL) | ((z + 0x7fffffffLL) << 32);
531 AUTO_VAR(it, chunks.find(id));
532
533 if( it != chunks.end() )
534 {
535 it->second->remove(player);
536 }
537}
538
539// 4J - added - actually create & add player to a playerchunk, if there is one queued for this player.
540void PlayerChunkMap::tickAddRequests(shared_ptr<ServerPlayer> player)
541{
542 if( addRequests.size() )
543 {
544 // Find the nearest chunk request to the player
545 int px = (int)player->x;
546 int pz = (int)player->z;
547 int minDistSq = -1;
548
549 AUTO_VAR(itNearest, addRequests.end());
550 for( AUTO_VAR(it, addRequests.begin()); it != addRequests.end(); it++ )
551 {
552 if( it->player == player )
553 {
554 int xm = ( it->x * 16 ) + 8;
555 int zm = ( it->z * 16 ) + 8;
556 int distSq = (xm - px) * (xm - px) +
557 (zm - pz) * (zm - pz);
558 if( ( minDistSq == -1 ) || ( distSq < minDistSq ) )
559 {
560 minDistSq = distSq;
561 itNearest = it;
562 }
563 }
564 }
565
566 // If we found one at all, then do this one
567 if( itNearest != addRequests.end() )
568 {
569 getChunk(itNearest->x, itNearest->z, true)->add(itNearest->player);
570 addRequests.erase(itNearest);
571 }
572 }
573}
574
575void PlayerChunkMap::broadcastTileUpdate(shared_ptr<Packet> packet, int x, int y, int z)
576{
577 int xc = x >> 4;
578 int zc = z >> 4;
579 PlayerChunk *chunk = getChunk(xc, zc, false);
580 if (chunk != NULL)
581 {
582 chunk->broadcast(packet);
583 }
584}
585
586void PlayerChunkMap::tileChanged(int x, int y, int z)
587{
588 int xc = x >> 4;
589 int zc = z >> 4;
590 PlayerChunk *chunk = getChunk(xc, zc, false);
591 if (chunk != NULL)
592 {
593 chunk->tileChanged(x & 15, y, z & 15);
594 }
595}
596
597bool PlayerChunkMap::isTrackingTile(int x, int y, int z)
598{
599 int xc = x >> 4;
600 int zc = z >> 4;
601 PlayerChunk *chunk = getChunk(xc, zc, false);
602 if( chunk ) return true;
603 return false;
604}
605
606// 4J added - make sure that any tile updates for the chunk at this location get prioritised for sending
607void PlayerChunkMap::prioritiseTileChanges(int x, int y, int z)
608{
609 int xc = x >> 4;
610 int zc = z >> 4;
611 PlayerChunk *chunk = getChunk(xc, zc, false);
612 if (chunk != NULL)
613 {
614 chunk->prioritiseTileChanges();
615 }
616}
617
618void PlayerChunkMap::add(shared_ptr<ServerPlayer> player)
619{
620 static int direction[4][2] = { { 1, 0 }, { 0, 1 }, { -1, 0 }, {0, -1} };
621
622 int xc = (int) player->x >> 4;
623 int zc = (int) player->z >> 4;
624
625 player->lastMoveX = player->x;
626 player->lastMoveZ = player->z;
627
628// for (int x = xc - radius; x <= xc + radius; x++)
629// for (int z = zc - radius; z <= zc + radius; z++) {
630// getChunk(x, z, true).add(player);
631// }
632
633 // CraftBukkit start
634 int facing = 0;
635 int size = radius;
636 int dx = 0;
637 int dz = 0;
638
639 // Origin
640 getChunk(xc, zc, true)->add(player, false);
641
642 // 4J Added so we send an area packet rather than one visibility packet per chunk
643 int minX, maxX, minZ, maxZ;
644 minX = maxX = xc;
645 minZ = maxZ = zc;
646
647 // 4J - added so that we don't fully create/send every chunk at this stage. Particularly since moving on to large worlds, where
648 // we can be adding 1024 chunks here of which a large % might need to be fully created, this can take a long time. Instead use
649 // the getChunkAndAddPlayer for anything but the central region of chunks, which adds them to a queue of chunks which are added
650 // one per tick per player.
651 const int maxLegSizeToAddNow = 14;
652
653 // All but the last leg
654 for (int legSize = 1; legSize <= size * 2; legSize++)
655 {
656 for (int leg = 0; leg < 2; leg++)
657 {
658 int *dir = direction[facing++ % 4];
659
660 for (int k = 0; k < legSize; k++)
661 {
662 dx += dir[0];
663 dz += dir[1];
664
665 int targetX, targetZ;
666 targetX = xc + dx;
667 targetZ = zc + dz;
668
669 if( ( legSize < maxLegSizeToAddNow ) ||
670 ( ( legSize == maxLegSizeToAddNow ) && ( ( leg == 0 ) || ( k < ( legSize - 1 ) ) ) ) )
671 {
672 if( targetX > maxX ) maxX = targetX;
673 if( targetX < minX ) minX = targetX;
674 if( targetZ > maxZ ) maxZ = targetZ;
675 if( targetZ < minZ ) minZ = targetZ;
676
677 getChunk(targetX, targetZ, true)->add(player, false);
678 }
679 else
680 {
681 getChunkAndAddPlayer(targetX, targetZ, player);
682 }
683 }
684 }
685 }
686
687 // Final leg
688 facing %= 4;
689 for (int k = 0; k < size * 2; k++)
690 {
691 dx += direction[facing][0];
692 dz += direction[facing][1];
693
694 int targetX, targetZ;
695 targetX = xc + dx;
696 targetZ = zc + dz;
697 if( ( size * 2 ) <= maxLegSizeToAddNow )
698 {
699 if( targetX > maxX ) maxX = targetX;
700 if( targetX < minX ) minX = targetX;
701 if( targetZ > maxZ ) maxZ = targetZ;
702 if( targetZ < minZ ) minZ = targetZ;
703
704 getChunk(targetX, targetZ, true)->add(player, false);
705 }
706 else
707 {
708 getChunkAndAddPlayer(targetX, targetZ, player);
709 }
710 }
711 // CraftBukkit end
712
713 player->connection->send( shared_ptr<ChunkVisibilityAreaPacket>( new ChunkVisibilityAreaPacket(minX, maxX, minZ, maxZ) ) );
714
715#ifdef _LARGE_WORLDS
716 getLevel()->cache->dontDrop(xc,zc);
717#endif
718
719 players.push_back(player);
720
721}
722
723void PlayerChunkMap::remove(shared_ptr<ServerPlayer> player)
724{
725 int xc = ((int) player->lastMoveX) >> 4;
726 int zc = ((int) player->lastMoveZ) >> 4;
727
728 for (int x = xc - radius; x <= xc + radius; x++)
729 for (int z = zc - radius; z <= zc + radius; z++)
730 {
731 PlayerChunk *playerChunk = getChunk(x, z, false);
732 if (playerChunk != NULL) playerChunk->remove(player);
733 }
734
735 AUTO_VAR(it, find(players.begin(),players.end(),player));
736 if( players.size() > 0 && it != players.end() )
737 players.erase(find(players.begin(),players.end(),player));
738
739 // 4J - added - also remove any queued requests to be added to playerchunks here
740 for( AUTO_VAR(it, addRequests.begin()); it != addRequests.end(); )
741 {
742 if( it->player == player )
743 {
744 it = addRequests.erase(it);
745 }
746 else
747 {
748 ++it;
749 }
750 }
751
752}
753
754bool PlayerChunkMap::chunkInRange(int x, int z, int xc, int zc)
755{
756 // If the distance between x and xc
757 int xd = x - xc;
758 int zd = z - zc;
759 if (xd < -radius || xd > radius) return false;
760 if (zd < -radius || zd > radius) return false;
761 return true;
762}
763
764// 4J - have changed this so that we queue requests to add the player to chunks if they
765// need to be created, so that we aren't creating potentially 20 chunks per player per tick
766void PlayerChunkMap::move(shared_ptr<ServerPlayer> player)
767{
768 int xc = ((int) player->x) >> 4;
769 int zc = ((int) player->z) >> 4;
770
771 double _xd = player->lastMoveX - player->x;
772 double _zd = player->lastMoveZ - player->z;
773 double dist = _xd * _xd + _zd * _zd;
774 if (dist < 8 * 8) return;
775
776 int last_xc = ((int) player->lastMoveX) >> 4;
777 int last_zc = ((int) player->lastMoveZ) >> 4;
778
779 int xd = xc - last_xc;
780 int zd = zc - last_zc;
781 if (xd == 0 && zd == 0) return;
782
783 for (int x = xc - radius; x <= xc + radius; x++)
784 for (int z = zc - radius; z <= zc + radius; z++)
785 {
786 if (!chunkInRange(x, z, last_xc, last_zc))
787 {
788 // 4J - changed from separate getChunk & add so we can wrap these operations up and queue
789 getChunkAndAddPlayer(x, z, player);
790 }
791
792 if (!chunkInRange(x - xd, z - zd, xc, zc))
793 {
794 // 4J - changed from separate getChunk & remove so we can wrap these operations up and queue
795 getChunkAndRemovePlayer(x - xd, z - zd, player);
796 }
797 }
798
799 player->lastMoveX = player->x;
800 player->lastMoveZ = player->z;
801}
802
803int PlayerChunkMap::getMaxRange()
804{
805 return radius * 16 - 16;
806}
807
808bool PlayerChunkMap::isPlayerIn(shared_ptr<ServerPlayer> player, int xChunk, int zChunk)
809{
810 PlayerChunk *chunk = getChunk(xChunk, zChunk, false);
811
812 if(chunk == NULL)
813 {
814 return false;
815 }
816 else
817 {
818 AUTO_VAR(it1, find(chunk->players.begin(), chunk->players.end(), player));
819 AUTO_VAR(it2, find(player->chunksToSend.begin(), player->chunksToSend.end(), chunk->pos));
820 return it1 != chunk->players.end() && it2 == player->chunksToSend.end();
821 }
822
823 //return chunk == NULL ? false : chunk->players->contains(player) && !player->chunksToSend->contains(chunk->pos);
824}
825
826int PlayerChunkMap::convertChunkRangeToBlock(int radius)
827{
828 return radius * 16 - 16;
829}
830
831// AP added for Vita so the range can be increased once the level starts
832void PlayerChunkMap::setRadius(int newRadius)
833{
834 if( radius != newRadius )
835 {
836 PlayerList* players = level->getServer()->getPlayerList();
837 for( int i = 0;i < players->players.size();i += 1 )
838 {
839 shared_ptr<ServerPlayer> player = players->players[i];
840 if( player->level == level )
841 {
842 int xc = ((int) player->x) >> 4;
843 int zc = ((int) player->z) >> 4;
844
845 for (int x = xc - newRadius; x <= xc + newRadius; x++)
846 for (int z = zc - newRadius; z <= zc + newRadius; z++)
847 {
848 // check if this chunk is outside the old radius area
849 if ( x < xc - radius || x > xc + radius || z < zc - radius || z > zc + radius )
850 {
851 getChunkAndAddPlayer(x, z, player);
852 }
853 }
854 }
855 }
856
857 assert(radius <= MAX_VIEW_DISTANCE);
858 assert(radius >= MIN_VIEW_DISTANCE);
859 this->radius = newRadius;
860 }
861}