#include "Dimension.h" #include #include "ChunkMap.h" #include "Constants.h" #include "Datei.h" #include "DimensionMap.h" #include "Entity.h" #include "EntityType.h" #include "Game.h" #include "NoBlock.h" #include "Player.h" #include "TickOrganizer.h" using namespace Framework; Dimension::Dimension(int id) : Thread(), nextStructureId(0), dimensionId(id), gravity(9.8f), chunks(new RCTrie()), entities(new RCArray()), map(new DimensionMap(id)), stop(0), currentDayTime(0.0), nightDuration(300.0), nightTransitionDuration(30.0), dayDuration(600.0) { Datei d; d.setDatei( Game::INSTANCE->getWorldDirectory() + "/dim/" + Text(id) + "/meta.dim"); if (d.existiert()) { d.open(Datei::Style::lesen); d.lese((char*)&nextStructureId, 8); d.lese((char*)¤tDayTime, 8); d.close(); } start(); } Dimension::~Dimension() { entities->release(); chunks->release(); map->release(); } void Dimension::configureDayNightCyncle( double nightDuration, double nightTransitionDuration, double dayDuration) { this->nightDuration = nightDuration; this->nightTransitionDuration = nightTransitionDuration; this->dayDuration = dayDuration; } void Dimension::api(Framework::InMemoryBuffer* zRequest, NetworkMessage* zResponse, Entity* zSource) { DoLaterHandler laterHandler; char type; zRequest->lese(&type, 1); switch (type) { case 0: // chunk message { Punkt center; zRequest->lese((char*)¢er.x, 4); zRequest->lese((char*)¢er.y, 4); cs.lock(); Chunk* cC = zChunk(Game::getChunkCenter(center.x, center.y)); if (!cC) { // TODO: have a max amount of waiting requests per player waitingRequests.add( {dynamic_cast(zRequest->getThis()), center, zSource->getId()}); Game::INSTANCE->requestArea({center.x - CHUNK_SIZE / 2, center.y - CHUNK_SIZE / 2, center.x + CHUNK_SIZE / 2 - 1, center.y + CHUNK_SIZE / 2 - 1, dimensionId}); } else { cC->api(zRequest, zSource, laterHandler); } cs.unlock(); break; } case 1: // block message { Vec3 location; zRequest->lese((char*)&location.x, 4); zRequest->lese((char*)&location.y, 4); zRequest->lese((char*)&location.z, 4); Framework::Either block = zBlock(location); if (block.isA()) { block.getA()->api(zRequest, zResponse); } break; } case 2: // map request { map->api(zRequest, zResponse, zSource, this); break; } } } void Dimension::tickEntities() { this->currentDayTime += 1.0 / MAX_TICKS_PER_SECOND; if (this->currentDayTime > dayDuration + nightDuration + nightTransitionDuration * 2) { this->currentDayTime -= dayDuration + nightDuration + nightTransitionDuration * 2; } for (auto entity : *entities) { if (!entity->isRemoved() && (entity->isMoving() || zChunk(Game::getChunkCenter((int)entity->getPosition().x, (int)entity->getPosition().y)))) entity->prepareTick(this); } int index = 0; for (auto entity : *entities) { if (!entity->isRemoved() && (entity->isMoving() || zChunk(Game::getChunkCenter((int)entity->getPosition().x, (int)entity->getPosition().y)))) entity->tick(this); index++; } } void Dimension::thread() { // light calculation int index = 0; ZeitMesser messer; messer.messungStart(); double time = 0; bool isForeground = 0; Framework::Array> internalLightUpdateQueue; while (!stop) { Vec3 position; if (internalLightUpdateQueue.getEintragAnzahl()) { position = internalLightUpdateQueue.get(0); internalLightUpdateQueue.remove(0); } else { removedChunksCs.lock(); if (removedChunks.getEintragAnzahl() > 0) { Chunk* removedChunk = removedChunks.z(0); removedChunksCs.unlock(); Text filePath = Game::INSTANCE->getWorldDirectory() + "/dim/" + getDimensionId() + "/"; filePath.appendHex(removedChunk->getCenter().x); filePath += "_"; filePath.appendHex(removedChunk->getCenter().y); filePath += ".chunk"; Datei d; d.setDatei(filePath); d.erstellen(); d.open(Datei::Style::schreiben); removedChunk->save(&d); char addr[8]; getAddrOfWorld(removedChunk->getCenter(), addr); map->removeMap(addr, 8); d.close(); removedChunksCs.lock(); removedChunks.remove(0); } removedChunksCs.unlock(); if (priorizedLightUpdateQueue.getEintragAnzahl()) { prioLightCs.lock(); position = priorizedLightUpdateQueue.get(0); priorizedLightUpdateQueue.remove(0); prioLightCs.unlock(); isForeground = 1; } else { if (!lightUpdateQueue.getEintragAnzahl()) { messer.messungEnde(); time += messer.getSekunden(); Sleep(500); messer.messungStart(); continue; } lightCs.lock(); position = lightUpdateQueue.get(0); lightUpdateQueue.remove(0); lightCs.unlock(); isForeground = 0; } } Chunk* chunk = zChunk(Game::INSTANCE->getChunkCenter(position.x, position.y)); if (position.z >= 0 && position.z < WORLD_HEIGHT) { if (chunk) { Vec3 chunkPos = chunkCoordinates(position); unsigned char* light = chunk->getLightData(chunkPos); unsigned char dayLight[6] = {255, 255, 255, 0, 0, 0}; unsigned char noLight[6] = {0, 0, 0, 0, 0, 0}; unsigned char newLight[6] = {0, 0, 0, 0, 0, 0}; // add neighbor light emission for (int i = 0; i < 6; i++) { unsigned char* neighborLeight; Vec3 neighborPos = position + getDirection(getDirectionFromIndex(i)); if (neighborPos.z < 0) { neighborLeight = noLight; } else if (neighborPos.z >= WORLD_HEIGHT) { neighborLeight = dayLight; } else { Chunk* neighborChunk = zChunk(Game::INSTANCE->getChunkCenter( neighborPos.x, neighborPos.y)); if (neighborChunk) neighborLeight = neighborChunk->getLightData( chunkCoordinates(neighborPos)); else neighborLeight = noLight; } for (int j = 0; j < 3; j++) newLight[j] = (unsigned char)MAX(newLight[j], i == getDirectionIndex(TOP) ? neighborLeight[j] : (unsigned char)((float)neighborLeight[j] * 0.8f)); for (int j = 3; j < 6; j++) newLight[j] = (unsigned char)MAX(newLight[j], (unsigned char)((float)neighborLeight[j] * 0.85f)); } const Block* current = zBlockOrDefault(position); // add own light emission for (int j = 3; j < 6; j++) newLight[j] = (unsigned char)MAX( newLight[j], current->getLightEmisionColor()[j - 3]); current->filterPassingLight(newLight); current->filterPassingLight(newLight + 3); for (int i = 0; i < 6; i++) { if (newLight[i] != light[i]) { chunk->setLightData(chunkPos, newLight, isForeground); for (int j = 0; j < 6; j++) internalLightUpdateQueue.add( position + getDirection(getDirectionFromIndex(j)), 0); break; } } } } index++; if (index > 100000) { messer.messungEnde(); time += messer.getSekunden(); Logging::debug() << "100000 light updates needed " << time << " seconds"; time = 0; index = 0; messer.messungStart(); } } Logging::info() << Text("Dimension ") + this->getDimensionId() + " update Thread exited."; } void Dimension::getAddrOf(Punkt cPos, char* addr) const { *(int*)addr = cPos.x; *((int*)addr + 1) = cPos.y; } void Dimension::getAddrOfWorld(Punkt wPos, char* addr) const { if (wPos.x < 0) wPos.x -= CHUNK_SIZE; if (wPos.y < 0) // needed because otherwise would (-8, -8) have the same // adress as (8, 8) wPos.y -= CHUNK_SIZE; wPos /= CHUNK_SIZE; getAddrOf(wPos, addr); } void Dimension::saveStructure(MultiblockStructure* zStructure) const { Datei d; Text path = Game::INSTANCE->getWorldDirectory() + "/dim/" + Text(dimensionId) + "/structures/"; path.appendHex(zStructure->getStructureId()); path += ".str"; d.setDatei(path); d.erstellen(); d.open(Datei::Style::schreiben); auto uPos = zStructure->getUniquePosition(); d.schreibe((char*)&uPos.x, 4); d.schreibe((char*)&uPos.y, 4); d.schreibe((char*)&uPos.z, 4); int typeId = zStructure->getStructureTypeId(); d.schreibe((char*)&typeId, 4); __int64 strId = zStructure->getStructureId(); d.schreibe((char*)&strId, 8); Game::INSTANCE->zMultiblockStructureType(zStructure->getStructureTypeId()) ->saveStructure(zStructure, &d); d.close(); } Chunk* Dimension::zChunk(Punkt wPos) const { char addr[8]; getAddrOfWorld(wPos, addr); return chunks->z(addr, 8); } Framework::Either Dimension::zBlock(Vec3 location) { Chunk* c = zChunk(Game::INSTANCE->getChunkCenter(location.x, location.y)); if (c) { location = chunkCoordinates(location); return c->zBlockAt(location); } return 0; } Block* Dimension::zRealBlockInstance(Framework::Vec3 location) { Chunk* c = zChunk(Game::INSTANCE->getChunkCenter(location.x, location.y)); if (c) { location = chunkCoordinates(location); c->instantiateBlock(location); auto result = c->zBlockAt(location); return result.isA() ? result.getA() : 0; } return 0; } const Block* Dimension::zBlockOrDefault(Framework::Vec3 location) { Chunk* c = zChunk(Game::INSTANCE->getChunkCenter(location.x, location.y)); if (c) { location = chunkCoordinates(location); return c->zBlockConst(location); } return &NoBlock::INSTANCE; } int Dimension::getBlockType(Framework::Vec3 location) { Chunk* c = zChunk(Game::INSTANCE->getChunkCenter(location.x, location.y)); if (c) { location = chunkCoordinates(location); return c->getBlockTypeAt(location); } return BlockTypeEnum::NO_BLOCK; } void Dimension::placeBlock( Framework::Vec3 location, Framework::Either block) { if (block.isB() && block.getB() == BlockTypeEnum::NO_BLOCK) return; Chunk* c = zChunk(Game::getChunkCenter(location.x, location.y)); if (c) { location = chunkCoordinates(location); if (block.isA()) c->putBlockAt(location, block); else { c->putBlockAt(location, 0); c->putBlockTypeAt(location, block); } } else if (block.isA()) block.getA()->release(); } void Dimension::sendBlockInfo(Framework::Vec3 location) { Chunk* c = zChunk(Game::getChunkCenter(location.x, location.y)); if (c) { location = chunkCoordinates(location); c->sendBlockInfo(location); } } void Dimension::addEntity(Entity* entity) { entities->add(entity); } void Dimension::setChunk(Chunk* chunk, Punkt center) { char addr[8]; getAddrOfWorld(center, addr); if (chunk) map->loadMap(addr, 8, chunk); chunkCs.lock(); Chunk* old = chunks->get(addr, 8); if (old) { Game::INSTANCE->zTickOrganizer()->removeTickSource(old); old->prepareRemove(); for (int i = 0; i < chunkList.getEintragAnzahl(); i++) { if (chunkList.get(i) == old) { chunkList.remove(i); break; } } } chunks->set(addr, 8, chunk); if (chunk) { chunkList.add(chunk); chunk->setAdded(); } getAddrOfWorld(center + Punkt(CHUNK_SIZE, 0), addr); Chunk* zChunk = chunks->z(addr, 8); if (zChunk) { zChunk->setNeighbor(WEST, chunk); if (chunk) { chunk->setNeighbor(EAST, zChunk); } } getAddrOfWorld(center + Punkt(-CHUNK_SIZE, 0), addr); zChunk = chunks->z(addr, 8); if (zChunk) { zChunk->setNeighbor(EAST, chunk); if (chunk) chunk->setNeighbor(WEST, zChunk); } getAddrOfWorld(center + Punkt(0, CHUNK_SIZE), addr); zChunk = chunks->z(addr, 8); if (zChunk) { zChunk->setNeighbor(NORTH, chunk); if (chunk) chunk->setNeighbor(SOUTH, zChunk); } getAddrOfWorld(center + Punkt(0, -CHUNK_SIZE), addr); zChunk = chunks->z(addr, 8); if (zChunk) { zChunk->setNeighbor(SOUTH, chunk); if (chunk) chunk->setNeighbor(NORTH, zChunk); } DoLaterHandler laterHandler; if (chunk) { cs.lock(); int index = 0; for (ArrayIterator iterator = waitingRequests.begin(); iterator;) { Entity* zE = Game::INSTANCE->zEntity(iterator.val().sourceId); if (zE) { if (iterator.val().chunkCenter == chunk->getCenter()) { chunk->api(iterator.val().request, zE, laterHandler); iterator.val().request->release(); iterator.remove(); continue; } } else { iterator.val().request->release(); iterator.remove(); continue; } iterator++; index++; } cs.unlock(); Game::INSTANCE->zTickOrganizer()->addTickSource(chunk); } chunkCs.unlock(); if (old) { old->onUnloaded(); removedChunksCs.lock(); removedChunks.add(old); removedChunksCs.unlock(); } if (chunk) chunk->onLoaded(); laterHandler.execute(); if (chunk) updateLightAtChunkBorders(center); } void Dimension::save(Text worldDir) const { Datei d; d.setDatei(Game::INSTANCE->getWorldDirectory() + "/dim/" + Text(dimensionId) + "/meta.dim"); d.erstellen(); d.open(Datei::Style::schreiben); d.schreibe((char*)&nextStructureId, 8); d.schreibe((char*)¤tDayTime, 8); d.close(); for (auto chunk = chunkList.begin(); chunk; chunk++) { if (!chunk._) continue; Datei* file = new Datei(); Text filePath = worldDir + "/dim/" + dimensionId + "/"; filePath.appendHex(chunk->getCenter().x); filePath += "_"; filePath.appendHex(chunk->getCenter().y); filePath += ".chunk"; file->setDatei(filePath); if (file->open(Datei::Style::schreiben)) chunk->save(file); file->close(); file->release(); char addr[8]; getAddrOfWorld(chunk->getCenter(), addr); map->saveMap(addr, 8); } Text filePath = worldDir + "/dim/" + dimensionId + "/entities"; Datei* file = new Datei(); file->setDatei(filePath); if (file->open(Datei::Style::schreiben)) { for (Entity* entity : *entities) { if (entity->zType()->getId() != EntityTypeEnum::PLAYER) { if (!entity->isRemoved()) { int type = entity->zType()->getId(); file->schreibe((char*)&type, 4); Game::INSTANCE->zEntityType(type)->saveEntity(entity, file); } } else { Datei pFile; pFile.setDatei(worldDir + "/player/" + Game::INSTANCE->getPlayerId( ((Player*)entity)->getName())); if (pFile.open(Datei::Style::schreiben)) Game::INSTANCE->zEntityType(EntityTypeEnum::PLAYER) ->saveEntity(entity, &pFile); } } file->close(); } for (MultiblockStructure* structure : structures) { saveStructure(structure); } } int Dimension::getDimensionId() const { return dimensionId; } bool Dimension::hasChunck(int x, int y) { if (zChunk(Punkt(x, y))) return 1; removedChunksCs.lock(); for (Chunk* c : removedChunks) { if (c->getCenter().x == x && c->getCenter().y == y) { removedChunksCs.unlock(); return 1; } } removedChunksCs.unlock(); return 0; } bool Dimension::reviveChunk(int x, int y) { chunkCs.lock(); if (zChunk(Punkt(x, y))) { chunkCs.unlock(); return 1; } removedChunksCs.lock(); int index = 0; for (ArrayIterator i = removedChunks.begin(); i; i++) { if (i->getCenter().x == x && i->getCenter().y == y) { setChunk(dynamic_cast(i->getThis()), Punkt(x, y)); if (index > 0) i.remove(); removedChunksCs.unlock(); chunkCs.unlock(); return 1; } index++; } removedChunksCs.unlock(); chunkCs.unlock(); return 0; } float Dimension::getGravity() const { return gravity; } void Dimension::removeOldChunks() { chunkCs.lock(); int index = 0; for (Chunk* chunk : chunkList) { if (!chunk->hasObservers()) setChunk(0, chunk->getCenter()); index++; } chunkCs.unlock(); structurCs.lock(); ArrayIterator i = structures.begin(); while (i) { if (i->isEmpty()) { i.remove(); continue; } else if (i->isFullyUnloaded()) { saveStructure(i); i.remove(); continue; } i++; } structurCs.unlock(); } Entity* Dimension::zEntity(int id) { for (auto entity : *entities) { if (!entity->isRemoved() && entity->getId() == id) return entity; } return 0; } Entity* Dimension::zNearestEntity( Framework::Vec3 pos, std::function filter) { Entity* result = 0; float sqDist = 0; for (auto entity : *entities) { if (!entity->isRemoved() && filter(entity)) { float d = pos.abstandSq(entity->getPosition()); if (!result || d < sqDist) { result = entity; sqDist = d; } } } return result; } void Dimension::removeEntity(int id) { int index = 0; for (auto entity : *entities) { if (entity->getId() == id) { entities->remove(index); return; } index++; } } void Dimension::removeSubscriptions(Entity* zEntity) { for (Chunk* chunk : chunkList) chunk->removeObserver(zEntity); } void Dimension::updateLightning(Vec3 location) { lightCs.lock(); lightUpdateQueue.add(location, 0); lightCs.unlock(); } void Dimension::updateLightningWithoutWait(Framework::Vec3 location) { prioLightCs.lock(); priorizedLightUpdateQueue.add(location, 0); prioLightCs.unlock(); } void Dimension::updateLightAtChunkBorders(Punkt chunkCenter) { if (lightUpdateQueue.getEintragAnzahl() > 300000) { Logging::warning() << "light calculation queue is over 300000 blocks long"; } for (int i = WORLD_HEIGHT - 1; i >= 0; i--) { for (int j = 0; j < CHUNK_SIZE; j++) { updateLightning(Vec3(chunkCenter.x - CHUNK_SIZE / 2 - 1, chunkCenter.y - CHUNK_SIZE / 2 + j, i)); updateLightning(Vec3(chunkCenter.x - CHUNK_SIZE / 2, chunkCenter.y - CHUNK_SIZE / 2 + j, i)); updateLightning(Vec3(chunkCenter.x + CHUNK_SIZE / 2 - 1, chunkCenter.y - CHUNK_SIZE / 2 + j, i)); updateLightning(Vec3(chunkCenter.x + CHUNK_SIZE / 2, chunkCenter.y - CHUNK_SIZE / 2 + j, i)); updateLightning(Vec3(chunkCenter.x - CHUNK_SIZE / 2 + j, chunkCenter.y - CHUNK_SIZE / 2 - 1, i)); updateLightning(Vec3(chunkCenter.x - CHUNK_SIZE / 2 + j, chunkCenter.y - CHUNK_SIZE / 2, i)); updateLightning(Vec3(chunkCenter.x - CHUNK_SIZE / 2 + j, chunkCenter.y + CHUNK_SIZE / 2 - 1, i)); updateLightning(Vec3(chunkCenter.x - CHUNK_SIZE / 2 + j, chunkCenter.y + CHUNK_SIZE / 2, i)); } } } __int64 Dimension::getNextStructureId() { return nextStructureId++; } void Dimension::addStructure(MultiblockStructure* structure) { structurCs.lock(); structures.add(structure); structurCs.unlock(); } MultiblockStructure* Dimension::zStructureByPosition( Framework::Vec3 uniquePosition) { structurCs.lock(); for (MultiblockStructure* str : structures) { if (str->getUniquePosition() == uniquePosition) { structurCs.unlock(); return str; } } // search for structure file Datei dir(Game::INSTANCE->getWorldDirectory() + "/dim/" + Text(dimensionId) + "/structures"); RCArray* names = dir.getDateiListe(); if (names) { Vec3 uPos; for (Text* name : *names) { Datei d(Text(dir.zPfad()->getText()) + "/" + name->getText()); if (d.open(Datei::Style::lesen)) { d.lese((char*)&uPos.x, 4); d.lese((char*)&uPos.y, 4); d.lese((char*)&uPos.z, 4); if (uPos == uniquePosition) { int type; d.lese((char*)&type, 4); __int64 strId; d.lese((char*)&strId, 8); MultiblockStructure* str = Game::INSTANCE->zMultiblockStructureType(type) ->loadStructure( dimensionId, strId, uniquePosition, &d); d.close(); structures.add(str); names->release(); structurCs.unlock(); return str; } d.close(); } } names->release(); } structurCs.unlock(); return 0; } MultiblockStructure* Dimension::zStructureById(__int64 id) { structurCs.lock(); for (MultiblockStructure* str : structures) { if (str->getStructureId() == id) { structurCs.unlock(); return str; } } // search for structure file Text path = Game::INSTANCE->getWorldDirectory() + "/dim/" + Text(dimensionId) + "/structures/"; path.appendHex(id); path += ".str"; Datei d(path); Vec3 uPos; if (d.open(Datei::Style::lesen)) { d.lese((char*)&uPos.x, 4); d.lese((char*)&uPos.y, 4); d.lese((char*)&uPos.z, 4); int type; d.lese((char*)&type, 4); __int64 strId; d.lese((char*)&strId, 8); MultiblockStructure* str = Game::INSTANCE->zMultiblockStructureType(type)->loadStructure( dimensionId, strId, uPos, &d); d.close(); structures.add(str); structurCs.unlock(); return str; } Logging::warning() << "did not find Structure information file '" << path << "'."; structurCs.unlock(); return 0; } void Dimension::requestStopAndWait() { stop = 1; warteAufThread(1000000); } void Dimension::updateMap(int x, int y, int height) { chunkCs.lock(); int h1 = height % 2 == 0 ? height : height - 1; int h2 = h1 + 1; const Block* b1 = zBlockOrDefault({x, y, h1}); const Block* b2 = zBlockOrDefault({x, y, h2}); bool visible = 1; if (h2 != WORLD_HEIGHT - 1) { const Block* b3 = zBlockOrDefault({x, y, h2 + 1}); visible = b3->isPassable() || b3->isTransparent(); } int color1 = (b2->isPassable() || b2->isTransparent()) ? b1->getMapColor() : 0; int color2 = visible ? b2->getMapColor() : 0; int color1m = 0; int color2m = 0; if (h1 > 0) { const Block* b1m = zBlockOrDefault({x, y, h1 - 2}); const Block* b2m = zBlockOrDefault({x, y, h1 - 1}); color1m = (b2m->isPassable() || b2m->isTransparent()) ? b1m->getMapColor() : 0; color2m = (b1->isPassable() || b1->isTransparent()) ? b2m->getMapColor() : 0; } char addr[8]; Punkt center = Game::INSTANCE->getChunkCenter(x, y); getAddrOfWorld(center, addr); ChunkMap* cMap = map->getMap(addr, 8, center); if (cMap) { Framework::Vec3 chunkLocation = chunkCoordinates({x, y, height}); if (cMap->update((char)chunkLocation.x, (char)chunkLocation.y, (unsigned char)(chunkLocation.z / 2), color1, color2) || (h1 > 0 && cMap->update((char)chunkLocation.x, (char)chunkLocation.y, (unsigned char)(chunkLocation.z / 2 - 1), color1m, color2m))) { map->onMapUpdated(addr, 8); } } chunkCs.unlock(); } int Dimension::getChunkCount() { return chunkList.getEintragAnzahl(); } double Dimension::getCurrentDayTime() const { return currentDayTime; } double Dimension::getNightDuration() const { return nightDuration; } double Dimension::getNightTransitionDuration() const { return nightTransitionDuration; } double Dimension::getDayDuration() const { return dayDuration; }