mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-16 19:20:05 +00:00
215da0130d
Was getting frustrated by the inconsistency in our include statements, which were causing me problems. As a result, I went through and manually re-organized every include statement in non-core files. I'm just gonna copy my rant from Discord: FOR HEADER FILES (.hpp): - everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp - you may NOT include ANYTHING ELSE FOR SOURCE FILES (.cpp): - you can #include whatever you want as long as the partner header is included first - anything that gets included by another include is fair game - redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean. the point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues
345 lines
12 KiB
C++
345 lines
12 KiB
C++
#include "Chunking.hpp"
|
|
|
|
#include "MobAI.hpp"
|
|
#include "NPCManager.hpp"
|
|
|
|
#include <assert.h>
|
|
|
|
using namespace Chunking;
|
|
|
|
/*
|
|
* The initial chunkPos value before a player is placed into the world.
|
|
*/
|
|
const ChunkPos Chunking::INVALID_CHUNK = {};
|
|
|
|
std::map<ChunkPos, Chunk*> Chunking::chunks;
|
|
|
|
static void newChunk(ChunkPos pos) {
|
|
if (chunkExists(pos)) {
|
|
std::cout << "[WARN] Tried to create a chunk that already exists" << std::endl;
|
|
return;
|
|
}
|
|
|
|
Chunk *chunk = new Chunk();
|
|
chunks[pos] = chunk;
|
|
|
|
// add the chunk to the cache of all players and NPCs in the surrounding chunks
|
|
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
|
for (Chunk* c : surroundings)
|
|
for (const EntityRef& ref : c->entities)
|
|
ref.getEntity()->viewableChunks.insert(chunk);
|
|
}
|
|
|
|
static void deleteChunk(ChunkPos pos) {
|
|
if (!chunkExists(pos)) {
|
|
std::cout << "[WARN] Tried to delete a chunk that doesn't exist" << std::endl;
|
|
return;
|
|
}
|
|
|
|
Chunk* chunk = chunks[pos];
|
|
|
|
// remove the chunk from the cache of all players and NPCs in the surrounding chunks
|
|
std::set<Chunk*> surroundings = getViewableChunks(pos);
|
|
for(Chunk* c : surroundings)
|
|
for (const EntityRef& ref : c->entities)
|
|
ref.getEntity()->viewableChunks.erase(chunk);
|
|
|
|
chunks.erase(pos); // remove from map
|
|
delete chunk; // free from memory
|
|
}
|
|
|
|
void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
|
if (!chunkExists(chunkPos))
|
|
return; // shouldn't happen
|
|
|
|
chunks[chunkPos]->entities.insert(ref);
|
|
|
|
if (ref.kind == EntityKind::PLAYER)
|
|
chunks[chunkPos]->nplayers++;
|
|
}
|
|
|
|
void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
|
if (!chunkExists(chunkPos))
|
|
return; // do nothing if chunk doesn't even exist
|
|
|
|
Chunk* chunk = chunks[chunkPos];
|
|
|
|
chunk->entities.erase(ref); // gone
|
|
|
|
if (ref.kind == EntityKind::PLAYER)
|
|
chunks[chunkPos]->nplayers--;
|
|
assert(chunks[chunkPos]->nplayers >= 0);
|
|
|
|
// if chunk is completely empty, free it
|
|
if (chunk->entities.size() == 0)
|
|
deleteChunk(chunkPos);
|
|
}
|
|
|
|
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
|
Entity *ent = ref.getEntity();
|
|
bool alive = ent->isExtant();
|
|
|
|
// TODO: maybe optimize this, potentially using AROUND packets?
|
|
for (Chunk *chunk : chnks) {
|
|
for (const EntityRef& otherRef : chunk->entities) {
|
|
// skip oneself
|
|
if (ref == otherRef)
|
|
continue;
|
|
|
|
Entity *other = otherRef.getEntity();
|
|
|
|
// notify all visible players of the existence of this Entity
|
|
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
|
ent->enterIntoViewOf(otherRef.sock);
|
|
}
|
|
|
|
// notify this *player* of the existence of all visible Entities
|
|
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
|
other->enterIntoViewOf(ref.sock);
|
|
}
|
|
|
|
// for mobs, increment playersInView
|
|
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
|
((Mob*)ent)->playersInView++;
|
|
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
|
((Mob*)other)->playersInView++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
|
Entity *ent = ref.getEntity();
|
|
bool alive = ent->isExtant();
|
|
|
|
// TODO: same as above
|
|
for (Chunk *chunk : chnks) {
|
|
for (const EntityRef& otherRef : chunk->entities) {
|
|
// skip oneself
|
|
if (ref == otherRef)
|
|
continue;
|
|
|
|
Entity *other = otherRef.getEntity();
|
|
|
|
// notify all visible players of the departure of this Entity
|
|
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
|
ent->disappearFromViewOf(otherRef.sock);
|
|
}
|
|
|
|
// notify this *player* of the departure of all visible Entities
|
|
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
|
other->disappearFromViewOf(ref.sock);
|
|
}
|
|
|
|
// for mobs, decrement playersInView
|
|
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
|
((Mob*)ent)->playersInView--;
|
|
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
|
((Mob*)other)->playersInView--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void emptyChunk(ChunkPos chunkPos) {
|
|
if (!chunkExists(chunkPos)) {
|
|
std::cout << "[WARN] Tried to empty chunk that doesn't exist\n";
|
|
return; // chunk doesn't exist, we don't need to do anything
|
|
}
|
|
|
|
Chunk* chunk = chunks[chunkPos];
|
|
|
|
if (chunk->nplayers > 0) {
|
|
std::cout << "[WARN] Tried to empty chunk that still had players\n";
|
|
return; // chunk doesn't exist, we don't need to do anything
|
|
}
|
|
|
|
// unspawn all of the mobs/npcs
|
|
std::set refs(chunk->entities);
|
|
for (const EntityRef& ref : refs) {
|
|
if (ref.kind == EntityKind::PLAYER)
|
|
assert(0);
|
|
|
|
// every call of this will check if the chunk is empty and delete it if so
|
|
NPCManager::destroyNPC(ref.id);
|
|
}
|
|
}
|
|
|
|
void Chunking::updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to) {
|
|
Entity* ent = ref.getEntity();
|
|
|
|
// move to other chunk's player set
|
|
untrackEntity(from, ref); // this will delete the chunk if it's empty
|
|
|
|
// if the new chunk doesn't exist, make it first
|
|
if (!chunkExists(to))
|
|
newChunk(to);
|
|
|
|
trackEntity(to, ref);
|
|
|
|
// calculate viewable chunks from both points
|
|
std::set<Chunk*> oldViewables = getViewableChunks(from);
|
|
std::set<Chunk*> newViewables = getViewableChunks(to);
|
|
std::set<Chunk*> toExit, toEnter;
|
|
|
|
/*
|
|
* Calculate diffs. This is done to prevent phasing on chunk borders.
|
|
* toExit will contain old viewables - new viewables, so the player will only be exited in chunks that are out of sight.
|
|
* toEnter contains the opposite: new viewables - old viewables, chunks where we previously weren't visible from before.
|
|
*/
|
|
std::set_difference(oldViewables.begin(), oldViewables.end(), newViewables.begin(), newViewables.end(),
|
|
std::inserter(toExit, toExit.end())); // chunks we must be EXITed from (old - new)
|
|
std::set_difference(newViewables.begin(), newViewables.end(), oldViewables.begin(), oldViewables.end(),
|
|
std::inserter(toEnter, toEnter.end())); // chunks we must be ENTERed into (new - old)
|
|
|
|
// update views
|
|
removeEntityFromChunks(toExit, ref);
|
|
addEntityToChunks(toEnter, ref);
|
|
|
|
ent->chunkPos = to; // update cached chunk position
|
|
// updated cached viewable chunks
|
|
ent->viewableChunks.clear();
|
|
ent->viewableChunks.insert(newViewables.begin(), newViewables.end());
|
|
}
|
|
|
|
bool Chunking::chunkExists(ChunkPos chunk) {
|
|
return chunks.find(chunk) != chunks.end();
|
|
}
|
|
|
|
ChunkPos Chunking::chunkPosAt(int posX, int posY, uint64_t instanceID) {
|
|
return ChunkPos(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
|
|
}
|
|
|
|
std::set<Chunk*> Chunking::getViewableChunks(ChunkPos chunk) {
|
|
std::set<Chunk*> chnks;
|
|
|
|
int x, y;
|
|
uint64_t inst;
|
|
std::tie(x, y, inst) = chunk;
|
|
|
|
// grabs surrounding chunks if they exist
|
|
for (int i = -1; i < 2; i++) {
|
|
for (int z = -1; z < 2; z++) {
|
|
ChunkPos pos = ChunkPos(x+i, y+z, inst);
|
|
|
|
// if chunk exists, add it to the set
|
|
if (chunkExists(pos))
|
|
chnks.insert(chunks[pos]);
|
|
}
|
|
}
|
|
|
|
return chnks;
|
|
}
|
|
|
|
/*
|
|
* inefficient algorithm to get all chunks from a specific instance
|
|
*/
|
|
std::vector<ChunkPos> Chunking::getChunksInMap(uint64_t mapNum) {
|
|
std::vector<ChunkPos> chnks;
|
|
|
|
for (auto it = chunks.begin(); it != chunks.end(); it++) {
|
|
if (std::get<2>(it->first) == mapNum) {
|
|
chnks.push_back(it->first);
|
|
}
|
|
}
|
|
|
|
return chnks;
|
|
}
|
|
|
|
/*
|
|
* Used only for eggs; use npc->playersInView for everything visible
|
|
*/
|
|
bool Chunking::inPopulatedChunks(std::set<Chunk*>* chnks) {
|
|
for (auto it = chnks->begin(); it != chnks->end(); it++) {
|
|
if ((*it)->nplayers > 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Chunking::createInstance(uint64_t instanceID) {
|
|
std::vector<ChunkPos> templateChunks = getChunksInMap(MAPNUM(instanceID)); // base instance chunks
|
|
|
|
// only instantiate if the instance doesn't exist already
|
|
if (getChunksInMap(instanceID).size() != 0) {
|
|
std::cout << "Instance " << instanceID << " already exists" << std::endl;
|
|
return;
|
|
}
|
|
|
|
std::cout << "Creating instance " << instanceID << std::endl;
|
|
for (ChunkPos &coords : templateChunks) {
|
|
for (const EntityRef& ref : chunks[coords]->entities) {
|
|
if (ref.kind == EntityKind::PLAYER)
|
|
continue;
|
|
|
|
int npcID = ref.id;
|
|
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
|
|
|
|
// make a copy of each NPC in the template chunks and put them in the new instance
|
|
if (baseNPC->kind == EntityKind::MOB) {
|
|
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
|
|
continue; // follower; don't copy individually
|
|
|
|
Mob* newMob = new Mob(baseNPC->x, baseNPC->y, baseNPC->z, baseNPC->angle,
|
|
instanceID, baseNPC->type, NPCManager::NPCData[baseNPC->type], NPCManager::nextId--);
|
|
NPCManager::NPCs[newMob->id] = newMob;
|
|
|
|
// if in a group, copy over group members as well
|
|
if (((Mob*)baseNPC)->groupLeader != 0) {
|
|
newMob->groupLeader = newMob->id; // set leader ID for new leader
|
|
Mob* mobData = (Mob*)baseNPC;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (mobData->groupMember[i] != 0) {
|
|
int followerID = NPCManager::nextId--; // id for follower
|
|
BaseNPC* baseFollower = NPCManager::NPCs[mobData->groupMember[i]]; // follower from template
|
|
// new follower instance
|
|
Mob* newMobFollower = new Mob(baseFollower->x, baseFollower->y, baseFollower->z, baseFollower->angle,
|
|
instanceID, baseFollower->type, NPCManager::NPCData[baseFollower->type], followerID);
|
|
// add follower to NPC maps
|
|
NPCManager::NPCs[followerID] = newMobFollower;
|
|
// set follower-specific properties
|
|
newMobFollower->groupLeader = newMob->id;
|
|
newMobFollower->offsetX = ((Mob*)baseFollower)->offsetX;
|
|
newMobFollower->offsetY = ((Mob*)baseFollower)->offsetY;
|
|
// add follower copy to leader copy
|
|
newMob->groupMember[i] = followerID;
|
|
NPCManager::updateNPCPosition(followerID, baseFollower->x, baseFollower->y, baseFollower->z,
|
|
instanceID, baseFollower->angle);
|
|
}
|
|
}
|
|
}
|
|
NPCManager::updateNPCPosition(newMob->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
|
instanceID, baseNPC->angle);
|
|
} else {
|
|
BaseNPC* newNPC = new BaseNPC(baseNPC->angle, instanceID, baseNPC->type, NPCManager::nextId--);
|
|
NPCManager::NPCs[newNPC->id] = newNPC;
|
|
NPCManager::updateNPCPosition(newNPC->id, baseNPC->x, baseNPC->y, baseNPC->z,
|
|
instanceID, baseNPC->angle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void destroyInstance(uint64_t instanceID) {
|
|
std::vector<ChunkPos> instanceChunks = getChunksInMap(instanceID);
|
|
std::cout << "Deleting instance " << instanceID << " (" << instanceChunks.size() << " chunks)" << std::endl;
|
|
for (ChunkPos& coords : instanceChunks) {
|
|
emptyChunk(coords);
|
|
}
|
|
}
|
|
|
|
void Chunking::destroyInstanceIfEmpty(uint64_t instanceID) {
|
|
if (PLAYERID(instanceID) == 0)
|
|
return; // don't clean up overworld/IZ chunks
|
|
|
|
std::vector<ChunkPos> sourceChunkCoords = getChunksInMap(instanceID);
|
|
|
|
for (ChunkPos& coords : sourceChunkCoords) {
|
|
Chunk* chunk = chunks[coords];
|
|
|
|
if (chunk->nplayers > 0)
|
|
return; // there are still players inside
|
|
}
|
|
|
|
destroyInstance(instanceID);
|
|
}
|