#pragma once #include #include "Abstract.hpp" #include #include #include #include #include #include #include namespace LV::Server { class RemoteClient; class GameServer; class World; struct ServerObjectPos { WorldId_t WorldId; Pos::Object ObjectPos; }; /* Сфера в которой отслеживаются события игроком */ struct ContentViewCircle { WorldId_t WorldId; // Позиция в чанках glm::i16vec3 Pos; // (Единица равна размеру чанка) в квадрате int32_t Range; inline int32_t sqrDistance(Pos::GlobalRegion regionPos) const { glm::i32vec3 vec = {Pos.x-((regionPos.X << 4) | 0b1000), Pos.y-((regionPos.Y << 4) | 0b1000), Pos.z-((regionPos.Z << 4) | 0b1000)}; return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z; }; inline int32_t sqrDistance(Pos::GlobalChunk chunkPos) const { glm::i32vec3 vec = {Pos.x-chunkPos.X, Pos.y-chunkPos.Y, Pos.z-chunkPos.Z}; return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z; }; inline int64_t sqrDistance(Pos::Object objectPos) const { glm::i32vec3 vec = {Pos.x-(objectPos.x >> 20), Pos.y-(objectPos.y >> 20), Pos.z-(objectPos.z >> 20)}; return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z; }; bool isIn(Pos::GlobalRegion regionPos) const { return sqrDistance(regionPos) < Range+192; // (8×sqrt(3))^2 } bool isIn(Pos::GlobalChunk chunkPos) const { return sqrDistance(chunkPos) < Range+3; // (1×sqrt(3))^2 } bool isIn(Pos::Object objectPos, int32_t size = 0) const { return sqrDistance(objectPos) < Range+3+size; } }; // Регион -> чанки попавшие под обозрение Pos::Local16_u using ContentViewWorld = std::map>; // 1 - чанк виден, 0 - не виден struct ContentViewGlobal_DiffInfo; struct ContentViewGlobal : public std::map { // Вычисляет половинную разницу между текущей и предыдущей области видимости // Возвращает области, которые появились по отношению к old, чтобы получить области потерянные из виду поменять местами *this и old ContentViewGlobal_DiffInfo calcDiffWith(const ContentViewGlobal &old) const; }; struct ContentViewGlobal_DiffInfo { // Новые увиденные чанки ContentViewGlobal View; // Регионы std::unordered_map> Regions; // Миры std::vector Worlds; bool empty() const { return View.empty() && Regions.empty() && Worlds.empty(); } }; inline ContentViewGlobal_DiffInfo ContentViewGlobal::calcDiffWith(const ContentViewGlobal &old) const { ContentViewGlobal_DiffInfo newView; // Рассматриваем разницу меж мирами for(const auto &[newWorldId, newWorldView] : *this) { auto oldWorldIter = old.find(newWorldId); if(oldWorldIter == old.end()) { // В старом состоянии нет мира newView.View[newWorldId] = newWorldView; newView.Worlds.push_back(newWorldId); auto &newRegions = newView.Regions[newWorldId]; for(const auto &[regionPos, _] : newWorldView) newRegions.push_back(regionPos); } else { const std::map> &newRegions = newWorldView; const std::map> &oldRegions = oldWorldIter->second; std::map> *diffRegions = nullptr; // Рассматриваем разницу меж регионами for(const auto &[newRegionPos, newRegionBitField] : newRegions) { auto oldRegionIter = oldRegions.find(newRegionPos); if(oldRegionIter == oldRegions.end()) { // В старой описи мира нет региона if(!diffRegions) diffRegions = &newView.View[newWorldId]; (*diffRegions)[newRegionPos] = newRegionBitField; newView.Regions[newWorldId].push_back(newRegionPos); } else { const std::bitset<4096> &oldChunks = oldRegionIter->second; std::bitset<4096> chunks = (~oldChunks) & newRegionBitField; // Останется поле с новыми чанками if(chunks._Find_first() != chunks.size()) { // Есть новые чанки if(!diffRegions) diffRegions = &newView.View[newWorldId]; (*diffRegions)[newRegionPos] = chunks; } } } } } return newView; } /* Мост контента, для отслеживания событий из удалённх точек По типу портала, через который можно видеть контент на расстоянии */ struct ContentBridge { /* false -> Из точки From видно контент из точки To true -> Контент виден в обе стороны */ bool IsTwoWay = false; WorldId_t LeftWorld; // Позиция в чанках glm::i16vec3 LeftPos; WorldId_t RightWorld; // Позиция в чанках glm::i16vec3 RightPos; }; /* Игрок */ class ContentEventController { private: struct SubscribedObj { // Используется регионами std::vector Portals; std::unordered_map>> Entities; } Subscribed; public: // Управляется сервером std::unique_ptr Remote; // Регионы сюда заглядывают // Каждый такт значения изменений обновляются GameServer'ом // Объявленная в чанках территория точно отслеживается (активная зона) ContentViewGlobal ContentViewState; ContentViewGlobal_DiffInfo ContentView_NewView, ContentView_LostView; // size_t CVCHash = 0; // Хэш для std::vector // std::unordered_map> SubscribedRegions; public: ContentEventController(std::unique_ptr &&remote); // Измеряется в чанках в радиусе (активная зона) uint16_t getViewRangeActive() const; // Измеряется в чанках в радиусе (Декоративная зона) + getViewRangeActive() uint16_t getViewRangeBackground() const; ServerObjectPos getLastPos() const; ServerObjectPos getPos() const; // Проверка на необходимость подгрузки новых определений миров // и очистка клиента от не наблюдаемых данных void checkContentViewChanges(); // Здесь приходят частично фильтрованные события // Фильтровать не отслеживаемые миры void onWorldUpdate(WorldId_t worldId, World *worldObj); // Нужно фильтровать неотслеживаемые чанки void onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map*> &chunks); void onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map*> &chunks); void onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map &chunks); void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set &enter, const std::unordered_set &lost); void onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos, LocalEntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, LocalEntityId_t newId); void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::vector &entities); void onPortalEnterLost(const std::vector &enter, const std::vector &lost); void onPortalUpdates(const std::vector &portals); inline const SubscribedObj& getSubscribed() { return Subscribed; }; }; } namespace std { template <> struct hash { std::size_t operator()(const LV::Server::ServerObjectPos& obj) const { return std::hash()(obj.WorldId) ^ std::hash()(obj.ObjectPos.x) ^ std::hash()(obj.ObjectPos.y) ^ std::hash()(obj.ObjectPos.z); } }; template <> struct hash { size_t operator()(const LV::Server::ContentViewCircle& obj) const noexcept { // Используем стандартную функцию хеширования для uint32_t, glm::i16vec3 и int32_t auto worldIdHash = std::hash{}(obj.WorldId) << 32; auto posHash = std::hash{}(obj.Pos.x) ^ (std::hash{}(obj.Pos.y) << 16) ^ (std::hash{}(obj.Pos.z) << 32); auto rangeHash = std::hash{}(obj.Range); return worldIdHash ^ posHash ^ (~rangeHash << 32); } }; }