#include "Game.h" #include "AddEntityUpdate.h" #include "AsynchronCall.h" #include "Entity.h" #include "EntityRemovedUpdate.h" #include "NetworkMessage.h" #include "NoBlock.h" #include "OverworldDimension.h" #include "Player.h" #include "Zeit.h" using namespace Framework; char* randomKey(int& len) { len = 1024 + (int)(((double)rand() / RAND_MAX - 0.5) * 512); char* key = new char[len]; for (int i = 0; i < len; i++) key[i] = (char)((((double)rand() / RAND_MAX) * 254) + 1); return key; } GameClient::GameClient(Player* zPlayer, FCKlient* client) : Thread(), zPlayer(zPlayer), client(client), viewDistance(DEFAULT_VIEW_DISTANCE), first(1), online(1), finished(0), backgroundFinished(0), foregroundFinished(0) { new AsynchronCall("Game Client Updates", [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; }); start(); } GameClient::~GameClient() { online = 0; updateSync.notify(); emptyForegroundQueueSync.notifyAll(); emptyBackgroundQueueSync.notifyAll(); foregroundQueueSync.notify(); backgroundQueueSync.notify(); while (!finished || !foregroundFinished || !backgroundFinished) Sleep(100); client->release(); } void GameClient::thread() { new AsynchronCall("Game Client Background", [this]() { while (online) { queueCs.lock(); if (backgroundQueue.hat(0)) { NetworkMessage* message = backgroundQueue.get(0); backgroundQueue.remove(0); queueCs.unlock(); background.lock(); message->writeTo(client->zBackgroundWriter()); background.unlock(); message->release(); } else { queueCs.unlock(); emptyBackgroundQueueSync.notifyAll(); backgroundQueueSync.wait(); } } backgroundFinished = 1; }); while (online) { queueCs.lock(); if (foregroundQueue.hat(0)) { NetworkMessage* message = foregroundQueue.get(0); foregroundQueue.remove(0); queueCs.unlock(); foreground.lock(); message->writeTo(client->zForegroundWriter()); foreground.unlock(); message->release(); } else { queueCs.unlock(); emptyForegroundQueueSync.notifyAll(); foregroundQueueSync.wait(); } } foregroundFinished = 1; } 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++; } 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(); if (first) { foreground.lock(); int id = zPlayer->getId(); client->zForegroundWriter()->schreibe( (char*)&Message::POSITION_UPDATE, 1); client->zForegroundWriter()->schreibe((char*)&id, 4); id = zPlayer->getCurrentDimensionId(); client->zForegroundWriter()->schreibe((char*)&id, 4); foreground.unlock(); first = 0; } } void GameClient::logout() { online = 0; updateSync.notify(); emptyForegroundQueueSync.notifyAll(); emptyBackgroundQueueSync.notifyAll(); foregroundQueueSync.notify(); backgroundQueueSync.notify(); } 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(NetworkMessage* response) { queueCs.lock(); if (response->isUseBackground()) { if (backgroundQueue.getEintragAnzahl() > 20) { queueCs.unlock(); emptyBackgroundQueueSync.wait(); queueCs.lock(); } backgroundQueue.add(response); queueCs.unlock(); backgroundQueueSync.notify(); } else { if (foregroundQueue.getEintragAnzahl() > 100) { queueCs.unlock(); std::cout << "WARNING: Game paused because nework connection to " << zPlayer->getName() << " is to slow.\n"; ZeitMesser m; m.messungStart(); emptyForegroundQueueSync.wait(); m.messungEnde(); std::cout << "WARNING: Game resumed after " << m.getSekunden() << " seconds.\n"; queueCs.lock(); } foregroundQueue.add(response); queueCs.unlock(); foregroundQueueSync.notify(); } } Player* GameClient::zEntity() const { return zPlayer; } void GameClient::sendTypes() { foreground.lock(); int count = StaticRegistry::INSTANCE.getCount(); client->zForegroundWriter()->schreibe((char*)&count, 4); for (int i = 0; i < count; i++) { BlockType* t = StaticRegistry::INSTANCE.zElement(i); t->writeTypeInfo(client->zForegroundWriter()); } count = 0; for (int i = 0; i < StaticRegistry::INSTANCE.getCount(); i++) { if (StaticRegistry::INSTANCE.zElement(i)) count++; } client->zForegroundWriter()->schreibe((char*)&count, 4); for (int i = 0; i < StaticRegistry::INSTANCE.getCount(); i++) { if (StaticRegistry::INSTANCE.zElement(i)) { ItemType* t = StaticRegistry::INSTANCE.zElement(i); int id = t->getId(); client->zForegroundWriter()->schreibe((char*)&id, 4); char len = (char)t->getName().getLength(); client->zForegroundWriter()->schreibe((char*)&len, 1); client->zForegroundWriter()->schreibe(t->getName().getText(), len); short tlen = (short)t->getTooltipUIML().getLength(); client->zForegroundWriter()->schreibe((char*)&tlen, 2); client->zForegroundWriter()->schreibe( t->getTooltipUIML().getText(), tlen); t->getModel().writeTo(client->zForegroundWriter()); } } count = StaticRegistry::INSTANCE.getCount(); client->zForegroundWriter()->schreibe((char*)&count, 4); for (int i = 0; i < count; i++) { EntityType* t = StaticRegistry::INSTANCE.zElement(i); int id = t->getId(); client->zForegroundWriter()->schreibe((char*)&id, 4); t->getModel().writeTo(client->zForegroundWriter()); } foreground.unlock(); } 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), totalTickTime(0), tickCounter(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 (const char* n = name; *n; n++) seed += (int)pow((float)*n * 31, (float)++index); generator = new WorldGenerator(seed); loader = new WorldLoader(); recipies.loadRecipies("data/recipies"); } void Game::thread() { ZeitMesser waitForLock; ZeitMesser removeOldClients; ZeitMesser tickEntities; ZeitMesser worldUpdates; ZeitMesser clientReply; ZeitMesser removeOldChunks; ZeitMesser m; while (!stop) { m.messungStart(); ticker->nextTick(); actionsCs.lock(); while (actions.getEintragAnzahl() > 0) { actions.get(0)(); actions.remove(0); } actionsCs.unlock(); Array removed; double waitTotal = 0; waitForLock.messungStart(); cs.lock(); waitForLock.messungEnde(); waitTotal += waitForLock.getSekunden(); removeOldClients.messungStart(); int index = 0; for (auto player : *clients) { if (!player->isOnline()) { std::cout << "player " << player->zEntity()->getName() << " disconnected.\n"; Datei pFile; pFile.setDatei( path + "/player/" + player->zEntity()->getName()); pFile.erstellen(); if (pFile.open(Datei::Style::schreiben)) StaticRegistry::INSTANCE .zElement(EntityTypeEnum::PLAYER) ->saveEntity(player->zEntity(), &pFile); pFile.close(); removed.add(index, 0); Dimension* dim = zDimension(player->zEntity()->getCurrentDimensionId()); dim->removeSubscriptions(player->zEntity()); this->requestWorldUpdate( new EntityRemovedUpdate(player->zEntity()->getId(), player->zEntity()->getCurrentDimensionId(), player->zEntity()->getPosition())); } index++; } for (auto i : removed) clients->remove(i); removeOldClients.messungEnde(); cs.unlock(); tickEntities.messungStart(); for (auto dim : *dimensions) dim->tickEntities(); tickEntities.messungEnde(); waitForLock.messungStart(); cs.lock(); waitForLock.messungEnde(); waitTotal += waitForLock.getSekunden(); worldUpdates.messungStart(); 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); } worldUpdates.messungEnde(); cs.unlock(); clientReply.messungStart(); for (auto client : *clients) client->reply(); clientReply.messungEnde(); waitForLock.messungStart(); cs.lock(); waitForLock.messungEnde(); waitTotal += waitForLock.getSekunden(); removeOldChunks.messungStart(); for (auto dim : *dimensions) dim->removeOldChunks(); removeOldChunks.messungEnde(); cs.unlock(); m.messungEnde(); double sec = m.getSekunden(); tickCounter++; totalTickTime += sec; if (tickCounter >= 1000) { std::cout << "Average Tick time: " << (totalTickTime / 1000) << "\n"; if ((totalTickTime / 1000) * 20 > 1) { std::cout << "The game runns slower than normal.\n"; } else { std::cout << "No performance issues detected.\n"; } totalTickTime = 0; tickCounter = 0; } if (sec < 0.05) Sleep((int)((0.05 - sec) * 1000)); else if (sec > 1) { std::cout << "WARNING: tick needed " << sec << " seconds. The game will run sower then normal.\n"; std::cout << "waiting: " << waitTotal << "\nremoveOldClients: " << removeOldClients.getSekunden() << "\ntickEntities:" << tickEntities.getSekunden() << "\nworldUpdates: " << worldUpdates.getSekunden() << "\nclientReply: " << clientReply.getSekunden() << "\nremoveOldChunks:" << removeOldChunks.getSekunden() << "\n"; } } save(); generator->exitAndWait(); loader->exitAndWait(); ticker->exitAndWait(); for (Dimension* dim : *dimensions) dim->requestStopAndWait(); std::cout << "Game thread exited\n"; } void Game::api(Framework::InMemoryBuffer* zRequest, GameClient* zOrigin) { char type; zRequest->lese(&type, 1); NetworkMessage* response = new NetworkMessage(); switch (type) { case 1: // world { Dimension* dim = zDimension(zOrigin->zEntity()->getCurrentDimensionId()); if (!dim) { dim = new Dimension( zOrigin->zEntity()->getCurrentDimensionId()); addDimension(dim); } dim->api(zRequest, response, zOrigin->zEntity()); break; } case 2: // player zOrigin->zEntity()->playerApi(zRequest, response); break; case 3: // entity { int id; zRequest->lese((char*)&id, 4); for (Dimension* dim : *dimensions) { Entity* entity = dim->zEntity(id); if (entity) { entity->api(zRequest, response, zOrigin->zEntity()); break; } } break; } case 4: { // inventory bool isEntity; zRequest->lese((char*)&isEntity, 1); Inventory* target; if (isEntity) { int id; zRequest->lese((char*)&id, 4); target = zEntity(id); } else { int dim; Vec3 pos; zRequest->lese((char*)&dim, 4); zRequest->lese((char*)&pos.x, 4); zRequest->lese((char*)&pos.y, 4); zRequest->lese((char*)&pos.z, 4); target = zBlockAt(pos, dim); } if (target) target->inventoryApi(zRequest, response, zOrigin->zEntity()); break; } case 5: { // crafting uiml request int id; zRequest->lese((char*)&id, 4); Text uiml = recipies.getCrafingUIML( StaticRegistry::INSTANCE.zElement(id)); Text dialogId = "crafting_"; dialogId += id; response->openDialog(dialogId); int msgSize = 4 + uiml.getLength(); char* msg = new char[msgSize]; *(int*)msg = uiml.getLength(); memcpy(msg + 4, uiml.getText(), uiml.getLength()); response->setMessage(msg, msgSize); break; } default: std::cout << "received unknown api request in game with type " << (int)type << "\n"; } if (!response->isEmpty()) { if (response->isBroadcast()) broadcastMessage(response); else zOrigin->sendResponse(response); } else { response->release(); } } void Game::updateLightning(int dimensionId, Vec3 location) { Dimension* zDim = zDimension(dimensionId); if (zDim) zDim->updateLightning(location); } void Game::updateLightningWithoutWait(int dimensionId, Vec3 location) { Dimension* zDim = zDimension(dimensionId); if (zDim) zDim->updateLightningWithoutWait(location); } void Game::broadcastMessage(NetworkMessage* response) { for (auto client : *clients) client->sendResponse( dynamic_cast(response->getThis())); } void Game::sendMessage(NetworkMessage* response, Entity* zTargetPlayer) { for (auto client : *clients) { if (client->zEntity()->getId() == zTargetPlayer->getId()) { client->sendResponse(response); return; } } response->release(); } 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; } bool Game::checkPlayer(Framework::Text name, Framework::Text secret) { Datei pFile; pFile.setDatei(path + "/player/" + name + ".key"); if (!pFile.existiert()) { if (!secret.getLength()) return 1; else { std::cout << "player " << name.getText() << " tryed to connect with an invalid secret.\n"; return 0; } } pFile.open(Datei::Style::lesen); char* buffer = new char[(int)pFile.getSize()]; pFile.lese(buffer, (int)pFile.getSize()); bool eq = 1; int sLen = secret.getLength(); for (int i = 0; i < pFile.getSize(); i++) // !!SECURITY!! runtime should not be dependent on the position of // the first unequal character in the secret eq &= buffer[i] == (sLen > i ? secret[i] : ~buffer[i]); delete[] buffer; pFile.close(); if (!eq) { std::cout << "player " << name.getText() << " tryed to connect with an invalid secret.\n"; } return eq; } bool Game::existsPlayer(Framework::Text name) { Datei pFile; pFile.setDatei(path + "/player/" + name + ".key"); return pFile.existiert(); } Framework::Text Game::createPlayer(Framework::Text name) { Datei pFile; pFile.setDatei(path + "/player/" + name + ".key"); if (!pFile.existiert()) { pFile.erstellen(); int keyLen; char* key = randomKey(keyLen); pFile.open(Datei::Style::schreiben); pFile.schreibe(key, keyLen); pFile.close(); Text res = ""; for (int i = 0; i < keyLen; i++) res.append(key[i]); delete[] key; return res; } return ""; } GameClient* Game::addPlayer(FCKlient* client, Framework::Text name) { cs.lock(); Datei pFile; pFile.setDatei(path + "/player/" + name); std::cout << "player " << name.getText() << " connected.\n"; Player* player; bool isNew = 0; if (!pFile.existiert() || !pFile.open(Datei::Style::lesen)) { player = (Player*)StaticRegistry::INSTANCE .zElement(EntityTypeEnum::PLAYER) ->createEntityAt( Vec3(0.5, 0.5, 0), DimensionEnum::OVERWORLD); player->setName(name); isNew = 1; } else { player = (Player*)StaticRegistry::INSTANCE .zElement(EntityTypeEnum::PLAYER) ->loadEntity(&pFile); pFile.close(); } if (player->getId() >= nextEntityId) { nextEntityId = player->getId() + 1; } GameClient* gameClient = new GameClient(player, client); gameClient->sendTypes(); clients->add(gameClient); if (!zDimension(player->getCurrentDimensionId())) { this->addDimension(new Dimension(player->getCurrentDimensionId())); } // subscribe the new player as an observer of the new chunk Dimension* dim = zDimension(player->getCurrentDimensionId()); InMemoryBuffer* buffer = new InMemoryBuffer(); buffer->schreibe("\0", 1); Punkt center = getChunkCenter( (int)player->getPosition().x, (int)player->getPosition().y); buffer->schreibe((char*)¢er.x, 4); buffer->schreibe((char*)¢er.y, 4); buffer->schreibe("\0", 1); dim->api(buffer, 0, player); buffer->release(); while (isNew && !dim->zChunk(getChunkCenter( (int)player->getPosition().x, (int)player->getPosition().y))) { cs.unlock(); Sleep(1000); cs.lock(); } if (isNew) { Either b = BlockTypeEnum::AIR; 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); cs.unlock(); return result; } void Game::blockTargetChanged(Block* zBlock) { for (GameClient* client : *this->clients) { if (client->zEntity()->zTarget() && client->zEntity()->zTarget()->isBlock( zBlock->getPos(), NO_DIRECTION)) { client->zEntity()->onTargetChange(); } } } void Game::entityTargetChanged(Entity* zEntity) { for (GameClient* client : *this->clients) { if (client->zEntity()->zTarget() && client->zEntity()->zTarget()->isEntity(zEntity->getId())) { client->zEntity()->onTargetChange(); } } } 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); std::cout << "Game was saved\n"; } 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::zEntity(int id) const { for (Dimension* d : *dimensions) { Entity* e = d->zEntity(id); if (e) return e; } // for new players that are currently loading for (GameClient* client : *clients) { if (client->zEntity()->getId() == id) { return client->zEntity(); } } 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); } const RecipieLoader& Game::getRecipies() const { return recipies; } void Game::doLater(std::function action) { actionsCs.lock(); actions.add(action); actionsCs.unlock(); } TickOrganizer* Game::zTickOrganizer() const { return ticker; }