mirror of
https://github.com/4jcraft/4jcraft.git
synced 2026-04-25 12:03:39 +00:00
1442 lines
53 KiB
C++
1442 lines
53 KiB
C++
#include "../Platform/stdafx.h"
|
|
#include "ServerLevel.h"
|
|
#include "../MinecraftServer.h"
|
|
#include "../Network/ServerChunkCache.h"
|
|
#include "../Network/PlayerList.h"
|
|
#include "../Player/ServerPlayer.h"
|
|
#include "../Network/PlayerConnection.h"
|
|
#include "../Player/EntityTracker.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.dimension.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.storage.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.chunk.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.tile.entity.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.biome.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.entity.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.entity.ai.village.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.entity.player.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.entity.npc.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.entity.global.h"
|
|
#include "../../Minecraft.World/Entities/ItemEntity.h"
|
|
#include "../../Minecraft.World/Entities/Mobs/Arrow.h"
|
|
#include "../../Minecraft.World/Entities/Mobs/PrimedTnt.h"
|
|
#include "../../Minecraft.World/Blocks/FallingTile.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.network.packet.h"
|
|
#include "../../Minecraft.World/Util/Mth.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.levelgen.feature.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.item.h"
|
|
#include "../../Minecraft.World/WorldGen/Structures/StructurePiece.h"
|
|
#include "ServerLevelListener.h"
|
|
#include "../../Minecraft.World/Util/WeighedTreasure.h"
|
|
#include "../Textures/Packs/TexturePackRepository.h"
|
|
#include "../Textures/Packs/DLCTexturePack.h"
|
|
#include "../../Minecraft.World/Util/ProgressListener.h"
|
|
#include "../Platform/PS3/PS3Extras/ShutdownManager.h"
|
|
#include "../Network/PlayerChunkMap.h"
|
|
|
|
WeighedTreasureArray ServerLevel::RANDOM_BONUS_ITEMS;
|
|
|
|
C4JThread* ServerLevel::m_updateThread = NULL;
|
|
C4JThread::EventArray* ServerLevel::m_updateTrigger;
|
|
CRITICAL_SECTION ServerLevel::m_updateCS[3];
|
|
|
|
Level* ServerLevel::m_level[3];
|
|
int ServerLevel::m_updateChunkX[3][LEVEL_CHUNKS_TO_UPDATE_MAX];
|
|
int ServerLevel::m_updateChunkZ[3][LEVEL_CHUNKS_TO_UPDATE_MAX];
|
|
int ServerLevel::m_updateChunkCount[3];
|
|
int ServerLevel::m_updateTileX[3][MAX_UPDATES];
|
|
int ServerLevel::m_updateTileY[3][MAX_UPDATES];
|
|
int ServerLevel::m_updateTileZ[3][MAX_UPDATES];
|
|
int ServerLevel::m_updateTileCount[3];
|
|
int ServerLevel::m_randValue[3];
|
|
|
|
void ServerLevel::staticCtor() {
|
|
m_updateTrigger = new C4JThread::EventArray(3);
|
|
InitializeCriticalSection(&m_updateCS[0]);
|
|
InitializeCriticalSection(&m_updateCS[1]);
|
|
InitializeCriticalSection(&m_updateCS[2]);
|
|
|
|
m_updateThread = new C4JThread(runUpdate, NULL, "Tile update");
|
|
m_updateThread->SetProcessor(CPU_CORE_TILE_UPDATE);
|
|
#ifdef __ORBIS__
|
|
m_updateThread->SetPriority(
|
|
THREAD_PRIORITY_BELOW_NORMAL); // On Orbis, this core is also used for
|
|
// Matching 2, and that priority of that
|
|
// seems to be always at default no
|
|
// matter what we set it to. Prioritise
|
|
// this below Matching 2.
|
|
#endif
|
|
m_updateThread->Run();
|
|
|
|
RANDOM_BONUS_ITEMS = WeighedTreasureArray(20);
|
|
|
|
RANDOM_BONUS_ITEMS[0] = new WeighedTreasure(Item::stick_Id, 0, 1, 3, 10);
|
|
RANDOM_BONUS_ITEMS[1] = new WeighedTreasure(Tile::wood_Id, 0, 1, 3, 10);
|
|
RANDOM_BONUS_ITEMS[2] =
|
|
new WeighedTreasure(Tile::treeTrunk_Id, 0, 1, 3, 10);
|
|
RANDOM_BONUS_ITEMS[3] =
|
|
new WeighedTreasure(Item::hatchet_stone_Id, 0, 1, 1, 3);
|
|
RANDOM_BONUS_ITEMS[4] =
|
|
new WeighedTreasure(Item::hatchet_wood_Id, 0, 1, 1, 5);
|
|
RANDOM_BONUS_ITEMS[5] =
|
|
new WeighedTreasure(Item::pickAxe_stone_Id, 0, 1, 1, 3);
|
|
RANDOM_BONUS_ITEMS[6] =
|
|
new WeighedTreasure(Item::pickAxe_wood_Id, 0, 1, 1, 5);
|
|
RANDOM_BONUS_ITEMS[7] = new WeighedTreasure(Item::apple_Id, 0, 2, 3, 5);
|
|
RANDOM_BONUS_ITEMS[8] = new WeighedTreasure(Item::bread_Id, 0, 2, 3, 3);
|
|
// 4J-PB - new items
|
|
RANDOM_BONUS_ITEMS[9] = new WeighedTreasure(Tile::sapling_Id, 0, 4, 4, 2);
|
|
RANDOM_BONUS_ITEMS[10] = new WeighedTreasure(Tile::sapling_Id, 1, 4, 4, 2);
|
|
RANDOM_BONUS_ITEMS[11] = new WeighedTreasure(Tile::sapling_Id, 2, 4, 4, 2);
|
|
RANDOM_BONUS_ITEMS[12] = new WeighedTreasure(Tile::sapling_Id, 3, 4, 4, 4);
|
|
RANDOM_BONUS_ITEMS[13] =
|
|
new WeighedTreasure(Item::seeds_melon_Id, 0, 1, 2, 3);
|
|
RANDOM_BONUS_ITEMS[14] =
|
|
new WeighedTreasure(Item::seeds_pumpkin_Id, 0, 1, 2, 3);
|
|
RANDOM_BONUS_ITEMS[15] = new WeighedTreasure(Tile::cactus_Id, 0, 1, 2, 3);
|
|
RANDOM_BONUS_ITEMS[16] =
|
|
new WeighedTreasure(Item::dye_powder_Id, DyePowderItem::BROWN, 1, 2, 2);
|
|
RANDOM_BONUS_ITEMS[17] = new WeighedTreasure(Item::potato_Id, 0, 1, 2, 3);
|
|
RANDOM_BONUS_ITEMS[18] = new WeighedTreasure(Item::carrots_Id, 0, 1, 2, 3);
|
|
RANDOM_BONUS_ITEMS[19] =
|
|
new WeighedTreasure(Tile::mushroom1_Id, 0, 1, 2, 2);
|
|
};
|
|
|
|
ServerLevel::ServerLevel(MinecraftServer* server,
|
|
std::shared_ptr<LevelStorage> levelStorage,
|
|
const std::wstring& levelName, int dimension,
|
|
LevelSettings* levelSettings)
|
|
: Level(levelStorage, levelName, levelSettings,
|
|
Dimension::getNew(dimension), false) {
|
|
InitializeCriticalSection(&m_limiterCS);
|
|
InitializeCriticalSection(&m_tickNextTickCS);
|
|
InitializeCriticalSection(&m_csQueueSendTileUpdates);
|
|
m_fallingTileCount = 0;
|
|
m_primedTntCount = 0;
|
|
|
|
// 4J - this this used to be called in parent ctor via a virtual fn
|
|
chunkSource = createChunkSource();
|
|
// 4J - optimisation - keep direct reference of underlying cache here
|
|
chunkSourceCache = chunkSource->getCache();
|
|
chunkSourceXZSize = chunkSource->m_XZSize;
|
|
|
|
// 4J - The listener used to be added in MinecraftServer::loadLevel but we
|
|
// need it to be set up before we do the next couple of things, or else
|
|
// chunks get loaded before we have the entity tracker set up to listen to
|
|
// them
|
|
this->server = server;
|
|
server->setLevel(dimension,
|
|
this); // The listener needs the server to have the level
|
|
// set up... this will be set up anyway on return
|
|
// of this ctor but setting up early here
|
|
addListener(new ServerLevelListener(server, this));
|
|
|
|
this->tracker = new EntityTracker(this);
|
|
this->chunkMap = new PlayerChunkMap(
|
|
this, dimension, server->getPlayers()->getViewDistance());
|
|
|
|
// This also used to be called in parent ctor, but can't be called until
|
|
// chunkSource is created. Call now if required.
|
|
if (!levelData->isInitialized()) {
|
|
initializeLevel(levelSettings);
|
|
levelData->setInitialized(true);
|
|
} else if ((dimension == 0) &&
|
|
levelData->getSpawnBonusChest()) // 4J-JEV, still would like
|
|
// bonus chests to respawn.
|
|
{
|
|
// 4J - added isFindingSpawn as we want any chunks we are looking in
|
|
// here for suitable locations for the bonus chest to actually create
|
|
// those chunks rather than just get emptychunks if they aren't loaded
|
|
isFindingSpawn = true;
|
|
generateBonusItemsNearSpawn();
|
|
isFindingSpawn = false;
|
|
}
|
|
|
|
// 4J - added initialisers
|
|
// 4J Stu - Allowing spawn edit for our game, and consider a better solution
|
|
// for the possible griefing
|
|
canEditSpawn = true; // false;
|
|
noSave = false;
|
|
allPlayersSleeping = false;
|
|
m_bAtLeastOnePlayerSleeping = false;
|
|
emptyTime = 0;
|
|
activeTileEventsList = 0;
|
|
|
|
#ifdef _LARGE_WORLDS
|
|
saveInterval = 3;
|
|
#else
|
|
saveInterval = 20 * 2;
|
|
#endif
|
|
}
|
|
|
|
ServerLevel::~ServerLevel() {
|
|
EnterCriticalSection(&m_csQueueSendTileUpdates);
|
|
for (AUTO_VAR(it, m_queuedSendTileUpdates.begin());
|
|
it != m_queuedSendTileUpdates.end(); ++it) {
|
|
Pos* p = *it;
|
|
delete p;
|
|
}
|
|
m_queuedSendTileUpdates.clear();
|
|
|
|
delete this->tracker; // MGH - added, we were losing about 500K going in
|
|
// and out the menus
|
|
delete this->chunkMap;
|
|
|
|
LeaveCriticalSection(&m_csQueueSendTileUpdates);
|
|
DeleteCriticalSection(&m_csQueueSendTileUpdates);
|
|
DeleteCriticalSection(&m_limiterCS);
|
|
DeleteCriticalSection(&m_tickNextTickCS);
|
|
|
|
// Make sure that the update thread isn't actually doing any updating
|
|
EnterCriticalSection(&m_updateCS[0]);
|
|
LeaveCriticalSection(&m_updateCS[0]);
|
|
EnterCriticalSection(&m_updateCS[1]);
|
|
LeaveCriticalSection(&m_updateCS[1]);
|
|
EnterCriticalSection(&m_updateCS[2]);
|
|
LeaveCriticalSection(&m_updateCS[2]);
|
|
m_updateTrigger->ClearAll();
|
|
}
|
|
|
|
void ServerLevel::tick() {
|
|
Level::tick();
|
|
if (getLevelData()->isHardcore() && difficulty < 3) {
|
|
difficulty = 3;
|
|
}
|
|
|
|
dimension->biomeSource->update();
|
|
|
|
if (allPlayersAreSleeping()) {
|
|
bool somebodyWokeUp = false;
|
|
if (spawnEnemies && difficulty >= Difficulty::EASY) {
|
|
}
|
|
|
|
if (!somebodyWokeUp) {
|
|
// skip time until new day
|
|
__int64 newTime = levelData->getTime() + TICKS_PER_DAY;
|
|
|
|
// 4J : WESTY : Changed so that time update goes through stats
|
|
// tracking update code.
|
|
// levelData->setTime(newTime - (newTime % TICKS_PER_DAY));
|
|
setTime(newTime - (newTime % TICKS_PER_DAY));
|
|
|
|
awakenAllPlayers();
|
|
}
|
|
}
|
|
|
|
PIXBeginNamedEvent(0, "Mob spawner tick");
|
|
// for Minecraft 1.8, spawn friendlies really rarely - 4J - altered
|
|
// from once every 400 ticks to 40 ticks as we depend on this a more than
|
|
// the original since we don't have chunk post-process spawning
|
|
MobSpawner::tick(this, spawnEnemies,
|
|
spawnFriendlies && (levelData->getTime() % 40) == 0);
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Chunk source tick");
|
|
chunkSource->tick();
|
|
PIXEndNamedEvent();
|
|
int newDark = this->getOldSkyDarken(1);
|
|
if (newDark != skyDarken) {
|
|
skyDarken = newDark;
|
|
if (!SharedConstants::TEXTURE_LIGHTING) // 4J - change brought forward
|
|
// from 1.8.2
|
|
{
|
|
AUTO_VAR(itEnd, listeners.end());
|
|
for (AUTO_VAR(it, listeners.begin()); it != itEnd; it++) {
|
|
(*it)->skyColorChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
PIXBeginNamedEvent(0, "runTileEvents");
|
|
// run after entity updates
|
|
runTileEvents();
|
|
PIXEndNamedEvent();
|
|
|
|
// 4J - temporarily disabling saves as they are causing gameplay to
|
|
// generally stutter quite a lot
|
|
|
|
__int64 time = levelData->getTime() + 1;
|
|
// 4J Stu - Putting this back in, but I have reduced the number of chunks that
|
|
// save when not forced
|
|
#ifdef _LARGE_WORLDS
|
|
if (time % (saveInterval) == (dimension->id + 1))
|
|
#else
|
|
if (time % (saveInterval) ==
|
|
(dimension->id * dimension->id * (saveInterval / 2)))
|
|
#endif
|
|
{
|
|
// app.DebugPrintf("Incremental save\n");
|
|
PIXBeginNamedEvent(0, "Incremental save");
|
|
save(false, NULL);
|
|
PIXEndNamedEvent();
|
|
}
|
|
|
|
// 4J : WESTY : Changed so that time update goes through stats tracking
|
|
// update code.
|
|
// levelData->setTime(time);
|
|
setTime(time);
|
|
|
|
PIXBeginNamedEvent(0, "Tick pending ticks");
|
|
// if (tickCount % 5 == 0) {
|
|
tickPendingTicks(false);
|
|
PIXEndNamedEvent();
|
|
|
|
PIXBeginNamedEvent(0, "Tick tiles");
|
|
MemSect(18);
|
|
tickTiles();
|
|
MemSect(0);
|
|
PIXEndNamedEvent();
|
|
|
|
chunkMap->tick();
|
|
|
|
PIXBeginNamedEvent(0, "Tick villages");
|
|
// MemSect(18);
|
|
villages->tick();
|
|
villageSiege->tick();
|
|
// MemSect(0);
|
|
PIXEndNamedEvent();
|
|
|
|
// repeat after tile ticks
|
|
runTileEvents();
|
|
|
|
// 4J Added
|
|
runQueuedSendTileUpdates();
|
|
}
|
|
|
|
Biome::MobSpawnerData* ServerLevel::getRandomMobSpawnAt(
|
|
MobCategory* mobCategory, int x, int y, int z) {
|
|
std::vector<Biome::MobSpawnerData*>* mobList =
|
|
getChunkSource()->getMobsAt(mobCategory, x, y, z);
|
|
if (mobList == NULL || mobList->empty()) return NULL;
|
|
|
|
return (Biome::MobSpawnerData*)WeighedRandom::getRandomItem(
|
|
random, (std::vector<WeighedRandomItem*>*)mobList);
|
|
}
|
|
|
|
void ServerLevel::updateSleepingPlayerList() {
|
|
allPlayersSleeping = !players.empty();
|
|
m_bAtLeastOnePlayerSleeping = false;
|
|
|
|
AUTO_VAR(itEnd, players.end());
|
|
for (std::vector<std::shared_ptr<Player> >::iterator it = players.begin();
|
|
it != itEnd; it++) {
|
|
if (!(*it)->isSleeping()) {
|
|
allPlayersSleeping = false;
|
|
// break;
|
|
} else {
|
|
m_bAtLeastOnePlayerSleeping = true;
|
|
}
|
|
if (m_bAtLeastOnePlayerSleeping && !allPlayersSleeping) break;
|
|
}
|
|
}
|
|
|
|
void ServerLevel::awakenAllPlayers() {
|
|
allPlayersSleeping = false;
|
|
m_bAtLeastOnePlayerSleeping = false;
|
|
|
|
AUTO_VAR(itEnd, players.end());
|
|
for (std::vector<std::shared_ptr<Player> >::iterator it = players.begin();
|
|
it != itEnd; it++) {
|
|
if ((*it)->isSleeping()) {
|
|
(*it)->stopSleepInBed(false, false, true);
|
|
}
|
|
}
|
|
|
|
stopWeather();
|
|
}
|
|
|
|
void ServerLevel::stopWeather() {
|
|
levelData->setRainTime(0);
|
|
levelData->setRaining(false);
|
|
levelData->setThunderTime(0);
|
|
levelData->setThundering(false);
|
|
}
|
|
|
|
bool ServerLevel::allPlayersAreSleeping() {
|
|
if (allPlayersSleeping && !isClientSide) {
|
|
// all players are sleeping, but have they slept long enough?
|
|
AUTO_VAR(itEnd, players.end());
|
|
for (std::vector<std::shared_ptr<Player> >::iterator it =
|
|
players.begin();
|
|
it != itEnd; it++) {
|
|
// System.out.println(player->entityId + ": " +
|
|
// player->getSleepTimer());
|
|
if (!(*it)->isSleepingLongEnough()) {
|
|
return false;
|
|
}
|
|
}
|
|
// yep
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ServerLevel::validateSpawn() {
|
|
if (levelData->getYSpawn() <= 0) {
|
|
levelData->setYSpawn(genDepth / 2);
|
|
}
|
|
int xSpawn = levelData->getXSpawn();
|
|
int zSpawn = levelData->getZSpawn();
|
|
int tries = 0;
|
|
while (getTopTile(xSpawn, zSpawn) == 0) {
|
|
xSpawn += random->nextInt(8) - random->nextInt(8);
|
|
zSpawn += random->nextInt(8) - random->nextInt(8);
|
|
if (++tries == 10000) break;
|
|
}
|
|
levelData->setXSpawn(xSpawn);
|
|
levelData->setZSpawn(zSpawn);
|
|
}
|
|
|
|
// 4J - Changes made here to move a section of code (which randomly determines
|
|
// which tiles in the current chunks to tick, and is very cache unfriendly by
|
|
// nature) This code now has a thread of its own so it can wait all it wants on
|
|
// the cache without holding the main game thread up. This slightly changes how
|
|
// things are processed, as we now tick the tiles that were determined in the
|
|
// previous tick. Have also limited the amount of tiles to be ticked to 256 (it
|
|
// never seemed to creep up much beyond this in normal play anyway, and we need
|
|
// some finite limit).
|
|
void ServerLevel::tickTiles() {
|
|
// Index into the arrays used by the update thread
|
|
int iLev = 0;
|
|
if (dimension->id == -1) {
|
|
iLev = 1;
|
|
} else if (dimension->id == 1) {
|
|
iLev = 2;
|
|
}
|
|
chunksToPoll.clear();
|
|
|
|
unsigned int tickCount = 0;
|
|
|
|
EnterCriticalSection(&m_updateCS[iLev]);
|
|
// This section processes the tiles that need to be ticked, which we worked
|
|
// out in the previous tick (or haven't yet, if this is the first frame)
|
|
/*int grassTicks = 0;
|
|
int lavaTicks = 0;
|
|
int otherTicks = 0;*/
|
|
for (int i = 0; i < m_updateTileCount[iLev]; i++) {
|
|
int x = m_updateTileX[iLev][i];
|
|
int y = m_updateTileY[iLev][i];
|
|
int z = m_updateTileZ[iLev][i];
|
|
if (hasChunkAt(x, y, z)) {
|
|
int id = getTile(x, y, z);
|
|
if (Tile::tiles[id] != NULL && Tile::tiles[id]->isTicking()) {
|
|
/*if(id == 2) ++grassTicks;
|
|
else if(id == 11) ++lavaTicks;
|
|
else ++otherTicks;*/
|
|
Tile::tiles[id]->tick(this, x, y, z, random);
|
|
}
|
|
}
|
|
}
|
|
// printf("Total ticks - Grass: %d, Lava: %d, Other: %d, Total: %d\n",
|
|
// grassTicks, lavaTicks, otherTicks, grassTicks + lavaTicks + otherTicks);
|
|
m_updateTileCount[iLev] = 0;
|
|
m_updateChunkCount[iLev] = 0;
|
|
LeaveCriticalSection(&m_updateCS[iLev]);
|
|
|
|
Level::tickTiles();
|
|
|
|
// AP moved this outside of the loop
|
|
int prob = 100000;
|
|
if (app.GetGameSettingsDebugMask() & (1L << eDebugSetting_RegularLightning))
|
|
prob = 100;
|
|
|
|
#ifdef __PSVITA__
|
|
// AP - see CustomSet.h for and explanation
|
|
for (int i = 0; i < chunksToPoll.end(); i += 1) {
|
|
ChunkPos cp = chunksToPoll.get(i);
|
|
#else
|
|
AUTO_VAR(itEndCtp, chunksToPoll.end());
|
|
for (AUTO_VAR(it, chunksToPoll.begin()); it != itEndCtp; it++) {
|
|
ChunkPos cp = *it;
|
|
#endif
|
|
int xo = cp.x * 16;
|
|
int zo = cp.z * 16;
|
|
|
|
// 4J added - don't let this actually load/create any chunks, we'll let
|
|
// the normal updateDirtyChunks etc. processes do that, so it can happen
|
|
// on another thread
|
|
if (!this->hasChunk(cp.x, cp.z)) continue;
|
|
|
|
// 4J Stu - When adding a 5th player to the game, the number of
|
|
// chunksToPoll is greater than the size of the m_updateChunkX &
|
|
// m_updateChunkZ arrays (19*19*4 at time of writing). It doesn't seem
|
|
// like there should ever be that many chunks needing polled, so this
|
|
// needs looked at in more detail. For now I have enlarged the size of
|
|
// the array to 19*19*8 but this seems way to big for our needs.
|
|
|
|
// The cause of this is largely because the chunksToPoll vector does not
|
|
// enforce unique elements The java version used a HashSet which would,
|
|
// although if our world size gets a lot larger then we may have no
|
|
// overlaps of players surrounding chunks
|
|
// assert(false);
|
|
|
|
// If you hit this assert, then a memory overwrite will occur when you
|
|
// continue
|
|
assert(m_updateChunkCount[iLev] < LEVEL_CHUNKS_TO_UPDATE_MAX);
|
|
|
|
m_updateChunkX[iLev][m_updateChunkCount[iLev]] = cp.x;
|
|
m_updateChunkZ[iLev][m_updateChunkCount[iLev]++] = cp.z;
|
|
|
|
LevelChunk* lc = this->getChunk(cp.x, cp.z);
|
|
tickClientSideTiles(xo, zo, lc);
|
|
|
|
if (random->nextInt(prob) == 0 && isRaining() && isThundering()) {
|
|
randValue = randValue * 3 + addend;
|
|
int val = (randValue >> 2);
|
|
int x = xo + (val & 15);
|
|
int z = zo + ((val >> 8) & 15);
|
|
int y = getTopRainBlock(x, z);
|
|
|
|
if (isRainingAt(x, y, z)) {
|
|
addGlobalEntity(std::shared_ptr<LightningBolt>(
|
|
new LightningBolt(this, x, y, z)));
|
|
lightningTime = 2;
|
|
}
|
|
}
|
|
|
|
// 4J - changes here brought forrward from 1.2.3
|
|
if (random->nextInt(16) == 0) {
|
|
// 4jcraft added cast to unsigned
|
|
randValue = (unsigned)randValue * 3 + (unsigned)addend;
|
|
int val = (randValue >> 2);
|
|
int x = (val & 15);
|
|
int z = ((val >> 8) & 15);
|
|
int yy = this->getTopRainBlock(x + xo, z + zo);
|
|
if (shouldFreeze(x + xo, yy - 1, z + zo)) {
|
|
setTile(x + xo, yy - 1, z + zo, Tile::ice_Id);
|
|
}
|
|
if (isRaining() && shouldSnow(x + xo, yy, z + zo)) {
|
|
setTile(x + xo, yy, z + zo, Tile::topSnow_Id);
|
|
}
|
|
if (isRaining()) {
|
|
Biome* b = getBiome(x + xo, z + zo);
|
|
if (b->hasRain()) {
|
|
int tile = getTile(x + xo, yy - 1, z + zo);
|
|
if (tile != 0) {
|
|
Tile::tiles[tile]->handleRain(this, x + xo, yy - 1,
|
|
z + zo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4J - lighting change brought forward from 1.8.2
|
|
checkLight(xo + random->nextInt(16), random->nextInt(128),
|
|
zo + random->nextInt(16));
|
|
}
|
|
|
|
m_level[iLev] = this;
|
|
m_randValue[iLev] = randValue;
|
|
// We've set up everything that the udpate thread needs, so kick it off
|
|
m_updateTrigger->Set(iLev);
|
|
}
|
|
|
|
void ServerLevel::addToTickNextTick(int x, int y, int z, int tileId,
|
|
int tickDelay) {
|
|
MemSect(27);
|
|
TickNextTickData td = TickNextTickData(x, y, z, tileId);
|
|
int r = 8;
|
|
if (getInstaTick()) {
|
|
if (hasChunksAt(td.x - r, td.y - r, td.z - r, td.x + r, td.y + r,
|
|
td.z + r)) {
|
|
int id = getTile(td.x, td.y, td.z);
|
|
if (id == td.tileId && id > 0) {
|
|
Tile::tiles[id]->tick(this, td.x, td.y, td.z, random);
|
|
}
|
|
}
|
|
MemSect(0);
|
|
return;
|
|
}
|
|
|
|
if (hasChunksAt(x - r, y - r, z - r, x + r, y + r, z + r)) {
|
|
if (tileId > 0) {
|
|
td.delay(tickDelay + levelData->getTime());
|
|
}
|
|
EnterCriticalSection(&m_tickNextTickCS);
|
|
if (tickNextTickSet.find(td) == tickNextTickSet.end()) {
|
|
tickNextTickSet.insert(td);
|
|
tickNextTickList.insert(td);
|
|
}
|
|
LeaveCriticalSection(&m_tickNextTickCS);
|
|
}
|
|
MemSect(0);
|
|
}
|
|
|
|
void ServerLevel::forceAddTileTick(int x, int y, int z, int tileId,
|
|
int tickDelay) {
|
|
TickNextTickData td = TickNextTickData(x, y, z, tileId);
|
|
|
|
if (tileId > 0) {
|
|
td.delay(tickDelay + levelData->getTime());
|
|
}
|
|
EnterCriticalSection(&m_tickNextTickCS);
|
|
if (tickNextTickSet.find(td) == tickNextTickSet.end()) {
|
|
tickNextTickSet.insert(td);
|
|
tickNextTickList.insert(td);
|
|
}
|
|
LeaveCriticalSection(&m_tickNextTickCS);
|
|
}
|
|
|
|
void ServerLevel::tickEntities() {
|
|
if (players.empty()) {
|
|
if (emptyTime++ >= EMPTY_TIME_NO_TICK) {
|
|
return;
|
|
}
|
|
} else {
|
|
emptyTime = 0;
|
|
}
|
|
|
|
Level::tickEntities();
|
|
}
|
|
|
|
bool ServerLevel::tickPendingTicks(bool force) {
|
|
EnterCriticalSection(&m_tickNextTickCS);
|
|
int count = (int)tickNextTickList.size();
|
|
int count2 = (int)tickNextTickSet.size();
|
|
if (count != tickNextTickSet.size()) {
|
|
// TODO 4J Stu - Add new exception types
|
|
// throw new IllegalStateException("TickNextTick list out of synch");
|
|
}
|
|
if (count > MAX_TICK_TILES_PER_TICK) count = MAX_TICK_TILES_PER_TICK;
|
|
|
|
AUTO_VAR(itTickList, tickNextTickList.begin());
|
|
for (int i = 0; i < count; i++) {
|
|
TickNextTickData td = *(itTickList);
|
|
if (!force && td.m_delay > levelData->getTime()) {
|
|
break;
|
|
}
|
|
|
|
itTickList = tickNextTickList.erase(itTickList);
|
|
tickNextTickSet.erase(td);
|
|
int r = 8;
|
|
if (hasChunksAt(td.x - r, td.y - r, td.z - r, td.x + r, td.y + r,
|
|
td.z + r)) {
|
|
int id = getTile(td.x, td.y, td.z);
|
|
if (id == td.tileId && id > 0) {
|
|
Tile::tiles[id]->tick(this, td.x, td.y, td.z, random);
|
|
}
|
|
}
|
|
}
|
|
|
|
int count3 = (int)tickNextTickList.size();
|
|
int count4 = (int)tickNextTickSet.size();
|
|
|
|
bool retval = tickNextTickList.size() != 0;
|
|
LeaveCriticalSection(&m_tickNextTickCS);
|
|
|
|
return retval;
|
|
}
|
|
|
|
std::vector<TickNextTickData>* ServerLevel::fetchTicksInChunk(LevelChunk* chunk,
|
|
bool remove) {
|
|
EnterCriticalSection(&m_tickNextTickCS);
|
|
std::vector<TickNextTickData>* results = new std::vector<TickNextTickData>;
|
|
|
|
ChunkPos* pos = chunk->getPos();
|
|
// 4jcraft added cast to unsigned
|
|
int west = (unsigned)pos->x << 4;
|
|
int east = west + 16;
|
|
int north = (unsigned)pos->z << 4;
|
|
int south = north + 16;
|
|
delete pos;
|
|
|
|
for (AUTO_VAR(it, tickNextTickSet.begin()); it != tickNextTickSet.end();) {
|
|
TickNextTickData td = *it;
|
|
|
|
if (td.x >= west && td.x < east && td.z >= north && td.z < south) {
|
|
if (remove) {
|
|
tickNextTickList.erase(td);
|
|
it = tickNextTickSet.erase(it);
|
|
} else {
|
|
it++;
|
|
}
|
|
|
|
results->push_back(td);
|
|
} else {
|
|
it++;
|
|
}
|
|
}
|
|
|
|
LeaveCriticalSection(&m_tickNextTickCS);
|
|
return results;
|
|
}
|
|
|
|
void ServerLevel::tick(std::shared_ptr<Entity> e, bool actual) {
|
|
if (!server->isAnimals() &&
|
|
((e->GetType() & eTYPE_ANIMAL) || (e->GetType() & eTYPE_WATERANIMAL))) {
|
|
e->remove();
|
|
}
|
|
if (!server->isNpcsEnabled() &&
|
|
(std::dynamic_pointer_cast<Npc>(e) != NULL)) {
|
|
e->remove();
|
|
}
|
|
if (e->rider.lock() == NULL ||
|
|
(std::dynamic_pointer_cast<Player>(e->rider.lock()) ==
|
|
NULL)) // 4J - was !(e->rider instanceof Player)
|
|
{
|
|
Level::tick(e, actual);
|
|
}
|
|
}
|
|
|
|
void ServerLevel::forceTick(std::shared_ptr<Entity> e, bool actual) {
|
|
Level::tick(e, actual);
|
|
}
|
|
|
|
ChunkSource* ServerLevel::createChunkSource() {
|
|
ChunkStorage* storage = levelStorage->createChunkStorage(dimension);
|
|
cache = new ServerChunkCache(this, storage,
|
|
dimension->createRandomLevelSource());
|
|
return cache;
|
|
}
|
|
|
|
std::vector<std::shared_ptr<TileEntity> >* ServerLevel::getTileEntitiesInRegion(
|
|
int x0, int y0, int z0, int x1, int y1, int z1) {
|
|
std::vector<std::shared_ptr<TileEntity> >* result =
|
|
new std::vector<std::shared_ptr<TileEntity> >;
|
|
for (unsigned int i = 0; i < tileEntityList.size(); i++) {
|
|
std::shared_ptr<TileEntity> te = tileEntityList[i];
|
|
if (te->x >= x0 && te->y >= y0 && te->z >= z0 && te->x < x1 &&
|
|
te->y < y1 && te->z < z1) {
|
|
result->push_back(te);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool ServerLevel::mayInteract(std::shared_ptr<Player> player, int xt, int yt,
|
|
int zt, int content) {
|
|
// 4J-PB - This will look like a bug to players, and we really should have a
|
|
// message to explain why we're not allowing lava to be placed at or near a
|
|
// spawn point We'll need to do this in a future update
|
|
|
|
// 4J-PB - Let's allow water near the spawn point, but not lava
|
|
if (content != Tile::lava_Id) {
|
|
// allow this to be used
|
|
return true;
|
|
} else if (dimension->id == 0) // 4J Stu - Only limit this in the overworld
|
|
{
|
|
int xd = (int)Mth::abs((float)(xt - levelData->getXSpawn()));
|
|
int zd = (int)Mth::abs((float)(zt - levelData->getZSpawn()));
|
|
if (xd > zd) zd = xd;
|
|
return (zd > 16 || server->getPlayers()->isOp(player->name));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ServerLevel::initializeLevel(LevelSettings* settings) {
|
|
setInitialSpawn(settings);
|
|
|
|
Level::initializeLevel(settings);
|
|
}
|
|
|
|
/**
|
|
* Sets the initial spawn, created this method so we could do a special
|
|
* location for the demo version.
|
|
*/
|
|
void ServerLevel::setInitialSpawn(LevelSettings* levelSettings) {
|
|
if (!dimension->mayRespawn()) {
|
|
levelData->setSpawn(0, dimension->getSpawnYPosition(), 0);
|
|
return;
|
|
}
|
|
|
|
isFindingSpawn = true;
|
|
|
|
BiomeSource* biomeSource = dimension->biomeSource;
|
|
std::vector<Biome*> playerSpawnBiomes = biomeSource->getPlayerSpawnBiomes();
|
|
Random random(getSeed());
|
|
|
|
TilePos* findBiome =
|
|
biomeSource->findBiome(0, 0, 16 * 16, playerSpawnBiomes, &random);
|
|
|
|
int xSpawn = 0; // (Level.MAX_LEVEL_SIZE - 100) * 0;
|
|
int ySpawn = dimension->getSpawnYPosition();
|
|
int zSpawn = 0; // (Level.MAX_LEVEL_SIZE - 100) * 0;
|
|
int minXZ = -(dimension->getXZSize() * 16) / 2;
|
|
int maxXZ = (dimension->getXZSize() * 16) / 2 - 1;
|
|
|
|
if (findBiome != NULL) {
|
|
xSpawn = findBiome->x;
|
|
zSpawn = findBiome->z;
|
|
delete findBiome;
|
|
} else {
|
|
app.DebugPrintf(
|
|
"Level::setInitialSpawn - Unable to find spawn biome\n");
|
|
}
|
|
|
|
int tries = 0;
|
|
|
|
while (!dimension->isValidSpawn(xSpawn, zSpawn)) {
|
|
// 4J-PB changed to stay within our level limits
|
|
xSpawn += random.nextInt(64) - random.nextInt(64);
|
|
if (xSpawn > maxXZ) xSpawn = 0;
|
|
if (xSpawn < minXZ) xSpawn = 0;
|
|
zSpawn += random.nextInt(64) - random.nextInt(64);
|
|
if (zSpawn > maxXZ) zSpawn = 0;
|
|
if (zSpawn < minXZ) zSpawn = 0;
|
|
|
|
if (++tries == 1000) break;
|
|
}
|
|
|
|
levelData->setSpawn(xSpawn, ySpawn, zSpawn);
|
|
if (levelSettings->hasStartingBonusItems()) {
|
|
generateBonusItemsNearSpawn();
|
|
}
|
|
isFindingSpawn = false;
|
|
}
|
|
|
|
// 4J - brought forward from 1.3.2
|
|
void ServerLevel::generateBonusItemsNearSpawn() {
|
|
// once we've found the initial spawn, try to find a location for the
|
|
// starting bonus chest
|
|
// 4J - added - scan the spawn area first to see if there's already a chest
|
|
// near here
|
|
|
|
static const int r = 20;
|
|
int xs = levelData->getXSpawn();
|
|
int zs = levelData->getZSpawn();
|
|
for (int xx = -r; xx <= r; xx++)
|
|
for (int zz = -r; zz <= r; zz++) {
|
|
int x = xx + xs;
|
|
int z = zz + zs;
|
|
int y = getTopSolidBlock(x, z) - 1;
|
|
|
|
if (getTile(x, y, z) == Tile::chest_Id) {
|
|
std::shared_ptr<ChestTileEntity> chest =
|
|
std::dynamic_pointer_cast<ChestTileEntity>(
|
|
getTileEntity(x, y, z));
|
|
if (chest != NULL) {
|
|
if (chest->isBonusChest) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BonusChestFeature* feature = new BonusChestFeature(RANDOM_BONUS_ITEMS, 16);
|
|
for (int attempt = 0; attempt < 16; attempt++) {
|
|
int x =
|
|
levelData->getXSpawn() + random->nextInt(6) - random->nextInt(6);
|
|
int z =
|
|
levelData->getZSpawn() + random->nextInt(6) - random->nextInt(6);
|
|
int y = getTopSolidBlock(x, z) + 1;
|
|
|
|
if (feature->place(this, random, x, y, z, (attempt == 15))) {
|
|
break;
|
|
}
|
|
}
|
|
delete feature;
|
|
}
|
|
|
|
Pos* ServerLevel::getDimensionSpecificSpawn() {
|
|
return dimension->getSpawnPos();
|
|
}
|
|
|
|
// 4j Added for XboxOne PLM
|
|
void ServerLevel::Suspend() {
|
|
if (StorageManager.GetSaveDisabled()) return;
|
|
saveLevelData();
|
|
chunkSource->saveAllEntities();
|
|
}
|
|
|
|
void ServerLevel::save(bool force, ProgressListener* progressListener,
|
|
bool bAutosave) {
|
|
if (!chunkSource->shouldSave()) return;
|
|
|
|
// 4J-PB - check that saves are enabled
|
|
if (StorageManager.GetSaveDisabled()) return;
|
|
|
|
if (progressListener != NULL) {
|
|
if (bAutosave) {
|
|
progressListener->progressStartNoAbort(
|
|
IDS_PROGRESS_AUTOSAVING_LEVEL);
|
|
} else {
|
|
progressListener->progressStartNoAbort(IDS_PROGRESS_SAVING_LEVEL);
|
|
}
|
|
}
|
|
PIXBeginNamedEvent(0, "Saving level data");
|
|
saveLevelData();
|
|
PIXEndNamedEvent();
|
|
|
|
if (progressListener != NULL)
|
|
progressListener->progressStage(IDS_PROGRESS_SAVING_CHUNKS);
|
|
|
|
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
|
// Our autosave is a minimal save. All the chunks are saves by the constant
|
|
// save process
|
|
if (bAutosave) {
|
|
chunkSource->saveAllEntities();
|
|
} else
|
|
#endif
|
|
{
|
|
chunkSource->save(force, progressListener);
|
|
|
|
#ifdef _LARGE_WORLDS
|
|
// 4J Stu - Only do this if there are players in the level
|
|
if (chunkMap->players.size() > 0) {
|
|
// 4J Stu - This will come in a later change anyway
|
|
// clean cache
|
|
std::vector<LevelChunk*>* loadedChunkList =
|
|
cache->getLoadedChunkList();
|
|
for (AUTO_VAR(it, loadedChunkList->begin());
|
|
it != loadedChunkList->end(); ++it) {
|
|
LevelChunk* lc = *it;
|
|
if (!chunkMap->hasChunk(lc->x, lc->z)) {
|
|
cache->drop(lc->x, lc->z);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// if( force && !isClientSide )
|
|
//{
|
|
// if (progressListener != NULL)
|
|
//progressListener->progressStage(IDS_PROGRESS_SAVING_TO_DISC);
|
|
// levelStorage->flushSaveFile();
|
|
// }
|
|
}
|
|
|
|
// 4J Added
|
|
void ServerLevel::saveToDisc(ProgressListener* progressListener,
|
|
bool autosave) {
|
|
// 4J-PB - check that saves are enabled
|
|
if (StorageManager.GetSaveDisabled()) return;
|
|
|
|
// Check if we are using a trial version of a texture pack (which will be
|
|
// the case for going into the mash-up pack world with a trial version)
|
|
if (!Minecraft::GetInstance()->skins->isUsingDefaultSkin()) {
|
|
TexturePack* tPack = Minecraft::GetInstance()->skins->getSelected();
|
|
DLCTexturePack* pDLCTexPack = (DLCTexturePack*)tPack;
|
|
|
|
DLCPack* pDLCPack = pDLCTexPack->getDLCInfoParentPack();
|
|
|
|
if (!pDLCPack->hasPurchasedFile(DLCManager::e_DLCType_Texture, L"")) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (progressListener != NULL)
|
|
progressListener->progressStage(IDS_PROGRESS_SAVING_TO_DISC);
|
|
levelStorage->flushSaveFile(autosave);
|
|
}
|
|
|
|
void ServerLevel::saveLevelData() {
|
|
checkSession();
|
|
|
|
levelStorage->saveLevelData(levelData, &players);
|
|
savedDataStorage->save();
|
|
}
|
|
|
|
void ServerLevel::entityAdded(std::shared_ptr<Entity> e) {
|
|
Level::entityAdded(e);
|
|
entitiesById[e->entityId] = e;
|
|
std::vector<std::shared_ptr<Entity> >* es = e->getSubEntities();
|
|
if (es != NULL) {
|
|
// for (int i = 0; i < es.length; i++)
|
|
for (AUTO_VAR(it, es->begin()); it != es->end(); ++it) {
|
|
entitiesById.insert(
|
|
intEntityMap::value_type((*it)->entityId, (*it)));
|
|
}
|
|
}
|
|
entityAddedExtra(e); // 4J added
|
|
}
|
|
|
|
void ServerLevel::entityRemoved(std::shared_ptr<Entity> e) {
|
|
Level::entityRemoved(e);
|
|
entitiesById.erase(e->entityId);
|
|
std::vector<std::shared_ptr<Entity> >* es = e->getSubEntities();
|
|
if (es != NULL) {
|
|
// for (int i = 0; i < es.length; i++)
|
|
for (AUTO_VAR(it, es->begin()); it != es->end(); ++it) {
|
|
entitiesById.erase((*it)->entityId);
|
|
}
|
|
}
|
|
entityRemovedExtra(e); // 4J added
|
|
}
|
|
|
|
std::shared_ptr<Entity> ServerLevel::getEntity(int id) {
|
|
return entitiesById[id];
|
|
}
|
|
|
|
bool ServerLevel::addGlobalEntity(std::shared_ptr<Entity> e) {
|
|
if (Level::addGlobalEntity(e)) {
|
|
server->getPlayers()->broadcast(e->x, e->y, e->z, 512, dimension->id,
|
|
std::shared_ptr<AddGlobalEntityPacket>(
|
|
new AddGlobalEntityPacket(e)));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ServerLevel::broadcastEntityEvent(std::shared_ptr<Entity> e,
|
|
uint8_t event) {
|
|
std::shared_ptr<Packet> p = std::shared_ptr<EntityEventPacket>(
|
|
new EntityEventPacket(e->entityId, event));
|
|
server->getLevel(dimension->id)->getTracker()->broadcastAndSend(e, p);
|
|
}
|
|
|
|
std::shared_ptr<Explosion> ServerLevel::explode(std::shared_ptr<Entity> source,
|
|
double x, double y, double z,
|
|
float r, bool fire,
|
|
bool destroyBlocks) {
|
|
// instead of calling super, we run the same explosion code here except
|
|
// we don't generate any particles
|
|
std::shared_ptr<Explosion> explosion =
|
|
std::shared_ptr<Explosion>(new Explosion(this, source, x, y, z, r));
|
|
explosion->fire = fire;
|
|
explosion->destroyBlocks = destroyBlocks;
|
|
explosion->explode();
|
|
explosion->finalizeExplosion(false);
|
|
|
|
if (!destroyBlocks) {
|
|
explosion->toBlow.clear();
|
|
}
|
|
|
|
std::vector<std::shared_ptr<ServerPlayer> > sentTo;
|
|
for (AUTO_VAR(it, players.begin()); it != players.end(); ++it) {
|
|
std::shared_ptr<ServerPlayer> player =
|
|
std::dynamic_pointer_cast<ServerPlayer>(*it);
|
|
if (player->dimension != dimension->id) continue;
|
|
|
|
bool knockbackOnly = false;
|
|
if (sentTo.size()) {
|
|
INetworkPlayer* thisPlayer = player->connection->getNetworkPlayer();
|
|
if (thisPlayer == NULL) {
|
|
continue;
|
|
} else {
|
|
for (unsigned int j = 0; j < sentTo.size(); j++) {
|
|
std::shared_ptr<ServerPlayer> player2 = sentTo[j];
|
|
INetworkPlayer* otherPlayer =
|
|
player2->connection->getNetworkPlayer();
|
|
if (otherPlayer != NULL &&
|
|
thisPlayer->IsSameSystem(otherPlayer)) {
|
|
knockbackOnly = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (player->distanceToSqr(x, y, z) < 64 * 64) {
|
|
Vec3* knockbackVec = explosion->getHitPlayerKnockback(player);
|
|
// app.DebugPrintf("Sending %s with knockback (%f,%f,%f)\n",
|
|
// knockbackOnly?"knockbackOnly":"allExplosion",knockbackVec->x,knockbackVec->y,knockbackVec->z);
|
|
// If the player is not the primary on the system, then we only
|
|
// want to send info for the knockback
|
|
player->connection->send(std::shared_ptr<ExplodePacket>(
|
|
new ExplodePacket(x, y, z, r, &explosion->toBlow, knockbackVec,
|
|
knockbackOnly)));
|
|
sentTo.push_back(player);
|
|
}
|
|
}
|
|
|
|
return explosion;
|
|
}
|
|
|
|
void ServerLevel::tileEvent(int x, int y, int z, int tile, int b0, int b1) {
|
|
// super.tileEvent(x, y, z, b0, b1);
|
|
// server.getPlayers().broadcast(x, y, z, 64, dimension.id, new
|
|
// TileEventPacket(x, y, z, b0, b1));
|
|
TileEventData newEvent(x, y, z, tile, b0, b1);
|
|
// for (TileEventData te : tileEvents[activeTileEventsList])
|
|
for (AUTO_VAR(it, tileEvents[activeTileEventsList].begin());
|
|
it != tileEvents[activeTileEventsList].end(); ++it) {
|
|
if ((*it).equals(newEvent)) {
|
|
return;
|
|
}
|
|
}
|
|
tileEvents[activeTileEventsList].push_back(newEvent);
|
|
}
|
|
|
|
void ServerLevel::runTileEvents() {
|
|
// use two lists until both are empty, intended to avoid concurrent
|
|
// modifications
|
|
while (!tileEvents[activeTileEventsList].empty()) {
|
|
int runList = activeTileEventsList;
|
|
activeTileEventsList ^= 1;
|
|
|
|
// for (TileEventData te : tileEvents[runList])
|
|
for (AUTO_VAR(it, tileEvents[runList].begin());
|
|
it != tileEvents[runList].end(); ++it) {
|
|
if (doTileEvent(&(*it))) {
|
|
TileEventData te = *it;
|
|
server->getPlayers()->broadcast(
|
|
te.getX(), te.getY(), te.getZ(), 64, dimension->id,
|
|
std::shared_ptr<TileEventPacket>(new TileEventPacket(
|
|
te.getX(), te.getY(), te.getZ(), te.getTile(),
|
|
te.getParamA(), te.getParamB())));
|
|
}
|
|
}
|
|
tileEvents[runList].clear();
|
|
}
|
|
}
|
|
|
|
bool ServerLevel::doTileEvent(TileEventData* te) {
|
|
int t = getTile(te->getX(), te->getY(), te->getZ());
|
|
if (t == te->getTile()) {
|
|
Tile::tiles[t]->triggerEvent(this, te->getX(), te->getY(), te->getZ(),
|
|
te->getParamA(), te->getParamB());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ServerLevel::closeLevelStorage() { levelStorage->closeAll(); }
|
|
|
|
void ServerLevel::tickWeather() {
|
|
bool wasRaining = isRaining();
|
|
Level::tickWeather();
|
|
|
|
if (wasRaining != isRaining()) {
|
|
if (wasRaining) {
|
|
server->getPlayers()->broadcastAll(std::shared_ptr<GameEventPacket>(
|
|
new GameEventPacket(GameEventPacket::STOP_RAINING, 0)));
|
|
} else {
|
|
server->getPlayers()->broadcastAll(std::shared_ptr<GameEventPacket>(
|
|
new GameEventPacket(GameEventPacket::START_RAINING, 0)));
|
|
}
|
|
}
|
|
}
|
|
|
|
MinecraftServer* ServerLevel::getServer() { return server; }
|
|
|
|
EntityTracker* ServerLevel::getTracker() { return tracker; }
|
|
|
|
void ServerLevel::setTimeAndAdjustTileTicks(__int64 newTime) {
|
|
__int64 delta = newTime - levelData->getTime();
|
|
// 4J - can't directly adjust m_delay in a set as it has a const interator,
|
|
// since changing values in here might change the ordering of the elements
|
|
// in the set. Instead move to a vector, do the adjustment, put back in the
|
|
// set.
|
|
std::vector<TickNextTickData> temp;
|
|
for (AUTO_VAR(it, tickNextTickList.begin()); it != tickNextTickList.end();
|
|
++it) {
|
|
temp.push_back(*it);
|
|
temp.back().m_delay += delta;
|
|
}
|
|
tickNextTickList.clear();
|
|
for (unsigned int i = 0; i < temp.size(); i++) {
|
|
tickNextTickList.insert(temp[i]);
|
|
}
|
|
setTime(newTime);
|
|
}
|
|
|
|
PlayerChunkMap* ServerLevel::getChunkMap() { return chunkMap; }
|
|
|
|
// 4J Stu - Sometimes we want to update tiles on the server from the main thread
|
|
// (eg SignTileEntity when string verify returns)
|
|
void ServerLevel::queueSendTileUpdate(int x, int y, int z) {
|
|
EnterCriticalSection(&m_csQueueSendTileUpdates);
|
|
m_queuedSendTileUpdates.push_back(new Pos(x, y, z));
|
|
LeaveCriticalSection(&m_csQueueSendTileUpdates);
|
|
}
|
|
|
|
void ServerLevel::runQueuedSendTileUpdates() {
|
|
EnterCriticalSection(&m_csQueueSendTileUpdates);
|
|
for (AUTO_VAR(it, m_queuedSendTileUpdates.begin());
|
|
it != m_queuedSendTileUpdates.end(); ++it) {
|
|
Pos* p = *it;
|
|
sendTileUpdated(p->x, p->y, p->z);
|
|
delete p;
|
|
}
|
|
m_queuedSendTileUpdates.clear();
|
|
LeaveCriticalSection(&m_csQueueSendTileUpdates);
|
|
}
|
|
|
|
// 4J - added special versions of addEntity and extra processing on entity
|
|
// removed and added so we can limit the number of itementities created
|
|
bool ServerLevel::addEntity(std::shared_ptr<Entity> e) {
|
|
// If its an item entity, and we've got to our capacity, delete the oldest
|
|
if (std::dynamic_pointer_cast<ItemEntity>(e) != NULL) {
|
|
// printf("Adding item entity count
|
|
//%d\n",m_itemEntities.size());
|
|
EnterCriticalSection(&m_limiterCS);
|
|
if (m_itemEntities.size() >= MAX_ITEM_ENTITIES) {
|
|
// printf("Adding - doing remove\n");
|
|
removeEntityImmediately(m_itemEntities.front());
|
|
}
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
}
|
|
// If its an hanging entity, and we've got to our capacity, delete the
|
|
// oldest
|
|
else if (std::dynamic_pointer_cast<HangingEntity>(e) != NULL) {
|
|
// printf("Adding item entity count
|
|
//%d\n",m_itemEntities.size());
|
|
EnterCriticalSection(&m_limiterCS);
|
|
if (m_hangingEntities.size() >= MAX_HANGING_ENTITIES) {
|
|
// printf("Adding - doing remove\n");
|
|
|
|
// 4J-PB - refuse to add the entity, since we'll be removing one
|
|
// already there, and it may be an item frame with something in it.
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
return FALSE;
|
|
|
|
// removeEntityImmediately(m_hangingEntities.front());
|
|
}
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
}
|
|
// If its an arrow entity, and we've got to our capacity, delete the oldest
|
|
else if (std::dynamic_pointer_cast<Arrow>(e) != NULL) {
|
|
// printf("Adding arrow entity count
|
|
//%d\n",m_arrowEntities.size());
|
|
EnterCriticalSection(&m_limiterCS);
|
|
if (m_arrowEntities.size() >= MAX_ARROW_ENTITIES) {
|
|
// printf("Adding - doing remove\n");
|
|
removeEntityImmediately(m_arrowEntities.front());
|
|
}
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
}
|
|
// If its an experience orb entity, and we've got to our capacity, delete
|
|
// the oldest
|
|
else if (std::dynamic_pointer_cast<ExperienceOrb>(e) != NULL) {
|
|
// printf("Adding arrow entity count
|
|
//%d\n",m_arrowEntities.size());
|
|
EnterCriticalSection(&m_limiterCS);
|
|
if (m_experienceOrbEntities.size() >= MAX_EXPERIENCEORB_ENTITIES) {
|
|
// printf("Adding - doing remove\n");
|
|
removeEntityImmediately(m_experienceOrbEntities.front());
|
|
}
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
}
|
|
return Level::addEntity(e);
|
|
}
|
|
|
|
// Maintain a cound of primed tnt & falling tiles in this level
|
|
void ServerLevel::entityAddedExtra(std::shared_ptr<Entity> e) {
|
|
if (std::dynamic_pointer_cast<ItemEntity>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
m_itemEntities.push_back(e);
|
|
// printf("entity added: item entity count now
|
|
//%d\n",m_itemEntities.size());
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<HangingEntity>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
m_hangingEntities.push_back(e);
|
|
// printf("entity added: item entity count now
|
|
//%d\n",m_itemEntities.size());
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<Arrow>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
m_arrowEntities.push_back(e);
|
|
// printf("entity added: arrow entity count now
|
|
//%d\n",m_arrowEntities.size());
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<ExperienceOrb>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
m_experienceOrbEntities.push_back(e);
|
|
// printf("entity added: experience orb entity count now
|
|
//%d\n",m_arrowEntities.size());
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<PrimedTnt>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
m_primedTntCount++;
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<FallingTile>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
m_fallingTileCount++;
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
}
|
|
}
|
|
|
|
// Maintain a cound of primed tnt & falling tiles in this level, and remove any
|
|
// item entities from our list
|
|
void ServerLevel::entityRemovedExtra(std::shared_ptr<Entity> e) {
|
|
if (std::dynamic_pointer_cast<ItemEntity>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
// printf("entity removed: item entity count
|
|
//%d\n",m_itemEntities.size());
|
|
AUTO_VAR(it, find(m_itemEntities.begin(), m_itemEntities.end(), e));
|
|
if (it != m_itemEntities.end()) {
|
|
// printf("Item to remove found\n");
|
|
m_itemEntities.erase(it);
|
|
}
|
|
// printf("entity removed: item entity count now
|
|
//%d\n",m_itemEntities.size());
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<HangingEntity>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
// printf("entity removed: item entity count
|
|
//%d\n",m_itemEntities.size());
|
|
AUTO_VAR(it,
|
|
find(m_hangingEntities.begin(), m_hangingEntities.end(), e));
|
|
if (it != m_hangingEntities.end()) {
|
|
// printf("Item to remove found\n");
|
|
m_hangingEntities.erase(it);
|
|
}
|
|
// printf("entity removed: item entity count now
|
|
//%d\n",m_itemEntities.size());
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<Arrow>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
// printf("entity removed: arrow entity count
|
|
//%d\n",m_arrowEntities.size());
|
|
AUTO_VAR(it, find(m_arrowEntities.begin(), m_arrowEntities.end(), e));
|
|
if (it != m_arrowEntities.end()) {
|
|
// printf("Item to remove found\n");
|
|
m_arrowEntities.erase(it);
|
|
}
|
|
// printf("entity removed: arrow entity count now
|
|
//%d\n",m_arrowEntities.size());
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<ExperienceOrb>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
// printf("entity removed: experience orb entity count
|
|
//%d\n",m_arrowEntities.size());
|
|
AUTO_VAR(it, find(m_experienceOrbEntities.begin(),
|
|
m_experienceOrbEntities.end(), e));
|
|
if (it != m_experienceOrbEntities.end()) {
|
|
// printf("Item to remove found\n");
|
|
m_experienceOrbEntities.erase(it);
|
|
}
|
|
// printf("entity removed: experience orb entity count now
|
|
//%d\n",m_arrowEntities.size());
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<PrimedTnt>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
m_primedTntCount--;
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
} else if (std::dynamic_pointer_cast<FallingTile>(e) != NULL) {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
m_fallingTileCount--;
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
}
|
|
}
|
|
|
|
bool ServerLevel::newPrimedTntAllowed() {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
bool retval = m_primedTntCount < MAX_PRIMED_TNT;
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
return retval;
|
|
}
|
|
|
|
bool ServerLevel::newFallingTileAllowed() {
|
|
EnterCriticalSection(&m_limiterCS);
|
|
bool retval = m_fallingTileCount < MAX_FALLING_TILE;
|
|
LeaveCriticalSection(&m_limiterCS);
|
|
return retval;
|
|
}
|
|
|
|
int ServerLevel::runUpdate(void* lpParam) {
|
|
ShutdownManager::HasStarted(ShutdownManager::eRunUpdateThread,
|
|
m_updateTrigger);
|
|
while (ShutdownManager::ShouldRun(ShutdownManager::eRunUpdateThread)) {
|
|
m_updateTrigger->WaitForAll(INFINITE);
|
|
|
|
if (!ShutdownManager::ShouldRun(ShutdownManager::eRunUpdateThread))
|
|
break;
|
|
|
|
PIXBeginNamedEvent(0, "Updating tiles to be ticked");
|
|
// 4J Stu - Grass and Lava ticks currently take up the majority of all
|
|
// tile updates, so I am limiting them
|
|
int grassTicks = 0;
|
|
int lavaTicks = 0;
|
|
for (unsigned int iLev = 0; iLev < 3; ++iLev) {
|
|
EnterCriticalSection(&m_updateCS[iLev]);
|
|
for (int i = 0; i < m_updateChunkCount[iLev]; i++) {
|
|
// 4J - some of these tile ticks will check things in
|
|
// neighbouring tiles, causing chunks to load/create that aren't
|
|
// yet in memory. Try and avoid this by limiting the min/max x &
|
|
// z values that we will try and inspect in this chunk according
|
|
// to what surround chunks are loaded
|
|
int cx = m_updateChunkX[iLev][i];
|
|
int cz = m_updateChunkZ[iLev][i];
|
|
int minx = 0;
|
|
int maxx = 15;
|
|
int minz = 0;
|
|
int maxz = 15;
|
|
if (!m_level[iLev]->hasChunk(cx, cz)) continue;
|
|
|
|
if (!m_level[iLev]->hasChunk(cx + 1, cz + 0)) {
|
|
maxx = 11;
|
|
}
|
|
if (!m_level[iLev]->hasChunk(cx + 0, cz + 1)) {
|
|
maxz = 11;
|
|
}
|
|
if (!m_level[iLev]->hasChunk(cx - 1, cz + 0)) {
|
|
minx = 4;
|
|
}
|
|
if (!m_level[iLev]->hasChunk(cx + 0, cz - 1)) {
|
|
minz = 4;
|
|
}
|
|
if (!m_level[iLev]->hasChunk(cx + 1, cz + 1)) {
|
|
maxx = 11;
|
|
maxz = 11;
|
|
}
|
|
if (!m_level[iLev]->hasChunk(cx + 1, cz - 1)) {
|
|
maxx = 11;
|
|
minz = 4;
|
|
}
|
|
if (!m_level[iLev]->hasChunk(cx - 1, cz - 1)) {
|
|
minx = 4;
|
|
minz = 4;
|
|
}
|
|
if (!m_level[iLev]->hasChunk(cx - 1, cz + 1)) {
|
|
minx = 4;
|
|
maxz = 11;
|
|
}
|
|
|
|
LevelChunk* lc = m_level[iLev]->getChunk(cx, cz);
|
|
|
|
for (int j = 0; j < 80; j++) {
|
|
// 4jcraft added cast to unsigned
|
|
m_randValue[iLev] = (unsigned)m_randValue[iLev] * 3 +
|
|
(unsigned)m_level[iLev]->addend;
|
|
int val = (m_randValue[iLev] >> 2);
|
|
int x = (val & 15);
|
|
if ((x < minx) || (x > maxx)) continue;
|
|
int z = ((val >> 8) & 15);
|
|
if ((z < minz) || (z > maxz)) continue;
|
|
int y = ((val >> 16) & (Level::maxBuildHeight - 1));
|
|
|
|
// This array access is a cache miss pretty much every time
|
|
int id = lc->getTile(x, y, z);
|
|
if (m_updateTileCount[iLev] >= MAX_UPDATES) break;
|
|
|
|
// 4J Stu - Grass and Lava ticks currently take up the
|
|
// majority of all tile updates, so I am limiting them
|
|
if ((id == Tile::grass_Id &&
|
|
grassTicks >= MAX_GRASS_TICKS) ||
|
|
(id == Tile::calmLava_Id &&
|
|
lavaTicks >= MAX_LAVA_TICKS))
|
|
continue;
|
|
|
|
// 4J Stu - Added shouldTileTick as some tiles won't even do
|
|
// anything if they are set to tick and use up one of our
|
|
// updates
|
|
if (Tile::tiles[id] != NULL &&
|
|
Tile::tiles[id]->isTicking() &&
|
|
Tile::tiles[id]->shouldTileTick(
|
|
m_level[iLev], x + (cx * 16), y, z + (cz * 16))) {
|
|
if (id == Tile::grass_Id)
|
|
++grassTicks;
|
|
else if (id == Tile::calmLava_Id)
|
|
++lavaTicks;
|
|
m_updateTileX[iLev][m_updateTileCount[iLev]] =
|
|
x + (cx * 16);
|
|
m_updateTileY[iLev][m_updateTileCount[iLev]] = y;
|
|
m_updateTileZ[iLev][m_updateTileCount[iLev]] =
|
|
z + (cz * 16);
|
|
|
|
m_updateTileCount[iLev]++;
|
|
}
|
|
}
|
|
}
|
|
LeaveCriticalSection(&m_updateCS[iLev]);
|
|
}
|
|
PIXEndNamedEvent();
|
|
#ifdef __PS3__
|
|
Sleep(10);
|
|
#endif //__PS3__
|
|
}
|
|
|
|
ShutdownManager::HasFinished(ShutdownManager::eRunUpdateThread);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ServerLevel::flagEntitiesToBeRemoved(unsigned int* flags,
|
|
bool* removedFound) {
|
|
if (chunkMap) {
|
|
chunkMap->flagEntitiesToBeRemoved(flags, removedFound);
|
|
}
|
|
}
|