the whole game
This commit is contained in:
632
src/world/level/storage/ExternalFileLevelStorage.cpp
Executable file
632
src/world/level/storage/ExternalFileLevelStorage.cpp
Executable file
@@ -0,0 +1,632 @@
|
||||
#if !defined(DEMO_MODE) && !defined(APPLE_DEMO_PROMOTION)
|
||||
|
||||
#include "LevelData.h"
|
||||
#include "RegionFile.h"
|
||||
#include "ExternalFileLevelStorage.h"
|
||||
#include "FolderMethods.h"
|
||||
#include "../chunk/LevelChunk.h"
|
||||
#include "../Level.h"
|
||||
#include "../LevelConstants.h"
|
||||
#include "../tile/TreeTile.h"
|
||||
#include "../../entity/EntityFactory.h"
|
||||
#include "../../../nbt/NbtIo.h"
|
||||
#include "../../../util/RakDataIO.h"
|
||||
#include "../../../raknet/GetTime.h"
|
||||
#include "../tile/entity/TileEntity.h"
|
||||
|
||||
static const int ChunkVersion_Light = 1;
|
||||
static const int ChunkVersion_Entity = 2;
|
||||
|
||||
const char* const fnLevelDatOld = "level.dat_old";
|
||||
const char* const fnLevelDatNew = "level.dat_new";
|
||||
const char* const fnLevelDat = "level.dat";
|
||||
const char* const fnPlayerDat = "player.dat";
|
||||
|
||||
//
|
||||
// Helpers for converting old levels to newer
|
||||
//
|
||||
class LevelConverters
|
||||
{
|
||||
public:
|
||||
// Replacing old Cloth (id based) with new Cloth (data based)
|
||||
static bool v1_ClothIdToClothData(LevelChunk* c) {
|
||||
bool changed = false;
|
||||
unsigned char* blocks = c->getBlockData();
|
||||
unsigned char newTile = Tile::cloth->id;
|
||||
|
||||
for (int i = 0; i < 16*16*128; ++i) {
|
||||
unsigned char oldTile = blocks[i];
|
||||
//Tile::cloth_00 to Tile::cloth_61
|
||||
if (oldTile >= 101 && oldTile <= 115) {
|
||||
int color = 0xf - (oldTile - 101);
|
||||
blocks[i] = newTile;
|
||||
c->data.set(i, color);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
// Replacing unavailable blocks with "Update!" blocks
|
||||
static bool ReplaceUnavailableBlocks(LevelChunk* c) {
|
||||
//int st = getTimeMs();
|
||||
|
||||
bool changed = false;
|
||||
unsigned char* blocks = c->getBlockData();
|
||||
|
||||
for (int i = 0; i < 16*16*128; ++i) {
|
||||
unsigned char oldTile = blocks[i];
|
||||
unsigned char newTile = Tile::transformToValidBlockId(oldTile);
|
||||
if (oldTile != newTile) {
|
||||
blocks[i] = newTile;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
//int et = getTimeMs();
|
||||
//LOGI("time: %d\n", et - st);
|
||||
return changed;
|
||||
}
|
||||
|
||||
//static bool ConvertPlayerDatToLevelDat() {
|
||||
// return false;
|
||||
//}
|
||||
};
|
||||
|
||||
|
||||
ExternalFileLevelStorage::ExternalFileLevelStorage(const std::string& levelId, const std::string& fullPath)
|
||||
: levelId(levelId),
|
||||
levelPath(fullPath),
|
||||
loadedLevelData(NULL),
|
||||
regionFile(NULL),
|
||||
entitiesFile(NULL),
|
||||
tickCount(0),
|
||||
lastSavedEntitiesTick(-999999),
|
||||
level(NULL),
|
||||
loadedStorageVersion(SharedConstants::StorageVersion)
|
||||
{
|
||||
createFolderIfNotExists(levelPath.c_str());
|
||||
|
||||
std::string datFileName = levelPath + "/" + fnLevelDat;
|
||||
std::string levelFileName = levelPath + "/" + fnPlayerDat;
|
||||
loadedLevelData = new LevelData();
|
||||
if (readLevelData(levelPath, *loadedLevelData))
|
||||
{
|
||||
loadedStorageVersion = loadedLevelData->getStorageVersion();
|
||||
readPlayerData(levelFileName, *loadedLevelData);
|
||||
} else {
|
||||
delete loadedLevelData;
|
||||
loadedLevelData = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ExternalFileLevelStorage::~ExternalFileLevelStorage()
|
||||
{
|
||||
delete regionFile;
|
||||
delete loadedLevelData;
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorage::saveLevelData(LevelData& levelData, std::vector<Player*>* players) {
|
||||
ExternalFileLevelStorage::saveLevelData(levelPath, levelData, players);
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorage::saveLevelData( const std::string& levelPath, LevelData& levelData, std::vector<Player*>* players )
|
||||
{
|
||||
std::string directory = levelPath + "/";
|
||||
std::string tmpFile = directory + fnLevelDatNew;
|
||||
std::string datFile = directory + fnLevelDat;
|
||||
std::string oldFile = directory + fnLevelDatOld;
|
||||
|
||||
levelData.setStorageVersion(SharedConstants::StorageVersion);
|
||||
if (!writeLevelData(tmpFile, levelData, players))
|
||||
return;
|
||||
|
||||
// Remove old backup
|
||||
remove(oldFile.c_str());
|
||||
|
||||
// If it exists, move the previous save to backup (and possibly delete it)
|
||||
if (exists(datFile.c_str())) {
|
||||
if (rename(datFile.c_str(), oldFile.c_str())) {
|
||||
LOGE("Error@saveLevelData: Couldn't move savefile to level.dat_old\n");
|
||||
return;
|
||||
}
|
||||
remove(datFile.c_str());
|
||||
}
|
||||
// Move the new save to level.dat
|
||||
if (rename(tmpFile.c_str(), datFile.c_str())) {
|
||||
LOGE("Error@saveLevelData: Couldn't move new file to level.dat\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the temporary save, if the rename didn't do it
|
||||
remove(tmpFile.c_str());
|
||||
}
|
||||
|
||||
LevelData* ExternalFileLevelStorage::prepareLevel(Level* _level)
|
||||
{
|
||||
level = _level;
|
||||
return loadedLevelData;
|
||||
}
|
||||
|
||||
bool ExternalFileLevelStorage::readLevelData(const std::string& directory, LevelData& levelData)
|
||||
{
|
||||
// Try to load level.dat
|
||||
std::string datFilename = directory + "/" + fnLevelDat;
|
||||
FILE* file = fopen(datFilename.c_str(), "rb");
|
||||
|
||||
// If that fails, try to load level.dat_old
|
||||
if (!file) {
|
||||
datFilename = directory + "/" + fnLevelDatOld;
|
||||
file = fopen(datFilename.c_str(), "rb");
|
||||
}
|
||||
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
int version = 0;
|
||||
int size = 0;
|
||||
unsigned char* data = NULL;
|
||||
|
||||
do {
|
||||
if (fread(&version, sizeof(version), 1, file) != 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (fread(&size, sizeof(size), 1, file) != 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int left = getRemainingFileSize(file);
|
||||
if (size > left || size <= 0)
|
||||
break;
|
||||
|
||||
data = new unsigned char[size];
|
||||
if (fread(data, 1, size, file) != size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (version == 1) {
|
||||
RakNet::BitStream bitStream(data, size, false);
|
||||
levelData.v1_read(bitStream, version);
|
||||
} else if (version >= 2) {
|
||||
//LOGI("---> Trying to load level with version %d\n", version);
|
||||
RakNet::BitStream bitStream(data, size, false);
|
||||
RakDataInput stream(bitStream);
|
||||
//LOGI("dat: %s\n", datFileName.c_str());
|
||||
CompoundTag* tag = NbtIo::read(&stream);
|
||||
if (tag) {
|
||||
levelData.getTagData(tag);
|
||||
tag->deleteChildren();
|
||||
delete tag;
|
||||
}
|
||||
//LOGI("<--- Finished reading level tag: %p\n", tag);
|
||||
}
|
||||
} while (false);
|
||||
|
||||
fclose(file);
|
||||
delete [] data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExternalFileLevelStorage::writeLevelData(const std::string& datFileName, LevelData& levelData, const std::vector<Player*>* players)
|
||||
{
|
||||
LOGI("Writing down level seed as: %ld\n", levelData.getSeed());
|
||||
//return true;
|
||||
|
||||
// Write level info
|
||||
FILE* file = fopen(datFileName.c_str(), "wb");
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
//if (levelData.getStorageVersion() == 1) {
|
||||
RakNet::BitStream data;
|
||||
if (levelData.getStorageVersion() == 1)
|
||||
levelData.v1_write(data);
|
||||
else {
|
||||
RakDataOutput buf(data);
|
||||
//LOGI("---> Trying to write level with version %d\n", version);
|
||||
CompoundTag* tag = NULL;
|
||||
if (players && !players->empty())
|
||||
tag = levelData.createTag(*players);
|
||||
else
|
||||
tag = levelData.createTag();
|
||||
|
||||
NbtIo::write(tag, &buf);
|
||||
tag->deleteChildren();
|
||||
delete tag;
|
||||
//LOGI("<--- Finished writing level data. Size: %d\n", fdout.bytesWritten);
|
||||
}
|
||||
|
||||
int version = levelData.getStorageVersion(); // 1
|
||||
fwrite(&version, sizeof(version), 1, file);
|
||||
int size = data.GetNumberOfBytesUsed();
|
||||
fwrite(&size, sizeof(size), 1, file);
|
||||
|
||||
fwrite(data.GetData(), 1, size, file);
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExternalFileLevelStorage::readPlayerData(const std::string& filename, LevelData& dest)
|
||||
{
|
||||
FILE* fp = fopen(filename.c_str(), "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
do {
|
||||
int version;
|
||||
if (fread(&version, 4, 1, fp) != 1)
|
||||
break;
|
||||
|
||||
int size;
|
||||
if (fread(&size, 4, 1, fp) != 1)
|
||||
break;
|
||||
|
||||
if (version == 1) {
|
||||
if (fread(&dest.playerData, 1, sizeof(dest.playerData), fp) != size)
|
||||
break;
|
||||
|
||||
// Fix coordinates
|
||||
Vec3& pos = dest.playerData.pos;
|
||||
if (pos.x < 0.5f) pos.x = 0.5f;
|
||||
if (pos.z < 0.5f) pos.z = 0.5f;
|
||||
if (pos.x > (LEVEL_WIDTH - 0.5f)) pos.x = LEVEL_WIDTH - 0.5f;
|
||||
if (pos.z > (LEVEL_DEPTH - 0.5f)) pos.z = LEVEL_DEPTH - 0.5f;
|
||||
if (pos.y < 0) pos.y = 64;
|
||||
|
||||
dest.playerDataVersion = version;
|
||||
}
|
||||
} while (false);
|
||||
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorage::tick()
|
||||
{
|
||||
tickCount++;
|
||||
if ((tickCount % 50) == 0 && level)
|
||||
{
|
||||
// look for chunks that needs to be saved
|
||||
for (int z = 0; z < CHUNK_CACHE_WIDTH; z++)
|
||||
{
|
||||
for (int x = 0; x < CHUNK_CACHE_WIDTH; x++)
|
||||
{
|
||||
LevelChunk* chunk = level->getChunk(x, z);
|
||||
if (chunk && chunk->unsaved)
|
||||
{
|
||||
int pos = x + z * CHUNK_CACHE_WIDTH;
|
||||
UnsavedChunkList::iterator prev = unsavedChunkList.begin();
|
||||
for ( ; prev != unsavedChunkList.end(); ++prev)
|
||||
{
|
||||
if ((*prev).pos == pos)
|
||||
{
|
||||
// the chunk has been modified again, so update its time
|
||||
(*prev).addedToList = RakNet::GetTimeMS();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prev == unsavedChunkList.end())
|
||||
{
|
||||
UnsavedLevelChunk unsaved;
|
||||
unsaved.pos = pos;
|
||||
unsaved.addedToList = RakNet::GetTimeMS();
|
||||
unsaved.chunk = chunk;
|
||||
unsavedChunkList.push_back(unsaved);
|
||||
}
|
||||
chunk->unsaved = false; // not actually saved, but in our working list at least
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
savePendingUnsavedChunks(2);
|
||||
}
|
||||
if (tickCount - lastSavedEntitiesTick > (60 * SharedConstants::TicksPerSecond)) {
|
||||
saveEntities(level, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorage::save(Level* level, LevelChunk* levelChunk)
|
||||
{
|
||||
if (!regionFile)
|
||||
{
|
||||
regionFile = new RegionFile(levelPath);
|
||||
if (!regionFile->open())
|
||||
{
|
||||
delete regionFile;
|
||||
regionFile = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Write chunk
|
||||
RakNet::BitStream chunkData;
|
||||
chunkData.Write((const char*)levelChunk->getBlockData(), CHUNK_BLOCK_COUNT);
|
||||
chunkData.Write((const char*)levelChunk->data.data, CHUNK_BLOCK_COUNT / 2);
|
||||
|
||||
chunkData.Write((const char*)levelChunk->skyLight.data, CHUNK_BLOCK_COUNT / 2);
|
||||
chunkData.Write((const char*)levelChunk->blockLight.data, CHUNK_BLOCK_COUNT / 2);
|
||||
|
||||
chunkData.Write((const char*)levelChunk->updateMap, CHUNK_COLUMNS);
|
||||
|
||||
regionFile->writeChunk(levelChunk->x, levelChunk->z, chunkData);
|
||||
|
||||
// Write entities
|
||||
|
||||
//LOGI("Saved chunk (%d, %d)\n", levelChunk->x, levelChunk->z);
|
||||
|
||||
}
|
||||
|
||||
LevelChunk* ExternalFileLevelStorage::load(Level* level, int x, int z)
|
||||
{
|
||||
if (!regionFile)
|
||||
{
|
||||
regionFile = new RegionFile(levelPath);
|
||||
if (!regionFile->open())
|
||||
{
|
||||
delete regionFile;
|
||||
regionFile = NULL;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
RakNet::BitStream* chunkData = NULL;
|
||||
if (!regionFile->readChunk(x, z, &chunkData))
|
||||
{
|
||||
//LOGI("Failed to read data for %d, %d\n", x, z);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
chunkData->ResetReadPointer();
|
||||
|
||||
unsigned char* blockIds = new unsigned char[CHUNK_BLOCK_COUNT];
|
||||
chunkData->Read((char*)blockIds, CHUNK_BLOCK_COUNT);
|
||||
|
||||
LevelChunk* levelChunk = new LevelChunk(level, blockIds, x, z);
|
||||
chunkData->Read((char*)levelChunk->data.data, CHUNK_BLOCK_COUNT / 2);
|
||||
if (loadedStorageVersion >= ChunkVersion_Light) {
|
||||
chunkData->Read((char*)levelChunk->skyLight.data, CHUNK_BLOCK_COUNT / 2);
|
||||
chunkData->Read((char*)levelChunk->blockLight.data, CHUNK_BLOCK_COUNT / 2);
|
||||
}
|
||||
chunkData->Read((char*)levelChunk->updateMap, CHUNK_COLUMNS);
|
||||
// This will be difficult to maintain.. Storage version could be per chunk
|
||||
// too (but probably better to just read all -> write all, so that all
|
||||
// chunks got same version anyway)
|
||||
//if (loadedStorageVersion >= ChunkVersion_Entity) {
|
||||
// int dictSize;
|
||||
// chunkData->Read(dictSize);
|
||||
|
||||
// RakDataInput dis(*chunkData);
|
||||
// Tag* tmp = Tag::readNamedTag(&dis);
|
||||
// if (tmp && tmp->getId() == Tag::TAG_Compound) {
|
||||
// CompoundTag* tag = (CompoundTag*) tmp;
|
||||
|
||||
// delete tmp;
|
||||
// }
|
||||
//}
|
||||
|
||||
delete [] chunkData->GetData();
|
||||
delete chunkData;
|
||||
|
||||
//bool dbg = (x == 7 && z == 9);
|
||||
|
||||
//int t = 0;
|
||||
//for (int i = 0; i < CHUNK_COLUMNS; ++i) {
|
||||
// char bits = levelChunk->updateMap[i];
|
||||
// t += (bits != 0);
|
||||
// int xx = x * 16 + i%16;
|
||||
// int zz = z * 16 + i/16;
|
||||
// if (dbg && xx == 125 && zz == 152) {
|
||||
// LOGI("xz: %d, %d: %d\n", xx, zz, bits);
|
||||
// for (int j = 0; j < 8; ++j) {
|
||||
// if (bits & (1 << j)) {
|
||||
// LOGI("%d - %d\n", j << 4, ((j+1) << 4) - 1);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//
|
||||
// Convert LevelChunks here if necessary
|
||||
//
|
||||
//LOGI("level version: %d: upd: %d - (%d, %d)\n", loadedStorageVersion, t, x, z);
|
||||
bool changed = false;
|
||||
|
||||
// Loaded level has old Cloth types (one Tile* per color)
|
||||
if (loadedStorageVersion == 1)
|
||||
changed |= LevelConverters::v1_ClothIdToClothData(levelChunk);
|
||||
|
||||
// Loaded level is newer than our level - replace all unavailable block types
|
||||
//if (loadedStorageVersion > SharedConstants::StorageVersion)
|
||||
changed |= LevelConverters::ReplaceUnavailableBlocks(levelChunk);
|
||||
|
||||
levelChunk->recalcHeightmap();
|
||||
levelChunk->unsaved = changed;
|
||||
levelChunk->terrainPopulated = true;
|
||||
levelChunk->createdFromSave = true;
|
||||
|
||||
return levelChunk;
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorage::saveEntities( Level* level, LevelChunk* levelChunk )
|
||||
{
|
||||
lastSavedEntitiesTick = tickCount;
|
||||
int count = 0;
|
||||
float st = getTimeS();
|
||||
|
||||
// Version 1: Save ALL Entities for all chunks in one structure
|
||||
EntityList& entities = level->entities;
|
||||
|
||||
ListTag* entityTags = new ListTag();
|
||||
for (unsigned int i = 0; i < entities.size(); ++i) {
|
||||
Entity* e = entities[i];
|
||||
|
||||
CompoundTag* tag = new CompoundTag();
|
||||
if (e->save(tag)) {
|
||||
count++;
|
||||
entityTags->add(tag);
|
||||
} else
|
||||
delete tag;
|
||||
}
|
||||
|
||||
// Version 1: Save ALL TileEntities for all chunks in one structure
|
||||
TileEntityList& tileEntities = level->tileEntities;
|
||||
//TileEntityList keep, dontKeep;
|
||||
//partitionTileEntities
|
||||
|
||||
ListTag* tileEntityTags = new ListTag();
|
||||
for (unsigned int i = 0; i < tileEntities.size(); ++i) {
|
||||
TileEntity* e = tileEntities[i];
|
||||
if (!e->shouldSave()) continue;
|
||||
|
||||
CompoundTag* tag = new CompoundTag();
|
||||
if (e->save(tag)) {
|
||||
count++;
|
||||
tileEntityTags->add(tag);
|
||||
} else
|
||||
delete tag;
|
||||
}
|
||||
|
||||
CompoundTag base;
|
||||
base.put("Entities", entityTags);
|
||||
base.put("TileEntities", tileEntityTags);
|
||||
|
||||
RakNet::BitStream stream;
|
||||
RakDataOutput dos(stream);
|
||||
NbtIo::write(&base, &dos);
|
||||
int numBytes = stream.GetNumberOfBytesUsed();
|
||||
|
||||
FILE* fp = fopen((levelPath + "/entities.dat").c_str(), "wb");
|
||||
if (fp) {
|
||||
int version = 1;
|
||||
fwrite("ENT\0", 1, 4, fp);
|
||||
fwrite(&version, sizeof(int), 1, fp);
|
||||
fwrite(&numBytes, sizeof(int), 1, fp);
|
||||
fwrite(stream.GetData(), 1, numBytes, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
base.deleteChildren();
|
||||
|
||||
float tt = getTimeS() - st;
|
||||
LOGI("Time to save %d entities: %f s. Size: %d bytes\n", count, tt, numBytes);
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorage::loadEntities(Level* level, LevelChunk* chunk) {
|
||||
lastSavedEntitiesTick = tickCount;
|
||||
FILE* fp = fopen((levelPath + "/entities.dat").c_str(), "rb");
|
||||
if (fp) {
|
||||
char header[5];
|
||||
int version, numBytes;
|
||||
fread(header, 1, 4, fp);
|
||||
fread(&version, sizeof(int), 1, fp);
|
||||
fread(&numBytes, sizeof(int), 1, fp);
|
||||
|
||||
int left = getRemainingFileSize(fp);
|
||||
if (numBytes <= left && numBytes > 0) {
|
||||
unsigned char* buf = new unsigned char[numBytes];
|
||||
|
||||
fread(buf, 1, numBytes, fp);
|
||||
|
||||
RakNet::BitStream stream(buf, numBytes, false);
|
||||
RakDataInput dis(stream);
|
||||
|
||||
CompoundTag* tag = NbtIo::read(&dis);
|
||||
//
|
||||
// Read Entity:es
|
||||
//
|
||||
if (tag->contains("Entities", Tag::TAG_List)) {
|
||||
ListTag* entityTags = tag->getList("Entities");
|
||||
for (int i = 0; i < entityTags->size(); ++i) {
|
||||
Tag* _et = entityTags->get(i);
|
||||
if (!_et || _et->getId() != Tag::TAG_Compound) {
|
||||
LOGE("Entity tag is either NULL or not a compoundTag: %p : %d!\n", _et, _et?_et->getId() : -1);
|
||||
continue;
|
||||
}
|
||||
CompoundTag* et = (CompoundTag*)_et;
|
||||
if (Entity* e = EntityFactory::loadEntity(et, level)) {
|
||||
level->addEntity(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// Read TileEntity:s
|
||||
//
|
||||
if (tag->contains("TileEntities", Tag::TAG_List)) {
|
||||
ListTag* tileEntityTags = tag->getList("TileEntities");
|
||||
for (int i = 0; i < tileEntityTags->size(); ++i) {
|
||||
Tag* _et = tileEntityTags->get(i);
|
||||
if (!_et || _et->getId() != Tag::TAG_Compound) {
|
||||
LOGE("TileEntity tag is either NULL or not a compoundTag: %p : %d!\n", _et, _et?_et->getId() : -1);
|
||||
continue;
|
||||
}
|
||||
CompoundTag* et = (CompoundTag*)_et;
|
||||
if (TileEntity* e = TileEntity::loadStatic(et)) {
|
||||
|
||||
LevelChunk* chunk = level->getChunkAt(e->x, e->z);
|
||||
if (chunk && !chunk->hasTileEntityAt(e)) {
|
||||
LOGI("Adding TileEntity %d to %d, %d, %d\n", e->type, e->x, e->y, e->z);
|
||||
chunk->addTileEntity(e);
|
||||
} else {
|
||||
if (!chunk)
|
||||
LOGE("Couldn't find chunk at %d, %d to add %d\n", e->x, e->z, e->type);
|
||||
else
|
||||
LOGE("Already have TileEntity at %d, %d to add %d\n", e->x, e->z, e->type);
|
||||
delete e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tag->deleteChildren();
|
||||
delete tag;
|
||||
|
||||
delete[] buf;
|
||||
}
|
||||
LOGI("header: %s, version: %d, bytes: %d (remaining: %d)\n", header, version, numBytes, left);
|
||||
|
||||
//fread(stream.GetData(), 1, numBytes, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorage::saveGame(Level* level) {
|
||||
saveEntities(level, NULL);
|
||||
}
|
||||
|
||||
int ExternalFileLevelStorage::savePendingUnsavedChunks( int maxCount ) {
|
||||
if (maxCount < 0)
|
||||
maxCount = unsavedChunkList.size();
|
||||
|
||||
int count = 0;
|
||||
while (++count <= maxCount && !unsavedChunkList.empty()) {
|
||||
|
||||
UnsavedChunkList::iterator it = unsavedChunkList.begin();
|
||||
UnsavedChunkList::iterator remove = unsavedChunkList.begin();
|
||||
UnsavedLevelChunk* oldest = &(*it);
|
||||
|
||||
for ( ; it != unsavedChunkList.end(); ++it) {
|
||||
if ((*it).addedToList < oldest->addedToList) {
|
||||
oldest = &(*it);
|
||||
remove = it;
|
||||
}
|
||||
}
|
||||
LevelChunk* chunk = oldest->chunk;
|
||||
unsavedChunkList.erase(remove);
|
||||
|
||||
save(level, chunk);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorage::saveAll( Level* level, std::vector<LevelChunk*>& levelChunks ) {
|
||||
ChunkStorage::saveAll(level, levelChunks);
|
||||
int numChunks = savePendingUnsavedChunks(-1);
|
||||
LOGI("Saving %d additional chunks.\n", numChunks);
|
||||
}
|
||||
|
||||
#endif /*DEMO_MODE*/
|
||||
88
src/world/level/storage/ExternalFileLevelStorage.h
Executable file
88
src/world/level/storage/ExternalFileLevelStorage.h
Executable file
@@ -0,0 +1,88 @@
|
||||
#if !defined(DEMO_MODE) && !defined(APPLE_DEMO_PROMOTION)
|
||||
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__ExternalFileLevelStorage_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__ExternalFileLevelStorage_H__
|
||||
|
||||
//package net.minecraft.world.level.storage;
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
//#include "com/mojang/nbt/CompoundTag.h"
|
||||
#include "LevelStorage.h"
|
||||
#include "../chunk/storage/ChunkStorage.h"
|
||||
|
||||
class Player;
|
||||
class Dimension;
|
||||
class RegionFile;
|
||||
|
||||
|
||||
typedef struct UnsavedLevelChunk
|
||||
{
|
||||
int pos;
|
||||
RakNet::TimeMS addedToList;
|
||||
LevelChunk* chunk;
|
||||
} UnsavedLevelChunk;
|
||||
|
||||
typedef std::list<UnsavedLevelChunk> UnsavedChunkList;
|
||||
|
||||
/*public*/
|
||||
class ExternalFileLevelStorage:
|
||||
public LevelStorage,
|
||||
public ChunkStorage
|
||||
//public PlayerIO
|
||||
{
|
||||
public:
|
||||
ExternalFileLevelStorage(const std::string& levelId, const std::string& fullPath);
|
||||
virtual ~ExternalFileLevelStorage();
|
||||
|
||||
LevelData* prepareLevel(Level* level);
|
||||
|
||||
//throws LevelConflictException
|
||||
void checkSession() {}
|
||||
|
||||
ChunkStorage* createChunkStorage(Dimension* dimension) { return this; }
|
||||
|
||||
void saveLevelData(LevelData& levelData, std::vector<Player*>* players);
|
||||
// PlayerIO getPlayerIO() { return this; }
|
||||
// CompoundTag loadPlayerDataTag(std::string playerName) { return NULL; }
|
||||
|
||||
void closeAll() {}
|
||||
|
||||
static bool readLevelData(const std::string& directory, LevelData& dest);
|
||||
static bool readPlayerData(const std::string& filename, LevelData& dest);
|
||||
static bool writeLevelData(const std::string& datFileName, LevelData& dest, const std::vector<Player*>* players);
|
||||
static void saveLevelData(const std::string& directory, LevelData& levelData, std::vector<Player*>* players);
|
||||
|
||||
int savePendingUnsavedChunks(int maxCount);
|
||||
|
||||
//
|
||||
// ChunkStorage methods
|
||||
//
|
||||
virtual LevelChunk* load(Level* level, int x, int z);
|
||||
void save(Level* level, LevelChunk* levelChunk);
|
||||
// @note, loadEntities and saveEntities dont use second parameter
|
||||
void loadEntities(Level* level, LevelChunk* levelChunk);
|
||||
void saveEntities(Level* level, LevelChunk* levelChunk);
|
||||
void saveGame(Level* level);
|
||||
void saveAll(Level* level, std::vector<LevelChunk*>& levelChunks);
|
||||
|
||||
virtual void tick();
|
||||
virtual void flush() {}
|
||||
private:
|
||||
std::string levelId;
|
||||
std::string levelPath;
|
||||
LevelData* loadedLevelData;
|
||||
RegionFile* regionFile;
|
||||
RegionFile* entitiesFile;
|
||||
|
||||
Level* level;
|
||||
int tickCount;
|
||||
int loadedStorageVersion;
|
||||
UnsavedChunkList unsavedChunkList;
|
||||
int lastSavedEntitiesTick;
|
||||
};
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__ExternalFileLevelStorage_H__*/
|
||||
|
||||
#endif /*DEMO_MODE*/
|
||||
193
src/world/level/storage/ExternalFileLevelStorageSource.cpp
Executable file
193
src/world/level/storage/ExternalFileLevelStorageSource.cpp
Executable file
@@ -0,0 +1,193 @@
|
||||
#if !defined(DEMO_MODE) && !defined(APPLE_DEMO_PROMOTION)
|
||||
|
||||
#include "LevelData.h"
|
||||
#include "ExternalFileLevelStorageSource.h"
|
||||
#include "ExternalFileLevelStorage.h"
|
||||
#include "FolderMethods.h"
|
||||
#include "../../../platform/file.h"
|
||||
#include "../../../util/StringUtils.h"
|
||||
#include <cstdio>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "MoveFolder.h"
|
||||
#endif
|
||||
|
||||
static const char ILLEGAL_FILE_CHARACTERS[] = {
|
||||
'/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':'
|
||||
};
|
||||
|
||||
ExternalFileLevelStorageSource::ExternalFileLevelStorageSource(const std::string& externalPath, const std::string& temporaryFilesPath)
|
||||
: _hasTempDirectory(temporaryFilesPath != externalPath)
|
||||
{
|
||||
#ifndef STANDALONE_SERVER
|
||||
const char* p0 = "/games";
|
||||
const char* p1 = "/com.mojang";
|
||||
const char* p2 = "/minecraftWorlds";
|
||||
const char* tree[] = {
|
||||
p0, p1, p2
|
||||
};
|
||||
|
||||
int treeLength = sizeof(tree) / sizeof(tree[0]);
|
||||
|
||||
createTree(externalPath.c_str(), tree, treeLength);
|
||||
|
||||
if (hasTempDirectory())
|
||||
createTree(temporaryFilesPath.c_str(), tree, treeLength);
|
||||
|
||||
basePath = externalPath + p0 + p1 + p2;
|
||||
tmpBasePath = temporaryFilesPath + p0 + p1 + p2;
|
||||
#else
|
||||
basePath = externalPath;
|
||||
tmpBasePath = temporaryFilesPath;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorageSource::addLevelSummaryIfExists(LevelSummaryList& dest, const char* dirName)
|
||||
{
|
||||
std::string directory = basePath;
|
||||
directory += "/";
|
||||
directory += dirName;
|
||||
|
||||
LevelData levelData;
|
||||
|
||||
if (ExternalFileLevelStorage::readLevelData(directory, levelData))
|
||||
{
|
||||
LevelSummary summary;
|
||||
summary.id = dirName;
|
||||
summary.name = levelData.levelName;
|
||||
summary.lastPlayed = levelData.getLastPlayed();
|
||||
summary.sizeOnDisk = (unsigned int)levelData.getSizeOnDisk();
|
||||
summary.gameType = levelData.getGameType();
|
||||
|
||||
dest.push_back(summary);
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorageSource::getLevelList(LevelSummaryList& dest)
|
||||
{
|
||||
#ifdef WIN32
|
||||
|
||||
WIN32_FIND_DATAA fileData;
|
||||
HANDLE hFind;
|
||||
|
||||
std::string searchString = basePath;
|
||||
searchString += "/*";
|
||||
|
||||
hFind = FindFirstFileA(searchString.c_str(), &fileData);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
addLevelSummaryIfExists(dest, fileData.cFileName);
|
||||
|
||||
} while (FindNextFileA(hFind, &fileData));
|
||||
FindClose(hFind);
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
DIR *dp;
|
||||
struct dirent *dirp;
|
||||
if((dp = opendir(basePath.c_str())) == NULL) {
|
||||
LOGI("Error listing base folder %s: %d", basePath.c_str(), _errno());
|
||||
return;
|
||||
}
|
||||
|
||||
while ((dirp = readdir(dp)) != NULL) {
|
||||
if (dirp->d_type == DT_DIR)
|
||||
{
|
||||
addLevelSummaryIfExists(dest, dirp->d_name);
|
||||
}
|
||||
}
|
||||
closedir(dp);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
LevelStorage* ExternalFileLevelStorageSource::selectLevel(const std::string& levelId, bool createPlayerDir) {
|
||||
return new ExternalFileLevelStorage(levelId, getFullPath(levelId));
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorageSource::deleteLevel( const std::string& levelId )
|
||||
{
|
||||
std::string path = getFullPath(levelId);
|
||||
if (!DeleteDirectory(path)) { // If we couldn't delete whole folder, try to remove chunks..
|
||||
remove((path + "/chunks.dat").c_str());
|
||||
remove((path + "/player.dat").c_str());
|
||||
remove((path + "/level.dat").c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getUniqueLevelName(const LevelSummaryList& levels, const std::string& level )
|
||||
{
|
||||
std::set<std::string> Set;
|
||||
for (unsigned int i = 0; i < levels.size(); ++i)
|
||||
Set.insert(levels[i].id);
|
||||
|
||||
std::string s = level;
|
||||
while ( Set.find(s) != Set.end() )
|
||||
s += "-";
|
||||
return s;
|
||||
}
|
||||
|
||||
void ExternalFileLevelStorageSource::renameLevel( const std::string& oldLevelId_, const std::string& newLevelName_ )
|
||||
{
|
||||
#define _renFULLPATH(s) ((basePath + "/" + s).c_str())
|
||||
bool isTempFile = (TempLevelId == oldLevelId_);
|
||||
std::string oldFolder = getFullPath(oldLevelId_);
|
||||
|
||||
if (_access(oldFolder.c_str(), 0) != 0) {
|
||||
LOGI("Couldn't access %s\n", oldFolder.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::string levelName = Util::stringTrim(newLevelName_);
|
||||
std::string levelId = levelName;
|
||||
for (int i = 0; i < sizeof(ILLEGAL_FILE_CHARACTERS) / sizeof(char); ++i)
|
||||
levelId = Util::stringReplace(levelId, std::string(1, ILLEGAL_FILE_CHARACTERS[i]), "");
|
||||
|
||||
LevelSummaryList levels;
|
||||
getLevelList(levels);
|
||||
levelId = getUniqueLevelName(levels, levelId);
|
||||
|
||||
bool couldRename = false;
|
||||
if (hasTempDirectory() && isTempFile) {
|
||||
#ifdef __APPLE__
|
||||
std::string newFolder = basePath + "/" + levelId;
|
||||
moveFolder(oldFolder, newFolder);
|
||||
couldRename = (_access(newFolder.c_str(), 0) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
couldRename = couldRename || rename(_renFULLPATH(oldLevelId_), _renFULLPATH(levelId)) == 0;
|
||||
if (!couldRename) // != 0: fail
|
||||
levelId = oldLevelId_; // Try to rewrite the level name anyway
|
||||
|
||||
// Rename the level name and write back to file
|
||||
LevelData levelData;
|
||||
ExternalFileLevelStorage::readLevelData(_renFULLPATH(levelId), levelData);
|
||||
levelData.setLevelName(newLevelName_);
|
||||
ExternalFileLevelStorage::saveLevelData(_renFULLPATH(levelId), levelData, NULL);
|
||||
}
|
||||
|
||||
std::string ExternalFileLevelStorageSource::getName()
|
||||
{
|
||||
return "External File Level Storage";
|
||||
}
|
||||
|
||||
LevelData* ExternalFileLevelStorageSource::getDataTagFor( const std::string& levelId )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ExternalFileLevelStorageSource::isNewLevelIdAcceptable( const std::string& levelId )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ExternalFileLevelStorageSource::getFullPath(const std::string& levelId) {
|
||||
return ((TempLevelId == levelId)? tmpBasePath : basePath) + "/" + levelId;
|
||||
}
|
||||
|
||||
|
||||
#endif /*DEMO_MODE*/
|
||||
45
src/world/level/storage/ExternalFileLevelStorageSource.h
Executable file
45
src/world/level/storage/ExternalFileLevelStorageSource.h
Executable file
@@ -0,0 +1,45 @@
|
||||
#if !defined(DEMO_MODE) && !defined(APPLE_DEMO_PROMOTION)
|
||||
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__ExternalFileLevelStorageSource_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__ExternalFileLevelStorageSource_H__
|
||||
|
||||
//package net.minecraft.world.level.storage;
|
||||
|
||||
#include "LevelStorageSource.h"
|
||||
#include "MemoryLevelStorage.h"
|
||||
|
||||
class ProgressListener;
|
||||
|
||||
class ExternalFileLevelStorageSource: public LevelStorageSource
|
||||
{
|
||||
public:
|
||||
ExternalFileLevelStorageSource(const std::string& externalPath, const std::string& temporaryFilesPath);
|
||||
|
||||
std::string getName();
|
||||
void getLevelList(LevelSummaryList& dest);
|
||||
|
||||
LevelStorage* selectLevel(const std::string& levelId, bool createPlayerDir);
|
||||
LevelData* getDataTagFor(const std::string& levelId);
|
||||
|
||||
bool isNewLevelIdAcceptable(const std::string& levelId);
|
||||
|
||||
void clearAll() {}
|
||||
void deleteLevel(const std::string& levelId);
|
||||
void renameLevel(const std::string& levelId, const std::string& newLevelName);
|
||||
|
||||
bool isConvertible(const std::string& levelId) { return false; }
|
||||
bool requiresConversion(const std::string& levelId) { return false; }
|
||||
bool convertLevel(const std::string& levelId, ProgressListener* progress) { return false; }
|
||||
private:
|
||||
void addLevelSummaryIfExists(LevelSummaryList& dest, const char* dirName);
|
||||
bool hasTempDirectory() { return _hasTempDirectory; }
|
||||
std::string getFullPath(const std::string& levelId);
|
||||
|
||||
std::string basePath;
|
||||
std::string tmpBasePath;
|
||||
bool _hasTempDirectory;
|
||||
};
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__ExternalFileLevelStorageSource_H__*/
|
||||
|
||||
#endif /*DEMO_MODE*/
|
||||
77
src/world/level/storage/FolderMethods.cpp
Executable file
77
src/world/level/storage/FolderMethods.cpp
Executable file
@@ -0,0 +1,77 @@
|
||||
#include "FolderMethods.h"
|
||||
#include <string>
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
int _mkdir( const char* name ) {
|
||||
return mkdir(name, 0755);
|
||||
}
|
||||
|
||||
int _access( const char* name, int mode ) {
|
||||
return access(name, mode);
|
||||
}
|
||||
|
||||
int _errno() {
|
||||
return errno;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
bool exists(const char* name) {
|
||||
return _access(name, 0) == 0;
|
||||
}
|
||||
|
||||
bool createFolderIfNotExists( const char* name ) {
|
||||
if (exists(name))
|
||||
return true;
|
||||
|
||||
int errorCode = 0;
|
||||
if ((errorCode = _mkdir(name)) != 0) {
|
||||
LOGI("FAILED to create folder %s, %d! Escape plan?\n", name, _errno());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGI("Created folder %s\n", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
int getRemainingFileSize( FILE* fp ) {
|
||||
if (!fp) return 0;
|
||||
int current = ftell(fp);
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int end = ftell(fp);
|
||||
fseek(fp, current, SEEK_SET);
|
||||
return end - current;
|
||||
}
|
||||
|
||||
int getFileSize( const char* filename ) {
|
||||
FILE* fp = fopen(filename, "rb");
|
||||
if (!fp)
|
||||
return -1;
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fclose(fp);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool createTree( const char* base, const char* tree[], int treeLength ) {
|
||||
if (!createFolderIfNotExists(base))
|
||||
return false;
|
||||
|
||||
std::string p(base);
|
||||
for (int i = 0; i < treeLength; ++i && tree[i]) {
|
||||
p += tree[i];
|
||||
if (!createFolderIfNotExists(p.c_str()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
22
src/world/level/storage/FolderMethods.h
Executable file
22
src/world/level/storage/FolderMethods.h
Executable file
@@ -0,0 +1,22 @@
|
||||
#ifndef _MINECRAFT_FOLDERMETHODS_H_
|
||||
#define _MINECRAFT_FOLDERMETHODS_H_
|
||||
|
||||
#include "../../../platform/log.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <io.h>
|
||||
#else
|
||||
int _mkdir(const char* name);
|
||||
int _access(const char* name, int mode);
|
||||
int _errno();
|
||||
#endif
|
||||
|
||||
bool exists(const char* name);
|
||||
bool createFolderIfNotExists(const char* name);
|
||||
|
||||
int getRemainingFileSize(FILE* fp);
|
||||
int getFileSize(const char* filename);
|
||||
|
||||
bool createTree(const char* base, const char* tree[], int treeLength);
|
||||
|
||||
#endif
|
||||
364
src/world/level/storage/LevelData.cpp
Executable file
364
src/world/level/storage/LevelData.cpp
Executable file
@@ -0,0 +1,364 @@
|
||||
#include "LevelData.h"
|
||||
|
||||
LevelData::LevelData()
|
||||
: xSpawn(128),
|
||||
ySpawn(64),
|
||||
zSpawn(128),
|
||||
|
||||
seed(0),
|
||||
lastPlayed(0),
|
||||
generatorVersion(SharedConstants::GeneratorVersion),
|
||||
time(0),
|
||||
dimension(Dimension::NORMAL),
|
||||
playerDataVersion(-1),
|
||||
storageVersion(0),
|
||||
gameType(GameType::Default),
|
||||
loadedPlayerTag(NULL)
|
||||
{
|
||||
//LOGI("ctor 1: %p\n", this);
|
||||
spawnMobs = (gameType == GameType::Survival);
|
||||
}
|
||||
|
||||
LevelData::LevelData( const LevelSettings& settings, const std::string& levelName, int generatorVersion /*= -1*/ )
|
||||
: seed(settings.getSeed()),
|
||||
gameType(settings.getGameType()),
|
||||
levelName(levelName),
|
||||
xSpawn(128),
|
||||
ySpawn(64),
|
||||
zSpawn(128),
|
||||
lastPlayed(0),
|
||||
time(0),
|
||||
dimension(Dimension::NORMAL),
|
||||
playerDataVersion(-1),
|
||||
loadedPlayerTag(NULL)
|
||||
{
|
||||
//LOGI("ctor 2: %p\n", this);
|
||||
|
||||
if (generatorVersion < 0)
|
||||
generatorVersion = SharedConstants::GeneratorVersion;
|
||||
|
||||
this->generatorVersion = generatorVersion;
|
||||
spawnMobs = (gameType == GameType::Survival);
|
||||
}
|
||||
|
||||
LevelData::LevelData( CompoundTag* tag )
|
||||
: loadedPlayerTag(NULL)
|
||||
{
|
||||
//LOGI("ctor 3: %p (%p)\n", this, tag);
|
||||
getTagData(tag);
|
||||
}
|
||||
|
||||
LevelData::LevelData( const LevelData& rhs )
|
||||
: seed(rhs.seed),
|
||||
gameType(rhs.gameType),
|
||||
levelName(rhs.levelName),
|
||||
xSpawn(rhs.xSpawn),
|
||||
ySpawn(rhs.ySpawn),
|
||||
zSpawn(rhs.zSpawn),
|
||||
lastPlayed(rhs.lastPlayed),
|
||||
time(rhs.time),
|
||||
dimension(rhs.dimension),
|
||||
storageVersion(rhs.storageVersion),
|
||||
playerDataVersion(rhs.playerDataVersion),
|
||||
generatorVersion(rhs.generatorVersion),
|
||||
spawnMobs(rhs.spawnMobs),
|
||||
loadedPlayerTag(NULL),
|
||||
playerData(rhs.playerData)
|
||||
{
|
||||
//LOGI("c-ctor: %p (%p)\n", this, &rhs);
|
||||
setPlayerTag(rhs.loadedPlayerTag);
|
||||
//PlayerData playerData;
|
||||
}
|
||||
|
||||
LevelData& LevelData::operator=( const LevelData& rhs )
|
||||
{
|
||||
//LOGI("as-op: %p (%p)\n", this, &rhs);
|
||||
if (this != &rhs) {
|
||||
seed = rhs.seed;
|
||||
gameType = rhs.gameType;
|
||||
levelName = rhs.levelName;
|
||||
xSpawn = rhs.xSpawn;
|
||||
ySpawn = rhs.ySpawn;
|
||||
zSpawn = rhs.zSpawn;
|
||||
lastPlayed = rhs.lastPlayed;
|
||||
time = rhs.time;
|
||||
dimension = rhs.dimension;
|
||||
spawnMobs = rhs.spawnMobs;
|
||||
playerData = rhs.playerData;
|
||||
playerDataVersion = rhs.playerDataVersion;
|
||||
generatorVersion = rhs.generatorVersion;
|
||||
storageVersion = rhs.storageVersion;
|
||||
setPlayerTag(rhs.loadedPlayerTag);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
LevelData::~LevelData()
|
||||
{
|
||||
//LOGI("dtor: %p\n", this);
|
||||
setPlayerTag(NULL);
|
||||
}
|
||||
|
||||
void LevelData::v1_write( RakNet::BitStream& bitStream )
|
||||
{
|
||||
bitStream.Write(seed);
|
||||
bitStream.Write(xSpawn);
|
||||
bitStream.Write(ySpawn);
|
||||
bitStream.Write(zSpawn);
|
||||
bitStream.Write(time);
|
||||
bitStream.Write(sizeOnDisk);
|
||||
bitStream.Write(getEpochTimeS());
|
||||
RakNet::RakString rakName = levelName.c_str();
|
||||
bitStream.Write(rakName);
|
||||
//LOGI("WBS: %d, %d, %d, %d, %d, %d\n", seed, xSpawn, ySpawn, zSpawn, time, sizeOnDisk);
|
||||
}
|
||||
|
||||
void LevelData::v1_read( RakNet::BitStream& bitStream, int storageVersion )
|
||||
{
|
||||
this->storageVersion = storageVersion;
|
||||
bitStream.Read(seed);
|
||||
bitStream.Read(xSpawn);
|
||||
bitStream.Read(ySpawn);
|
||||
bitStream.Read(zSpawn);
|
||||
bitStream.Read(time);
|
||||
bitStream.Read(sizeOnDisk);
|
||||
bitStream.Read(lastPlayed);
|
||||
RakNet::RakString rakName;
|
||||
bitStream.Read(rakName);
|
||||
levelName = rakName.C_String();
|
||||
//LOGI("RBS: %d, %d, %d, %d, %d, %d\n", seed, xSpawn, ySpawn, zSpawn, time, sizeOnDisk);
|
||||
}
|
||||
|
||||
// Caller's responsibility to destroy this Tag
|
||||
CompoundTag* LevelData::createTag()
|
||||
{
|
||||
CompoundTag* tag = new CompoundTag();
|
||||
CompoundTag* player = loadedPlayerTag? (CompoundTag*)loadedPlayerTag->copy() : NULL;
|
||||
setTagData(tag, player);
|
||||
return tag;
|
||||
}
|
||||
|
||||
CompoundTag* LevelData::createTag( const std::vector<Player*>& players )
|
||||
{
|
||||
CompoundTag* tag = new CompoundTag();
|
||||
|
||||
Player* player = NULL;
|
||||
CompoundTag* playerTag = NULL;
|
||||
|
||||
if (!players.empty()) player = players[0];
|
||||
if (player != NULL) {
|
||||
playerTag = new CompoundTag();
|
||||
player->saveWithoutId(playerTag);
|
||||
}
|
||||
setTagData(tag, playerTag);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
void LevelData::setTagData( CompoundTag* tag, CompoundTag* playerTag )
|
||||
{
|
||||
if (!tag) return;
|
||||
tag->putLong("RandomSeed", seed);
|
||||
tag->putInt("GameType", gameType);
|
||||
tag->putInt("SpawnX", xSpawn);
|
||||
tag->putInt("SpawnY", ySpawn);
|
||||
tag->putInt("SpawnZ", zSpawn);
|
||||
tag->putLong("Time", time);
|
||||
tag->putLong("SizeOnDisk", sizeOnDisk);
|
||||
tag->putLong("LastPlayed", getEpochTimeS());
|
||||
tag->putString("LevelName", levelName);
|
||||
tag->putInt("StorageVersion", storageVersion);
|
||||
tag->putInt("Platform", 2);
|
||||
|
||||
if (playerTag != NULL) {
|
||||
tag->putCompound("Player", playerTag);
|
||||
}
|
||||
}
|
||||
|
||||
void LevelData::getTagData( const CompoundTag* tag )
|
||||
{
|
||||
if (!tag) return;
|
||||
seed = (long)tag->getLong("RandomSeed");
|
||||
gameType = tag->getInt("GameType");
|
||||
xSpawn = tag->getInt("SpawnX");
|
||||
ySpawn = tag->getInt("SpawnY");
|
||||
zSpawn = tag->getInt("SpawnZ");
|
||||
time = (long)tag->getLong("Time");
|
||||
lastPlayed = (int)tag->getLong("LastPlayed");
|
||||
sizeOnDisk = (int)tag->getLong("SizeOnDisk");
|
||||
levelName = tag->getString("LevelName");
|
||||
storageVersion = tag->getInt("StorageVersion");
|
||||
|
||||
spawnMobs = (gameType == GameType::Survival);
|
||||
|
||||
if (tag->contains("Player", Tag::TAG_Compound)) {
|
||||
setPlayerTag(tag->getCompound("Player"));
|
||||
|
||||
//dimension = loadedPlayerTag.getInt("Dimension");
|
||||
}
|
||||
}
|
||||
|
||||
void LevelData::setPlayerTag( CompoundTag* tag )
|
||||
{
|
||||
if (loadedPlayerTag) {
|
||||
loadedPlayerTag->deleteChildren();
|
||||
delete loadedPlayerTag;
|
||||
loadedPlayerTag = NULL;
|
||||
}
|
||||
if (tag)
|
||||
loadedPlayerTag = (CompoundTag*)tag->copy();
|
||||
}
|
||||
|
||||
long LevelData::getSeed() const
|
||||
{
|
||||
return seed;
|
||||
}
|
||||
|
||||
int LevelData::getXSpawn() const
|
||||
{
|
||||
return xSpawn;
|
||||
}
|
||||
|
||||
int LevelData::getYSpawn() const
|
||||
{
|
||||
return ySpawn;
|
||||
}
|
||||
|
||||
int LevelData::getZSpawn() const
|
||||
{
|
||||
return zSpawn;
|
||||
}
|
||||
|
||||
long LevelData::getTime() const
|
||||
{
|
||||
return time;
|
||||
}
|
||||
|
||||
long LevelData::getSizeOnDisk() const
|
||||
{
|
||||
return sizeOnDisk;
|
||||
}
|
||||
|
||||
CompoundTag* LevelData::getLoadedPlayerTag()
|
||||
{
|
||||
return loadedPlayerTag;
|
||||
}
|
||||
|
||||
void LevelData::setLoadedPlayerTo( Player* p )
|
||||
{
|
||||
if (playerDataVersion == 1)
|
||||
playerData.loadPlayer(p);
|
||||
}
|
||||
|
||||
int LevelData::getDimension()
|
||||
{
|
||||
return dimension;
|
||||
}
|
||||
|
||||
void LevelData::setSeed( long seed )
|
||||
{
|
||||
this->seed = seed;
|
||||
}
|
||||
|
||||
void LevelData::setXSpawn( int xSpawn )
|
||||
{
|
||||
this->xSpawn = xSpawn;
|
||||
}
|
||||
|
||||
void LevelData::setYSpawn( int ySpawn )
|
||||
{
|
||||
this->ySpawn = ySpawn;
|
||||
}
|
||||
|
||||
void LevelData::setZSpawn( int zSpawn )
|
||||
{
|
||||
this->zSpawn = zSpawn;
|
||||
}
|
||||
|
||||
void LevelData::setTime( long time )
|
||||
{
|
||||
this->time = time;
|
||||
}
|
||||
|
||||
void LevelData::setSizeOnDisk( long sizeOnDisk )
|
||||
{
|
||||
this->sizeOnDisk = sizeOnDisk;
|
||||
}
|
||||
|
||||
void LevelData::setLoadedPlayerTag( CompoundTag* playerTag )
|
||||
{
|
||||
LOGI("set-p: %p (%p <- %p)\n", this, loadedPlayerTag, playerTag);
|
||||
if (loadedPlayerTag) {
|
||||
loadedPlayerTag->deleteChildren();
|
||||
delete loadedPlayerTag;
|
||||
}
|
||||
loadedPlayerTag = playerTag;
|
||||
}
|
||||
|
||||
void LevelData::setDimension( int dimension )
|
||||
{
|
||||
this->dimension = dimension;
|
||||
}
|
||||
|
||||
void LevelData::setSpawn( int xSpawn, int ySpawn, int zSpawn )
|
||||
{
|
||||
this->xSpawn = xSpawn;
|
||||
this->ySpawn = ySpawn;
|
||||
this->zSpawn = zSpawn;
|
||||
}
|
||||
|
||||
std::string LevelData::getLevelName()
|
||||
{
|
||||
return levelName;
|
||||
}
|
||||
|
||||
void LevelData::setLevelName( const std::string& levelName )
|
||||
{
|
||||
this->levelName = levelName;
|
||||
}
|
||||
|
||||
int LevelData::getGeneratorVersion() const
|
||||
{
|
||||
return generatorVersion;
|
||||
}
|
||||
|
||||
void LevelData::setGeneratorVersion( int version )
|
||||
{
|
||||
this->generatorVersion = version;
|
||||
}
|
||||
|
||||
long LevelData::getLastPlayed() const
|
||||
{
|
||||
return lastPlayed;
|
||||
}
|
||||
|
||||
int LevelData::getStorageVersion() const
|
||||
{
|
||||
return storageVersion;
|
||||
}
|
||||
|
||||
void LevelData::setStorageVersion( int version )
|
||||
{
|
||||
storageVersion = version;
|
||||
}
|
||||
|
||||
int LevelData::getGameType() const
|
||||
{
|
||||
return gameType;
|
||||
}
|
||||
|
||||
void LevelData::setGameType( int type )
|
||||
{
|
||||
gameType = type;
|
||||
}
|
||||
|
||||
bool LevelData::getSpawnMobs() const
|
||||
{
|
||||
return spawnMobs;
|
||||
}
|
||||
|
||||
void LevelData::setSpawnMobs( bool doSpawn )
|
||||
{
|
||||
spawnMobs = doSpawn;
|
||||
}
|
||||
97
src/world/level/storage/LevelData.h
Executable file
97
src/world/level/storage/LevelData.h
Executable file
@@ -0,0 +1,97 @@
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelData_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelData_H__
|
||||
|
||||
//package net.minecraft.world.level.storage;
|
||||
|
||||
#include <string>
|
||||
#include "PlayerData.h"
|
||||
#include "../LevelSettings.h"
|
||||
#include "../dimension/Dimension.h"
|
||||
|
||||
// sorry for RakNet dependency, but I really like using BitStream
|
||||
#include "../../../raknet/BitStream.h"
|
||||
#include "../../../platform/time.h"
|
||||
#include "../../../nbt/CompoundTag.h"
|
||||
|
||||
class LevelData
|
||||
{
|
||||
public:
|
||||
LevelData();
|
||||
LevelData(const LevelSettings& settings, const std::string& levelName, int generatorVersion = -1);
|
||||
LevelData(CompoundTag* tag);
|
||||
LevelData(const LevelData& rhs);
|
||||
LevelData& operator=(const LevelData& rhs);
|
||||
~LevelData();
|
||||
|
||||
void v1_write(RakNet::BitStream& bitStream);
|
||||
void v1_read(RakNet::BitStream& bitStream, int storageVersion);
|
||||
|
||||
// Caller's responsibility to destroy this Tag
|
||||
CompoundTag* createTag();
|
||||
CompoundTag* createTag(const std::vector<Player*>& players);
|
||||
|
||||
void getTagData(const CompoundTag* tag);
|
||||
void setTagData(CompoundTag* tag, CompoundTag* playerTag);
|
||||
|
||||
long getSeed() const;
|
||||
int getXSpawn() const;
|
||||
int getYSpawn() const;
|
||||
int getZSpawn() const;
|
||||
long getTime() const;
|
||||
long getSizeOnDisk() const;
|
||||
|
||||
void setPlayerTag(CompoundTag* tag);
|
||||
CompoundTag* getLoadedPlayerTag();
|
||||
void setLoadedPlayerTo(Player* p);
|
||||
|
||||
int getDimension();
|
||||
|
||||
void setSeed(long seed);
|
||||
void setXSpawn(int xSpawn);
|
||||
void setYSpawn(int ySpawn);
|
||||
void setZSpawn(int zSpawn);
|
||||
void setSpawn(int xSpawn, int ySpawn, int zSpawn);
|
||||
|
||||
void setTime(long time);
|
||||
void setSizeOnDisk(long sizeOnDisk);
|
||||
void setLoadedPlayerTag(CompoundTag* playerTag);
|
||||
void setDimension(int dimension);
|
||||
|
||||
std::string getLevelName();
|
||||
void setLevelName(const std::string& levelName);
|
||||
|
||||
int getGeneratorVersion() const;
|
||||
void setGeneratorVersion(int version);
|
||||
|
||||
long getLastPlayed() const;
|
||||
|
||||
int getStorageVersion() const;
|
||||
void setStorageVersion(int version);
|
||||
int getGameType() const;
|
||||
void setGameType(int type);
|
||||
bool getSpawnMobs() const;
|
||||
void setSpawnMobs(bool doSpawn);
|
||||
|
||||
public:
|
||||
PlayerData playerData;
|
||||
int playerDataVersion;
|
||||
std::string levelName;
|
||||
private:
|
||||
long seed;
|
||||
int xSpawn;
|
||||
int ySpawn;
|
||||
int zSpawn;
|
||||
long time;
|
||||
int lastPlayed;
|
||||
long sizeOnDisk;
|
||||
CompoundTag* loadedPlayerTag;
|
||||
int dimension;
|
||||
int gameType;
|
||||
int storageVersion;
|
||||
bool spawnMobs;
|
||||
//@note: This version is never written or loaded to disk. The only purpose
|
||||
// is to use it in the level generator on server and clients.
|
||||
int generatorVersion;
|
||||
};
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelData_H__*/
|
||||
39
src/world/level/storage/LevelStorage.h
Executable file
39
src/world/level/storage/LevelStorage.h
Executable file
@@ -0,0 +1,39 @@
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelStorage_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelStorage_H__
|
||||
|
||||
//package net.minecraft.world.level.storage;
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class LevelData;
|
||||
class ChunkStorage;
|
||||
class Dimension;
|
||||
class Player;
|
||||
class Level;
|
||||
class LevelChunk;
|
||||
|
||||
class LevelStorage
|
||||
{
|
||||
public:
|
||||
virtual ~LevelStorage() {}
|
||||
|
||||
virtual LevelData* prepareLevel(Level* level) = 0;
|
||||
|
||||
virtual ChunkStorage* createChunkStorage(Dimension* dimension) = 0;
|
||||
|
||||
virtual void saveLevelData(LevelData& levelData, std::vector<Player*>* players) = 0;
|
||||
virtual void saveLevelData(LevelData& levelData) {
|
||||
saveLevelData(levelData, NULL);
|
||||
}
|
||||
|
||||
virtual void closeAll() = 0;
|
||||
|
||||
virtual void saveGame(Level* level) {}
|
||||
virtual void loadEntities(Level* level, LevelChunk* levelChunk) {}
|
||||
|
||||
//void checkSession() throws LevelConflictException;
|
||||
//PlayerIO getPlayerIO();
|
||||
};
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelStorage_H__*/
|
||||
3
src/world/level/storage/LevelStorageSource.cpp
Executable file
3
src/world/level/storage/LevelStorageSource.cpp
Executable file
@@ -0,0 +1,3 @@
|
||||
#include "LevelStorageSource.h"
|
||||
|
||||
const std::string LevelStorageSource::TempLevelId = "_LastJoinedServer";
|
||||
60
src/world/level/storage/LevelStorageSource.h
Executable file
60
src/world/level/storage/LevelStorageSource.h
Executable file
@@ -0,0 +1,60 @@
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelStorageSource_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelStorageSource_H__
|
||||
|
||||
//package net.minecraft.world.level.storage;
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ProgressListener;
|
||||
class LevelData;
|
||||
class LevelStorage;
|
||||
|
||||
struct LevelSummary
|
||||
{
|
||||
std::string id;
|
||||
std::string name;
|
||||
int lastPlayed;
|
||||
int gameType;
|
||||
unsigned int sizeOnDisk;
|
||||
|
||||
bool operator<(const LevelSummary& rhs) const {
|
||||
return lastPlayed > rhs.lastPlayed;
|
||||
}
|
||||
};
|
||||
typedef std::vector<LevelSummary> LevelSummaryList;
|
||||
|
||||
class LevelStorageSource
|
||||
{
|
||||
public:
|
||||
static const std::string TempLevelId;
|
||||
|
||||
virtual ~LevelStorageSource() {}
|
||||
|
||||
virtual std::string getName() = 0;
|
||||
virtual void getLevelList(LevelSummaryList& dest) {};
|
||||
|
||||
virtual LevelData* getDataTagFor(const std::string& levelId) = 0;
|
||||
virtual LevelStorage* selectLevel(const std::string& levelId, bool createPlayerDir) = 0;
|
||||
/**
|
||||
* Tests if a levelId can be used to store a level. For example, a levelId
|
||||
* can't be called COM1 on Windows systems, because that is a reserved file
|
||||
* handle.
|
||||
* <p>
|
||||
* Also, a new levelId may not overwrite an existing one.
|
||||
*
|
||||
* @param levelId
|
||||
* @return
|
||||
*/
|
||||
virtual bool isNewLevelIdAcceptable(const std::string& levelId) = 0;
|
||||
|
||||
virtual void clearAll() = 0;
|
||||
virtual void deleteLevel(const std::string& levelId) = 0;
|
||||
virtual void renameLevel(const std::string& levelId, const std::string& newLevelName) = 0;
|
||||
|
||||
virtual bool isConvertible(const std::string& levelId) = 0;
|
||||
virtual bool requiresConversion(const std::string& levelId) = 0;
|
||||
virtual bool convertLevel(const std::string& levelId, ProgressListener* progress) = 0;
|
||||
};
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__LevelStorageSource_H__*/
|
||||
53
src/world/level/storage/MemoryLevelStorage.h
Executable file
53
src/world/level/storage/MemoryLevelStorage.h
Executable file
@@ -0,0 +1,53 @@
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__MemoryLevelStorage_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__MemoryLevelStorage_H__
|
||||
|
||||
//package net.minecraft.world.level.storage;
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "LevelStorage.h"
|
||||
#include "../chunk/storage/MemoryChunkStorage.h"
|
||||
|
||||
class Player;
|
||||
class Dimension;
|
||||
|
||||
class MemoryLevelStorage: public LevelStorage //public PlayerIO
|
||||
{
|
||||
public:
|
||||
MemoryLevelStorage()
|
||||
: _storage(NULL)
|
||||
{}
|
||||
|
||||
~MemoryLevelStorage() {
|
||||
delete _storage;
|
||||
}
|
||||
|
||||
LevelData* prepareLevel(Level* level) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void checkSession() //throws LevelConflictException
|
||||
{}
|
||||
|
||||
ChunkStorage* createChunkStorage(Dimension* dimension) {
|
||||
if (_storage) {
|
||||
LOGW(">WARNING< Creating a MemoryChunkStorage over another (#%p). A memory leak will occur.\n", _storage);
|
||||
}
|
||||
|
||||
return _storage = new MemoryChunkStorage();
|
||||
}
|
||||
|
||||
void saveLevelData(LevelData& levelData, std::vector<Player*>* players) {}
|
||||
|
||||
void closeAll() {}
|
||||
|
||||
void save(Player* player) {}
|
||||
void load(Player* player) {}
|
||||
|
||||
/* CompoundTag loadPlayerDataTag(std::string playerName) {
|
||||
return NULL;
|
||||
} */
|
||||
MemoryChunkStorage* _storage;
|
||||
};
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__MemoryLevelStorage_H__*/
|
||||
58
src/world/level/storage/MemoryLevelStorageSource.h
Executable file
58
src/world/level/storage/MemoryLevelStorageSource.h
Executable file
@@ -0,0 +1,58 @@
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__MemoryLevelStorageSource_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__MemoryLevelStorageSource_H__
|
||||
|
||||
//package net.minecraft.world.level.storage;
|
||||
|
||||
#include "LevelStorageSource.h"
|
||||
#include "MemoryLevelStorage.h"
|
||||
class ProgressListener;
|
||||
|
||||
class MemoryLevelStorageSource: public LevelStorageSource
|
||||
{
|
||||
public:
|
||||
MemoryLevelStorageSource() {
|
||||
}
|
||||
|
||||
std::string getName() {
|
||||
return "Memory Storage";
|
||||
}
|
||||
|
||||
LevelStorage* selectLevel(const std::string& levelId, bool createPlayerDir) {
|
||||
return new MemoryLevelStorage();
|
||||
}
|
||||
|
||||
//List<LevelSummary> getLevelList() {
|
||||
// return /*new*/ ArrayList<LevelSummary>();
|
||||
//}
|
||||
|
||||
void clearAll() {
|
||||
}
|
||||
|
||||
LevelData* getDataTagFor(const std::string& levelId) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool isNewLevelIdAcceptable(const std::string& levelId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void deleteLevel(const std::string& levelId) {
|
||||
}
|
||||
|
||||
void renameLevel(const std::string& levelId, const std::string& newLevelName) {
|
||||
}
|
||||
|
||||
bool isConvertible(const std::string& levelId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool requiresConversion(const std::string& levelId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool convertLevel(const std::string& levelId, ProgressListener* progress) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__MemoryLevelStorageSource_H__*/
|
||||
8
src/world/level/storage/MoveFolder.h
Executable file
8
src/world/level/storage/MoveFolder.h
Executable file
@@ -0,0 +1,8 @@
|
||||
#ifndef MOVEFOLDER_H__
|
||||
#define MOVEFOLDER_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
void moveFolder(const std::string& src, const std::string& dst);
|
||||
|
||||
#endif /*MOVEFOLDER_H__*/
|
||||
11
src/world/level/storage/MoveFolder.mm
Executable file
11
src/world/level/storage/MoveFolder.mm
Executable file
@@ -0,0 +1,11 @@
|
||||
#include "MoveFolder.h"
|
||||
|
||||
void moveFolder(const std::string& src, const std::string& dst) {
|
||||
NSError* error = NULL;
|
||||
[[NSFileManager defaultManager]
|
||||
moveItemAtPath:[NSString stringWithUTF8String:src.c_str()]
|
||||
toPath:[NSString stringWithUTF8String:dst.c_str()] error:&error];
|
||||
|
||||
if (error)
|
||||
NSLog(@"Error: %@\n: %@\n", [error description], [error debugDescription]);
|
||||
}
|
||||
66
src/world/level/storage/PlayerData.h
Executable file
66
src/world/level/storage/PlayerData.h
Executable file
@@ -0,0 +1,66 @@
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__PlayerData_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__PlayerData_H__
|
||||
|
||||
#include "../../entity/player/Player.h"
|
||||
#include "../../entity/player/Inventory.h"
|
||||
#include "../../phys/Vec3.h"
|
||||
#include "../../../util/Mth.h"
|
||||
|
||||
static float clampRot(float r) {
|
||||
return Mth::clamp(r, -100000.0f, 100000.0f);
|
||||
}
|
||||
static float clampXZ(float r) {
|
||||
return Mth::clamp(r, 4.0f, 252.0f);
|
||||
}
|
||||
static float clampY(float r) {
|
||||
return Mth::clamp(r, 4.0f, 126.0f);
|
||||
}
|
||||
|
||||
class PlayerData {
|
||||
public:
|
||||
void loadPlayer(Player* p) const {
|
||||
p->setPos(0, 0, 0);
|
||||
|
||||
p->x = p->xo = p->xOld = clampXZ(pos.x);
|
||||
p->y = p->yo = p->yOld = clampY (pos.y);
|
||||
p->z = p->zo = p->zOld = clampXZ(pos.z);
|
||||
|
||||
float motionX = motion.x;
|
||||
float motionY = motion.y;
|
||||
float motionZ = motion.z;
|
||||
if (Mth::abs(motionX) > 10.0f) motionX = 0;
|
||||
if (Mth::abs(motionY) > 10.0f) motionY = 0;
|
||||
if (Mth::abs(motionZ) > 10.0f) motionZ = 0;
|
||||
|
||||
p->xd = motionX;
|
||||
p->yd = motionY;
|
||||
p->zd = motionZ;
|
||||
|
||||
p->xRot = p->xRotO = clampRot(xRot);
|
||||
p->yRot = p->yRotO = clampRot(yRot);
|
||||
|
||||
p->fallDistance = fallDistance;
|
||||
p->onFire = onFire;
|
||||
p->airSupply = airSupply;
|
||||
p->onGround = onGround;
|
||||
|
||||
p->setPos(pos.x, pos.y, pos.z);
|
||||
|
||||
//@deprecated
|
||||
//for (int i = 0; i < Inventory::MAX_SELECTION_SIZE; ++i)
|
||||
//p->inventory->setSelectionSlotItemId(i, inventorySlots[i]);
|
||||
}
|
||||
|
||||
Vec3 pos;
|
||||
Vec3 motion;
|
||||
float xRot, yRot;
|
||||
|
||||
float fallDistance;
|
||||
short onFire;
|
||||
short airSupply;
|
||||
bool onGround;
|
||||
|
||||
int inventorySlots[Inventory::MAX_SELECTION_SIZE];
|
||||
};
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__PlayerData_H__*/
|
||||
216
src/world/level/storage/RegionFile.cpp
Executable file
216
src/world/level/storage/RegionFile.cpp
Executable file
@@ -0,0 +1,216 @@
|
||||
#include "RegionFile.h"
|
||||
#include "../../../platform/log.h"
|
||||
|
||||
const int SECTOR_BYTES = 4096;
|
||||
const int SECTOR_INTS = SECTOR_BYTES / 4;
|
||||
const int SECTOR_COLS = 32;
|
||||
|
||||
static const char* const REGION_DAT_NAME = "chunks.dat";
|
||||
|
||||
static void logAssert(int actual, int expected) {
|
||||
if (actual != expected) {
|
||||
LOGI("ERROR: I/O operation failed (%d vs %d)\n", actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
RegionFile::RegionFile(const std::string& basePath)
|
||||
: file(NULL)
|
||||
{
|
||||
filename = basePath;
|
||||
filename += "/";
|
||||
filename += REGION_DAT_NAME;
|
||||
|
||||
offsets = new int[SECTOR_INTS];
|
||||
|
||||
emptyChunk = new int[SECTOR_INTS];
|
||||
memset(emptyChunk, 0, SECTOR_INTS * sizeof(int));
|
||||
}
|
||||
|
||||
RegionFile::~RegionFile()
|
||||
{
|
||||
close();
|
||||
delete [] offsets;
|
||||
delete [] emptyChunk;
|
||||
}
|
||||
|
||||
bool RegionFile::open()
|
||||
{
|
||||
close();
|
||||
|
||||
memset(offsets, 0, SECTOR_INTS * sizeof(int));
|
||||
|
||||
// attempt to open file
|
||||
file = fopen(filename.c_str(), "r+b");
|
||||
if (file)
|
||||
{
|
||||
// read offset table
|
||||
logAssert(fread(offsets, sizeof(int), SECTOR_INTS, file), SECTOR_INTS);
|
||||
|
||||
// mark initial sector as blocked
|
||||
sectorFree[0] = false;
|
||||
|
||||
// setup blocked sectors
|
||||
for (int sector = 0; sector < SECTOR_INTS; sector++)
|
||||
{
|
||||
int offset = offsets[sector];
|
||||
if (offset)
|
||||
{
|
||||
int base = offset >> 8;
|
||||
int count = offset & 0xff;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sectorFree[base + i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// new world
|
||||
file = fopen(filename.c_str(), "w+b");
|
||||
if (!file)
|
||||
{
|
||||
LOGI("Failed to create chunk file %s\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// write sector header (all zeroes)
|
||||
logAssert(fwrite(offsets, sizeof(int), SECTOR_INTS, file), SECTOR_INTS);
|
||||
|
||||
// mark initial sector as blocked
|
||||
sectorFree[0] = false;
|
||||
}
|
||||
|
||||
return file != NULL;
|
||||
}
|
||||
|
||||
void RegionFile::close()
|
||||
{
|
||||
if (file)
|
||||
{
|
||||
fclose(file);
|
||||
file = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool RegionFile::readChunk(int x, int z, RakNet::BitStream** destChunkData)
|
||||
{
|
||||
int offset = offsets[x + z * SECTOR_COLS];
|
||||
|
||||
if (offset == 0)
|
||||
{
|
||||
// not written to file yet
|
||||
return false;
|
||||
}
|
||||
|
||||
int sectorNum = offset >> 8;
|
||||
|
||||
fseek(file, sectorNum * SECTOR_BYTES, SEEK_SET);
|
||||
int length = 0;
|
||||
fread(&length, sizeof(int), 1, file);
|
||||
|
||||
assert(length < ((offset & 0xff) * SECTOR_BYTES));
|
||||
length -= sizeof(int);
|
||||
if (length <= 0) {
|
||||
//*destChunkData = NULL;
|
||||
//return false;
|
||||
}
|
||||
|
||||
unsigned char* data = new unsigned char[length];
|
||||
logAssert(fread(data, 1, length, file), length);
|
||||
*destChunkData = new RakNet::BitStream(data, length, false);
|
||||
//delete [] data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegionFile::writeChunk(int x, int z, RakNet::BitStream& chunkData)
|
||||
{
|
||||
int size = chunkData.GetNumberOfBytesUsed() + sizeof(int);
|
||||
|
||||
int offset = offsets[x + z * SECTOR_COLS];
|
||||
int sectorNum = offset >> 8;
|
||||
int sectorCount = offset & 0xff;
|
||||
int sectorsNeeded = (size / SECTOR_BYTES) + 1;
|
||||
|
||||
if (sectorsNeeded > 256)
|
||||
{
|
||||
LOGI("ERROR: Chunk is too big to be saved to file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sectorNum != 0 && sectorCount == sectorsNeeded) {
|
||||
// the sector can be written on top of its old data
|
||||
write(sectorNum, chunkData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we need a new location
|
||||
|
||||
// mark old sectors as free
|
||||
for (int i = 0; i < sectorCount; i++)
|
||||
{
|
||||
sectorFree[sectorNum + i] = true;
|
||||
}
|
||||
|
||||
// find an available slot with enough run length
|
||||
int slot = 0;
|
||||
int runLength = 0;
|
||||
bool extendFile = false;
|
||||
while (runLength < sectorsNeeded)
|
||||
{
|
||||
if (sectorFree.find(slot + runLength) == sectorFree.end())
|
||||
{
|
||||
extendFile = true;
|
||||
break;
|
||||
}
|
||||
if (sectorFree[slot + runLength] == true)
|
||||
{
|
||||
runLength++;
|
||||
}
|
||||
else
|
||||
{
|
||||
slot = slot + runLength + 1;
|
||||
runLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (extendFile)
|
||||
{
|
||||
fseek(file, 0, SEEK_END);
|
||||
for (int i = 0; i < (sectorsNeeded - runLength); i++)
|
||||
{
|
||||
fwrite(emptyChunk, sizeof(int), SECTOR_INTS, file);
|
||||
sectorFree[slot + i] = true;
|
||||
}
|
||||
}
|
||||
offsets[x + z * SECTOR_COLS] = (slot << 8) | sectorsNeeded;
|
||||
// mark slots as taken
|
||||
for (int i = 0; i < sectorsNeeded; i++)
|
||||
{
|
||||
sectorFree[slot + i] = false;
|
||||
}
|
||||
|
||||
// write!
|
||||
write(slot, chunkData);
|
||||
|
||||
// write sector data
|
||||
fseek(file, (x + z * SECTOR_COLS) * sizeof(int), SEEK_SET);
|
||||
fwrite(&offsets[x + z * SECTOR_COLS], sizeof(int), 1, file);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegionFile::write(int sector, RakNet::BitStream& chunkData)
|
||||
{
|
||||
fseek(file, sector * SECTOR_BYTES, SEEK_SET);
|
||||
//LOGI("writing %d B to sector %d\n", chunkData.GetNumberOfBytesUsed(), sector);
|
||||
int size = chunkData.GetNumberOfBytesUsed() + sizeof(int);
|
||||
logAssert(fwrite(&size, sizeof(int), 1, file), 1);
|
||||
logAssert(fwrite(chunkData.GetData(), 1, chunkData.GetNumberOfBytesUsed(), file), chunkData.GetNumberOfBytesUsed());
|
||||
|
||||
return true;
|
||||
}
|
||||
31
src/world/level/storage/RegionFile.h
Executable file
31
src/world/level/storage/RegionFile.h
Executable file
@@ -0,0 +1,31 @@
|
||||
#ifndef NET_MINECRAFT_WORLD_LEVEL_STORAGE__RegionFile_H__
|
||||
#define NET_MINECRAFT_WORLD_LEVEL_STORAGE__RegionFile_H__
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "../../../raknet/BitStream.h"
|
||||
|
||||
typedef std::map<int, bool> FreeSectorMap;
|
||||
|
||||
class RegionFile
|
||||
{
|
||||
public:
|
||||
RegionFile(const std::string& basePath);
|
||||
virtual ~RegionFile();
|
||||
|
||||
bool open();
|
||||
bool readChunk(int x, int z, RakNet::BitStream** destChunkData);
|
||||
bool writeChunk(int x, int z, RakNet::BitStream& chunkData);
|
||||
private:
|
||||
bool write(int sector, RakNet::BitStream& chunkData);
|
||||
void close();
|
||||
|
||||
FILE* file;
|
||||
std::string filename;
|
||||
int* offsets;
|
||||
int* emptyChunk;
|
||||
FreeSectorMap sectorFree;
|
||||
};
|
||||
|
||||
|
||||
#endif /*NET_MINECRAFT_WORLD_LEVEL_STORAGE__RegionFile_H__*/
|
||||
Reference in New Issue
Block a user