aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.World/OldChunkStorage.cpp
diff options
context:
space:
mode:
authordaoge_cmd <3523206925@qq.com>2026-03-01 12:16:08 +0800
committerdaoge_cmd <3523206925@qq.com>2026-03-01 12:16:08 +0800
commitb691c43c44ff180d10e7d4a9afc83b98551ff586 (patch)
tree3e9849222cbc6ba49f2f1fc6e5fe7179632c7390 /Minecraft.World/OldChunkStorage.cpp
parentdef8cb415354ac390b7e89052a50605285f1aca9 (diff)
Initial commit
Diffstat (limited to 'Minecraft.World/OldChunkStorage.cpp')
-rw-r--r--Minecraft.World/OldChunkStorage.cpp594
1 files changed, 594 insertions, 0 deletions
diff --git a/Minecraft.World/OldChunkStorage.cpp b/Minecraft.World/OldChunkStorage.cpp
new file mode 100644
index 00000000..50c29a96
--- /dev/null
+++ b/Minecraft.World/OldChunkStorage.cpp
@@ -0,0 +1,594 @@
+#include "stdafx.h"
+#include "File.h"
+#include "InputOutputStream.h"
+#include "net.minecraft.world.entity.h"
+#include "net.minecraft.world.level.h"
+#include "net.minecraft.world.level.chunk.h"
+#include "net.minecraft.world.level.tile.entity.h"
+#include "net.minecraft.world.level.storage.h"
+#include "FileHeader.h"
+#include "OldChunkStorage.h"
+DWORD OldChunkStorage::tlsIdx = 0;
+OldChunkStorage::ThreadStorage *OldChunkStorage::tlsDefault = NULL;
+
+OldChunkStorage::ThreadStorage::ThreadStorage()
+{
+ blockData = byteArray(Level::CHUNK_TILE_COUNT);
+ dataData = byteArray(Level::HALF_CHUNK_TILE_COUNT);
+ skyLightData = byteArray(Level::HALF_CHUNK_TILE_COUNT);
+ blockLightData = byteArray(Level::HALF_CHUNK_TILE_COUNT);
+}
+
+OldChunkStorage::ThreadStorage::~ThreadStorage()
+{
+ delete [] blockData.data;
+ delete [] dataData.data;
+ delete [] skyLightData.data;
+ delete [] blockLightData.data;
+}
+
+void OldChunkStorage::CreateNewThreadStorage()
+{
+ ThreadStorage *tls = new ThreadStorage();
+ if(tlsDefault == NULL )
+ {
+ tlsIdx = TlsAlloc();
+ tlsDefault = tls;
+ }
+ TlsSetValue(tlsIdx, tls);
+}
+
+void OldChunkStorage::UseDefaultThreadStorage()
+{
+ TlsSetValue(tlsIdx, tlsDefault);
+}
+
+void OldChunkStorage::ReleaseThreadStorage()
+{
+ ThreadStorage *tls = (ThreadStorage *)TlsGetValue(tlsIdx);
+ if( tls == tlsDefault ) return;
+
+ delete tls;
+}
+
+OldChunkStorage::OldChunkStorage(File dir, bool create)
+{
+ this->dir = dir;
+ this->create = create;
+}
+
+File OldChunkStorage::getFile(int x, int z)
+{
+ wchar_t name[MAX_PATH_SIZE];
+ wchar_t path1[MAX_PATH_SIZE];
+ wchar_t path2[MAX_PATH_SIZE];
+
+ wchar_t xRadix36[64];
+ wchar_t zRadix36[64];
+#if ( defined __PS3__ || defined __ORBIS__ || defined __PSVITA__ )
+ assert(0); // need a gcc verison of _itow ?
+#else
+ _itow(x,xRadix36,36);
+ _itow(z,zRadix36,36);
+ swprintf(name,MAX_PATH_SIZE,L"c.%ls.%ls.dat",xRadix36,zRadix36);
+ _itow(x & 63,path1,36);
+ _itow(z & 63,path2,36);
+#endif
+ //sprintf(file,"%s\\%s",dir,path1);
+ File file( dir, wstring( path1 ) );
+ if( !file.exists() )
+ {
+ if(create) file.mkdir();
+ else
+ {
+ return File(L"");
+ }
+ }
+
+ //strcat(file,"\\");
+ //strcat(file,path2);
+ file = File( file, wstring( path2 ) );
+ if( !file.exists() )
+ {
+ if(create) file.mkdir();
+ else
+ {
+ return File(L"");
+ }
+ }
+
+ //strcat(file,"\\");
+ //strcat(file,name);
+ //sprintf(file,"%s\\%s",file,name);
+ file = File( file, wstring( name ) );
+ if ( !file.exists() )
+ {
+ if (!create)
+ {
+ return File(L"");
+ }
+ }
+ return file;
+}
+
+LevelChunk *OldChunkStorage::load(Level *level, int x, int z)
+{
+ File file = getFile(x, z);
+ if (!file.getPath().empty() && file.exists())
+ {
+ // 4J - removed try/catch
+ // try {
+ // System.out.println("Loading chunk "+x+", "+z);
+ FileInputStream fis = FileInputStream(file);
+ CompoundTag *tag = NbtIo::readCompressed(&fis);
+ if (!tag->contains(L"Level"))
+ {
+ char buf[256];
+ sprintf(buf,"Chunk file at %d, %d is missing level data, skipping\n",x,z);
+ app.DebugPrintf(buf);
+ return NULL;
+ }
+ if (!tag->getCompound(L"Level")->contains(L"Blocks"))
+ {
+ char buf[256];
+ sprintf(buf,"Chunk file at %d, %d is missing block data, skipping\n",x,z);
+ app.DebugPrintf(buf);
+ return NULL;
+ }
+ LevelChunk *levelChunk = OldChunkStorage::load(level, tag->getCompound(L"Level"));
+ if (!levelChunk->isAt(x, z))
+ {
+ char buf[256];
+ sprintf(buf,"Chunk fileat %d, %d is in the wrong location; relocating. Expected %d, %d, got %d, %d\n",
+ x, z, x, z, levelChunk->x, levelChunk->z);
+ app.DebugPrintf(buf);
+ tag->putInt(L"xPos", x);
+ tag->putInt(L"zPos", z);
+ levelChunk = OldChunkStorage::load(level, tag->getCompound(L"Level"));
+ }
+
+ return levelChunk;
+ // } catch (Exception e) {
+ // e.printStackTrace();
+ // }
+ }
+ return NULL;
+}
+
+void OldChunkStorage::save(Level *level, LevelChunk *levelChunk)
+{
+ level->checkSession();
+ File file = getFile(levelChunk->x, levelChunk->z);
+ if (file.exists())
+ {
+ LevelData *levelData = level->getLevelData();
+ levelData->setSizeOnDisk( levelData->getSizeOnDisk() - file.length() );
+ }
+
+ // 4J - removed try/catch
+ // try {
+ //char tmpFileName[MAX_PATH_SIZE];
+ //sprintf(tmpFileName,"%s\\%s",dir,"tmp_chunk.dat");
+ File tmpFile( dir, L"tmp_chunk.dat" );
+ // System.out.println("Saving chunk "+levelChunk.x+", "+levelChunk.z);
+
+ FileOutputStream fos = FileOutputStream(tmpFile);
+ CompoundTag *tag = new CompoundTag();
+ CompoundTag *levelData = new CompoundTag();
+ tag->put(L"Level", levelData);
+ OldChunkStorage::save(levelChunk, level, levelData);
+ NbtIo::writeCompressed(tag, &fos);
+ fos.close();
+
+ if (file.exists())
+ {
+ //DeleteFile(file);
+ file._delete();
+ }
+ //MoveFile(tmpFile,file);
+ tmpFile.renameTo( file );
+
+ LevelData *levelInfo = level->getLevelData();
+ levelInfo->setSizeOnDisk(levelInfo->getSizeOnDisk() + file.length() );
+ // } catch (Exception e) {
+ // e.printStackTrace();
+ // }
+}
+
+bool OldChunkStorage::saveEntities(LevelChunk *lc, Level *level, CompoundTag *tag)
+{
+ // If we saved and it had no entities, and nothing has been added since skip this one
+ if(!lc->lastSaveHadEntities) return false;
+
+ lc->lastSaveHadEntities = false;
+ ListTag<CompoundTag> *entityTags = new ListTag<CompoundTag>();
+
+#ifdef _ENTITIES_RW_SECTION
+ EnterCriticalRWSection(&lc->m_csEntities, true);
+#else
+ EnterCriticalSection(&lc->m_csEntities);
+#endif
+ for (int i = 0; i < lc->ENTITY_BLOCKS_LENGTH; i++)
+ {
+ AUTO_VAR(itEnd, lc->entityBlocks[i]->end());
+ for( vector<shared_ptr<Entity> >::iterator it = lc->entityBlocks[i]->begin(); it != itEnd; it++ )
+ {
+ shared_ptr<Entity> e = *it;
+ lc->lastSaveHadEntities = true;
+ CompoundTag *teTag = new CompoundTag();
+ if (e->save(teTag))
+ {
+ entityTags->add(teTag);
+ }
+
+ }
+ }
+#ifdef _ENTITIES_RW_SECTION
+ LeaveCriticalRWSection(&lc->m_csEntities, true);
+#else
+ LeaveCriticalSection(&lc->m_csEntities);
+#endif
+
+ tag->put(L"Entities", entityTags);
+
+ return lc->lastSaveHadEntities;
+}
+
+void OldChunkStorage::save(LevelChunk *lc, Level *level, DataOutputStream *dos)
+{
+ dos->writeShort(SAVE_FILE_VERSION_NUMBER);
+ dos->writeInt(lc->x);
+ dos->writeInt(lc->z);
+ dos->writeLong(level->getTime());
+
+ PIXBeginNamedEvent(0,"Getting block data");
+ lc->writeCompressedBlockData(dos);
+ PIXEndNamedEvent();
+
+ PIXBeginNamedEvent(0,"Getting data data");
+ lc->writeCompressedDataData(dos);
+ PIXEndNamedEvent();
+
+ PIXBeginNamedEvent(0,"Getting sky and block light data");
+ lc->writeCompressedSkyLightData(dos);
+ lc->writeCompressedBlockLightData(dos);
+ PIXEndNamedEvent();
+
+ dos->write(lc->heightmap);
+ dos->writeShort(lc->terrainPopulated);
+ dos->write(lc->getBiomes());
+
+ PIXBeginNamedEvent(0,"Saving entities");
+ CompoundTag *tag = new CompoundTag();
+#ifndef SPLIT_SAVES
+ saveEntities(lc, level, tag);
+#endif
+
+ PIXBeginNamedEvent(0,"Saving tile entities");
+ ListTag<CompoundTag> *tileEntityTags = new ListTag<CompoundTag>();
+
+ AUTO_VAR(itEnd, lc->tileEntities.end());
+ for( unordered_map<TilePos, shared_ptr<TileEntity>, TilePosKeyHash, TilePosKeyEq>::iterator it = lc->tileEntities.begin();
+ it != itEnd; it++)
+ {
+ shared_ptr<TileEntity> te = it->second;
+ CompoundTag *teTag = new CompoundTag();
+ te->save(teTag);
+ tileEntityTags->add(teTag);
+ }
+ tag->put(L"TileEntities", tileEntityTags);
+ PIXEndNamedEvent();
+
+ PIXBeginNamedEvent(0,"Saving tile tick data");
+ vector<TickNextTickData > *ticksInChunk = level->fetchTicksInChunk(lc, false);
+ if (ticksInChunk != NULL)
+ {
+ __int64 levelTime = level->getTime();
+
+ ListTag<CompoundTag> *tickTags = new ListTag<CompoundTag>();
+ for( int i = 0; i < ticksInChunk->size(); i++ )
+ {
+ TickNextTickData td = ticksInChunk->at(i);
+ CompoundTag *teTag = new CompoundTag();
+ teTag->putInt(L"i", td.tileId);
+ teTag->putInt(L"x", td.x);
+ teTag->putInt(L"y", td.y);
+ teTag->putInt(L"z", td.z);
+ teTag->putInt(L"t", (int) (td.m_delay - levelTime));
+
+ tickTags->add(teTag);
+ }
+ tag->put(L"TileTicks", tickTags);
+ }
+ delete ticksInChunk;
+ PIXEndNamedEvent();
+
+ NbtIo::write(tag,dos);
+ delete tag;
+ PIXEndNamedEvent();
+}
+
+void OldChunkStorage::save(LevelChunk *lc, Level *level, CompoundTag *tag)
+{
+ level->checkSession();
+ tag->putInt(L"xPos", lc->x);
+ tag->putInt(L"zPos", lc->z);
+ tag->putLong(L"LastUpdate", level->getTime());
+ // 4J - changes here for new storage. Now have static storage for getting lighting data for block, data, and sky & block lighting. This
+ // wasn't required in the original version as we could just reference the information in the level itself, but with our new storage system
+ // the full data doesn't normally exist & so getSkyLightData/getBlockLightData etc. need somewhere to output this data. Making this static so
+ // that we aren't dynamically allocating memory in the server thread when writing chunks as this causes serious stalling on the main thread.
+ // Will be fine so long as we only actually create tags for once chunk at a time.
+
+ // 4J Stu - As we now save on multiple threads, the static data has been moved to TLS
+ ThreadStorage *tls = (ThreadStorage *)TlsGetValue(tlsIdx);
+
+ PIXBeginNamedEvent(0,"Getting block data");
+ //static byteArray blockData = byteArray(32768);
+ lc->getBlockData(tls->blockData);
+ tag->putByteArray(L"Blocks", tls->blockData);
+ PIXEndNamedEvent();
+
+ PIXBeginNamedEvent(0,"Getting data data");
+ //static byteArray dataData = byteArray(16384);
+ lc->getDataData(tls->dataData);
+ tag->putByteArray(L"Data", tls->dataData);
+ PIXEndNamedEvent();
+
+ PIXBeginNamedEvent(0,"Getting sky and block light data");
+ //static byteArray skyLightData = byteArray(16384);
+ //static byteArray blockLightData = byteArray(16384);
+ lc->getSkyLightData(tls->skyLightData);
+ lc->getBlockLightData(tls->blockLightData);
+ tag->putByteArray(L"SkyLight", tls->skyLightData);
+ tag->putByteArray(L"BlockLight", tls->blockLightData);
+ PIXEndNamedEvent();
+
+ tag->putByteArray(L"HeightMap", lc->heightmap);
+ tag->putShort(L"TerrainPopulatedFlags", lc->terrainPopulated); // 4J - changed from "TerrainPopulated" to "TerrainPopulatedFlags" as now stores a bitfield, java stores a bool
+ tag->putByteArray(L"Biomes", lc->getBiomes());
+
+ PIXBeginNamedEvent(0,"Saving entities");
+#ifndef SPLIT_SAVES
+ saveEntities(lc, level, tag);
+#endif
+
+ PIXBeginNamedEvent(0,"Saving tile entities");
+ ListTag<CompoundTag> *tileEntityTags = new ListTag<CompoundTag>();
+
+ AUTO_VAR(itEnd, lc->tileEntities.end());
+ for( unordered_map<TilePos, shared_ptr<TileEntity>, TilePosKeyHash, TilePosKeyEq>::iterator it = lc->tileEntities.begin();
+ it != itEnd; it++)
+ {
+ shared_ptr<TileEntity> te = it->second;
+ CompoundTag *teTag = new CompoundTag();
+ te->save(teTag);
+ tileEntityTags->add(teTag);
+ }
+ tag->put(L"TileEntities", tileEntityTags);
+ PIXEndNamedEvent();
+
+ PIXBeginNamedEvent(0,"Saving tile tick data");
+ vector<TickNextTickData > *ticksInChunk = level->fetchTicksInChunk(lc, false);
+ if (ticksInChunk != NULL)
+ {
+ __int64 levelTime = level->getTime();
+
+ ListTag<CompoundTag> *tickTags = new ListTag<CompoundTag>();
+ for( int i = 0; i < ticksInChunk->size(); i++ )
+ {
+ TickNextTickData td = ticksInChunk->at(i);
+ CompoundTag *teTag = new CompoundTag();
+ teTag->putInt(L"i", td.tileId);
+ teTag->putInt(L"x", td.x);
+ teTag->putInt(L"y", td.y);
+ teTag->putInt(L"z", td.z);
+ teTag->putInt(L"t", (int) (td.m_delay - levelTime));
+
+ tickTags->add(teTag);
+ }
+ tag->put(L"TileTicks", tickTags);
+ }
+ delete ticksInChunk;
+ PIXEndNamedEvent();
+ PIXEndNamedEvent();
+}
+
+void OldChunkStorage::loadEntities(LevelChunk *lc, Level *level, CompoundTag *tag)
+{
+ ListTag<CompoundTag> *entityTags = (ListTag<CompoundTag> *) tag->getList(L"Entities");
+ if (entityTags != NULL)
+ {
+ for (int i = 0; i < entityTags->size(); i++)
+ {
+ CompoundTag *teTag = entityTags->get(i);
+ shared_ptr<Entity> te = EntityIO::loadStatic(teTag, level);
+ lc->lastSaveHadEntities = true;
+ if (te != NULL)
+ {
+ lc->addEntity(te);
+ }
+ }
+ }
+
+ ListTag<CompoundTag> *tileEntityTags = (ListTag<CompoundTag> *) tag->getList(L"TileEntities");
+ if (tileEntityTags != NULL)
+ {
+ for (int i = 0; i < tileEntityTags->size(); i++)
+ {
+ CompoundTag *teTag = tileEntityTags->get(i);
+ shared_ptr<TileEntity> te = TileEntity::loadStatic(teTag);
+ if (te != NULL)
+ {
+ lc->addTileEntity(te);
+ }
+ }
+ }
+}
+
+LevelChunk *OldChunkStorage::load(Level *level, DataInputStream *dis)
+{
+ short version = dis->readShort();
+ int x = dis->readInt();
+ int z = dis->readInt();
+ int time = dis->readLong();
+
+ LevelChunk *levelChunk = new LevelChunk(level, x, z);
+
+ levelChunk->readCompressedBlockData(dis);
+ levelChunk->readCompressedDataData(dis);
+ levelChunk->readCompressedSkyLightData(dis);
+ levelChunk->readCompressedBlockLightData(dis);
+
+ dis->readFully(levelChunk->heightmap);
+
+ levelChunk->terrainPopulated = dis->readShort();
+ // If all neighbours have been post-processed, then we should have done the post-post-processing now. Check that this is set as if it isn't then we won't be able
+ // to send network data for chunks, and we won't ever try and set it again as all the directional flags are now already set - should only be an issue for old maps
+ // before this flag was added.
+ if( ( levelChunk->terrainPopulated & LevelChunk::sTerrainPopulatedAllNeighbours ) == LevelChunk::sTerrainPopulatedAllNeighbours )
+ {
+ levelChunk->terrainPopulated |= LevelChunk::sTerrainPostPostProcessed;
+ }
+
+ dis->readFully(levelChunk->biomes);
+
+ CompoundTag *tag = NbtIo::read(dis);
+
+ loadEntities(levelChunk, level, tag);
+
+ if (tag->contains(L"TileTicks"))
+ {
+ ListTag<CompoundTag> *tileTicks = (ListTag<CompoundTag> *) tag->getList(L"TileTicks");
+
+ if (tileTicks != NULL)
+ {
+ for (int i = 0; i < tileTicks->size(); i++)
+ {
+ CompoundTag *teTag = tileTicks->get(i);
+
+ level->forceAddTileTick(teTag->getInt(L"x"), teTag->getInt(L"y"), teTag->getInt(L"z"), teTag->getInt(L"i"), teTag->getInt(L"t"));
+ }
+ }
+ }
+
+ delete tag;
+
+ return levelChunk;
+}
+
+LevelChunk *OldChunkStorage::load(Level *level, CompoundTag *tag)
+{
+ int x = tag->getInt(L"xPos");
+ int z = tag->getInt(L"zPos");
+
+ LevelChunk *levelChunk = new LevelChunk(level, x, z);
+ // 4J - the original code uses the data in the tag directly, but this is now just used as a source when creating the compressed data, so
+ // we need to free up the data in the tag once we are done
+ levelChunk->setBlockData(tag->getByteArray(L"Blocks"));
+ delete [] tag->getByteArray(L"Blocks").data;
+ // levelChunk->blocks = tag->getByteArray(L"Blocks");
+
+ // 4J - the original code uses the data in the tag directly, but this is now just used as a source when creating the compressed data, so
+ // we need to free up the data in the tag once we are done
+ levelChunk->setDataData(tag->getByteArray(L"Data"));
+ delete [] tag->getByteArray(L"Data").data;
+
+ // 4J - changed to use our new methods for accessing lighting
+ levelChunk->setSkyLightData(tag->getByteArray(L"SkyLight"));
+ levelChunk->setBlockLightData(tag->getByteArray(L"BlockLight"));
+
+ // In the original code (commented out below) constructing DataLayers from these arrays uses the data directly and so it doesn't need deleted. The new
+ // setSkyLightData/setBlockLightData take a copy of the data so we need to delete the local one now
+ delete [] tag->getByteArray(L"SkyLight").data;
+ delete [] tag->getByteArray(L"BlockLight").data;
+
+ // levelChunk->skyLight = new DataLayer(tag->getByteArray(L"SkyLight"), level->depthBits);
+ // levelChunk->blockLight = new DataLayer(tag->getByteArray(L"BlockLight"), level->depthBits);
+
+ delete [] levelChunk->heightmap.data;
+ levelChunk->heightmap = tag->getByteArray(L"HeightMap");
+ // 4J - TerrainPopulated was a bool (java), then changed to be a byte bitfield, then replaced with TerrainPopulatedShort to store a wider bitfield
+ if( tag->get(L"TerrainPopulated") )
+ {
+ // Java bool type or byte bitfield
+ levelChunk->terrainPopulated = tag->getByte(L"TerrainPopulated");
+ if( levelChunk->terrainPopulated >= 1 ) levelChunk->terrainPopulated = LevelChunk::sTerrainPopulatedAllNeighbours | LevelChunk::sTerrainPostPostProcessed; // Convert from old bool type to new bitfield
+ }
+ else
+ {
+ // New style short
+ levelChunk->terrainPopulated = tag->getShort(L"TerrainPopulatedFlags");
+ // If all neighbours have been post-processed, then we should have done the post-post-processing now. Check that this is set as if it isn't then we won't be able
+ // to send network data for chunks, and we won't ever try and set it again as all the directional flags are now already set - should only be an issue for old maps
+ // before this flag was added.
+ if( ( levelChunk->terrainPopulated & LevelChunk::sTerrainPopulatedAllNeighbours ) == LevelChunk::sTerrainPopulatedAllNeighbours )
+ {
+ levelChunk->terrainPopulated |= LevelChunk::sTerrainPostPostProcessed;
+ }
+ }
+
+#if 0
+ // 4J - removed - we shouldn't need this any more
+ if (!levelChunk->data->isValid())
+ {
+ levelChunk->data = new DataLayer(LevelChunk::BLOCKS_LENGTH, level->depthBits); // 4J - BLOCKS_LENGTH was levelChunk->blocks.length
+ }
+#endif
+
+ // 4J removed - we shouldn't need this any more
+#if 0
+ if (levelChunk->heightmap.data == NULL || !levelChunk->skyLight->isValid())
+ {
+ static int chunksUpdated = 0;
+ delete [] levelChunk->heightmap.data;
+ levelChunk->heightmap = byteArray(16 * 16);
+ delete levelChunk->skyLight;
+ levelChunk->skyLight = new DataLayer(levelChunk->blocks.length, level->depthBits);
+ levelChunk->recalcHeightmap();
+ }
+
+ if (!levelChunk->blockLight->isValid())
+ {
+ delete levelChunk->blockLight;
+ levelChunk->blockLight = new DataLayer(levelChunk->blocks.length, level->depthBits);
+ levelChunk->recalcBlockLights();
+ }
+#endif
+
+ if (tag->contains(L"Biomes"))
+ {
+ levelChunk->setBiomes(tag->getByteArray(L"Biomes"));
+ }
+
+ loadEntities(levelChunk, level, tag);
+
+ if (tag->contains(L"TileTicks"))
+ {
+ ListTag<CompoundTag> *tileTicks = (ListTag<CompoundTag> *) tag->getList(L"TileTicks");
+
+ if (tileTicks != NULL)
+ {
+ for (int i = 0; i < tileTicks->size(); i++)
+ {
+ CompoundTag *teTag = tileTicks->get(i);
+
+ level->forceAddTileTick(teTag->getInt(L"x"), teTag->getInt(L"y"), teTag->getInt(L"z"), teTag->getInt(L"i"), teTag->getInt(L"t"));
+ }
+ }
+ }
+
+ return levelChunk;
+}
+
+void OldChunkStorage::tick()
+{
+}
+
+void OldChunkStorage::flush()
+{
+}
+
+void OldChunkStorage::saveEntities(Level *level, LevelChunk *levelChunk)
+{
+}