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 "net.minecraft.h"
3#include "net.minecraft.world.entity.h"
4#include "net.minecraft.world.entity.animal.h"
5#include "net.minecraft.world.entity.monster.h"
6#include "net.minecraft.world.entity.player.h"
7#include "net.minecraft.world.level.h"
8#include "net.minecraft.world.level.biome.h"
9#include "net.minecraft.world.level.material.h"
10#include "net.minecraft.world.level.pathfinder.h"
11#include "net.minecraft.world.level.tile.h"
12#include "Difficulty.h"
13#include "WeighedRandom.h"
14#include "Level.h"
15#include "ChunkPos.h"
16#include "TilePos.h"
17#include "..\Minecraft.Client\ServerLevel.h"
18#include "MobSpawner.h"
19#include "Dimension.h"
20
21const int MobSpawner::MIN_SPAWN_DISTANCE = 24;
22
23TilePos MobSpawner::getRandomPosWithin(Level *level, int cx, int cz)
24{
25 // 4J Stu - Added 1.2.3 but we don't need it as it was only used to access sections
26 // Leaving here though to help explain why chunk coords are not passed in rather than full coords
27 //LevelChunk *chunk = level->getChunk(cx, cz);
28 int x = cx * 16 + level->random->nextInt(16);
29 int y = level->random->nextInt(level->getHeight());
30 int z = cz * 16 + level->random->nextInt(16);
31
32 return TilePos(x, y, z);
33}
34
35#ifdef __PSVITA__
36 // AP - See CustomMap.h for an explanation of this
37 CustomMap MobSpawner::chunksToPoll;
38#else
39 unordered_map<ChunkPos,bool,ChunkPosKeyHash,ChunkPosKeyEq> MobSpawner::chunksToPoll;
40#endif
41
42const int MobSpawner::tick(ServerLevel *level, bool spawnEnemies, bool spawnFriendlies, bool spawnPersistent)
43{
44#ifndef _CONTENT_PACKAGE
45
46#if 0
47 // PIX output for mob counters - generally disabling as Entity::countFlagsForPIX is reasonably expensive
48 if( level->dimension->id == 0 )
49 {
50 Entity::countFlagsForPIX();
51 PIXAddNamedCounter( level->countInstanceOf(eTYPE_WATERANIMAL ,false), "eTYPE_WATERANIMAL");
52 PIXAddNamedCounter( level->countInstanceOf(eTYPE_ANIMALS_SPAWN_LIMIT_CHECK ,false), "eTYPE_ANIMAL");
53 PIXAddNamedCounter( level->countInstanceOf(eTYPE_MONSTER ,false), "eTYPE_MONSTER");
54 PIXAddNamedCounter( level->countInstanceOf(eTYPE_SQUID ,true ), "eTYPE_SQUID");
55 PIXAddNamedCounter( level->countInstanceOf(eTYPE_VILLAGER ,true ), "eTYPE_VILLAGER");
56
57 unsigned int totalCount[4];
58 unsigned int protectedCount[4];
59 unsigned int unprotectedCount[4];
60 unsigned int couldWanderCount[4];
61
62 totalCount[0] = level->countInstanceOf(eTYPE_COW ,true, &protectedCount[0], &couldWanderCount[0] );
63 totalCount[1] = level->countInstanceOf(eTYPE_SHEEP ,true, &protectedCount[1], &couldWanderCount[1] );
64 totalCount[2] = level->countInstanceOf(eTYPE_CHICKEN ,true, &protectedCount[2], &couldWanderCount[2] );
65 totalCount[3] = level->countInstanceOf(eTYPE_PIG ,true, &protectedCount[3], &couldWanderCount[3] );
66
67 for( int i = 0; i < 4; i++ ) unprotectedCount[i] = totalCount[i] - protectedCount[i];
68
69 PIXAddNamedCounter( unprotectedCount[0], "eTYPE_COW (unprotected)");
70 PIXAddNamedCounter( unprotectedCount[1], "eTYPE_SHEEP (unprotected)");
71 PIXAddNamedCounter( unprotectedCount[2], "eTYPE_CHICKEN (unprotected)");
72 PIXAddNamedCounter( unprotectedCount[3], "eTYPE_PIG (unprotected)");
73
74 PIXAddNamedCounter( protectedCount[0], "eTYPE_COW (protected)");
75 PIXAddNamedCounter( protectedCount[1], "eTYPE_SHEEP (protected)");
76 PIXAddNamedCounter( protectedCount[2], "eTYPE_CHICKEN (protected)");
77 PIXAddNamedCounter( protectedCount[3], "eTYPE_PIG (protected)");
78
79 PIXAddNamedCounter( couldWanderCount[0], "eTYPE_COW (could wander)");
80 PIXAddNamedCounter( couldWanderCount[1], "eTYPE_SHEEP (could wander)");
81 PIXAddNamedCounter( couldWanderCount[2], "eTYPE_CHICKEN (could wander)");
82 PIXAddNamedCounter( couldWanderCount[3], "eTYPE_PIG (could wander)");
83
84 PIXAddNamedCounter( level->countInstanceOf(eTYPE_WOLF ,true ), "eTYPE_WOLF");
85 PIXAddNamedCounter( level->countInstanceOf(eTYPE_CREEPER ,true ), "eTYPE_CREEPER");
86 PIXAddNamedCounter( level->countInstanceOf(eTYPE_GIANT ,true ), "eTYPE_GIANT");
87 PIXAddNamedCounter( level->countInstanceOf(eTYPE_SKELETON ,true ), "eTYPE_SKELETON");
88 PIXAddNamedCounter( level->countInstanceOf(eTYPE_SPIDER ,true ), "eTYPE_SPIDER");
89 PIXAddNamedCounter( level->countInstanceOf(eTYPE_ZOMBIE ,true ), "eTYPE_ZOMBIE");
90 PIXAddNamedCounter( level->countInstanceOf(eTYPE_PIGZOMBIE ,true ), "eTYPE_PIGZOMBIE");
91 PIXAddNamedCounter( level->countInstanceOf(eTYPE_SLIME ,true ), "eTYPE_SLIME");
92 PIXAddNamedCounter( level->countInstanceOf(eTYPE_GHAST ,true ), "eTYPE_GHAST");
93 }
94#endif
95#endif
96
97 if (!spawnEnemies && !spawnFriendlies && !spawnPersistent)
98 {
99 return 0;
100 }
101 MemSect(20);
102 chunksToPoll.clear();
103
104#if 0
105 AUTO_VAR(itEnd, level->players.end());
106 for (AUTO_VAR(it, level->players.begin()); it != itEnd; it++)
107 {
108 shared_ptr<Player> player = *it; //level->players.at(i);
109 int xx = Mth::floor(player->x / 16);
110 int zz = Mth::floor(player->z / 16);
111
112 int r = 128 / 16;
113 for (int x = -r; x <= r; x++)
114 for (int z = -r; z <= r; z++)
115 {
116 chunksToPoll.insert(ChunkPos(x + xx, z + zz));
117 }
118 }
119#else
120 // 4J - rewritten to add chunks interleaved by player, and to add them from the centre outwards. We're going to be
121 // potentially adding less creatures than the original so that our count stays consistent with number of players added, so
122 // we want to make sure as best we can that the ones we do add are near the active players
123 int playerCount = (int)level->players.size();
124 int *xx = new int[playerCount];
125 int *zz = new int[playerCount];
126 for (int i = 0; i < playerCount; i++)
127 {
128 shared_ptr<Player> player = level->players[i];
129 xx[i] = Mth::floor(player->x / 16);
130 zz[i] = Mth::floor(player->z / 16);
131#ifdef __PSVITA__
132 chunksToPoll.insert(ChunkPos(xx[i], zz[i] ),false);
133#else
134 chunksToPoll.insert(std::pair<ChunkPos,bool>(ChunkPos(xx[i], zz[i] ),false));
135#endif
136 }
137
138 for( int r = 1; r <= 8; r++ )
139 {
140 for( int l = 0; l < ( r * 2 ) ; l++ )
141 {
142 for( int i = 0; i < playerCount; i++ )
143 {
144 bool edgeChunk = ( r == 8 );
145
146 // If this chunk isn't at the edge of the region for this player, then always store with a flag of false
147 // so that if it was at the edge of another player, then this will remove that
148 if( !edgeChunk )
149 {
150#ifdef __PSVITA__
151 chunksToPoll.insert(ChunkPos( ( xx[i] - r ) + l , ( zz[i] - r ) ), false);
152 chunksToPoll.insert(ChunkPos( ( xx[i] + r ) , ( zz[i] - r ) + l ), false);
153 chunksToPoll.insert(ChunkPos( ( xx[i] + r ) - l , ( zz[i] + r ) ), false);
154 chunksToPoll.insert(ChunkPos( ( xx[i] - r ) , ( zz[i] + r ) - l ), false);
155#else
156 chunksToPoll.insert(std::pair<ChunkPos,bool>(ChunkPos( ( xx[i] - r ) + l , ( zz[i] - r ) ), false));
157 chunksToPoll.insert(std::pair<ChunkPos,bool>(ChunkPos( ( xx[i] + r ) , ( zz[i] - r ) + l ), false));
158 chunksToPoll.insert(std::pair<ChunkPos,bool>(ChunkPos( ( xx[i] + r ) - l , ( zz[i] + r ) ), false));
159 chunksToPoll.insert(std::pair<ChunkPos,bool>(ChunkPos( ( xx[i] - r ) , ( zz[i] + r ) - l ), false));
160#endif
161 }
162 else
163 {
164#ifdef __PSVITA__
165 ChunkPos cp = ChunkPos( ( xx[i] - r ) + l , ( zz[i] - r ));
166 if( chunksToPoll.find( cp ) ) chunksToPoll.insert(cp, true);
167 cp = ChunkPos( ( xx[i] + r ), ( zz[i] - r ) + l );
168 if( chunksToPoll.find( cp ) ) chunksToPoll.insert(cp, true);
169 cp = ChunkPos( ( xx[i] + r ) - l , ( zz[i] + r ));
170 if( chunksToPoll.find( cp ) ) chunksToPoll.insert(cp, true);
171 cp = ChunkPos( ( xx[i] - r ), ( zz[i] + r ) - l);
172 if( chunksToPoll.find( cp ) ) chunksToPoll.insert(cp, true);
173#else
174 ChunkPos cp = ChunkPos( ( xx[i] - r ) + l , ( zz[i] - r ));
175 if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair<ChunkPos,bool>(cp, true));
176 cp = ChunkPos( ( xx[i] + r ), ( zz[i] - r ) + l );
177 if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair<ChunkPos,bool>(cp, true));
178 cp = ChunkPos( ( xx[i] + r ) - l , ( zz[i] + r ));
179 if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair<ChunkPos,bool>(cp, true));
180 cp = ChunkPos( ( xx[i] - r ), ( zz[i] + r ) - l);
181 if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair<ChunkPos,bool>(cp, true));
182#endif
183
184 }
185 }
186 }
187 }
188 delete [] xx;
189 delete [] zz;
190#endif
191 MemSect(0);
192 int count = 0;
193 MemSect(31);
194 Pos *spawnPos = level->getSharedSpawnPos();
195 MemSect(0);
196
197 for (unsigned int i = 0; i < MobCategory::values.length; i++)
198 {
199 MobCategory *mobCategory = MobCategory::values[i];
200 if ((mobCategory->isFriendly() && !spawnFriendlies) || (!mobCategory->isFriendly() && !spawnEnemies) || (mobCategory->isPersistent() && !spawnPersistent))
201 {
202 continue;
203 }
204
205 // 4J - early out for non-main dimensions, if spawning anything friendly
206 if( mobCategory->isFriendly() )
207 {
208 if( level->dimension->id != 0 )
209 {
210 continue;
211 }
212 }
213
214 // 4J - this is now quite different to the java version. We just have global max counts for the level whereas the original has a max per chunk that
215 // scales with the number of chunks to be polled.
216 int categoryCount = level->countInstanceOf( mobCategory->getEnumBaseClass(), mobCategory->isSingleType());
217 if( categoryCount >= mobCategory->getMaxInstancesPerLevel())
218 {
219 continue;
220 }
221
222#ifdef __PSVITA__
223 for( int i = 0;i < chunksToPoll.end();i += 1 )
224 {
225 SCustomMapNode *it = chunksToPoll.get(i);
226#else
227 AUTO_VAR(itEndCTP, chunksToPoll.end());
228 for (AUTO_VAR(it, chunksToPoll.begin()); it != itEndCTP; it++)
229 {
230#endif
231 if( it->second )
232 {
233 // don't add mobs to edge chunks, to prevent adding mobs "outside" of the active playground
234 continue;
235 }
236 ChunkPos *cp = (ChunkPos *) (&it->first);
237
238 // 4J - don't let this actually create/load a chunk that isn't here already - we'll let the normal updateDirtyChunks etc. processes do that, so it can happen on another thread
239 if( !level->hasChunk(cp->x,cp->z) ) continue;
240
241 TilePos start = getRandomPosWithin(level, cp->x, cp->z);
242 int xStart = start.x;
243 int yStart = start.y;
244 int zStart = start.z;
245
246 if (level->isSolidBlockingTile(xStart, yStart, zStart)) continue;
247 if (level->getMaterial(xStart, yStart, zStart) != mobCategory->getSpawnPositionMaterial()) continue;
248 int clusterSize = 0;
249
250 for (int dd = 0; dd < 3; dd++)
251 {
252 int x = xStart;
253 int y = yStart;
254 int z = zStart;
255 int ss = 6;
256
257 Biome::MobSpawnerData *currentMobType = NULL;
258 MobGroupData *groupData = NULL;
259
260 for (int ll = 0; ll < 4; ll++)
261 {
262 x += level->random->nextInt(ss) - level->random->nextInt(ss);
263 y += level->random->nextInt(1) - level->random->nextInt(1);
264 z += level->random->nextInt(ss) - level->random->nextInt(ss);
265 // int y = heightMap[x + z * w] + 1;
266
267 // 4J - don't let this actually create/load a chunk that isn't here already - we'll let the normal updateDirtyChunks etc. processes do that, so it can happen on another thread
268 if( !level->hasChunkAt( x, y, z ) ) continue;
269
270 if (isSpawnPositionOk(mobCategory, level, x, y, z))
271 {
272 float xx = x + 0.5f;
273 float yy = (float) y;
274 float zz = z + 0.5f;
275 if (level->getNearestPlayer(xx, yy, zz, MIN_SPAWN_DISTANCE) != NULL)
276 {
277 continue;
278 }
279 else
280 {
281 float xd = xx - spawnPos->x;
282 float yd = yy - spawnPos->y;
283 float zd = zz - spawnPos->z;
284 float sd = xd * xd + yd * yd + zd * zd;
285 if (sd < MIN_SPAWN_DISTANCE * MIN_SPAWN_DISTANCE)
286 {
287 continue;
288 }
289 }
290
291 if (currentMobType == NULL)
292 {
293 currentMobType = level->getRandomMobSpawnAt(mobCategory, x, y, z);
294 if (currentMobType == NULL)
295 {
296 break;
297 }
298 }
299
300 shared_ptr<Mob> mob;
301 // 4J - removed try/catch
302// try
303// {
304 MemSect(29);
305 //mob = type.mobClass.getConstructor(Level.class).newInstance(level);
306 mob = dynamic_pointer_cast<Mob>(EntityIO::newByEnumType(currentMobType->mobClass, level));
307 MemSect(0);
308// }
309// catch (exception e)
310// {
311// // TODO 4J We can't print a stack trace, and the newInstance function doesn't throw an exception just now anyway
312// //e.printStackTrace();
313// return count;
314// }
315
316 // 4J - If it is an animal or a monster, don't let any one type of mob represent more than 50% of the total amount of these things. This
317 // was added initially to stop flat lands being totally populated with slimes but seems like a generally good rule.
318 eINSTANCEOF mobType = mob->GetType();
319
320 if( ( mobType & eTYPE_ANIMALS_SPAWN_LIMIT_CHECK ) || ( mobType & eTYPE_MONSTER ) )
321 {
322 // even more special rule for ghasts, because filling up the nether with 25 of them is a bit unpleasant. In the java version they are
323 // only limited by the fact that the world fills up with pig zombies (the only other type of enemy mob in the nether) before them - they
324 // aren't actually even counted properly themselves
325 if( mobType == eTYPE_GHAST )
326 {
327 if( level->countInstanceOf(mobType, true) >= 4 ) continue;
328 }
329 else if( mobType == eTYPE_ENDERMAN && level->dimension->id == 1 )
330 {
331 // Special rule for the end, as we only have Endermen (plus the dragon). Increase the spawnable counts based on level difficulty
332 int maxEndermen = mobCategory->getMaxInstancesPerLevel();
333
334 if( level->difficulty == Difficulty::NORMAL )
335 {
336 maxEndermen -= mobCategory->getMaxInstancesPerLevel()/4;
337 }
338 else if( level->difficulty <= Difficulty::EASY)
339 {
340 maxEndermen -= mobCategory->getMaxInstancesPerLevel()/2;
341 }
342
343 if( level->countInstanceOf(mobType, true) >= maxEndermen ) continue;
344 }
345 else if( level->countInstanceOf(mobType, true) >= ( mobCategory->getMaxInstancesPerLevel() / 2 ) ) continue;
346 }
347
348 mob->moveTo(xx, yy, zz, level->random->nextFloat() * 360, 0);
349
350 if (mob->canSpawn())
351 {
352 // 4J - check if we are going to despawn straight away too, and don't add if we will - otherwise we'll be sending
353 // network packets for adding & removal that we don't need
354 mob->checkDespawn();
355 if( !mob->removed )
356 {
357 clusterSize++;
358 categoryCount++;
359 mob->setDespawnProtected(); // 4J added - default to protected against despawning
360 level->addEntity(mob);
361 groupData = mob->finalizeMobSpawn(groupData);
362 // 4J - change here so that we can't ever make more than the desired amount of entities in each priority. In the original java version
363 // depending on the random spawn positions being considered the only limit as to the number of entities created per category is the number
364 // of chunks to poll.
365 if (categoryCount >= mobCategory->getMaxInstancesPerLevel() ) goto categoryLoop;
366 if (clusterSize >= mob->getMaxSpawnClusterSize()) goto chunkLoop;
367 }
368 }
369 count += clusterSize;
370 }
371 }
372 }
373 chunkLoop: continue;
374 }
375 categoryLoop: continue;
376 }
377 delete spawnPos;
378
379 return count;
380}
381
382bool MobSpawner::isSpawnPositionOk(MobCategory *category, Level *level, int x, int y, int z)
383{
384 // 4J - don't let this actually create/load a chunk that isn't here already - we'll let the normal updateDirtyChunks etc. processes do that, so it can happen on another thread
385 if( !level->hasChunkAt(x, y, z ) ) return false;
386
387#ifdef __PSVITA__
388 // AP - added this for Vita. Make sure a new spawn point has 2 chunks around it. This will make sure monsters don't keep getting spawned on the edge preventing other new monsters
389 // from being spawned
390 int r = 32;
391 if( !level->hasChunksAt(x - r, 0, z - r, x + r, 0, z + r))
392 {
393 return false;
394 }
395#endif
396
397 if (category->getSpawnPositionMaterial() == Material::water)
398 {
399 // 4J - changed to spawn water things only in deep water
400 int yo = 0;
401 int liquidCount = 0;
402
403 while( ( y - yo ) >= 0 && ( yo < 5 ) )
404 {
405 if( level->getMaterial(x, y - yo, z)->isLiquid() ) liquidCount++;
406 yo++;
407 }
408
409 // 4J - Sometimes deep water could be just a waterfall, so check that it's wide as well
410 bool inEnoughWater = false;
411 if( liquidCount == 5 )
412 {
413 if( level->getMaterial(x+5, y, z)->isLiquid() &&
414 level->getMaterial(x-5, y, z)->isLiquid() &&
415 level->getMaterial(x, y, z+5)->isLiquid() &&
416 level->getMaterial(x, y, z-5)->isLiquid()
417 )
418 {
419 inEnoughWater = true;
420 }
421 }
422
423 return inEnoughWater && !level->isSolidBlockingTile(x, y + 1, z);
424 }
425 else
426 {
427 if (!level->isTopSolidBlocking(x, y - 1, z)) return false;
428 int tt = level->getTile(x, y - 1, z);
429 return tt != Tile::unbreakable_Id && !level->isSolidBlockingTile(x, y, z) && !level->getMaterial(x, y, z)->isLiquid() && !level->isSolidBlockingTile(x, y + 1, z);
430 }
431 }
432
433void MobSpawner::postProcessSpawnMobs(Level *level, Biome *biome, int xo, int zo, int cellWidth, int cellHeight, Random *random)
434{
435 // 4J - not for our version. Creates a few too many mobs.
436#if 0
437 vector<Biome::MobSpawnerData *> *mobs = biome->getMobs(MobCategory::creature);
438 if (mobs->empty())
439 {
440 return;
441 }
442
443 while (random->nextFloat() < biome->getCreatureProbability())
444 {
445 Biome::MobSpawnerData *type = (Biome::MobSpawnerData *) WeighedRandom::getRandomItem(level->random, ((vector<WeighedRandomItem *> *)mobs));
446 MobGroupData *groupData = NULL;
447 int count = type->minCount + random->nextInt(1 + type->maxCount - type->minCount);
448
449 int x = xo + random->nextInt(cellWidth);
450 int z = zo + random->nextInt(cellHeight);
451 int startX = x, startZ = z;
452
453 for (int c = 0; c < count; c++)
454 {
455 bool success = false;
456 for (int attempts = 0; !success && attempts < 4; attempts++)
457 {
458 // these mobs always spawn at the topmost position
459 int y = level->getTopSolidBlock(x, z);
460 if (isSpawnPositionOk(MobCategory::creature, level, x, y, z))
461 {
462
463 float xx = x + 0.5f;
464 float yy = (float)y;
465 float zz = z + 0.5f;
466
467 shared_ptr<Mob> mob;
468 //try {
469 mob = dynamic_pointer_cast<Mob>( EntityIO::newByEnumType(type->mobClass, level ) );
470 //} catch (Exception e) {
471 // e.printStackTrace();
472 // continue;
473 //}
474
475 // System.out.println("Placing night mob");
476 mob->moveTo(xx, yy, zz, random->nextFloat() * 360, 0);
477
478 mob->setDespawnProtected();
479
480 level->addEntity(mob);
481 groupData = mob->finalizeMobSpawn(groupData);
482 success = true;
483 }
484
485 x += random->nextInt(5) - random->nextInt(5);
486 z += random->nextInt(5) - random->nextInt(5);
487 while (x < xo || x >= (xo + cellWidth) || z < zo || z >= (zo + cellWidth))
488 {
489 x = startX + random->nextInt(5) - random->nextInt(5);
490 z = startZ + random->nextInt(5) - random->nextInt(5);
491 }
492 }
493 }
494 }
495#endif
496}