mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 13:30:06 +00:00
4ece1bb89b
This was initially a merge commit that got ironed out by rebase cleanup. Notable changes are: - Copying EntityRef instead of referencing it - Some changes to includes
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);
|
|
}
|