#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("Game Client", [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 }, zPlayer->getCurrentDimensionId())) { if (zResponse->isUseBackground()) { background.lock(); client->zBackgroundWriter()->schreibe((char*)&Message::API_MESSAGE, 1); zResponse->writeTo(client->zBackgroundWriter()); background.unlock(); } else { foreground.lock(); client->zForegroundWriter()->schreibe((char*)&Message::API_MESSAGE, 1); zResponse->writeTo(client->zForegroundWriter()); foreground.unlock(); } } } 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); int id = t->getId(); client->zForegroundWriter()->schreibe((char*)&id, 4); bool inst = t->doesNeedClientInstance(); client->zForegroundWriter()->schreibe((char*)&inst, 1); int maxHp = t->getInitialMaxHP(); client->zForegroundWriter()->schreibe((char*)&maxHp, 4); t->getModel().writeTo(client->zForegroundWriter()); } count = StaticRegistry::INSTANCE.getCount(); client->zForegroundWriter()->schreibe((char*)&count, 4); for (int i = 0; i < count; i++) { ItemType* t = StaticRegistry::INSTANCE.zElement(i); int id = t->getId(); client->zForegroundWriter()->schreibe((char*)&id, 4); 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) { 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(); recipies.loadRecipies("data/recipies"); } 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()->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); break; } } 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); gameClient->sendTypes(); 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); }