#include "Game.h" #include "Zeit.h" #include "Player.h" #include "OverworldDimension.h" #include "AddChunkUpdate.h" #include "NoBlock.h" #include "AsynchronCall.h" #include "Entity.h" #include "AddEntityUpdate.h" using namespace Framework; GameClient::GameClient( Player* zPlayer, FCKlient* client ) : ReferenceCounter(), zPlayer( zPlayer ), client( client ), viewDistance( DEFAULT_VIEW_DISTANCE ), first( 1 ), online( 1 ), finished( 0 ) { new AsynchronCall( [this]() { while( online ) { other.lock(); if( updateQueue.hat( 0 ) ) { WorldUpdate* update = updateQueue.get( 0 ); updateQueue.remove( 0 ); other.unlock(); background.lock(); this->client->zBackgroundWriter()->schreibe( (char*)&Message::WORLD_UPDATE, 1 ); update->writeAndCheck( this->client->zBackgroundWriter() ); background.unlock(); update->release(); } else { other.unlock(); updateSync.wait(); } } finished = 1; } ); } GameClient::~GameClient() { online = 0; updateSync.notify(); while( !finished ) Sleep( 100 ); client->release(); } void GameClient::sendWorldUpdate( WorldUpdate* update ) { bool add = 0; if( zPlayer->getCurrentDimensionId() == update->getAffectedDimension() ) { auto pos = (Vec3)zPlayer->getPosition(); int dist = update->distanceTo( pos.x, pos.y ); if( dist < viewDistance * CHUNK_SIZE ) { other.lock(); int index = 0; for( auto update2 : updateQueue ) { int dist2 = update2->distanceTo( pos.x, pos.y ); if( dist2 > dist ) break; index++; } if( update->getType() == AddChunkUpdateType::ID ) ((AddChunkUpdate*)update)->zChunk()->addView( zPlayer ); updateQueue.add( update, index ); other.unlock(); updateSync.notify(); add = 1; } } if( !add ) update->release(); } void GameClient::reply() { other.lock(); for( auto req : requests ) Game::INSTANCE->api( req, this ); requests.leeren(); other.unlock(); int x = (int)floor( zPlayer->getPosition().x ); int y = (int)floor( zPlayer->getPosition().y ); int d = zPlayer->getCurrentDimensionId(); // send world to client if( first ) { foreground.lock(); int id = zPlayer->getId(); client->zForegroundWriter()->schreibe( (char*)&Message::POSITION_UPDATE, 1 ); client->zForegroundWriter()->schreibe( (char*)&id, 4 ); foreground.unlock(); first = 0; Dimension* dim = Game::INSTANCE->zDimension( d ); if( dim ) { for( int xP = x - CHUNK_SIZE * viewDistance; xP <= x + CHUNK_SIZE * viewDistance; xP += CHUNK_SIZE ) { for( int yP = y - CHUNK_SIZE * viewDistance; yP <= y + CHUNK_SIZE * viewDistance; yP += CHUNK_SIZE ) { Chunk* chunk = dim->zChunk( Game::INSTANCE->getChunkCenter( xP, yP ) ); if( chunk ) sendWorldUpdate( new AddChunkUpdate( dynamic_cast(chunk->getThis()) ) ); } } } Game::INSTANCE->requestArea( { x - CHUNK_SIZE * viewDistance, y - CHUNK_SIZE * viewDistance, x + CHUNK_SIZE * viewDistance, y + CHUNK_SIZE * viewDistance, d } ); } else { Punkt lastMin = Game::INSTANCE->getChunkCenter( (int)floor( lastPos.x ) - CHUNK_SIZE * viewDistance, (int)floor( lastPos.y ) - CHUNK_SIZE * viewDistance ); Punkt curMin = Game::INSTANCE->getChunkCenter( x - CHUNK_SIZE * viewDistance, y - CHUNK_SIZE * viewDistance ); Punkt lastMax = Game::INSTANCE->getChunkCenter( (int)floor( lastPos.x ) + CHUNK_SIZE * viewDistance, (int)floor( lastPos.y ) + CHUNK_SIZE * viewDistance ); Punkt curMax = Game::INSTANCE->getChunkCenter( x + CHUNK_SIZE * viewDistance, y + CHUNK_SIZE * viewDistance ); Dimension* dim = Game::INSTANCE->zDimension( d ); if( dim ) { for( int xP = curMin.x; xP <= curMax.x; xP += CHUNK_SIZE ) { for( int yP = curMin.y; yP <= curMax.y; yP += CHUNK_SIZE ) { if( xP < lastMin.x || xP > lastMax.x || yP < lastMin.y || yP > lastMax.y ) { Chunk* chunk = dim->zChunk( Game::INSTANCE->getChunkCenter( xP, yP ) ); if( chunk ) sendWorldUpdate( new AddChunkUpdate( dynamic_cast(chunk->getThis()) ) ); else Game::INSTANCE->requestArea( Game::INSTANCE->getChunckArea( Game::INSTANCE->getChunkCenter( xP, yP ) ) ); } } } for( int xP = lastMin.x; xP <= lastMax.x; xP += CHUNK_SIZE ) { for( int yP = lastMin.y; yP <= lastMax.y; yP += CHUNK_SIZE ) { if( xP < curMin.x || xP > curMax.x || yP < curMin.y || yP > curMax.y ) { Chunk* chunk = dim->zChunk( Game::INSTANCE->getChunkCenter( xP, yP ) ); if( chunk ) chunk->removeView( zPlayer ); } } } } } lastPos = zPlayer->getPosition(); } void GameClient::logout() { online = 0; } void GameClient::addMessage( StreamReader* reader ) { short len = 0; reader->lese( (char*)&len, 2 ); InMemoryBuffer* buffer = new InMemoryBuffer(); char* tmp = new char[ len ]; reader->lese( tmp, len ); buffer->schreibe( tmp, len ); delete[]tmp; other.lock(); requests.add( buffer ); other.unlock(); } bool GameClient::isOnline() const { return online; } void GameClient::sendResponse( NetworkResponse* zResponse ) { if( zResponse->isAreaAffected( { lastPos.x - (float)CHUNK_SIZE * (float)viewDistance, lastPos.y - (float)CHUNK_SIZE * (float)viewDistance, 0.f }, { lastPos.x + (float)CHUNK_SIZE * (float)viewDistance, lastPos.y + (float)CHUNK_SIZE * (float)viewDistance, (float)WORLD_HEIGHT } ) ) { if( zResponse->isUseBackground() ) { background.unlock(); client->zBackgroundWriter()->schreibe( (char*)&Message::API_MESSAGE, 1 ); zResponse->writeTo( client->zBackgroundWriter() ); background.unlock(); } else { foreground.unlock(); client->zForegroundWriter()->schreibe( (char*)&Message::API_MESSAGE, 1 ); zResponse->writeTo( client->zForegroundWriter() ); foreground.unlock(); } } } Player* GameClient::zEntity() const { return zPlayer; } Game::Game( Framework::Text name, Framework::Text worldsDir ) : Thread(), name( name ), dimensions( new RCArray() ), updates( new RCArray() ), clients( new RCArray() ), ticker( new TickOrganizer() ), path( (const char*)(worldsDir + "/" + name) ), stop( 0 ), tickId( 0 ), nextEntityId( 0 ), generator( 0 ), loader( 0 ) { if( !DateiExistiert( worldsDir + "/" + name ) ) DateiPfadErstellen( worldsDir + "/" + name + "/" ); Datei d; d.setDatei( path + "/eid" ); if( d.existiert() ) { d.open( Datei::Style::lesen ); d.lese( (char*)&nextEntityId, 4 ); d.close(); } start(); } Game::~Game() { dimensions->release(); updates->release(); clients->release(); generator->release(); loader->release(); } void Game::initialize() { int seed = 0; int index = 0; for( char* n = name; *n; n++ ) seed += (int)pow( (float)*n * 31, (float)++index ); generator = new WorldGenerator( seed ); loader = new WorldLoader(); } void Game::thread() { ZeitMesser m; while( !stop ) { m.messungStart(); ticker->nextTick(); Array removed; cs.lock(); int index = 0; for( auto player : *clients ) { if( !player->isOnline() ) { Datei pFile; pFile.setDatei( path + "/player/" + player->zEntity()->getName() ); if( pFile.open( Datei::Style::schreiben ) ) PlayerEntityType::INSTANCE->saveEntity( player->zEntity(), &pFile ); removed.add( index, 0 ); } index++; } for( auto i : removed ) clients->remove( i ); cs.unlock(); for( auto dim : *dimensions ) dim->tickEntities(); cs.lock(); while( updates->hat( 0 ) ) { WorldUpdate* update = updates->z( 0 ); for( auto client : *clients ) client->sendWorldUpdate( dynamic_cast(update->getThis()) ); if( !zDimension( update->getAffectedDimension() ) ) addDimension( new Dimension( update->getAffectedDimension() ) ); update->onUpdate( zDimension( update->getAffectedDimension() ) ); updates->remove( 0 ); } cs.unlock(); for( auto client : *clients ) client->reply(); cs.lock(); for( auto dim : *dimensions ) dim->removeOldChunks(); cs.unlock(); m.messungEnde(); double sec = m.getSekunden(); if( sec < 0.05 ) Sleep( (int)((0.05 - sec) * 1000) ); else { std::cout << "WARNING: tick needed " << sec << " seconds. The game will run sower then normal.\n"; } } save(); } void Game::api( Framework::StreamReader* zRequest, GameClient* zOrigin ) { char type; zRequest->lese( &type, 1 ); NetworkResponse response; switch( type ) { case 1: // world { int dimensionId; zRequest->lese( (char*)&dimensionId, 4 ); Dimension* dim = zDimension( dimensionId ); if( !dim ) { dim = new Dimension( dimensionId ); addDimension( dim ); } dim->api( zRequest, &response ); break; } case 2: // player zOrigin->zEntity()->api( zRequest, &response ); break; default: std::cout << "received unknown api request in game with type " << (int)type << "\n"; } if( !response.isEmpty() ) { if( response.isBroadcast() ) distributeResponse( &response ); else zOrigin->sendResponse( &response ); } } void Game::distributeResponse( NetworkResponse* zResponse ) { for( auto client : *clients ) client->sendResponse( zResponse ); } bool Game::requestWorldUpdate( WorldUpdate* update ) { cs.lock(); for( WorldUpdate* u : *updates ) { if( u->getMaxAffectedPoint().x >= update->getMinAffectedPoint().x && u->getMinAffectedPoint().x <= update->getMaxAffectedPoint().x && u->getMaxAffectedPoint().y >= update->getMinAffectedPoint().y && u->getMinAffectedPoint().y <= update->getMaxAffectedPoint().y && u->getMaxAffectedPoint().z >= update->getMinAffectedPoint().z && u->getMinAffectedPoint().z <= update->getMaxAffectedPoint().z && u->getType() == update->getType() ) { cs.unlock(); update->release(); return 0; } } updates->add( update ); cs.unlock(); return 1; } GameClient* Game::addPlayer( FCKlient* client, Framework::Text name ) { cs.lock(); Datei pFile; pFile.setDatei( path + "/player/" + name ); Player* player; bool isNew = 0; if( !pFile.existiert() || !pFile.open( Datei::Style::lesen ) ) { player = (Player*)PlayerEntityType::INSTANCE->createEntityAt( Vec3( 0.5, 0.5, 0 ), OverworldDimension::ID ); player->setName( name ); isNew = 1; } else { player = (Player*)PlayerEntityType::INSTANCE->loadEntity( &pFile ); pFile.close(); } GameClient* gameClient = new GameClient( player, client ); clients->add( gameClient ); requestArea( getChunckArea( getChunkCenter( (int)player->getPosition().x, (int)player->getPosition().y ) ) ); while( !zDimension( player->getCurrentDimensionId() ) || (isNew && !zDimension( player->getCurrentDimensionId() )->zChunk( getChunkCenter( (int)player->getPosition().x, (int)player->getPosition().y ) )) ) { cs.unlock(); Sleep( 1000 ); cs.lock(); } if( isNew ) { Either b = AirBlockBlockType::ID; int h = WORLD_HEIGHT; while( ((b.isA() && (!(Block*)b || ((Block*)b)->isPassable())) || (b.isB() && StaticRegistry::INSTANCE.zElement( b )->zDefault()->isPassable())) && h > 0 ) b = zBlockAt( { (int)player->getPosition().x, (int)player->getPosition().y, --h }, player->getCurrentDimensionId() ); player->setPosition( { player->getPosition().x, player->getPosition().y, (float)h + 1.f } ); } requestWorldUpdate( new AddEntityUpdate( player, player->getCurrentDimensionId() ) ); cs.unlock(); return dynamic_cast(gameClient->getThis()); } bool Game::isChunkLoaded( int x, int y, int dimension ) const { Dimension* dim = zDimension( dimension ); return (dim && dim->hasChunck( x, y )); } bool Game::doesChunkExist( int x, int y, int dimension ) { cs.lock(); bool result = isChunkLoaded( x, y, dimension ) || loader->existsChunk( x, y, dimension ); if( !result ) { for( WorldUpdate* update : *updates ) { if( update->getType() == AddChunkUpdateType::ID ) result |= ((AddChunkUpdate*)update)->zChunk()->getCenter() == Framework::Punkt( x, y ); } } cs.unlock(); return result; } Framework::Either Game::zBlockAt( Framework::Vec3 location, int dimension ) const { Dimension* dim = zDimension( dimension ); if( dim ) return dim->zBlock( location ); return 0; } Block* Game::zRealBlockInstance( Framework::Vec3 location, int dimension ) { Dimension* dim = zDimension( dimension ); if( dim ) return dim->zRealBlockInstance( location ); return 0; } Dimension* Game::zDimension( int id ) const { for( auto dim : *dimensions ) { if( dim->getDimensionId() == id ) return dim; } return 0; } Framework::Punkt Game::getChunkCenter( int x, int y ) { return Punkt( ((x < 0 ? x + 1 : x) / CHUNK_SIZE) * CHUNK_SIZE + (x < 0 ? -CHUNK_SIZE : CHUNK_SIZE) / 2, ((y < 0 ? y + 1 : y) / CHUNK_SIZE) * CHUNK_SIZE + (y < 0 ? -CHUNK_SIZE : CHUNK_SIZE) / 2 ); } Area Game::getChunckArea( Punkt center ) const { return { center.x - CHUNK_SIZE / 2, center.y - CHUNK_SIZE / 2, center.x + CHUNK_SIZE / 2 - 1, center.y + CHUNK_SIZE / 2 - 1, 0 }; } Framework::Text Game::getWorldDirectory() const { return path; } void Game::requestArea( Area area ) { generator->requestGeneration( area ); loader->requestLoading( area ); } void Game::save() const { Datei d; d.setDatei( path + "/eid" ); d.open( Datei::Style::schreiben ); d.schreibe( (char*)&nextEntityId, 4 ); d.close(); for( auto dim : *dimensions ) dim->save( path ); } void Game::requestStop() { stop = 1; warteAufThread( 1000000 ); } void Game::addDimension( Dimension* d ) { dimensions->add( d ); } int Game::getNextEntityId() { cs.lock(); int result = nextEntityId++; cs.unlock(); return result; } WorldGenerator* Game::zGenerator() const { return generator; } Game* Game::INSTANCE = 0; void Game::initialize( Framework::Text name, Framework::Text worldsDir ) { if( !Game::INSTANCE ) { Game::INSTANCE = new Game( name, worldsDir ); Game::INSTANCE->initialize(); } } Entity* Game::zEntity( int id, int dimensionId ) const { Dimension* d = zDimension( dimensionId ); if( d ) return d->zEntity( id ); return 0; } Entity* Game::zNearestEntity( int dimensionId, Framework::Vec3 pos, std::function filter ) { Dimension* d = zDimension( dimensionId ); if( !d ) return 0; return d->zNearestEntity( pos, filter ); }