From 8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Sat, 6 Jul 2013 19:56:03 +0000 Subject: AnvilStats: moved into the Tools folder git-svn-id: http://mc-server.googlecode.com/svn/trunk@1658 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- AnvilStats/AnvilStats.cpp | 69 ----- AnvilStats/AnvilStats.sln | 34 --- AnvilStats/AnvilStats.txt | 27 -- AnvilStats/AnvilStats.vcproj | 452 ----------------------------- AnvilStats/BiomeMap.cpp | 172 ----------- AnvilStats/BiomeMap.h | 69 ----- AnvilStats/Callback.h | 165 ----------- AnvilStats/ChunkExtract.cpp | 104 ------- AnvilStats/ChunkExtract.h | 66 ----- AnvilStats/Globals.cpp | 10 - AnvilStats/Globals.h | 228 --------------- AnvilStats/HeightMap.cpp | 265 ----------------- AnvilStats/HeightMap.h | 81 ------ AnvilStats/Processor.cpp | 579 ------------------------------------- AnvilStats/Processor.h | 77 ----- AnvilStats/SpringStats.cpp | 279 ------------------ AnvilStats/SpringStats.h | 102 ------- AnvilStats/Statistics.cpp | 523 --------------------------------- AnvilStats/Statistics.h | 138 --------- AnvilStats/Utils.cpp | 291 ------------------- AnvilStats/Utils.h | 61 ---- AnvilStats/profile_run.cmd | 70 ----- Tools/AnvilStats/AnvilStats.cpp | 69 +++++ Tools/AnvilStats/AnvilStats.sln | 34 +++ Tools/AnvilStats/AnvilStats.txt | 27 ++ Tools/AnvilStats/AnvilStats.vcproj | 452 +++++++++++++++++++++++++++++ Tools/AnvilStats/BiomeMap.cpp | 172 +++++++++++ Tools/AnvilStats/BiomeMap.h | 69 +++++ Tools/AnvilStats/Callback.h | 165 +++++++++++ Tools/AnvilStats/ChunkExtract.cpp | 104 +++++++ Tools/AnvilStats/ChunkExtract.h | 66 +++++ Tools/AnvilStats/Globals.cpp | 10 + Tools/AnvilStats/Globals.h | 228 +++++++++++++++ Tools/AnvilStats/HeightMap.cpp | 265 +++++++++++++++++ Tools/AnvilStats/HeightMap.h | 81 ++++++ Tools/AnvilStats/Processor.cpp | 579 +++++++++++++++++++++++++++++++++++++ Tools/AnvilStats/Processor.h | 77 +++++ Tools/AnvilStats/SpringStats.cpp | 279 ++++++++++++++++++ Tools/AnvilStats/SpringStats.h | 102 +++++++ Tools/AnvilStats/Statistics.cpp | 523 +++++++++++++++++++++++++++++++++ Tools/AnvilStats/Statistics.h | 138 +++++++++ Tools/AnvilStats/Utils.cpp | 291 +++++++++++++++++++ Tools/AnvilStats/Utils.h | 61 ++++ Tools/AnvilStats/profile_run.cmd | 70 +++++ 44 files changed, 3862 insertions(+), 3862 deletions(-) delete mode 100644 AnvilStats/AnvilStats.cpp delete mode 100644 AnvilStats/AnvilStats.sln delete mode 100644 AnvilStats/AnvilStats.txt delete mode 100644 AnvilStats/AnvilStats.vcproj delete mode 100644 AnvilStats/BiomeMap.cpp delete mode 100644 AnvilStats/BiomeMap.h delete mode 100644 AnvilStats/Callback.h delete mode 100644 AnvilStats/ChunkExtract.cpp delete mode 100644 AnvilStats/ChunkExtract.h delete mode 100644 AnvilStats/Globals.cpp delete mode 100644 AnvilStats/Globals.h delete mode 100644 AnvilStats/HeightMap.cpp delete mode 100644 AnvilStats/HeightMap.h delete mode 100644 AnvilStats/Processor.cpp delete mode 100644 AnvilStats/Processor.h delete mode 100644 AnvilStats/SpringStats.cpp delete mode 100644 AnvilStats/SpringStats.h delete mode 100644 AnvilStats/Statistics.cpp delete mode 100644 AnvilStats/Statistics.h delete mode 100644 AnvilStats/Utils.cpp delete mode 100644 AnvilStats/Utils.h delete mode 100644 AnvilStats/profile_run.cmd create mode 100644 Tools/AnvilStats/AnvilStats.cpp create mode 100644 Tools/AnvilStats/AnvilStats.sln create mode 100644 Tools/AnvilStats/AnvilStats.txt create mode 100644 Tools/AnvilStats/AnvilStats.vcproj create mode 100644 Tools/AnvilStats/BiomeMap.cpp create mode 100644 Tools/AnvilStats/BiomeMap.h create mode 100644 Tools/AnvilStats/Callback.h create mode 100644 Tools/AnvilStats/ChunkExtract.cpp create mode 100644 Tools/AnvilStats/ChunkExtract.h create mode 100644 Tools/AnvilStats/Globals.cpp create mode 100644 Tools/AnvilStats/Globals.h create mode 100644 Tools/AnvilStats/HeightMap.cpp create mode 100644 Tools/AnvilStats/HeightMap.h create mode 100644 Tools/AnvilStats/Processor.cpp create mode 100644 Tools/AnvilStats/Processor.h create mode 100644 Tools/AnvilStats/SpringStats.cpp create mode 100644 Tools/AnvilStats/SpringStats.h create mode 100644 Tools/AnvilStats/Statistics.cpp create mode 100644 Tools/AnvilStats/Statistics.h create mode 100644 Tools/AnvilStats/Utils.cpp create mode 100644 Tools/AnvilStats/Utils.h create mode 100644 Tools/AnvilStats/profile_run.cmd diff --git a/AnvilStats/AnvilStats.cpp b/AnvilStats/AnvilStats.cpp deleted file mode 100644 index ae3f901dc..000000000 --- a/AnvilStats/AnvilStats.cpp +++ /dev/null @@ -1,69 +0,0 @@ - -// AnvilStats.cpp - -// Implements the main app entrypoint - -#include "Globals.h" -#include "Processor.h" -#include "Statistics.h" -#include "BiomeMap.h" -#include "HeightMap.h" -#include "ChunkExtract.h" -#include "SpringStats.h" - - - - - -int main(int argc, char * argv[]) -{ - if (argc < 2) - { - LOG("Usage: %s []", argv[0]); - LOG("Available methods:"); - LOG(" 0 - statistics"); - LOG(" 1 - biome map"); - LOG(" 2 - height map"); - LOG(" 3 - extract chunks"); - LOG(" 4 - count lava- and water- springs"); - LOG("\nNo method number present, aborting."); - return -1; - } - - AString WorldFolder; - if (argc > 2) - { - WorldFolder = argv[2]; - } - else - { - WorldFolder = "." + cFile::PathSeparator; - } - - cCallbackFactory * Factory = NULL; - switch (atol(argv[1])) - { - case 0: Factory = new cStatisticsFactory; break; - case 1: Factory = new cBiomeMapFactory; break; - case 2: Factory = new cHeightMapFactory; break; - case 3: Factory = new cChunkExtractFactory(WorldFolder); break; - case 4: Factory = new cSpringStatsFactory; break; - default: - { - LOG("Unknown method \"%s\", aborting.", argv[1]); - return -2; - } - } - cProcessor Processor; - Processor.ProcessWorld(WorldFolder, *Factory); - - LOG("Processing finished"); - - delete Factory; - - LOG("Done"); -} - - - - diff --git a/AnvilStats/AnvilStats.sln b/AnvilStats/AnvilStats.sln deleted file mode 100644 index 8e143dc58..000000000 --- a/AnvilStats/AnvilStats.sln +++ /dev/null @@ -1,34 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual C++ Express 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AnvilStats", "AnvilStats.vcproj", "{CF996A5E-0A86-4004-9710-682B06B5AEBA}" - ProjectSection(ProjectDependencies) = postProject - {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA} = {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\VC2008\zlib.vcproj", "{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release profiled|Win32 = Release profiled|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Debug|Win32.ActiveCfg = Debug|Win32 - {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Debug|Win32.Build.0 = Debug|Win32 - {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release profiled|Win32.ActiveCfg = Release profiled|Win32 - {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release profiled|Win32.Build.0 = Release profiled|Win32 - {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release|Win32.ActiveCfg = Release|Win32 - {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release|Win32.Build.0 = Release|Win32 - {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Debug|Win32.ActiveCfg = Debug|Win32 - {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Debug|Win32.Build.0 = Debug|Win32 - {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release profiled|Win32.ActiveCfg = Release profiled|Win32 - {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release profiled|Win32.Build.0 = Release profiled|Win32 - {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release|Win32.ActiveCfg = Release|Win32 - {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/AnvilStats/AnvilStats.txt b/AnvilStats/AnvilStats.txt deleted file mode 100644 index 07c518e4c..000000000 --- a/AnvilStats/AnvilStats.txt +++ /dev/null @@ -1,27 +0,0 @@ - -// AnvilStats.txt - -// A Readme for the project - -/* -AnvilStats -========== - -This is a project for measuring various metrics throughout an Anvil world, presumably created by a vanilla MC. -It works by parsing the MCA files in the path specified as its param (or current directory, if no params) and -feeding each decompressed chunk into the statistics-gathering callback function. - -Possible usage: - - count the per-chunk density of specific blocks - - count the per-chunk density of dungeons, by measuring the number of zombie/skeleton/regularspider spawners - - count the per-chunk-per-biome density of trees, by measuring the number of dirt-log vertical transitions, correlating to biome data - -This project is Windows-only, although it shouldn't be too difficult to make it portable. - -Because this project uses NBT extensively, it runs much faster in Release mode. - - -*/ - - - diff --git a/AnvilStats/AnvilStats.vcproj b/AnvilStats/AnvilStats.vcproj deleted file mode 100644 index 7a7c78360..000000000 --- a/AnvilStats/AnvilStats.vcproj +++ /dev/null @@ -1,452 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AnvilStats/BiomeMap.cpp b/AnvilStats/BiomeMap.cpp deleted file mode 100644 index eca235c5f..000000000 --- a/AnvilStats/BiomeMap.cpp +++ /dev/null @@ -1,172 +0,0 @@ - -// BiomeMap.cpp - -// Implements the cBiomeMap class representing a cCallback descendant that draws a map of biomes for the world - -#include "Globals.h" -#include "BiomeMap.h" - - - - - -static const int g_BiomePalette[] = -{ - // ARGB: - 0xff0000ff, /* Ocean */ - 0xff00cf3f, /* Plains */ - 0xffffff00, /* Desert */ - 0xff7f7f7f, /* Extreme Hills */ - 0xff00cf00, /* Forest */ - 0xff007f3f, /* Taiga */ - 0xff3f7f00, /* Swampland */ - 0xff003fff, /* River */ - 0xff7f0000, /* Hell */ - 0xff007fff, /* Sky */ - 0xff3f3fff, /* Frozen Ocean */ - 0xff3f3fff, /* Frozen River */ - 0xff7fffcf, /* Ice Plains */ - 0xff3fcf7f, /* Ice Mountains */ - 0xffcf00cf, /* Mushroom Island */ - 0xff7f00ff, /* Mushroom Island Shore */ - 0xffffff3f, /* Beach */ - 0xffcfcf00, /* Desert Hills */ - 0xff00cf3f, /* Forest Hills */ - 0xff006f1f, /* Taiga Hills */ - 0xff7f8f7f, /* Extreme Hills Edge */ - 0xff004f00, /* Jungle */ - 0xff003f00, /* Jungle Hills */ -} ; - - - - - -static const unsigned char g_BMPHeader[] = -{ - 0x42, 0x4D, 0x36, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -} ; - - - - - -cBiomeMap::cBiomeMap(void) : - m_CurrentRegionX(0), - m_CurrentRegionZ(0), - m_IsCurrentRegionValid(false) -{ -} - - - - - -void cBiomeMap::Finish(void) -{ - if (m_IsCurrentRegionValid) - { - StartNewRegion(0, 0); - } -} - - - - - -bool cBiomeMap::OnNewChunk(int a_ChunkX, int a_ChunkZ) -{ - int RegionX = (a_ChunkX < 0) ? (a_ChunkX - 31) / 32 : a_ChunkX / 32; - int RegionZ = (a_ChunkZ < 0) ? (a_ChunkZ - 31) / 32 : a_ChunkZ / 32; - if ((RegionX != m_CurrentRegionX) || (RegionZ != m_CurrentRegionZ)) - { - if (m_IsCurrentRegionValid) - { - StartNewRegion(RegionX, RegionZ); - } - m_CurrentRegionX = RegionX; - m_CurrentRegionZ = RegionZ; - } - m_IsCurrentRegionValid = true; - m_CurrentChunkX = a_ChunkX; - m_CurrentChunkZ = a_ChunkZ; - m_CurrentChunkOffX = m_CurrentChunkX - m_CurrentRegionX * 32; - m_CurrentChunkOffZ = m_CurrentChunkZ - m_CurrentRegionZ * 32; - return false; -} - - - - - -bool cBiomeMap::OnBiomes(const unsigned char * a_BiomeData) -{ - ASSERT(m_CurrentChunkOffX >= 0); - ASSERT(m_CurrentChunkOffX < 32); - ASSERT(m_CurrentChunkOffZ >= 0); - ASSERT(m_CurrentChunkOffZ < 32); - char * BaseBiomes = m_Biomes + m_CurrentChunkOffZ * 16 * 512 + m_CurrentChunkOffX * 16; - for (int z = 0; z < 16; z++) - { - char * Row = BaseBiomes + z * 512; - memcpy(Row, a_BiomeData + z * 16, 16); - } // for z - return true; -} - - - - - -void cBiomeMap::StartNewRegion(int a_RegionX, int a_RegionZ) -{ - AString FileName; - Printf(FileName, "Biomes.%d.%d.bmp", m_CurrentRegionX, m_CurrentRegionZ); - cFile f; - if (!f.Open(FileName, cFile::fmWrite)) - { - LOG("Cannot open file \"%s\" for writing the biome map. Data for this region lost.", FileName.c_str()); - } - else - { - f.Write(g_BMPHeader, sizeof(g_BMPHeader)); - for (int z = 0; z < 512; z++) - { - int RowData[512]; - unsigned char * BiomeRow = (unsigned char *)m_Biomes + z * 512; - for (int x = 0; x < 512; x++) - { - RowData[x] = g_BiomePalette[BiomeRow[x]]; - } - f.Write(RowData, sizeof(RowData)); - } // for z - } - - memset(m_Biomes, 0, sizeof(m_Biomes)); - m_CurrentRegionX = a_RegionX; - m_CurrentRegionZ = a_RegionZ; -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cBiomeMapFactory: - -cBiomeMapFactory::~cBiomeMapFactory() -{ - // Force all threads to save their last regions: - for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) - { - ((cBiomeMap *)(*itr))->Finish(); - } - // TODO: Join all the files into one giant image file -} - - - - diff --git a/AnvilStats/BiomeMap.h b/AnvilStats/BiomeMap.h deleted file mode 100644 index f0d306c04..000000000 --- a/AnvilStats/BiomeMap.h +++ /dev/null @@ -1,69 +0,0 @@ - -// BiomeMap.h - -// Interfaces to the cBiomeMap class representing a cCallback descendant that draws a map of biomes for the world - - - - - -#pragma once - -#include "Callback.h" - - - - - -class cBiomeMap : - public cCallback -{ -public: - cBiomeMap(void); - - /// Saves the last region that it was processing - void Finish(void); - -protected: - int m_CurrentChunkX; // Absolute chunk coords - int m_CurrentChunkZ; - int m_CurrentChunkOffX; // Chunk offset from the start of the region - int m_CurrentChunkOffZ; - int m_CurrentRegionX; - int m_CurrentRegionZ; - bool m_IsCurrentRegionValid; - char m_Biomes[16 * 32 * 16 * 32]; // Biome map of the entire current region [x + 16 * 32 * z] - - // cCallback overrides: - virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; - virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } - virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } - virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } - virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } - virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } - virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! - virtual bool OnBiomes(const unsigned char * a_BiomeData) override; - - void StartNewRegion(int a_RegionX, int a_RegionZ); -} ; - - - - - -class cBiomeMapFactory : - public cCallbackFactory -{ -public: - virtual ~cBiomeMapFactory(); - - virtual cCallback * CreateNewCallback(void) override - { - return new cBiomeMap; - } - -} ; - - - - diff --git a/AnvilStats/Callback.h b/AnvilStats/Callback.h deleted file mode 100644 index 92d394d0e..000000000 --- a/AnvilStats/Callback.h +++ /dev/null @@ -1,165 +0,0 @@ - -// Callback.h - -// Interfaces to the cCallback base class used as the base class for all statistical callbacks - - - - - -#pragma once - - - - - -// fwd: -class cParsedNBT; - - - - - -/** The base class for all chunk-processor callbacks, declares the interface. -The processor calls each virtual function in the order they are declared here with the specified args. -If the function returns true, the processor moves on to next chunk and starts calling the callbacks again from start with -the new chunk data. -So if a statistics collector doesn't need data decompression at all, it can stop the processor from doing so early-enough -and still get meaningful data. -A callback is guaranteed to run in a single thread and always the same thread. -A callback is guaranteed to run on all chunks in a region and one region is guaranteed to be handled by only callback. -*/ -class cCallback abstract -{ -public: - virtual ~cCallback() {} // Force a virtual destructor in each descendant - - /// Called to inform the stats module of the chunk coords for newly processing chunk - virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) = 0; - - /// Called to inform about the chunk's data offset in the file (chunk mini-header), the number of sectors it uses and the timestamp field value - virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) { return true; } - - /// Called to inform of the compressed chunk data size and position in the file (offset from file start to the actual data) - virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) { return true; } - - /// Just in case you wanted to process the NBT yourself ;) - virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) { return true; } - - /// The chunk's NBT should specify chunk coords, these are sent here: - virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) { return true; } - - /// The chunk contains a LastUpdate value specifying the last tick in which it was saved. - virtual bool OnLastUpdate(Int64 a_LastUpdate) { return true; } - - virtual bool OnTerrainPopulated(bool a_Populated) { return true; } - - virtual bool OnBiomes(const unsigned char * a_BiomeData) { return true; } - - /** Called when a heightmap for the chunk is read from the file. - Note that the heightmap is given in big-endian ints, so if you want it, you need to ntohl() it first! - */ - virtual bool OnHeightMap(const int * a_HeightMapBE) { return true; } - - /** If there is data for the section, this callback is called; otherwise OnEmptySection() is called instead. - All OnSection() callbacks are called first, and only then all the remaining sections are reported in OnEmptySection(). - */ - virtual bool OnSection( - unsigned char a_Y, - const BLOCKTYPE * a_BlockTypes, - const NIBBLETYPE * a_BlockAdditional, - const NIBBLETYPE * a_BlockMeta, - const NIBBLETYPE * a_BlockLight, - const NIBBLETYPE * a_BlockSkyLight - ) { return true; } - - /** If there is no data for a section, this callback is called; otherwise OnSection() is called instead. - OnEmptySection() callbacks are called after all OnSection() callbacks. - */ - virtual bool OnEmptySection(unsigned char a_Y) { return false; } - - /** Called after all sections have been processed via either OnSection() or OnEmptySection(). - */ - virtual bool OnSectionsFinished(void) { return true; } - - /** Called for each entity in the chunk. - Common parameters are parsed from the NBT. - The callback may parse any other param from the a_NBT and a_NBTTag parameters. - The a_NBTTag parameter points to the entity compound tag inside the Entities tag. - */ - virtual bool OnEntity( - const AString & a_EntityType, - double a_PosX, double a_PosY, double a_PosZ, - double a_SpeedX, double a_SpeedY, double a_SpeedZ, - float a_Yaw, float a_Pitch, - float a_FallDistance, - short a_FireTicksLeft, - short a_AirTicks, - char a_IsOnGround, - cParsedNBT & a_NBT, - int a_NBTTag - ) { return true; } - - /** Called for each tile entity in the chunk. - Common parameters are parsed from the NBT. - The callback may parse any other param from the a_NBT and a_NBTTag parameters. - The a_NBTTag parameter points to the tile entity compound tag inside the TileEntities tag. - */ - virtual bool OnTileEntity( - const AString & a_EntityType, - int a_PosX, int a_PosY, int a_PosZ, - cParsedNBT & a_NBT, - int a_NBTTag - ) { return true; } - - /// Called for each tile tick in the chunk - virtual bool OnTileTick( - int a_BlockType, - int a_TicksLeft, - int a_PosX, int a_PosY, int a_PosZ - ) { return true; } -} ; - -typedef std::vector cCallbacks; - - - - - -/** The base class for a factory that creates callback objects for separate threads. -The processor creates a callback for each thread on which it runs using this factory. -The factory is guaranteed to be called from a single thread. -The factory keeps track of all the callbacks that it has created and deletes them when destructed -*/ -class cCallbackFactory -{ -public: - virtual ~cCallbackFactory() - { - for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) - { - delete *itr; - } - } - - /// Descendants override this method to return the correct callback type - virtual cCallback * CreateNewCallback(void) = 0; - - /// cProcessor uses this method to request a new callback - cCallback * GetNewCallback(void) - { - cCallback * Callback = CreateNewCallback(); - if (Callback != NULL) - { - m_Callbacks.push_back(Callback); - } - return Callback; - } - -protected: - cCallbacks m_Callbacks; -} ; - - - - diff --git a/AnvilStats/ChunkExtract.cpp b/AnvilStats/ChunkExtract.cpp deleted file mode 100644 index 30bdda9f3..000000000 --- a/AnvilStats/ChunkExtract.cpp +++ /dev/null @@ -1,104 +0,0 @@ - -// ChunkExtract.cpp - -// Implements the cChunkExtract class representing a cCallback descendant that extracts raw chunk data into separate .chunk files - -#include "Globals.h" -#include "ChunkExtract.h" -#include "../source/OSSupport/GZipFile.h" - - - - - -cChunkExtract::cChunkExtract(const AString & iWorldFolder) : - mWorldFolder(iWorldFolder) -{ -} - - - - - -bool cChunkExtract::OnNewChunk(int a_ChunkX, int a_ChunkZ) -{ - int AnvilX = (a_ChunkX - ((a_ChunkX > 0) ? 0 : 31)) / 32; - int AnvilZ = (a_ChunkZ - ((a_ChunkZ > 0) ? 0 : 31)) / 32; - if ((AnvilX != mCurAnvilX) || (AnvilZ != mCurAnvilZ)) - { - OpenAnvilFile(AnvilX, AnvilZ); - } - mCurChunkX = a_ChunkX; - mCurChunkZ = a_ChunkZ; - return false; -} - - - - - -bool cChunkExtract::OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) -{ - if (!mAnvilFile.IsOpen()) - { - return true; - } - cFile ChunkFile; - AString ChunkPath = Printf("%d.%d.zchunk", mCurChunkX, mCurChunkZ); - if (!ChunkFile.Open(ChunkPath, cFile::fmWrite)) - { - LOG("Cannot open zchunk file \"%s\" for writing. Chunk [%d, %d] skipped.", ChunkPath.c_str(), mCurChunkX, mCurChunkZ); - return false; - } - - // Copy data from mAnvilFile to ChunkFile: - mAnvilFile.Seek(a_DataOffset); - for (int BytesToCopy = a_CompressedDataSize; BytesToCopy > 0; ) - { - char Buffer[64000]; - int NumBytes = std::min(BytesToCopy, (int)sizeof(Buffer)); - int BytesRead = mAnvilFile.Read(Buffer, NumBytes); - if (BytesRead != NumBytes) - { - LOG("Cannot copy chunk data, chunk [%d, %d] is probably corrupted. Skipping chunk.", mCurChunkX, mCurChunkZ); - return false; - } - ChunkFile.Write(Buffer, BytesRead); - BytesToCopy -= BytesRead; - } // for BytesToCopy - return false; -} - - - - - -bool cChunkExtract::OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) -{ - ASSERT(mAnvilFile.IsOpen()); // If it weren't, the OnCompressedDataSizePos would've prevented this from running - AString FileName = Printf("%d.%d.gzchunk", mCurChunkX, mCurChunkZ); - cGZipFile GZipChunk; - if (!GZipChunk.Open(FileName, cGZipFile::fmWrite)) - { - LOG("Cannot open gzchunk file \"%s\" for writing. Chunk [%d, %d] skipped.", FileName.c_str(), mCurChunkX, mCurChunkZ); - return true; - } - GZipChunk.Write(a_DecompressedNBT, a_DataSize); - return true; -} - - - - - -void cChunkExtract::OpenAnvilFile(int a_AnvilX, int a_AnvilZ) -{ - mAnvilFile.Close(); - AString FileName = Printf("%s/r.%d.%d.mca", mWorldFolder.c_str(), a_AnvilX, a_AnvilZ); - if (!mAnvilFile.Open(FileName, cFile::fmRead)) - { - LOG("Cannot open Anvil file \"%s\" for reading", FileName.c_str()); - } - mCurAnvilX = a_AnvilX; - mCurAnvilZ = a_AnvilZ; -} \ No newline at end of file diff --git a/AnvilStats/ChunkExtract.h b/AnvilStats/ChunkExtract.h deleted file mode 100644 index 5e0ed8a9a..000000000 --- a/AnvilStats/ChunkExtract.h +++ /dev/null @@ -1,66 +0,0 @@ - -// ChunkExtract.h - -// Declares the cChunkExtract class representing a cCallback descendant that extracts raw chunk data into separate .chunk files - - - - - -#pragma once - -#include "Callback.h" - - - - - -class cChunkExtract : - public cCallback -{ -public: - cChunkExtract(const AString & iWorldFolder); - -protected: - AString mWorldFolder; - cFile mAnvilFile; - int mCurAnvilX; // X-coord of mAnvilFile, in Anvil-coords (1 Anvil-coord = 32 chunks) - int mCurAnvilZ; // Z-coord of mAnvilFile, -"- - int mCurChunkX; // X-coord of the chunk being processed - int mCurChunkZ; // Z-coord of the chunk being processed - - /// Opens new anvil file into mAnvilFile, sets mCurAnvilX and mCurAnvilZ - void OpenAnvilFile(int a_AnvilX, int a_AnvilZ); - - // cCallback overrides: - virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; - virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } - virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override; - virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override; -} ; - - - - - -class cChunkExtractFactory : - public cCallbackFactory -{ -public: - cChunkExtractFactory(const AString & iWorldFolder) : - mWorldFolder(iWorldFolder) - { - } - - virtual cCallback * CreateNewCallback(void) override - { - return new cChunkExtract(mWorldFolder); - } - -protected: - AString mWorldFolder; -} ; - - - - diff --git a/AnvilStats/Globals.cpp b/AnvilStats/Globals.cpp deleted file mode 100644 index 2c60fd698..000000000 --- a/AnvilStats/Globals.cpp +++ /dev/null @@ -1,10 +0,0 @@ - -// Globals.cpp - -// This file is used for precompiled header generation in MSVC environments - -#include "Globals.h" - - - - diff --git a/AnvilStats/Globals.h b/AnvilStats/Globals.h deleted file mode 100644 index 3a1c4f78d..000000000 --- a/AnvilStats/Globals.h +++ /dev/null @@ -1,228 +0,0 @@ - -// Globals.h - -// This file gets included from every module in the project, so that global symbols may be introduced easily -// Also used for precompiled header generation in MSVC environments - - - - - -// Compiler-dependent stuff: -#if defined(_MSC_VER) - // MSVC produces warning C4481 on the override keyword usage, so disable the warning altogether - #pragma warning(disable:4481) - - // Disable some warnings that we don't care about: - #pragma warning(disable:4100) - - #define _CRT_SECURE_NO_WARNINGS - - #define OBSOLETE __declspec(deprecated) - - // No alignment needed in MSVC - #define ALIGN_8 - #define ALIGN_16 - -#elif defined(__GNUC__) - - // TODO: Can GCC explicitly mark classes as abstract (no instances can be created)? - #define abstract - - // TODO: Can GCC mark virtual methods as overriding (forcing them to have a virtual function of the same signature in the base class) - #define override - - #define OBSOLETE __attribute__((deprecated)) - - #define ALIGN_8 __attribute__((aligned(8))) - #define ALIGN_16 __attribute__((aligned(16))) - - // Some portability macros :) - #define stricmp strcasecmp - -#else - - #error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler" - - /* - // Copy and uncomment this into another #elif section based on your compiler identification - - // Explicitly mark classes as abstract (no instances can be created) - #define abstract - - // Mark virtual methods as overriding (forcing them to have a virtual function of the same signature in the base class) - #define override - - // Mark functions as obsolete, so that their usage results in a compile-time warning - #define OBSOLETE - - // Mark types / variables for alignment. Do the platforms need it? - #define ALIGN_8 - #define ALIGN_16 - */ - -#endif - - - - - -// Integral types with predefined sizes: -typedef long long Int64; -typedef int Int32; -typedef short Int16; - -typedef unsigned long long UInt64; -typedef unsigned int UInt32; -typedef unsigned short UInt16; - - - - - -// A macro to disallow the copy constructor and operator= functions -// This should be used in the private: declarations for any class that shouldn't allow copying itself -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName &); \ - void operator=(const TypeName &) - -// A macro that is used to mark unused function parameters, to avoid pedantic warnings in gcc -#define UNUSED(X) (void)(X) - - - - -// OS-dependent stuff: -#ifdef _WIN32 - #define WIN32_LEAN_AND_MEAN - #include - #include - - // Windows SDK defines min and max macros, messing up with our std::min and std::max usage - #undef min - #undef max - - // Windows SDK defines GetFreeSpace as a constant, probably a Win16 API remnant - #ifdef GetFreeSpace - #undef GetFreeSpace - #endif // GetFreeSpace -#else - #include - #include // for mkdir - #include - #include - #include - #include - #include - #include - #include - #include - #include - - #include - #include - #include - #include - #include - #include -#endif - - - - - -#define FILE_IO_PREFIX "" - - - - - -// CRT stuff: -#include -#include -#include -#include - - - - - -// STL stuff: -#include -#include -#include -#include -#include -#include -#include -#include - - - - - -// Common headers (part 1, without macros): -#include "../source/StringUtils.h" -#include "../source/OSSupport/CriticalSection.h" -#include "../source/OSSupport/Semaphore.h" -#include "../source/OSSupport/Event.h" -#include "../source/OSSupport/IsThread.h" -#include "../source/OSSupport/File.h" - - - - - -// Common definitions: - -#define LOG(x,...) printf(x "\n", __VA_ARGS__) -#define LOGERROR LOG -#define LOGWARNING LOG -#define LOGINFO LOG -#define LOGWARN LOG - -/// Evaluates to the number of elements in an array (compile-time!) -#define ARRAYCOUNT(X) (sizeof(X) / sizeof(*(X))) - -/// Allows arithmetic expressions like "32 KiB" (but consider using parenthesis around it, "(32 KiB)" ) -#define KiB * 1024 - -/// Allows arithmetic expressions like "32 MiB" (but consider using parenthesis around it, "(32 MiB)" ) -#define MiB * 1024 * 1024 - -/// Faster than (int)floorf((float)x / (float)div) -#define FAST_FLOOR_DIV( x, div ) ( (x) < 0 ? (((int)x / div) - 1) : ((int)x / div) ) - -// Own version of assert() that writes failed assertions to the log for review -#ifdef _DEBUG - #define ASSERT( x ) ( !!(x) || ( LOGERROR("Assertion failed: %s, file %s, line %i", #x, __FILE__, __LINE__ ), assert(0), 0 ) ) -#else - #define ASSERT(x) ((void)0) -#endif - -// Pretty much the same as ASSERT() but stays in Release builds -#define VERIFY( x ) ( !!(x) || ( LOGERROR("Verification failed: %s, file %s, line %i", #x, __FILE__, __LINE__ ), exit(1), 0 ) ) - - - - - -/// A generic interface used mainly in ForEach() functions -template class cItemCallback -{ -public: - /// Called for each item in the internal list; return true to stop the loop, or false to continue enumerating - virtual bool Item(Type * a_Type) = 0; -} ; - - - - - -// Common headers (part 2, with macros): -#include "../source/ChunkDef.h" -#include "../source/BlockID.h" - - - - diff --git a/AnvilStats/HeightMap.cpp b/AnvilStats/HeightMap.cpp deleted file mode 100644 index 9f52c2109..000000000 --- a/AnvilStats/HeightMap.cpp +++ /dev/null @@ -1,265 +0,0 @@ - -// HeightMap.cpp - -// Implements the cHeightMap class representing a cCallback descendant that draws a B&W map of heights for the world - -#include "Globals.h" -#include "HeightMap.h" - - - - -static const unsigned char g_BMPHeader[] = -{ - 0x42, 0x4D, 0x36, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -} ; - - - - - -cHeightMap::cHeightMap(void) : - m_CurrentRegionX(0), - m_CurrentRegionZ(0), - m_IsCurrentRegionValid(false) -{ -} - - - - - -void cHeightMap::Finish(void) -{ - if (m_IsCurrentRegionValid) - { - StartNewRegion(0, 0); - } -} - - - - - -bool cHeightMap::OnNewChunk(int a_ChunkX, int a_ChunkZ) -{ - int RegionX = (a_ChunkX < 0) ? (a_ChunkX - 31) / 32 : a_ChunkX / 32; - int RegionZ = (a_ChunkZ < 0) ? (a_ChunkZ - 31) / 32 : a_ChunkZ / 32; - if ((RegionX != m_CurrentRegionX) || (RegionZ != m_CurrentRegionZ)) - { - if (m_IsCurrentRegionValid) - { - StartNewRegion(RegionX, RegionZ); - } - m_CurrentRegionX = RegionX; - m_CurrentRegionZ = RegionZ; - } - m_IsCurrentRegionValid = true; - m_CurrentChunkX = a_ChunkX; - m_CurrentChunkZ = a_ChunkZ; - m_CurrentChunkOffX = m_CurrentChunkX - m_CurrentRegionX * 32; - m_CurrentChunkOffZ = m_CurrentChunkZ - m_CurrentRegionZ * 32; - memset(m_BlockTypes, 0, sizeof(m_BlockTypes)); - return false; -} - - - - - -bool cHeightMap::OnHeightMap(const int * a_HeightMapBE) -{ - ASSERT(m_CurrentChunkOffX >= 0); - ASSERT(m_CurrentChunkOffX < 32); - ASSERT(m_CurrentChunkOffZ >= 0); - ASSERT(m_CurrentChunkOffZ < 32); - int * BaseHeight = m_Height + m_CurrentChunkOffZ * 16 * 512 + m_CurrentChunkOffX * 16; - for (int z = 0; z < 16; z++) - { - int * Row = BaseHeight + z * 512; - for (int x = 0; x < 16; x++) - { - Row[x] = ntohl(a_HeightMapBE[z * 16 + x]); - } - } // for z - return false; // Still want blockdata to remove trees from the heightmap -} - - - - - -bool cHeightMap::OnSection( - unsigned char a_Y, - const BLOCKTYPE * a_BlockTypes, - const NIBBLETYPE * a_BlockAdditional, - const NIBBLETYPE * a_BlockMeta, - const NIBBLETYPE * a_BlockLight, - const NIBBLETYPE * a_BlockSkyLight -) -{ - // Copy the section data into the appropriate place in the internal buffer - memcpy(m_BlockTypes + a_Y * 16 * 16 * 16, a_BlockTypes, 16 * 16 * 16); - return false; -} - - - - - -bool cHeightMap::OnSectionsFinished(void) -{ - // Remove trees from the heightmap: - for (int z = 0; z < 16; z++) - { - for (int x = 0; x < 16; x++) - { - for (int y = m_Height[512 * (16 * m_CurrentChunkOffZ + z) + 16 * m_CurrentChunkOffX + x]; y >= 0; y--) - { - if (IsGround(m_BlockTypes[256 * y + 16 * z + x])) - { - m_Height[512 * (16 * m_CurrentChunkOffZ + z) + 16 * m_CurrentChunkOffX + x] = y; - break; // for y - } - } // for y - } // for x - } // for z - return true; -} - - - - - -void cHeightMap::StartNewRegion(int a_RegionX, int a_RegionZ) -{ - AString FileName; - Printf(FileName, "Height.%d.%d.bmp", m_CurrentRegionX, m_CurrentRegionZ); - cFile f; - if (!f.Open(FileName, cFile::fmWrite)) - { - LOG("Cannot open file \"%s\" for writing the height map. Data for this region lost.", FileName.c_str()); - } - else - { - f.Write(g_BMPHeader, sizeof(g_BMPHeader)); - for (int z = 0; z < 512; z++) - { - int RowData[512]; - int * HeightRow = m_Height + z * 512; - for (int x = 0; x < 512; x++) - { - RowData[x] = std::max(std::min(HeightRow[x], 255), 0) * 0x010101; - } - f.Write(RowData, sizeof(RowData)); - } // for z - } - - memset(m_Height, 0, sizeof(m_Height)); - m_CurrentRegionX = a_RegionX; - m_CurrentRegionZ = a_RegionZ; -} - - - - - -bool cHeightMap::IsGround(BLOCKTYPE a_BlockType) -{ - // Name all blocks that are NOT ground, return false for them: - switch (a_BlockType) - { - case E_BLOCK_AIR: - case E_BLOCK_BED: - case E_BLOCK_BREWING_STAND: - case E_BLOCK_BROWN_MUSHROOM: - case E_BLOCK_CACTUS: - case E_BLOCK_CAKE: - case E_BLOCK_CARROTS: - case E_BLOCK_CAULDRON: - case E_BLOCK_CHEST: - case E_BLOCK_COBBLESTONE_WALL: - case E_BLOCK_COBWEB: - case E_BLOCK_COCOA_POD: - case E_BLOCK_CROPS: - case E_BLOCK_DEAD_BUSH: - case E_BLOCK_DETECTOR_RAIL: - case E_BLOCK_DIRT: - case E_BLOCK_DRAGON_EGG: - case E_BLOCK_END_PORTAL: - case E_BLOCK_ENDER_CHEST: - case E_BLOCK_FENCE: - case E_BLOCK_FENCE_GATE: - case E_BLOCK_FIRE: - case E_BLOCK_FLOWER_POT: - case E_BLOCK_HEAD: - case E_BLOCK_IRON_BARS: - case E_BLOCK_LADDER: - case E_BLOCK_LAVA: - case E_BLOCK_LEAVES: - case E_BLOCK_LEVER: - case E_BLOCK_LILY_PAD: - case E_BLOCK_LOG: // NOTE: This block is actually solid, but we don't want it because it's the thing that trees are made of, and we're getting rid of trees - case E_BLOCK_MELON: - case E_BLOCK_MELON_STEM: - case E_BLOCK_NETHER_BRICK_FENCE: - case E_BLOCK_NETHER_PORTAL: - case E_BLOCK_POWERED_RAIL: - case E_BLOCK_PUMPKIN: - case E_BLOCK_PUMPKIN_STEM: - case E_BLOCK_RAIL: - case E_BLOCK_RED_ROSE: - case E_BLOCK_RED_MUSHROOM: - case E_BLOCK_REDSTONE_REPEATER_OFF: - case E_BLOCK_REDSTONE_REPEATER_ON: - case E_BLOCK_REDSTONE_TORCH_OFF: - case E_BLOCK_REDSTONE_TORCH_ON: - case E_BLOCK_REDSTONE_WIRE: - case E_BLOCK_REEDS: - case E_BLOCK_SAPLING: - case E_BLOCK_SIGN_POST: - case E_BLOCK_SNOW: - case E_BLOCK_STATIONARY_LAVA: - case E_BLOCK_STATIONARY_WATER: - case E_BLOCK_STONE_BUTTON: - case E_BLOCK_STONE_PRESSURE_PLATE: - case E_BLOCK_TALL_GRASS: - case E_BLOCK_TORCH: - case E_BLOCK_TRIPWIRE: - case E_BLOCK_TRIPWIRE_HOOK: - case E_BLOCK_VINES: - case E_BLOCK_WALLSIGN: - case E_BLOCK_WATER: - case E_BLOCK_WOODEN_BUTTON: - case E_BLOCK_WOODEN_PRESSURE_PLATE: - case E_BLOCK_YELLOW_FLOWER: - { - return false; - } - } - return true; -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cHeightMapFactory: - -cHeightMapFactory::~cHeightMapFactory() -{ - // Force all threads to save their last regions: - for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) - { - ((cHeightMap *)(*itr))->Finish(); - } - // TODO: Join all the files into one giant image file -} - - - - diff --git a/AnvilStats/HeightMap.h b/AnvilStats/HeightMap.h deleted file mode 100644 index 4f9e702d5..000000000 --- a/AnvilStats/HeightMap.h +++ /dev/null @@ -1,81 +0,0 @@ - -// HeightMap.h - -// Declares the cHeightMap class representing a cCallback descendant that draws a B&W map of heights for the world - - - - - -#pragma once - -#include "Callback.h" - - - - - -class cHeightMap : - public cCallback -{ -public: - cHeightMap(void); - - void Finish(void); - -protected: - int m_CurrentChunkX; // Absolute chunk coords - int m_CurrentChunkZ; - int m_CurrentChunkOffX; // Chunk offset from the start of the region - int m_CurrentChunkOffZ; - int m_CurrentRegionX; - int m_CurrentRegionZ; - bool m_IsCurrentRegionValid; - int m_Height[16 * 32 * 16 * 32]; ///< Height-map of the entire current region [x + 16 * 32 * z] - BLOCKTYPE m_BlockTypes[16 * 16 * 256]; ///< Block data of the currently processed chunk (between OnSection() and OnSectionsFinished() ) - - // cCallback overrides: - virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; - virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } - virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } - virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } - virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } - virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } - virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! - virtual bool OnBiomes(const unsigned char * a_BiomeData) { return false; } - virtual bool OnHeightMap(const int * a_HeightMapBE) override; - virtual bool OnSection( - unsigned char a_Y, - const BLOCKTYPE * a_BlockTypes, - const NIBBLETYPE * a_BlockAdditional, - const NIBBLETYPE * a_BlockMeta, - const NIBBLETYPE * a_BlockLight, - const NIBBLETYPE * a_BlockSkyLight - ) override; - virtual bool OnSectionsFinished(void) override; - - void StartNewRegion(int a_RegionX, int a_RegionZ); - - static bool IsGround(BLOCKTYPE a_BlockType); -} ; - - - - - -class cHeightMapFactory : - public cCallbackFactory -{ -public: - virtual ~cHeightMapFactory(); - - virtual cCallback * CreateNewCallback(void) override - { - return new cHeightMap; - } - -} ; - - - - diff --git a/AnvilStats/Processor.cpp b/AnvilStats/Processor.cpp deleted file mode 100644 index 07fb8be3f..000000000 --- a/AnvilStats/Processor.cpp +++ /dev/null @@ -1,579 +0,0 @@ - -// Processor.cpp - -// Implements the cProcessor class representing the overall processor engine that manages threads, calls callbacks etc. - -#include "Globals.h" -#include "Processor.h" -#include "Callback.h" -#include "../source/WorldStorage/FastNBT.h" -#include "zlib.h" -#include "Utils.h" - - - - - -const int CHUNK_INFLATE_MAX = 1 MiB; - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cProcessor::cThread: - -cProcessor::cThread::cThread(cCallback & a_Callback, cProcessor & a_ParentProcessor) : - super("cProcessor::cThread"), - m_Callback(a_Callback), - m_ParentProcessor(a_ParentProcessor) -{ - super::Start(); -} - - - - - -void cProcessor::cThread::Execute(void) -{ - LOG("Started a new thread: %d", cIsThread::GetCurrentID()); - - m_ParentProcessor.m_ThreadsHaveStarted.Set(); - - for (;;) - { - AString FileName = m_ParentProcessor.GetOneFileName(); - if (FileName.empty()) - { - // All done, terminate the thread - break; - } - ProcessFile(FileName); - } // for-ever - - LOG("Thread %d terminated", cIsThread::GetCurrentID()); -} - - - - - -void cProcessor::cThread::ProcessFile(const AString & a_FileName) -{ - LOG("Processing file \"%s\"", a_FileName.c_str()); - - size_t idx = a_FileName.rfind("r."); - if (idx == AString::npos) - { - LOG("Cannot parse filename \"%s\", skipping file.", a_FileName.c_str()); - return; - } - int RegionX = 0, RegionZ = 0; - if (sscanf_s(a_FileName.c_str() + idx, "r.%d.%d.mca", &RegionX, &RegionZ) != 2) - { - LOG("Cannot parse filename \"%s\" into coords, skipping file.", a_FileName.c_str()); - return; - } - - cFile f; - if (!f.Open(a_FileName, cFile::fmRead)) - { - LOG("Cannot open file \"%s\", skipping file.", a_FileName.c_str()); - return; - } - - AString FileContents; - f.ReadRestOfFile(FileContents); - if (FileContents.size() < sizeof(8 KiB)) - { - LOG("Cannot read header in file \"%s\", skipping file.", a_FileName.c_str()); - return; - } - - ProcessFileData(FileContents.data(), FileContents.size(), RegionX * 32, RegionZ * 32); -} - - - - - -void cProcessor::cThread::ProcessFileData(const char * a_FileData, size_t a_Size, int a_ChunkBaseX, int a_ChunkBaseZ) -{ - int Header[2048]; - int * HeaderPtr = (int *)a_FileData; - for (int i = 0; i < ARRAYCOUNT(Header); i++) - { - Header[i] = ntohl(HeaderPtr[i]); - } - - for (int i = 0; i < 1024; i++) - { - unsigned Location = Header[i]; - unsigned Timestamp = Header[i + 1024]; - if ( - ((Location == 0) && (Timestamp == 0)) || // Official docs' "not present" - (Location >> 8 < 2) || // Logical - no chunk can start inside the header - ((Location & 0xff) == 0) || // Logical - no chunk can be zero bytes - ((Location >> 8) * 4096 > a_Size) // Logical - no chunk can start at beyond the file end - ) - { - // Chunk not present in the file - continue; - } - int ChunkX = a_ChunkBaseX + (i % 32); - int ChunkZ = a_ChunkBaseZ + (i / 32); - if (m_Callback.OnNewChunk(ChunkX, ChunkZ)) - { - continue; - } - ProcessChunk(a_FileData, ChunkX, ChunkZ, Location >> 8, Location & 0xff, Timestamp); - } // for i - chunk index -} - - - - - -void cProcessor::cThread::ProcessChunk(const char * a_FileData, int a_ChunkX, int a_ChunkZ, unsigned a_SectorStart, unsigned a_SectorSize, unsigned a_TimeStamp) -{ - if (m_Callback.OnHeader(a_SectorStart * 4096, a_SectorSize, a_TimeStamp)) - { - return; - } - - const char * ChunkStart = a_FileData + a_SectorStart * 4096; - int ByteSize = ntohl(*(int *)ChunkStart); - char CompressionMethod = ChunkStart[4]; - - if (m_Callback.OnCompressedDataSizePos(ByteSize, a_SectorStart * 4096 + 5, CompressionMethod)) - { - return; - } - - ProcessCompressedChunkData(a_ChunkX, a_ChunkZ, ChunkStart + 5, ByteSize); -} - - - - - -void cProcessor::cThread::ProcessCompressedChunkData(int a_ChunkX, int a_ChunkZ, const char * a_CompressedData, int a_CompressedSize) -{ - char Decompressed[CHUNK_INFLATE_MAX]; - z_stream strm; - strm.zalloc = (alloc_func)NULL; - strm.zfree = (free_func)NULL; - strm.opaque = NULL; - inflateInit(&strm); - strm.next_out = (Bytef *)Decompressed; - strm.avail_out = sizeof(Decompressed); - strm.next_in = (Bytef *)a_CompressedData; - strm.avail_in = a_CompressedSize; - int res = inflate(&strm, Z_FINISH); - inflateEnd(&strm); - if (res != Z_STREAM_END) - { - LOG("Decompression failed, skipping chunk [%d, %d]", a_ChunkX, a_ChunkZ); - return; - } - - if (m_Callback.OnDecompressedData(Decompressed, strm.total_out)) - { - return; - } - - // Parse the NBT data: - cParsedNBT NBT(Decompressed, strm.total_out); - if (!NBT.IsValid()) - { - LOG("NBT Parsing failed, skipping chunk [%d, %d]", a_ChunkX, a_ChunkZ); - return; - } - - ProcessParsedChunkData(a_ChunkX, a_ChunkZ, NBT); -} - - - - - -void cProcessor::cThread::ProcessParsedChunkData(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT) -{ - int LevelTag = a_NBT.FindChildByName(0, "Level"); - if (LevelTag < 0) - { - LOG("Bad logical structure of the NBT, skipping chunk [%d, %d].", a_ChunkX, a_ChunkZ); - return; - } - int XPosTag = a_NBT.FindChildByName(LevelTag, "xPos"); - int ZPosTag = a_NBT.FindChildByName(LevelTag, "zPos"); - if ((XPosTag < 0) || (ZPosTag < 0)) - { - LOG("Pos tags missing in NTB, skipping chunk [%d, %d].", a_ChunkX, a_ChunkZ); - return; - } - if (m_Callback.OnRealCoords(a_NBT.GetInt(XPosTag), a_NBT.GetInt(ZPosTag))) - { - return; - } - - int LastUpdateTag = a_NBT.FindChildByName(LevelTag, "LastUpdate"); - if (LastUpdateTag > 0) - { - if (m_Callback.OnLastUpdate(a_NBT.GetLong(LastUpdateTag))) - { - return; - } - } - - int TerrainPopulatedTag = a_NBT.FindChildByName(LevelTag, "TerrainPopulated"); - bool TerrainPopulated = (TerrainPopulatedTag < 0) ? false : (a_NBT.GetByte(TerrainPopulatedTag) != 0); - if (m_Callback.OnTerrainPopulated(TerrainPopulated)) - { - return; - } - - int BiomesTag = a_NBT.FindChildByName(LevelTag, "Biomes"); - if (BiomesTag > 0) - { - if (m_Callback.OnBiomes((const unsigned char *)(a_NBT.GetData(BiomesTag)))) - { - return; - } - } - - int HeightMapTag = a_NBT.FindChildByName(LevelTag, "HeightMap"); - if (HeightMapTag > 0) - { - if (m_Callback.OnHeightMap((const int *)(a_NBT.GetData(HeightMapTag)))) - { - return; - } - } - - if (ProcessChunkSections(a_ChunkX, a_ChunkZ, a_NBT, LevelTag)) - { - return; - } - - if (ProcessChunkEntities(a_ChunkX, a_ChunkZ, a_NBT, LevelTag)) - { - return; - } - - if (ProcessChunkTileEntities(a_ChunkX, a_ChunkZ, a_NBT, LevelTag)) - { - return; - } - - if (ProcessChunkTileTicks(a_ChunkX, a_ChunkZ, a_NBT, LevelTag)) - { - return; - } -} - - - - - -bool cProcessor::cThread::ProcessChunkSections(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag) -{ - int Sections = a_NBT.FindChildByName(a_LevelTag, "Sections"); - if (Sections < 0) - { - return false; - } - - bool SectionProcessed[16]; - memset(SectionProcessed, 0, sizeof(SectionProcessed)); - for (int Tag = a_NBT.GetFirstChild(Sections); Tag > 0; Tag = a_NBT.GetNextSibling(Tag)) - { - int YTag = a_NBT.FindChildByName(Tag, "Y"); - int BlocksTag = a_NBT.FindChildByName(Tag, "Blocks"); - int AddTag = a_NBT.FindChildByName(Tag, "Add"); - int DataTag = a_NBT.FindChildByName(Tag, "Data"); - int BlockLightTag = a_NBT.FindChildByName(Tag, "BlockLightTag"); - int SkyLightTag = a_NBT.FindChildByName(Tag, "SkyLight"); - - if ((YTag < 0) || (BlocksTag < 0) || (DataTag < 0)) - { - continue; - } - - unsigned char SectionY = a_NBT.GetByte(YTag); - if (SectionY >= 16) - { - LOG("WARNING: Section Y >= 16 (%d), high world, wtf? Skipping section!", SectionY); - continue; - } - if (m_Callback.OnSection( - SectionY, - (const BLOCKTYPE *) (a_NBT.GetData(BlocksTag)), - (AddTag > 0) ? (const NIBBLETYPE *)(a_NBT.GetData(AddTag)) : NULL, - (const NIBBLETYPE *)(a_NBT.GetData(DataTag)), - (BlockLightTag > 0) ? (const NIBBLETYPE *)(a_NBT.GetData(BlockLightTag)) : NULL, - (BlockLightTag > 0) ? (const NIBBLETYPE *)(a_NBT.GetData(BlockLightTag)) : NULL - )) - { - return true; - } - SectionProcessed[SectionY] = true; - } // for Tag - Sections[] - - // Call the callback for empty sections: - for (unsigned char y = 0; y < 16; y++) - { - if (!SectionProcessed[y]) - { - if (m_Callback.OnEmptySection(y)) - { - return true; - } - } - } - - if (m_Callback.OnSectionsFinished()) - { - return true; - } - - return false; -} - - - - - -bool cProcessor::cThread::ProcessChunkEntities(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag) -{ - int EntitiesTag = a_NBT.FindChildByName(a_LevelTag, "Entities"); - if (EntitiesTag < 0) - { - return false; - } - - for (int EntityTag = a_NBT.GetFirstChild(EntitiesTag); EntityTag > 0; EntityTag = a_NBT.GetNextSibling(EntityTag)) - { - int PosTag = a_NBT.FindChildByName(EntityTag, "Pos"); - if (PosTag < 0) - { - continue; - } - int SpeedTag = a_NBT.FindChildByName(EntityTag, "Motion"); - if (SpeedTag < 0) - { - continue; - } - int RotTag = a_NBT.FindChildByName(EntityTag, "Rotation"); - if (RotTag < 0) - { - continue; - } - double Pos[3]; - for (int i = 0, tag = a_NBT.GetFirstChild(PosTag); (i < 3) && (tag > 0); i++) - { - Pos[i] = a_NBT.GetDouble(tag); - } - double Speed[3]; - for (int i = 0, tag = a_NBT.GetFirstChild(SpeedTag); (i < 3) && (tag > 0); i++) - { - Speed[i] = a_NBT.GetDouble(tag); - } - float Rot[2]; - for (int i = 0, tag = a_NBT.GetFirstChild(RotTag); (i < 2) && (tag > 0); i++) - { - Rot[i] = a_NBT.GetFloat(tag); - } - - if (m_Callback.OnEntity( - a_NBT.GetString(a_NBT.FindChildByName(EntityTag, "id")), - Pos[0], Pos[1], Pos[2], - Speed[0], Speed[1], Speed[2], - Rot[0], Rot[1], - a_NBT.GetFloat(a_NBT.FindChildByName(EntityTag, "FallDistance")), - a_NBT.GetShort(a_NBT.FindChildByName(EntityTag, "Fire")), - a_NBT.GetShort(a_NBT.FindChildByName(EntityTag, "Air")), - a_NBT.GetByte(a_NBT.FindChildByName(EntityTag, "OnGround")), - a_NBT, EntityTag - )) - { - return true; - } - } // for EntityTag - Entities[] - return false; -} - - - - - -bool cProcessor::cThread::ProcessChunkTileEntities(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag) -{ - int TileEntitiesTag = a_NBT.FindChildByName(a_LevelTag, "TileEntities"); - if (TileEntitiesTag < 0) - { - return false; - } - - for (int TileEntityTag = a_NBT.GetFirstChild(TileEntitiesTag); TileEntityTag > 0; TileEntityTag = a_NBT.GetNextSibling(TileEntityTag)) - { - if (m_Callback.OnTileEntity( - a_NBT.GetString(a_NBT.FindChildByName(TileEntityTag, "id")), - a_NBT.GetInt(a_NBT.FindChildByName(TileEntityTag, "x")), - a_NBT.GetInt(a_NBT.FindChildByName(TileEntityTag, "y")), - a_NBT.GetInt(a_NBT.FindChildByName(TileEntityTag, "z")), - a_NBT, TileEntityTag - )) - { - return true; - } - } // for EntityTag - Entities[] - return false; -} - - - - - -bool cProcessor::cThread::ProcessChunkTileTicks(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag) -{ - int TileTicksTag = a_NBT.FindChildByName(a_LevelTag, "TileTicks"); - if (TileTicksTag < 0) - { - return false; - } - - for (int TileTickTag = a_NBT.GetFirstChild(TileTicksTag); TileTickTag > 0; TileTickTag = a_NBT.GetNextSibling(TileTickTag)) - { - int iTag = a_NBT.FindChildByName(TileTicksTag, "i"); - int tTag = a_NBT.FindChildByName(TileTicksTag, "t"); - int xTag = a_NBT.FindChildByName(TileTicksTag, "x"); - int yTag = a_NBT.FindChildByName(TileTicksTag, "y"); - int zTag = a_NBT.FindChildByName(TileTicksTag, "z"); - if ((iTag < 0) || (tTag < 0) || (xTag < 0) || (yTag < 0) || (zTag < 0)) - { - continue; - } - if (m_Callback.OnTileTick( - a_NBT.GetInt(iTag), - a_NBT.GetInt(iTag), - a_NBT.GetInt(iTag), - a_NBT.GetInt(iTag), - a_NBT.GetInt(iTag) - )) - { - return true; - } - } // for EntityTag - Entities[] - return false; -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cProcessor: - -cProcessor::cProcessor(void) : - m_IsShuttingDown(false) -{ -} - - - - - -cProcessor::~cProcessor() -{ -} - - - - - -void cProcessor::ProcessWorld(const AString & a_WorldFolder, cCallbackFactory & a_CallbackFactory) -{ - PopulateFileQueue(a_WorldFolder); - - if (m_FileQueue.empty()) - { - LOG("No files to process, exitting."); - return; - } - - // Start as many threads as there are cores, plus one: - // (One more thread can be in the file-read IO block while all other threads crunch the numbers) - int NumThreads = GetNumCores() + 1; - - /* - // Limit the number of threads in DEBUG mode to 1 for easier debugging - #ifdef _DEBUG - NumThreads = 1; - #endif // _DEBUG - //*/ - - for (int i = 0; i < NumThreads; i++) - { - cCallback * Callback = a_CallbackFactory.GetNewCallback(); - m_Threads.push_back(new cThread(*Callback, *this)); - } - - // Wait for the first thread to start processing: - m_ThreadsHaveStarted.Wait(); - - // Wait for all threads to finish - // simply by calling each thread's destructor sequentially - LOG("Waiting for threads to finish"); - for (cThreads::iterator itr = m_Threads.begin(), end = m_Threads.end(); itr != end; ++itr) - { - delete *itr; - } // for itr - m_Threads[] - LOG("Processor finished"); -} - - - - - -void cProcessor::PopulateFileQueue(const AString & a_WorldFolder) -{ - LOG("Processing world in \"%s\"...", a_WorldFolder.c_str()); - - AString Path = a_WorldFolder; - if (!Path.empty() && (Path[Path.length() - 1] != cFile::PathSeparator)) - { - Path.push_back(cFile::PathSeparator); - } - AStringList AllFiles = GetDirectoryContents(Path.c_str()); - for (AStringList::iterator itr = AllFiles.begin(), end = AllFiles.end(); itr != end; ++itr) - { - if (itr->rfind(".mca") != itr->length() - 4) - { - // Not a .mca file - continue; - } - m_FileQueue.push_back(Path + *itr); - } // for itr - AllFiles[] -} - - - - - -AString cProcessor::GetOneFileName(void) -{ - cCSLock Lock(m_CS); - if (m_FileQueue.empty()) - { - return ""; - } - AString res = m_FileQueue.back(); - m_FileQueue.pop_back(); - return res; -} - - - - diff --git a/AnvilStats/Processor.h b/AnvilStats/Processor.h deleted file mode 100644 index b7d3f392e..000000000 --- a/AnvilStats/Processor.h +++ /dev/null @@ -1,77 +0,0 @@ - -// Processor.h - -// Interfaces to the cProcessor class representing the overall processor engine that manages threads, calls callbacks etc. - - - - -#pragma once - - - - - -// fwd: -class cCallback; -class cCallbackFactory; -class cParsedNBT; - - - - - -class cProcessor -{ - class cThread : - public cIsThread - { - typedef cIsThread super; - - cCallback & m_Callback; - cProcessor & m_ParentProcessor; - - // cIsThread override: - virtual void Execute(void) override; - - void ProcessFile(const AString & a_FileName); - void ProcessFileData(const char * a_FileData, size_t a_Size, int a_ChunkBaseX, int a_ChunkBaseZ); - void ProcessChunk(const char * a_FileData, int a_ChunkX, int a_ChunkZ, unsigned a_SectorStart, unsigned a_SectorSize, unsigned a_TimeStamp); - void ProcessCompressedChunkData(int a_ChunkX, int a_ChunkZ, const char * a_CompressedData, int a_CompressedSize); - void ProcessParsedChunkData(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT); - - // The following processing parts return true if they were interrupted by the callback, causing the processing of current chunk to abort - bool ProcessChunkSections(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag); - bool ProcessChunkEntities(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag); - bool ProcessChunkTileEntities(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag); - bool ProcessChunkTileTicks(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag); - - public: - cThread(cCallback & a_Callback, cProcessor & a_ParentProcessor); - } ; - - typedef std::vector cThreads; - -public: - cProcessor(void); - ~cProcessor(); - - void ProcessWorld(const AString & a_WorldFolder, cCallbackFactory & a_CallbackFactory); - -protected: - bool m_IsShuttingDown; // If true, the threads should stop ASAP - - cCriticalSection m_CS; - AStringList m_FileQueue; - - cThreads m_Threads; - cEvent m_ThreadsHaveStarted; // This is signalled by each thread to notify the parent thread that it can start waiting for those threads - - void PopulateFileQueue(const AString & a_WorldFolder); - - AString GetOneFileName(void); -} ; - - - - diff --git a/AnvilStats/SpringStats.cpp b/AnvilStats/SpringStats.cpp deleted file mode 100644 index 14ede6889..000000000 --- a/AnvilStats/SpringStats.cpp +++ /dev/null @@ -1,279 +0,0 @@ - -// SpringStats.cpp - -// Implements the cSpringStats class representing a cCallback descendant that collects statistics on lava and water springs - -#include "Globals.h" -#include "SpringStats.h" - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cSpringStats::cStats - -cSpringStats::cStats::cStats(void) : - m_TotalChunks(0) -{ - memset(m_LavaSprings, 0, sizeof(m_LavaSprings)); - memset(m_WaterSprings, 0, sizeof(m_WaterSprings)); -} - - - - - -void cSpringStats::cStats::Add(const cSpringStats::cStats & a_Other) -{ - m_TotalChunks += a_Other.m_TotalChunks; - for (int Biome = 0; Biome < 256; Biome++) - { - for (int Height = 0; Height < 256; Height++) - { - m_LavaSprings[Biome][Height] += a_Other.m_LavaSprings[Biome][Height]; - m_WaterSprings[Biome][Height] += a_Other.m_WaterSprings[Biome][Height]; - } - } -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cSpringStats: - -cSpringStats::cSpringStats(void) : - m_AreBiomesValid(false) -{ -} - - - - - -bool cSpringStats::OnNewChunk(int a_ChunkX, int a_ChunkZ) -{ - memset(m_BlockTypes, 0, sizeof(m_BlockTypes)); - m_AreBiomesValid = false; - return false; -} - - - - - -bool cSpringStats::OnBiomes(const unsigned char * a_BiomeData) -{ - memcpy(m_Biomes, a_BiomeData, sizeof(m_Biomes)); - m_AreBiomesValid = true; - return false; -} - - - - - -bool cSpringStats::OnSection( - unsigned char a_Y, - const BLOCKTYPE * a_BlockTypes, - const NIBBLETYPE * a_BlockAdditional, - const NIBBLETYPE * a_BlockMeta, - const NIBBLETYPE * a_BlockLight, - const NIBBLETYPE * a_BlockSkyLight -) -{ - memcpy(m_BlockTypes + ((int)a_Y) * 16 * 16 * 16, a_BlockTypes, 16 * 16 * 16); - memcpy(m_BlockMetas + ((int)a_Y) * 16 * 16 * 16 / 2, a_BlockMeta, 16 * 16 * 16 / 2); - return false; -} - - - - - -bool cSpringStats::OnSectionsFinished(void) -{ - if (!m_AreBiomesValid) - { - return true; - } - - // Calc the spring stats: - for (int y = 1; y < 255; y++) - { - int BaseY = y * 16 * 16; - for (int z = 1; z < 15; z++) - { - int Base = BaseY + z * 16; - for (int x = 1; x < 15; x++) - { - if (cChunkDef::GetNibble(m_BlockMetas, Base + x) != 0) - { - // Not a source block - continue; - } - switch (m_BlockTypes[Base + x]) - { - case E_BLOCK_WATER: - case E_BLOCK_STATIONARY_WATER: - { - TestSpring(x, y, z, m_Stats.m_WaterSprings); - break; - } - case E_BLOCK_LAVA: - case E_BLOCK_STATIONARY_LAVA: - { - TestSpring(x, y, z, m_Stats.m_LavaSprings); - break; - } - } // switch (BlockType) - } // for x - } // for z - } // for y - m_Stats.m_TotalChunks += 1; - return true; -} - - - - - -void cSpringStats::TestSpring(int a_RelX, int a_RelY, int a_RelZ, cSpringStats::cStats::SpringStats & a_Stats) -{ - static const struct - { - int x, y, z; - } Coords[] = - { - {-1, 0, 0}, - { 1, 0, 0}, - { 0, -1, 0}, - { 0, 1, 0}, - { 0, 0, -1}, - { 0, 0, 1}, - } ; - bool HasFluidNextToIt = false; - for (int i = 0; i < ARRAYCOUNT(Coords); i++) - { - switch (cChunkDef::GetBlock(m_BlockTypes, a_RelX + Coords[i].x, a_RelY + Coords[i].y, a_RelZ + Coords[i].z)) - { - case E_BLOCK_WATER: - case E_BLOCK_STATIONARY_WATER: - case E_BLOCK_LAVA: - case E_BLOCK_STATIONARY_LAVA: - { - if (cChunkDef::GetNibble(m_BlockMetas, a_RelX + Coords[i].x, a_RelY + Coords[i].y, a_RelZ + Coords[i].z) == 0) - { - // There is another source block next to this, so this is not a spring - return; - } - HasFluidNextToIt = true; - } - } // switch (BlockType) - } // for i - Coords[] - - if (!HasFluidNextToIt) - { - // Surrounded by solids on all sides, this is probably not a spring, - // but rather a bedrocked lake or something similar. Dont want. - return; - } - - // No source blocks next to the specified block, so it is a spring. Add it to stats: - a_Stats[a_RelY][((unsigned char *)m_Biomes)[a_RelX + 16 * a_RelZ]] += 1; -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cSpringStatsFactory: - -cSpringStatsFactory::~cSpringStatsFactory() -{ - LOG("cSpringStats:"); - LOG(" Joining results..."); - JoinResults(); - LOG(" Total %llu chunks went through", m_CombinedStats.m_TotalChunks); - - // Save statistics: - LOG(" Saving statistics into files:"); - LOG(" Springs.xls"); - SaveTotals("Springs.xls"); - LOG(" BiomeWaterSprings.xls"); - SaveStatistics(m_CombinedStats.m_WaterSprings, "BiomeWaterSprings.xls"); - LOG(" BiomeLavaSprings.xls"); - SaveStatistics(m_CombinedStats.m_LavaSprings, "BiomeLavaSprings.xls"); -} - - - - - -void cSpringStatsFactory::JoinResults(void) -{ - for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) - { - m_CombinedStats.Add(((cSpringStats *)(*itr))->GetStats()); - } // for itr - m_Callbacks[] -} - - - - - -void cSpringStatsFactory::SaveTotals(const AString & a_FileName) -{ - cFile f(a_FileName, cFile::fmWrite); - if (!f.IsOpen()) - { - LOG("Cannot open file \"%s\" for writing!", a_FileName.c_str()); - return; - } - f.Printf("Height\tWater\tLava\n"); - for (int Height = 0; Height < 256; Height++) - { - UInt64 TotalW = 0; - UInt64 TotalL = 0; - for (int Biome = 0; Biome < 256; Biome++) - { - TotalW += m_CombinedStats.m_WaterSprings[Height][Biome]; - TotalL += m_CombinedStats.m_LavaSprings[Height][Biome]; - } - f.Printf("%d\t%llu\t%llu\n", Height, TotalW, TotalL); - } - f.Printf("\n# Chunks\t%llu", m_CombinedStats.m_TotalChunks); -} - - - - - -void cSpringStatsFactory::SaveStatistics(const cSpringStats::cStats::SpringStats & a_Stats, const AString & a_FileName) -{ - cFile f(a_FileName, cFile::fmWrite); - if (!f.IsOpen()) - { - LOG("Cannot open file \"%s\" for writing!", a_FileName.c_str()); - return; - } - for (int Height = 0; Height < 256; Height++) - { - AString Line; - Line.reserve(2000); - Printf(Line, "%d\t", Height); - for (int Biome = 0; Biome < 256; Biome++) - { - AppendPrintf(Line, "%llu\t", a_Stats[Height][Biome]); - } - Line.append("\n"); - f.Write(Line.c_str(), Line.size()); - } -} - - - - diff --git a/AnvilStats/SpringStats.h b/AnvilStats/SpringStats.h deleted file mode 100644 index 292c5b82d..000000000 --- a/AnvilStats/SpringStats.h +++ /dev/null @@ -1,102 +0,0 @@ - -// SpringStats.h - -// Declares the cSpringStats class representing a cCallback descendant that collects statistics on lava and water springs - - - - - -#pragma once - -#include "Callback.h" - - - - - -class cSpringStats : - public cCallback -{ -public: - class cStats - { - public: - /// Per-height, per-biome frequencies of springs - typedef UInt64 SpringStats[256][256]; - - SpringStats m_LavaSprings; - SpringStats m_WaterSprings; - - UInt64 m_TotalChunks; ///< Total number of chunks that are fully processed through this callback(OnSectionsFinished()) - - cStats(void); - void Add(const cStats & a_Other); - } ; - - cSpringStats(void); - - const cStats & GetStats(void) const { return m_Stats; } - -protected: - - BLOCKTYPE m_BlockTypes[16 * 16 * 256]; - NIBBLETYPE m_BlockMetas[16 * 16 * 256 / 2]; - char m_Biomes[16 * 16]; - bool m_AreBiomesValid; - - cStats m_Stats; - - // cCallback overrides: - virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; - virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } - virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } - virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } - virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } - virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } - virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! - virtual bool OnBiomes(const unsigned char * a_BiomeData) override; - virtual bool OnHeightMap(const int * a_HeightMap) override { return false; } - virtual bool OnSection( - unsigned char a_Y, - const BLOCKTYPE * a_BlockTypes, - const NIBBLETYPE * a_BlockAdditional, - const NIBBLETYPE * a_BlockMeta, - const NIBBLETYPE * a_BlockLight, - const NIBBLETYPE * a_BlockSkyLight - ) override; - virtual bool OnSectionsFinished(void) override; - - /// Tests the specified block, if it appears to be a spring, it is added to a_Stats - void TestSpring(int a_RelX, int a_RelY, int a_RelZ, cStats::SpringStats & a_Stats); -} ; - - - - - -class cSpringStatsFactory : - public cCallbackFactory -{ -public: - virtual ~cSpringStatsFactory(); - - virtual cCallback * CreateNewCallback(void) override - { - return new cSpringStats; - } - - cSpringStats::cStats m_CombinedStats; - - void JoinResults(void); - - /// Saves total per-height data (summed through biomes) for both spring types to the file - void SaveTotals(const AString & a_FileName); - - /// Saves complete per-height, per-biome statistics for the springs to the file - void SaveStatistics(const cSpringStats::cStats::SpringStats & a_Stats, const AString & a_FileName); -} ; - - - - diff --git a/AnvilStats/Statistics.cpp b/AnvilStats/Statistics.cpp deleted file mode 100644 index 57bad7342..000000000 --- a/AnvilStats/Statistics.cpp +++ /dev/null @@ -1,523 +0,0 @@ - -// Statistics.cpp - -// Implements the various statistics-collecting classes - -#include "Globals.h" -#include "Statistics.h" -#include "../source/WorldStorage/FastNBT.h" - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cStatistics::cStats: - -cStatistics::cStats::cStats(void) : - m_TotalChunks(0), - m_BiomeNumChunks(0), - m_BlockNumChunks(0), - m_NumEntities(0), - m_NumTileEntities(0), - m_NumTileTicks(0), - m_MinChunkX(0x7fffffff), - m_MaxChunkX(0x80000000), - m_MinChunkZ(0x7fffffff), - m_MaxChunkZ(0x80000000) -{ - memset(m_BiomeCounts, 0, sizeof(m_BiomeCounts)); - memset(m_BlockCounts, 0, sizeof(m_BlockCounts)); - memset(m_SpawnerEntity, 0, sizeof(m_SpawnerEntity)); -} - - - - - -void cStatistics::cStats::Add(const cStatistics::cStats & a_Stats) -{ - for (int i = 0; i <= 255; i++) - { - m_BiomeCounts[i] += a_Stats.m_BiomeCounts[i]; - } - for (int i = 0; i <= 255; i++) - { - for (int j = 0; j <= 255; j++) - { - m_BlockCounts[i][j] += a_Stats.m_BlockCounts[i][j]; - } - } - for (int i = 0; i < ARRAYCOUNT(m_SpawnerEntity); i++) - { - m_SpawnerEntity[i] += a_Stats.m_SpawnerEntity[i]; - } - m_BiomeNumChunks += a_Stats.m_BiomeNumChunks; - m_BlockNumChunks += a_Stats.m_BlockNumChunks; - m_TotalChunks += a_Stats.m_TotalChunks; - m_NumEntities += a_Stats.m_NumEntities; - m_NumTileEntities += a_Stats.m_NumTileEntities; - m_NumTileTicks += a_Stats.m_NumTileTicks; - UpdateCoordsRange(a_Stats.m_MinChunkX, a_Stats.m_MinChunkZ); - UpdateCoordsRange(a_Stats.m_MinChunkX, a_Stats.m_MinChunkZ); -} - - - - - -void cStatistics::cStats::UpdateCoordsRange(int a_ChunkX, int a_ChunkZ) -{ - if (a_ChunkX < m_MinChunkX) - { - m_MinChunkX = a_ChunkX; - } - if (a_ChunkX > m_MaxChunkX) - { - m_MaxChunkX = a_ChunkX; - } - if (a_ChunkZ < m_MinChunkZ) - { - m_MinChunkZ = a_ChunkZ; - } - if (a_ChunkZ > m_MaxChunkZ) - { - m_MaxChunkZ = a_ChunkZ; - } -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cStatistics: - -cStatistics::cStatistics(void) -{ -} - - - - - -bool cStatistics::OnNewChunk(int a_ChunkX, int a_ChunkZ) -{ - m_Stats.m_TotalChunks++; - m_Stats.UpdateCoordsRange(a_ChunkX, a_ChunkZ); - m_IsBiomesValid = false; - m_IsFirstSectionInChunk = true; - return false; -} - - - - - -bool cStatistics::OnBiomes(const unsigned char * a_BiomeData) -{ - for (int i = 0; i < 16 * 16; i++) - { - m_Stats.m_BiomeCounts[a_BiomeData[i]] += 1; - } - m_Stats.m_BiomeNumChunks += 1; - memcpy(m_BiomeData, a_BiomeData, sizeof(m_BiomeData)); - m_IsBiomesValid = true; - return false; -} - - - - - - -bool cStatistics::OnSection -( - unsigned char a_Y, - const BLOCKTYPE * a_BlockTypes, - const NIBBLETYPE * a_BlockAdditional, - const NIBBLETYPE * a_BlockMeta, - const NIBBLETYPE * a_BlockLight, - const NIBBLETYPE * a_BlockSkyLight -) -{ - if (!m_IsBiomesValid) - { - // The current biome data is not valid, we don't have the means for sorting the BlockTypes into per-biome arrays - return true; - } - - for (int y = 0; y < 16; y++) - { - for (int z = 0; z < 16; z++) - { - for (int x = 0; x < 16; x++) - { - unsigned char Biome = m_BiomeData[x + 16 * z]; // Cannot use cChunkDef, different datatype - unsigned char BlockType = cChunkDef::GetBlock(a_BlockTypes, x, y, z); - m_Stats.m_BlockCounts[Biome][BlockType] += 1; - } - } - } - - m_Stats.m_BlockNumChunks += m_IsFirstSectionInChunk ? 1 : 0; - m_IsFirstSectionInChunk = false; - - return false; -} - - - - - -bool cStatistics::OnEmptySection(unsigned char a_Y) -{ - if (!m_IsBiomesValid) - { - // The current biome data is not valid, we don't have the means for sorting the BlockTypes into per-biome arrays - return true; - } - - // Add air to all columns: - for (int z = 0; z < 16; z++) - { - for (int x = 0; x < 16; x++) - { - unsigned char Biome = m_BiomeData[x + 16 * z]; // Cannot use cChunkDef, different datatype - m_Stats.m_BlockCounts[Biome][0] += 16; // 16 blocks in a column, all air - } - } - - m_Stats.m_BlockNumChunks += m_IsFirstSectionInChunk ? 1 : 0; - m_IsFirstSectionInChunk = false; - - return false; -} - - - - - -bool cStatistics::OnEntity( - const AString & a_EntityType, - double a_PosX, double a_PosY, double a_PosZ, - double a_SpeedX, double a_SpeedY, double a_SpeedZ, - float a_Yaw, float a_Pitch, - float a_FallDistance, - short a_FireTicksLeft, - short a_AirTicks, - char a_IsOnGround, - cParsedNBT & a_NBT, - int a_NBTTag -) -{ - m_Stats.m_NumEntities += 1; - - // TODO - - return false; -} - - - - - -bool cStatistics::OnTileEntity( - const AString & a_EntityType, - int a_PosX, int a_PosY, int a_PosZ, - cParsedNBT & a_NBT, - int a_NBTTag -) -{ - m_Stats.m_NumTileEntities += 1; - - if (a_EntityType == "MobSpawner") - { - OnSpawner(a_NBT, a_NBTTag); - } - - return false; -} - - - - - -bool cStatistics::OnTileTick( - int a_BlockType, - int a_TicksLeft, - int a_PosX, int a_PosY, int a_PosZ -) -{ - m_Stats.m_NumTileTicks += 1; - return false; -} - - - - - -void cStatistics::OnSpawner(cParsedNBT & a_NBT, int a_TileEntityTag) -{ - int EntityIDTag = a_NBT.FindChildByName(a_TileEntityTag, "EntityId"); - if ((EntityIDTag < 0) || (a_NBT.GetType(EntityIDTag) != TAG_String)) - { - return; - } - eEntityType Ent = GetEntityType(a_NBT.GetString(EntityIDTag)); - if (Ent < ARRAYCOUNT(m_Stats.m_SpawnerEntity)) - { - m_Stats.m_SpawnerEntity[Ent] += 1; - } -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cStatisticsFactory: - -cStatisticsFactory::cStatisticsFactory(void) : - m_BeginTick(clock()) -{ -} - - - - - -cStatisticsFactory::~cStatisticsFactory() -{ - // Join the results together: - LOG("cStatistics:"); - LOG(" Joining results..."); - JoinResults(); - LOG(" Total %llu chunks went through", m_CombinedStats.m_TotalChunks); - LOG(" Biomes processed for %llu chunks", m_CombinedStats.m_BiomeNumChunks); - - // Check the number of blocks processed - UInt64 TotalBlocks = 0; - for (int i = 0; i <= 255; i++) - { - for (int j = 0; j < 255; j++) - { - TotalBlocks += m_CombinedStats.m_BlockCounts[i][j]; - } - } - UInt64 ExpTotalBlocks = m_CombinedStats.m_BlockNumChunks * 16LL * 16LL * 256LL; - LOG(" BlockIDs processed for %llu chunks, %llu blocks (exp %llu; %s)", m_CombinedStats.m_BlockNumChunks, TotalBlocks, ExpTotalBlocks, (TotalBlocks == ExpTotalBlocks) ? "match" : "failed"); - - // Save statistics: - LOG(" Saving statistics into files:"); - LOG(" Statistics.txt"); - SaveStatistics(); - LOG(" Biomes.xls"); - SaveBiomes(); - LOG(" BlockTypes.xls"); - SaveBlockTypes(); - LOG(" BiomeBlockTypes.xls"); - SaveBiomeBlockTypes(); - LOG(" Spawners.xls"); - SaveSpawners(); -} - - - - - -void cStatisticsFactory::JoinResults(void) -{ - for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) - { - m_CombinedStats.Add(((cStatistics *)(*itr))->GetStats()); - } // for itr - m_Callbacks[] -} - - - - - -void cStatisticsFactory::SaveBiomes(void) -{ - cFile f; - if (!f.Open("Biomes.xls", cFile::fmWrite)) - { - LOG("Cannot write to file Biomes.xls. Statistics not written."); - return; - } - double TotalColumns = (double)(m_CombinedStats.m_BiomeNumChunks) * 16 * 16 / 100; // Total number of columns processed; convert into percent - if (TotalColumns < 1) - { - // Avoid division by zero - TotalColumns = 1; - } - for (int i = 0; i <= 255; i++) - { - AString Line; - Printf(Line, "%s\t%d\t%llu\t%.05f\n", GetBiomeString(i), i, m_CombinedStats.m_BiomeCounts[i], ((double)(m_CombinedStats.m_BiomeCounts[i])) / TotalColumns); - f.Write(Line.c_str(), Line.length()); - } -} - - - - - -void cStatisticsFactory::SaveBlockTypes(void) -{ - cFile f; - if (!f.Open("BlockTypes.xls", cFile::fmWrite)) - { - LOG("Cannot write to file Biomes.xls. Statistics not written."); - return; - } - double TotalBlocks = ((double)(m_CombinedStats.m_BlockNumChunks)) * 16 * 16 * 256 / 100; // Total number of blocks processed; convert into percent - if (TotalBlocks < 1) - { - // Avoid division by zero - TotalBlocks = 1; - } - for (int i = 0; i <= 255; i++) - { - UInt64 Count = 0; - for (int Biome = 0; Biome <= 255; ++Biome) - { - Count += m_CombinedStats.m_BlockCounts[Biome][i]; - } - AString Line; - Printf(Line, "%s\t%d\t%llu\t%.08f\n", GetBlockTypeString(i), i, Count, ((double)Count) / TotalBlocks); - f.Write(Line.c_str(), Line.length()); - } -} - - - - - -void cStatisticsFactory::SaveBiomeBlockTypes(void) -{ - // Export as two tables: biomes 0-127 and 128-255, because OpenOffice doesn't support more than 256 columns - cFile f; - if (!f.Open("BiomeBlockTypes.xls", cFile::fmWrite)) - { - LOG("Cannot write to file BiomeBlockTypes.xls. Statistics not written."); - return; - } - - AString FileHeader("Biomes 0-127:\n"); - f.Write(FileHeader.c_str(), FileHeader.length()); - - AString Header("BlockType\tBlockType"); - for (int Biome = 0; Biome <= 127; Biome++) - { - const char * BiomeName = GetBiomeString(Biome); - if ((BiomeName != NULL) && (BiomeName[0] != 0)) - { - AppendPrintf(Header, "\t%s (%d)", BiomeName, Biome); - } - else - { - AppendPrintf(Header, "\t%d", Biome); - } - } - Header.append("\n"); - f.Write(Header.c_str(), Header.length()); - - for (int BlockType = 0; BlockType <= 255; BlockType++) - { - AString Line; - Printf(Line, "%s\t%d", GetBlockTypeString(BlockType), BlockType); - for (int Biome = 0; Biome <= 127; Biome++) - { - AppendPrintf(Line, "\t%llu", m_CombinedStats.m_BlockCounts[Biome][BlockType]); - } - Line.append("\n"); - f.Write(Line.c_str(), Line.length()); - } - - Header.assign("\n\nBiomes 127-255:\nBlockType\tBlockType"); - for (int Biome = 0; Biome <= 127; Biome++) - { - const char * BiomeName = GetBiomeString(Biome); - if ((BiomeName != NULL) && (BiomeName[0] != 0)) - { - AppendPrintf(Header, "\t%s (%d)", BiomeName, Biome); - } - else - { - AppendPrintf(Header, "\t%d", Biome); - } - } - Header.append("\n"); - f.Write(Header.c_str(), Header.length()); - - for (int BlockType = 0; BlockType <= 255; BlockType++) - { - AString Line; - Printf(Line, "%s\t%d", GetBlockTypeString(BlockType), BlockType); - for (int Biome = 128; Biome <= 255; Biome++) - { - AppendPrintf(Line, "\t%llu", m_CombinedStats.m_BlockCounts[Biome][BlockType]); - } - Line.append("\n"); - f.Write(Line.c_str(), Line.length()); - } -} - - - - - - -void cStatisticsFactory::SaveStatistics(void) -{ - cFile f; - if (!f.Open("Statistics.txt", cFile::fmWrite)) - { - LOG("Cannot write to file Statistics.txt. Statistics not written."); - return; - } - - int Elapsed = (clock() - m_BeginTick) / CLOCKS_PER_SEC; - f.Printf("Time elapsed: %d seconds (%d hours, %d minutes and %d seconds)\n", Elapsed, Elapsed / 3600, (Elapsed / 60) % 60, Elapsed % 60); - f.Printf("Total chunks processed: %llu\n", m_CombinedStats.m_TotalChunks); - if (Elapsed > 0) - { - f.Printf("Chunk processing speed: %.02f chunks per second\n", (double)(m_CombinedStats.m_TotalChunks) / Elapsed); - } - f.Printf("Biomes counted for %llu chunks.\n", m_CombinedStats.m_BiomeNumChunks); - f.Printf("Blocktypes counted for %llu chunks.\n", m_CombinedStats.m_BlockNumChunks); - f.Printf("Total blocks counted: %llu\n", m_CombinedStats.m_BlockNumChunks * 16 * 16 * 256); - f.Printf("Total biomes counted: %llu\n", m_CombinedStats.m_BiomeNumChunks * 16 * 16); - f.Printf("Total entities counted: %llu\n", m_CombinedStats.m_NumEntities); - f.Printf("Total tile entities counted: %llu\n", m_CombinedStats.m_NumTileEntities); - f.Printf("Total tile ticks counted: %llu\n", m_CombinedStats.m_NumTileTicks); - f.Printf("Chunk coord ranges:\n"); - f.Printf("\tX: %d .. %d\n", m_CombinedStats.m_MinChunkX, m_CombinedStats.m_MaxChunkX); - f.Printf("\tZ: %d .. %d\n", m_CombinedStats.m_MinChunkZ, m_CombinedStats.m_MaxChunkZ); -} - - - - - -void cStatisticsFactory::SaveSpawners(void) -{ - cFile f; - if (!f.Open("Spawners.xls", cFile::fmWrite)) - { - LOG("Cannot write to file Spawners.xls. Statistics not written."); - return; - } - - f.Printf("Entity type\tTotal count\tCount per chunk\n"); - for (int i = 0; i < entMax; i++) - { - f.Printf("%s\t%llu\t%0.4f\n", GetEntityTypeString((eEntityType)i), m_CombinedStats.m_SpawnerEntity[i], (double)(m_CombinedStats.m_SpawnerEntity[i]) / m_CombinedStats.m_BlockNumChunks); - } -} - - - - diff --git a/AnvilStats/Statistics.h b/AnvilStats/Statistics.h deleted file mode 100644 index 2c0a86cc1..000000000 --- a/AnvilStats/Statistics.h +++ /dev/null @@ -1,138 +0,0 @@ - -// Statistics.h - -// Interfaces to the cStatistics class representing a statistics-collecting callback - - - - - -#pragma once - -#include "Callback.h" -#include "Utils.h" - - - - - -class cStatistics : - public cCallback -{ -public: - class cStats - { - public: - UInt64 m_TotalChunks; // Total number of chunks that go through this callback (OnNewChunk()) - UInt64 m_BiomeCounts[256]; - UInt64 m_BlockCounts[256][256]; // First dimension is the biome, second dimension is BlockType - UInt64 m_BiomeNumChunks; // Num chunks that have been processed for biome stats - UInt64 m_BlockNumChunks; // Num chunks that have been processed for block stats - UInt64 m_NumEntities; - UInt64 m_NumTileEntities; - UInt64 m_NumTileTicks; - int m_MinChunkX, m_MaxChunkX; // X coords range - int m_MinChunkZ, m_MaxChunkZ; // Z coords range - - Int64 m; - UInt64 m_SpawnerEntity[entMax + 1]; - - cStats(void); - void Add(const cStats & a_Stats); - void UpdateCoordsRange(int a_ChunkX, int a_ChunkZ); - } ; - - cStatistics(void); - - const cStats & GetStats(void) const { return m_Stats; } - -protected: - cStats m_Stats; - - bool m_IsBiomesValid; // Set to true in OnBiomes(), reset to false in OnNewChunk(); if true, the m_BiomeData is valid for the current chunk - unsigned char m_BiomeData[16 * 16]; - bool m_IsFirstSectionInChunk; // True if there was no section in the chunk yet. Set by OnNewChunk(), reset by OnSection() - - // cCallback overrides: - virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; - virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } - virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } - virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } - virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } - virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } - virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! - virtual bool OnBiomes(const unsigned char * a_BiomeData) override; - virtual bool OnHeightMap(const int * a_HeightMap) override { return false; } - virtual bool OnSection( - unsigned char a_Y, - const BLOCKTYPE * a_BlockTypes, - const NIBBLETYPE * a_BlockAdditional, - const NIBBLETYPE * a_BlockMeta, - const NIBBLETYPE * a_BlockLight, - const NIBBLETYPE * a_BlockSkyLight - ) override; - - virtual bool OnEmptySection(unsigned char a_Y) override; - - virtual bool OnEntity( - const AString & a_EntityType, - double a_PosX, double a_PosY, double a_PosZ, - double a_SpeedX, double a_SpeedY, double a_SpeedZ, - float a_Yaw, float a_Pitch, - float a_FallDistance, - short a_FireTicksLeft, - short a_AirTicks, - char a_IsOnGround, - cParsedNBT & a_NBT, - int a_NBTTag - ) override; - - virtual bool OnTileEntity( - const AString & a_EntityType, - int a_PosX, int a_PosY, int a_PosZ, - cParsedNBT & a_NBT, - int a_NBTTag - ) override; - - virtual bool OnTileTick( - int a_BlockType, - int a_TicksLeft, - int a_PosX, int a_PosY, int a_PosZ - ) override; - - void OnSpawner(cParsedNBT & a_NBT, int a_TileEntityTag); -} ; - - - - - -class cStatisticsFactory : - public cCallbackFactory -{ -public: - cStatisticsFactory(void); - virtual ~cStatisticsFactory(); - - virtual cCallback * CreateNewCallback(void) - { - return new cStatistics; - } - -protected: - // The results, combined, are stored here: - cStatistics::cStats m_CombinedStats; - - clock_t m_BeginTick; - - void JoinResults(void); - void SaveBiomes(void); - void SaveBlockTypes(void); - void SaveBiomeBlockTypes(void); - void SaveStatistics(void); - void SaveSpawners(void); -} ; - - - - diff --git a/AnvilStats/Utils.cpp b/AnvilStats/Utils.cpp deleted file mode 100644 index be1f067c0..000000000 --- a/AnvilStats/Utils.cpp +++ /dev/null @@ -1,291 +0,0 @@ - -// Utils.cpp - -// Implements utility functions - -#include "Globals.h" -#include "Utils.h" - - - - - -struct -{ - eEntityType Type; - const char * String; -} g_EntityTypes[] = -{ - {entBat, "Bat"}, - {entBlaze, "Blaze"}, - {entCaveSpider, "CaveSpider"}, - {entChicken, "Chicken"}, - {entCow, "Cow"}, - {entCreeper, "Creeper"}, - {entEnderDragon, "EnderDragon"}, - {entEnderman, "Enderman"}, - {entGhast, "Ghast"}, - {entGiant, "Giant"}, - {entLavaSlime, "LavaSlime"}, - {entMushroomCow, "MushroomCow"}, - {entOzelot, "Ozelot"}, - {entPig, "Pig"}, - {entPigZombie, "PigZombie"}, - {entSheep, "Sheep"}, - {entSilverfish, "Slverfish"}, - {entSkeleton, "Skeleton"}, - {entSlime, "Slime"}, - {entSnowMan, "SnowMan"}, - {entSpider, "Spider"}, - {entSquid, "Squid"}, - {entVillager, "Villager"}, - {entVillagerGolem, "VillagerGolem"}, - {entWitch, "Witch"}, - {entWitherBoss, "WitherBoss"}, - {entWolf, "Wolf"}, - {entZombie, "Zombie"}, - {entUnknown, "Unknown"}, -} ; - - - - - -const char * GetBiomeString(unsigned char a_Biome) -{ - static const char * BiomeNames[] = // Biome names, as equivalent to their index - { - "Ocean", - "Plains", - "Desert", - "Extreme Hills", - "Forest", - "Taiga", - "Swampland", - "River", - "Hell", - "Sky", - "Frozen Ocean", - "Frozen River", - "Ice Plains", - "Ice Mountains", - "Mushroom Island", - "Mushroom Island Shore", - "Beach", - "Desert Hills", - "Forest Hills", - "Taiga Hills", - "Extreme Hills Edge", - "Jungle", - "Jungle Hills", - } ; - return (a_Biome < ARRAYCOUNT(BiomeNames)) ? BiomeNames[a_Biome] : ""; -} - - - - - -const char * GetBlockTypeString(unsigned char a_BlockType) -{ - static const char * BlockTypeNames[] = // Block type names, as equivalent to their index - { - "air", - "stone", - "grass", - "dirt", - "cobblestone", - "planks", - "sapling", - "bedrock", - "water", - "stillwater", - "lava", - "stilllava", - "sand", - "gravel", - "goldore", - "ironore", - "coalore", - "log", - "leaves", - "sponge", - "glass", - "lapisore", - "lapisblock", - "dispenser", - "sandstone", - "noteblock", - "bedblock", - "poweredrail", - "detectorrail", - "stickypiston", - "cobweb", - "tallgrass", - "deadbush", - "piston", - "pistonhead", - "wool", - "pistonmovedblock", - "flower", - "rose", - "brownmushroom", - "redmushroom", - "goldblock", - "ironblock", - "doubleslab", - "slab", - "brickblock", - "tnt", - "bookcase", - "mossycobblestone", - "obsidian", - "torch", - "fire", - "mobspawner", - "woodstairs", - "chest", - "redstonedust", - "diamondore", - "diamondblock", - "workbench", - "crops", - "soil", - "furnace", - "litfurnace", - "signblock", - "wooddoorblock", - "ladder", - "tracks", - "cobblestonestairs", - "wallsign", - "lever", - "stoneplate", - "irondoorblock", - "woodplate", - "redstoneore", - "redstoneorealt", - "redstonetorchoff", - "redstonetorchon", - "button", - "snow", - "ice", - "snowblock", - "cactus", - "clayblock", - "reedblock", - "jukebox", - "fence", - "pumpkin", - "netherrack", - "soulsand", - "glowstone", - "portal", - "jack-o-lantern", - "cakeblock", - "repeateroff", - "repeateron", - "lockedchest", - "trapdoor", - "silverfishblock", - "stonebricks", - "hugebrownmushroom", - "hugeredmushroom", - "ironbars", - "glasspane", - "melon", - "pumpkinstem", - "melonstem", - "vines", - "fencegate", - "brickstairs", - "stonebrickstairs", - "mycelium", - "lilypad", - "netherbrick", - "netherbrickfence", - "netherbrickstairs", - "netherwartblock", - "enchantmenttable", - "brewingstandblock", - "cauldronblock", - "endportal", - "endportalframe", - "endstone", - "dragonegg", - "redstonelampoff", - "redstonelampon", - "woodendoubleslab", - "woodenslab", - "cocoapod", - "sandstonestairs", /* 128 */ - "Emerald Ore", - "Ender Chest", - "Tripwire Hook", - "Tripwire", - "Block of Emerald", - "Spruce Wood Stairs", - "Birch Wood Stairs", - "Jungle Wood Stairs", - "Command Block", - "Beacon", - "Cobblestone Wall", - "Flower Pot", - "Carrots", - "Potatoes", - "Wooden Button", - "Head", - } ; - - return (a_BlockType < ARRAYCOUNT(BlockTypeNames)) ? BlockTypeNames[a_BlockType] : ""; -} - - - - - -eEntityType GetEntityType(const AString & a_EntityTypeString) -{ - for (int i = 0; i < ARRAYCOUNT(g_EntityTypes); i++) - { - if (a_EntityTypeString == g_EntityTypes[i].String) - { - return g_EntityTypes[i].Type; - } - } - return entUnknown; -} - - - - - -extern const char * GetEntityTypeString(eEntityType a_EntityType) -{ - return g_EntityTypes[a_EntityType].String; -} - - - - - -int GetNumCores(void) -{ - // Get number of cores by querying the system process affinity mask (Windows-specific) - DWORD Affinity, ProcAffinity; - GetProcessAffinityMask(GetCurrentProcess(), &ProcAffinity, &Affinity); - int NumCores = 0; - while (Affinity > 0) - { - if ((Affinity & 1) == 1) - { - ++NumCores; - } - Affinity >>= 1; - } // while (Affinity > 0) - return NumCores; -} - - - - diff --git a/AnvilStats/Utils.h b/AnvilStats/Utils.h deleted file mode 100644 index 095abc99e..000000000 --- a/AnvilStats/Utils.h +++ /dev/null @@ -1,61 +0,0 @@ - -// Utils.h - -// Interfaces to utility functions - - - - - -#pragma once - - - - - -enum eEntityType -{ - entBat, - entBlaze, - entCaveSpider, - entChicken, - entCow, - entCreeper, - entEnderDragon, - entEnderman, - entGhast, - entGiant, - entLavaSlime, - entMushroomCow, - entOzelot, - entPig, - entPigZombie, - entSheep, - entSilverfish, - entSkeleton, - entSlime, - entSnowMan, - entSpider, - entSquid, - entVillager, - entVillagerGolem, - entWitch, - entWitherBoss, - entWolf, - entZombie, - entUnknown, - entMax = entUnknown, -} ; - - - - - -extern const char * GetBiomeString(unsigned char a_Biome); -extern const char * GetBlockTypeString(unsigned char a_BlockType); -extern eEntityType GetEntityType(const AString & a_EntityTypeString); -extern const char * GetEntityTypeString(eEntityType a_EntityType); -extern int GetNumCores(void); - - - diff --git a/AnvilStats/profile_run.cmd b/AnvilStats/profile_run.cmd deleted file mode 100644 index 2a9285614..000000000 --- a/AnvilStats/profile_run.cmd +++ /dev/null @@ -1,70 +0,0 @@ -@echo off -:: -:: Profiling using a MSVC standalone profiler -:: -:: See http://www.codeproject.com/Articles/144643/Profiling-of-C-Applications-in-Visual-Studio-for-F for details -:: - - - - -set pt="C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Performance Tools" -set appdir="Release profiled" -set app="Release profiled\AnvilStats.exe" -set args="0 c:\Games\MLG\world\region" - -:: outputdir is relative to appdir! -set outputdir=Profiling -set output=profile.vsp - - - - - -::Create the output directory, if it didn't exist -mkdir %outputdir% - - - - - -:: Start the profiler -%pt%\vsperfcmd /start:sample /output:%outputdir%\%output% -if errorlevel 1 goto haderror - -:: Launch the application via the profiler -%pt%\vsperfcmd /launch:%app% /args:%args% -if errorlevel 1 goto haderror - -:: Shut down the profiler (this command waits, until the application is terminated) -%pt%\vsperfcmd /shutdown -if errorlevel 1 goto haderror - - - - - -:: cd to outputdir, so that the reports are generated there -cd %outputdir% - -:: generate the report files (.csv) -%pt%\vsperfreport /summary:all %output% /symbolpath:"srv*C:\Programovani\Symbols*http://msdl.microsoft.com/download/symbols" -if errorlevel 1 goto haderror - - - - - -goto finished - - - - -:haderror -echo An error was encountered -pause - - - - -:finished diff --git a/Tools/AnvilStats/AnvilStats.cpp b/Tools/AnvilStats/AnvilStats.cpp new file mode 100644 index 000000000..ae3f901dc --- /dev/null +++ b/Tools/AnvilStats/AnvilStats.cpp @@ -0,0 +1,69 @@ + +// AnvilStats.cpp + +// Implements the main app entrypoint + +#include "Globals.h" +#include "Processor.h" +#include "Statistics.h" +#include "BiomeMap.h" +#include "HeightMap.h" +#include "ChunkExtract.h" +#include "SpringStats.h" + + + + + +int main(int argc, char * argv[]) +{ + if (argc < 2) + { + LOG("Usage: %s []", argv[0]); + LOG("Available methods:"); + LOG(" 0 - statistics"); + LOG(" 1 - biome map"); + LOG(" 2 - height map"); + LOG(" 3 - extract chunks"); + LOG(" 4 - count lava- and water- springs"); + LOG("\nNo method number present, aborting."); + return -1; + } + + AString WorldFolder; + if (argc > 2) + { + WorldFolder = argv[2]; + } + else + { + WorldFolder = "." + cFile::PathSeparator; + } + + cCallbackFactory * Factory = NULL; + switch (atol(argv[1])) + { + case 0: Factory = new cStatisticsFactory; break; + case 1: Factory = new cBiomeMapFactory; break; + case 2: Factory = new cHeightMapFactory; break; + case 3: Factory = new cChunkExtractFactory(WorldFolder); break; + case 4: Factory = new cSpringStatsFactory; break; + default: + { + LOG("Unknown method \"%s\", aborting.", argv[1]); + return -2; + } + } + cProcessor Processor; + Processor.ProcessWorld(WorldFolder, *Factory); + + LOG("Processing finished"); + + delete Factory; + + LOG("Done"); +} + + + + diff --git a/Tools/AnvilStats/AnvilStats.sln b/Tools/AnvilStats/AnvilStats.sln new file mode 100644 index 000000000..886a7a8c8 --- /dev/null +++ b/Tools/AnvilStats/AnvilStats.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AnvilStats", "AnvilStats.vcproj", "{CF996A5E-0A86-4004-9710-682B06B5AEBA}" + ProjectSection(ProjectDependencies) = postProject + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA} = {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\..\VC2008\zlib.vcproj", "{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release profiled|Win32 = Release profiled|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Debug|Win32.ActiveCfg = Debug|Win32 + {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Debug|Win32.Build.0 = Debug|Win32 + {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release profiled|Win32.ActiveCfg = Release profiled|Win32 + {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release profiled|Win32.Build.0 = Release profiled|Win32 + {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release|Win32.ActiveCfg = Release|Win32 + {CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release|Win32.Build.0 = Release|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Debug|Win32.ActiveCfg = Debug|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Debug|Win32.Build.0 = Debug|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release profiled|Win32.ActiveCfg = Release profiled|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release profiled|Win32.Build.0 = Release profiled|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release|Win32.ActiveCfg = Release|Win32 + {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Tools/AnvilStats/AnvilStats.txt b/Tools/AnvilStats/AnvilStats.txt new file mode 100644 index 000000000..07c518e4c --- /dev/null +++ b/Tools/AnvilStats/AnvilStats.txt @@ -0,0 +1,27 @@ + +// AnvilStats.txt + +// A Readme for the project + +/* +AnvilStats +========== + +This is a project for measuring various metrics throughout an Anvil world, presumably created by a vanilla MC. +It works by parsing the MCA files in the path specified as its param (or current directory, if no params) and +feeding each decompressed chunk into the statistics-gathering callback function. + +Possible usage: + - count the per-chunk density of specific blocks + - count the per-chunk density of dungeons, by measuring the number of zombie/skeleton/regularspider spawners + - count the per-chunk-per-biome density of trees, by measuring the number of dirt-log vertical transitions, correlating to biome data + +This project is Windows-only, although it shouldn't be too difficult to make it portable. + +Because this project uses NBT extensively, it runs much faster in Release mode. + + +*/ + + + diff --git a/Tools/AnvilStats/AnvilStats.vcproj b/Tools/AnvilStats/AnvilStats.vcproj new file mode 100644 index 000000000..1726cbfbf --- /dev/null +++ b/Tools/AnvilStats/AnvilStats.vcproj @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tools/AnvilStats/BiomeMap.cpp b/Tools/AnvilStats/BiomeMap.cpp new file mode 100644 index 000000000..eca235c5f --- /dev/null +++ b/Tools/AnvilStats/BiomeMap.cpp @@ -0,0 +1,172 @@ + +// BiomeMap.cpp + +// Implements the cBiomeMap class representing a cCallback descendant that draws a map of biomes for the world + +#include "Globals.h" +#include "BiomeMap.h" + + + + + +static const int g_BiomePalette[] = +{ + // ARGB: + 0xff0000ff, /* Ocean */ + 0xff00cf3f, /* Plains */ + 0xffffff00, /* Desert */ + 0xff7f7f7f, /* Extreme Hills */ + 0xff00cf00, /* Forest */ + 0xff007f3f, /* Taiga */ + 0xff3f7f00, /* Swampland */ + 0xff003fff, /* River */ + 0xff7f0000, /* Hell */ + 0xff007fff, /* Sky */ + 0xff3f3fff, /* Frozen Ocean */ + 0xff3f3fff, /* Frozen River */ + 0xff7fffcf, /* Ice Plains */ + 0xff3fcf7f, /* Ice Mountains */ + 0xffcf00cf, /* Mushroom Island */ + 0xff7f00ff, /* Mushroom Island Shore */ + 0xffffff3f, /* Beach */ + 0xffcfcf00, /* Desert Hills */ + 0xff00cf3f, /* Forest Hills */ + 0xff006f1f, /* Taiga Hills */ + 0xff7f8f7f, /* Extreme Hills Edge */ + 0xff004f00, /* Jungle */ + 0xff003f00, /* Jungle Hills */ +} ; + + + + + +static const unsigned char g_BMPHeader[] = +{ + 0x42, 0x4D, 0x36, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +} ; + + + + + +cBiomeMap::cBiomeMap(void) : + m_CurrentRegionX(0), + m_CurrentRegionZ(0), + m_IsCurrentRegionValid(false) +{ +} + + + + + +void cBiomeMap::Finish(void) +{ + if (m_IsCurrentRegionValid) + { + StartNewRegion(0, 0); + } +} + + + + + +bool cBiomeMap::OnNewChunk(int a_ChunkX, int a_ChunkZ) +{ + int RegionX = (a_ChunkX < 0) ? (a_ChunkX - 31) / 32 : a_ChunkX / 32; + int RegionZ = (a_ChunkZ < 0) ? (a_ChunkZ - 31) / 32 : a_ChunkZ / 32; + if ((RegionX != m_CurrentRegionX) || (RegionZ != m_CurrentRegionZ)) + { + if (m_IsCurrentRegionValid) + { + StartNewRegion(RegionX, RegionZ); + } + m_CurrentRegionX = RegionX; + m_CurrentRegionZ = RegionZ; + } + m_IsCurrentRegionValid = true; + m_CurrentChunkX = a_ChunkX; + m_CurrentChunkZ = a_ChunkZ; + m_CurrentChunkOffX = m_CurrentChunkX - m_CurrentRegionX * 32; + m_CurrentChunkOffZ = m_CurrentChunkZ - m_CurrentRegionZ * 32; + return false; +} + + + + + +bool cBiomeMap::OnBiomes(const unsigned char * a_BiomeData) +{ + ASSERT(m_CurrentChunkOffX >= 0); + ASSERT(m_CurrentChunkOffX < 32); + ASSERT(m_CurrentChunkOffZ >= 0); + ASSERT(m_CurrentChunkOffZ < 32); + char * BaseBiomes = m_Biomes + m_CurrentChunkOffZ * 16 * 512 + m_CurrentChunkOffX * 16; + for (int z = 0; z < 16; z++) + { + char * Row = BaseBiomes + z * 512; + memcpy(Row, a_BiomeData + z * 16, 16); + } // for z + return true; +} + + + + + +void cBiomeMap::StartNewRegion(int a_RegionX, int a_RegionZ) +{ + AString FileName; + Printf(FileName, "Biomes.%d.%d.bmp", m_CurrentRegionX, m_CurrentRegionZ); + cFile f; + if (!f.Open(FileName, cFile::fmWrite)) + { + LOG("Cannot open file \"%s\" for writing the biome map. Data for this region lost.", FileName.c_str()); + } + else + { + f.Write(g_BMPHeader, sizeof(g_BMPHeader)); + for (int z = 0; z < 512; z++) + { + int RowData[512]; + unsigned char * BiomeRow = (unsigned char *)m_Biomes + z * 512; + for (int x = 0; x < 512; x++) + { + RowData[x] = g_BiomePalette[BiomeRow[x]]; + } + f.Write(RowData, sizeof(RowData)); + } // for z + } + + memset(m_Biomes, 0, sizeof(m_Biomes)); + m_CurrentRegionX = a_RegionX; + m_CurrentRegionZ = a_RegionZ; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBiomeMapFactory: + +cBiomeMapFactory::~cBiomeMapFactory() +{ + // Force all threads to save their last regions: + for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) + { + ((cBiomeMap *)(*itr))->Finish(); + } + // TODO: Join all the files into one giant image file +} + + + + diff --git a/Tools/AnvilStats/BiomeMap.h b/Tools/AnvilStats/BiomeMap.h new file mode 100644 index 000000000..f0d306c04 --- /dev/null +++ b/Tools/AnvilStats/BiomeMap.h @@ -0,0 +1,69 @@ + +// BiomeMap.h + +// Interfaces to the cBiomeMap class representing a cCallback descendant that draws a map of biomes for the world + + + + + +#pragma once + +#include "Callback.h" + + + + + +class cBiomeMap : + public cCallback +{ +public: + cBiomeMap(void); + + /// Saves the last region that it was processing + void Finish(void); + +protected: + int m_CurrentChunkX; // Absolute chunk coords + int m_CurrentChunkZ; + int m_CurrentChunkOffX; // Chunk offset from the start of the region + int m_CurrentChunkOffZ; + int m_CurrentRegionX; + int m_CurrentRegionZ; + bool m_IsCurrentRegionValid; + char m_Biomes[16 * 32 * 16 * 32]; // Biome map of the entire current region [x + 16 * 32 * z] + + // cCallback overrides: + virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } + virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } + virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! + virtual bool OnBiomes(const unsigned char * a_BiomeData) override; + + void StartNewRegion(int a_RegionX, int a_RegionZ); +} ; + + + + + +class cBiomeMapFactory : + public cCallbackFactory +{ +public: + virtual ~cBiomeMapFactory(); + + virtual cCallback * CreateNewCallback(void) override + { + return new cBiomeMap; + } + +} ; + + + + diff --git a/Tools/AnvilStats/Callback.h b/Tools/AnvilStats/Callback.h new file mode 100644 index 000000000..92d394d0e --- /dev/null +++ b/Tools/AnvilStats/Callback.h @@ -0,0 +1,165 @@ + +// Callback.h + +// Interfaces to the cCallback base class used as the base class for all statistical callbacks + + + + + +#pragma once + + + + + +// fwd: +class cParsedNBT; + + + + + +/** The base class for all chunk-processor callbacks, declares the interface. +The processor calls each virtual function in the order they are declared here with the specified args. +If the function returns true, the processor moves on to next chunk and starts calling the callbacks again from start with +the new chunk data. +So if a statistics collector doesn't need data decompression at all, it can stop the processor from doing so early-enough +and still get meaningful data. +A callback is guaranteed to run in a single thread and always the same thread. +A callback is guaranteed to run on all chunks in a region and one region is guaranteed to be handled by only callback. +*/ +class cCallback abstract +{ +public: + virtual ~cCallback() {} // Force a virtual destructor in each descendant + + /// Called to inform the stats module of the chunk coords for newly processing chunk + virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) = 0; + + /// Called to inform about the chunk's data offset in the file (chunk mini-header), the number of sectors it uses and the timestamp field value + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) { return true; } + + /// Called to inform of the compressed chunk data size and position in the file (offset from file start to the actual data) + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) { return true; } + + /// Just in case you wanted to process the NBT yourself ;) + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) { return true; } + + /// The chunk's NBT should specify chunk coords, these are sent here: + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) { return true; } + + /// The chunk contains a LastUpdate value specifying the last tick in which it was saved. + virtual bool OnLastUpdate(Int64 a_LastUpdate) { return true; } + + virtual bool OnTerrainPopulated(bool a_Populated) { return true; } + + virtual bool OnBiomes(const unsigned char * a_BiomeData) { return true; } + + /** Called when a heightmap for the chunk is read from the file. + Note that the heightmap is given in big-endian ints, so if you want it, you need to ntohl() it first! + */ + virtual bool OnHeightMap(const int * a_HeightMapBE) { return true; } + + /** If there is data for the section, this callback is called; otherwise OnEmptySection() is called instead. + All OnSection() callbacks are called first, and only then all the remaining sections are reported in OnEmptySection(). + */ + virtual bool OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight + ) { return true; } + + /** If there is no data for a section, this callback is called; otherwise OnSection() is called instead. + OnEmptySection() callbacks are called after all OnSection() callbacks. + */ + virtual bool OnEmptySection(unsigned char a_Y) { return false; } + + /** Called after all sections have been processed via either OnSection() or OnEmptySection(). + */ + virtual bool OnSectionsFinished(void) { return true; } + + /** Called for each entity in the chunk. + Common parameters are parsed from the NBT. + The callback may parse any other param from the a_NBT and a_NBTTag parameters. + The a_NBTTag parameter points to the entity compound tag inside the Entities tag. + */ + virtual bool OnEntity( + const AString & a_EntityType, + double a_PosX, double a_PosY, double a_PosZ, + double a_SpeedX, double a_SpeedY, double a_SpeedZ, + float a_Yaw, float a_Pitch, + float a_FallDistance, + short a_FireTicksLeft, + short a_AirTicks, + char a_IsOnGround, + cParsedNBT & a_NBT, + int a_NBTTag + ) { return true; } + + /** Called for each tile entity in the chunk. + Common parameters are parsed from the NBT. + The callback may parse any other param from the a_NBT and a_NBTTag parameters. + The a_NBTTag parameter points to the tile entity compound tag inside the TileEntities tag. + */ + virtual bool OnTileEntity( + const AString & a_EntityType, + int a_PosX, int a_PosY, int a_PosZ, + cParsedNBT & a_NBT, + int a_NBTTag + ) { return true; } + + /// Called for each tile tick in the chunk + virtual bool OnTileTick( + int a_BlockType, + int a_TicksLeft, + int a_PosX, int a_PosY, int a_PosZ + ) { return true; } +} ; + +typedef std::vector cCallbacks; + + + + + +/** The base class for a factory that creates callback objects for separate threads. +The processor creates a callback for each thread on which it runs using this factory. +The factory is guaranteed to be called from a single thread. +The factory keeps track of all the callbacks that it has created and deletes them when destructed +*/ +class cCallbackFactory +{ +public: + virtual ~cCallbackFactory() + { + for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) + { + delete *itr; + } + } + + /// Descendants override this method to return the correct callback type + virtual cCallback * CreateNewCallback(void) = 0; + + /// cProcessor uses this method to request a new callback + cCallback * GetNewCallback(void) + { + cCallback * Callback = CreateNewCallback(); + if (Callback != NULL) + { + m_Callbacks.push_back(Callback); + } + return Callback; + } + +protected: + cCallbacks m_Callbacks; +} ; + + + + diff --git a/Tools/AnvilStats/ChunkExtract.cpp b/Tools/AnvilStats/ChunkExtract.cpp new file mode 100644 index 000000000..08517a58d --- /dev/null +++ b/Tools/AnvilStats/ChunkExtract.cpp @@ -0,0 +1,104 @@ + +// ChunkExtract.cpp + +// Implements the cChunkExtract class representing a cCallback descendant that extracts raw chunk data into separate .chunk files + +#include "Globals.h" +#include "ChunkExtract.h" +#include "../../source/OSSupport/GZipFile.h" + + + + + +cChunkExtract::cChunkExtract(const AString & iWorldFolder) : + mWorldFolder(iWorldFolder) +{ +} + + + + + +bool cChunkExtract::OnNewChunk(int a_ChunkX, int a_ChunkZ) +{ + int AnvilX = (a_ChunkX - ((a_ChunkX > 0) ? 0 : 31)) / 32; + int AnvilZ = (a_ChunkZ - ((a_ChunkZ > 0) ? 0 : 31)) / 32; + if ((AnvilX != mCurAnvilX) || (AnvilZ != mCurAnvilZ)) + { + OpenAnvilFile(AnvilX, AnvilZ); + } + mCurChunkX = a_ChunkX; + mCurChunkZ = a_ChunkZ; + return false; +} + + + + + +bool cChunkExtract::OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) +{ + if (!mAnvilFile.IsOpen()) + { + return true; + } + cFile ChunkFile; + AString ChunkPath = Printf("%d.%d.zchunk", mCurChunkX, mCurChunkZ); + if (!ChunkFile.Open(ChunkPath, cFile::fmWrite)) + { + LOG("Cannot open zchunk file \"%s\" for writing. Chunk [%d, %d] skipped.", ChunkPath.c_str(), mCurChunkX, mCurChunkZ); + return false; + } + + // Copy data from mAnvilFile to ChunkFile: + mAnvilFile.Seek(a_DataOffset); + for (int BytesToCopy = a_CompressedDataSize; BytesToCopy > 0; ) + { + char Buffer[64000]; + int NumBytes = std::min(BytesToCopy, (int)sizeof(Buffer)); + int BytesRead = mAnvilFile.Read(Buffer, NumBytes); + if (BytesRead != NumBytes) + { + LOG("Cannot copy chunk data, chunk [%d, %d] is probably corrupted. Skipping chunk.", mCurChunkX, mCurChunkZ); + return false; + } + ChunkFile.Write(Buffer, BytesRead); + BytesToCopy -= BytesRead; + } // for BytesToCopy + return false; +} + + + + + +bool cChunkExtract::OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) +{ + ASSERT(mAnvilFile.IsOpen()); // If it weren't, the OnCompressedDataSizePos would've prevented this from running + AString FileName = Printf("%d.%d.gzchunk", mCurChunkX, mCurChunkZ); + cGZipFile GZipChunk; + if (!GZipChunk.Open(FileName, cGZipFile::fmWrite)) + { + LOG("Cannot open gzchunk file \"%s\" for writing. Chunk [%d, %d] skipped.", FileName.c_str(), mCurChunkX, mCurChunkZ); + return true; + } + GZipChunk.Write(a_DecompressedNBT, a_DataSize); + return true; +} + + + + + +void cChunkExtract::OpenAnvilFile(int a_AnvilX, int a_AnvilZ) +{ + mAnvilFile.Close(); + AString FileName = Printf("%s/r.%d.%d.mca", mWorldFolder.c_str(), a_AnvilX, a_AnvilZ); + if (!mAnvilFile.Open(FileName, cFile::fmRead)) + { + LOG("Cannot open Anvil file \"%s\" for reading", FileName.c_str()); + } + mCurAnvilX = a_AnvilX; + mCurAnvilZ = a_AnvilZ; +} \ No newline at end of file diff --git a/Tools/AnvilStats/ChunkExtract.h b/Tools/AnvilStats/ChunkExtract.h new file mode 100644 index 000000000..5e0ed8a9a --- /dev/null +++ b/Tools/AnvilStats/ChunkExtract.h @@ -0,0 +1,66 @@ + +// ChunkExtract.h + +// Declares the cChunkExtract class representing a cCallback descendant that extracts raw chunk data into separate .chunk files + + + + + +#pragma once + +#include "Callback.h" + + + + + +class cChunkExtract : + public cCallback +{ +public: + cChunkExtract(const AString & iWorldFolder); + +protected: + AString mWorldFolder; + cFile mAnvilFile; + int mCurAnvilX; // X-coord of mAnvilFile, in Anvil-coords (1 Anvil-coord = 32 chunks) + int mCurAnvilZ; // Z-coord of mAnvilFile, -"- + int mCurChunkX; // X-coord of the chunk being processed + int mCurChunkZ; // Z-coord of the chunk being processed + + /// Opens new anvil file into mAnvilFile, sets mCurAnvilX and mCurAnvilZ + void OpenAnvilFile(int a_AnvilX, int a_AnvilZ); + + // cCallback overrides: + virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override; + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override; +} ; + + + + + +class cChunkExtractFactory : + public cCallbackFactory +{ +public: + cChunkExtractFactory(const AString & iWorldFolder) : + mWorldFolder(iWorldFolder) + { + } + + virtual cCallback * CreateNewCallback(void) override + { + return new cChunkExtract(mWorldFolder); + } + +protected: + AString mWorldFolder; +} ; + + + + diff --git a/Tools/AnvilStats/Globals.cpp b/Tools/AnvilStats/Globals.cpp new file mode 100644 index 000000000..2c60fd698 --- /dev/null +++ b/Tools/AnvilStats/Globals.cpp @@ -0,0 +1,10 @@ + +// Globals.cpp + +// This file is used for precompiled header generation in MSVC environments + +#include "Globals.h" + + + + diff --git a/Tools/AnvilStats/Globals.h b/Tools/AnvilStats/Globals.h new file mode 100644 index 000000000..b9d37e029 --- /dev/null +++ b/Tools/AnvilStats/Globals.h @@ -0,0 +1,228 @@ + +// Globals.h + +// This file gets included from every module in the project, so that global symbols may be introduced easily +// Also used for precompiled header generation in MSVC environments + + + + + +// Compiler-dependent stuff: +#if defined(_MSC_VER) + // MSVC produces warning C4481 on the override keyword usage, so disable the warning altogether + #pragma warning(disable:4481) + + // Disable some warnings that we don't care about: + #pragma warning(disable:4100) + + #define _CRT_SECURE_NO_WARNINGS + + #define OBSOLETE __declspec(deprecated) + + // No alignment needed in MSVC + #define ALIGN_8 + #define ALIGN_16 + +#elif defined(__GNUC__) + + // TODO: Can GCC explicitly mark classes as abstract (no instances can be created)? + #define abstract + + // TODO: Can GCC mark virtual methods as overriding (forcing them to have a virtual function of the same signature in the base class) + #define override + + #define OBSOLETE __attribute__((deprecated)) + + #define ALIGN_8 __attribute__((aligned(8))) + #define ALIGN_16 __attribute__((aligned(16))) + + // Some portability macros :) + #define stricmp strcasecmp + +#else + + #error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler" + + /* + // Copy and uncomment this into another #elif section based on your compiler identification + + // Explicitly mark classes as abstract (no instances can be created) + #define abstract + + // Mark virtual methods as overriding (forcing them to have a virtual function of the same signature in the base class) + #define override + + // Mark functions as obsolete, so that their usage results in a compile-time warning + #define OBSOLETE + + // Mark types / variables for alignment. Do the platforms need it? + #define ALIGN_8 + #define ALIGN_16 + */ + +#endif + + + + + +// Integral types with predefined sizes: +typedef long long Int64; +typedef int Int32; +typedef short Int16; + +typedef unsigned long long UInt64; +typedef unsigned int UInt32; +typedef unsigned short UInt16; + + + + + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for any class that shouldn't allow copying itself +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName &); \ + void operator=(const TypeName &) + +// A macro that is used to mark unused function parameters, to avoid pedantic warnings in gcc +#define UNUSED(X) (void)(X) + + + + +// OS-dependent stuff: +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #include + + // Windows SDK defines min and max macros, messing up with our std::min and std::max usage + #undef min + #undef max + + // Windows SDK defines GetFreeSpace as a constant, probably a Win16 API remnant + #ifdef GetFreeSpace + #undef GetFreeSpace + #endif // GetFreeSpace +#else + #include + #include // for mkdir + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include + #include +#endif + + + + + +#define FILE_IO_PREFIX "" + + + + + +// CRT stuff: +#include +#include +#include +#include + + + + + +// STL stuff: +#include +#include +#include +#include +#include +#include +#include +#include + + + + + +// Common headers (part 1, without macros): +#include "../../source/StringUtils.h" +#include "../../source/OSSupport/CriticalSection.h" +#include "../../source/OSSupport/Semaphore.h" +#include "../../source/OSSupport/Event.h" +#include "../../source/OSSupport/IsThread.h" +#include "../../source/OSSupport/File.h" + + + + + +// Common definitions: + +#define LOG(x,...) printf(x "\n", __VA_ARGS__) +#define LOGERROR LOG +#define LOGWARNING LOG +#define LOGINFO LOG +#define LOGWARN LOG + +/// Evaluates to the number of elements in an array (compile-time!) +#define ARRAYCOUNT(X) (sizeof(X) / sizeof(*(X))) + +/// Allows arithmetic expressions like "32 KiB" (but consider using parenthesis around it, "(32 KiB)" ) +#define KiB * 1024 + +/// Allows arithmetic expressions like "32 MiB" (but consider using parenthesis around it, "(32 MiB)" ) +#define MiB * 1024 * 1024 + +/// Faster than (int)floorf((float)x / (float)div) +#define FAST_FLOOR_DIV( x, div ) ( (x) < 0 ? (((int)x / div) - 1) : ((int)x / div) ) + +// Own version of assert() that writes failed assertions to the log for review +#ifdef _DEBUG + #define ASSERT( x ) ( !!(x) || ( LOGERROR("Assertion failed: %s, file %s, line %i", #x, __FILE__, __LINE__ ), assert(0), 0 ) ) +#else + #define ASSERT(x) ((void)0) +#endif + +// Pretty much the same as ASSERT() but stays in Release builds +#define VERIFY( x ) ( !!(x) || ( LOGERROR("Verification failed: %s, file %s, line %i", #x, __FILE__, __LINE__ ), exit(1), 0 ) ) + + + + + +/// A generic interface used mainly in ForEach() functions +template class cItemCallback +{ +public: + /// Called for each item in the internal list; return true to stop the loop, or false to continue enumerating + virtual bool Item(Type * a_Type) = 0; +} ; + + + + + +// Common headers (part 2, with macros): +#include "../../source/ChunkDef.h" +#include "../../source/BlockID.h" + + + + diff --git a/Tools/AnvilStats/HeightMap.cpp b/Tools/AnvilStats/HeightMap.cpp new file mode 100644 index 000000000..9f52c2109 --- /dev/null +++ b/Tools/AnvilStats/HeightMap.cpp @@ -0,0 +1,265 @@ + +// HeightMap.cpp + +// Implements the cHeightMap class representing a cCallback descendant that draws a B&W map of heights for the world + +#include "Globals.h" +#include "HeightMap.h" + + + + +static const unsigned char g_BMPHeader[] = +{ + 0x42, 0x4D, 0x36, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +} ; + + + + + +cHeightMap::cHeightMap(void) : + m_CurrentRegionX(0), + m_CurrentRegionZ(0), + m_IsCurrentRegionValid(false) +{ +} + + + + + +void cHeightMap::Finish(void) +{ + if (m_IsCurrentRegionValid) + { + StartNewRegion(0, 0); + } +} + + + + + +bool cHeightMap::OnNewChunk(int a_ChunkX, int a_ChunkZ) +{ + int RegionX = (a_ChunkX < 0) ? (a_ChunkX - 31) / 32 : a_ChunkX / 32; + int RegionZ = (a_ChunkZ < 0) ? (a_ChunkZ - 31) / 32 : a_ChunkZ / 32; + if ((RegionX != m_CurrentRegionX) || (RegionZ != m_CurrentRegionZ)) + { + if (m_IsCurrentRegionValid) + { + StartNewRegion(RegionX, RegionZ); + } + m_CurrentRegionX = RegionX; + m_CurrentRegionZ = RegionZ; + } + m_IsCurrentRegionValid = true; + m_CurrentChunkX = a_ChunkX; + m_CurrentChunkZ = a_ChunkZ; + m_CurrentChunkOffX = m_CurrentChunkX - m_CurrentRegionX * 32; + m_CurrentChunkOffZ = m_CurrentChunkZ - m_CurrentRegionZ * 32; + memset(m_BlockTypes, 0, sizeof(m_BlockTypes)); + return false; +} + + + + + +bool cHeightMap::OnHeightMap(const int * a_HeightMapBE) +{ + ASSERT(m_CurrentChunkOffX >= 0); + ASSERT(m_CurrentChunkOffX < 32); + ASSERT(m_CurrentChunkOffZ >= 0); + ASSERT(m_CurrentChunkOffZ < 32); + int * BaseHeight = m_Height + m_CurrentChunkOffZ * 16 * 512 + m_CurrentChunkOffX * 16; + for (int z = 0; z < 16; z++) + { + int * Row = BaseHeight + z * 512; + for (int x = 0; x < 16; x++) + { + Row[x] = ntohl(a_HeightMapBE[z * 16 + x]); + } + } // for z + return false; // Still want blockdata to remove trees from the heightmap +} + + + + + +bool cHeightMap::OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight +) +{ + // Copy the section data into the appropriate place in the internal buffer + memcpy(m_BlockTypes + a_Y * 16 * 16 * 16, a_BlockTypes, 16 * 16 * 16); + return false; +} + + + + + +bool cHeightMap::OnSectionsFinished(void) +{ + // Remove trees from the heightmap: + for (int z = 0; z < 16; z++) + { + for (int x = 0; x < 16; x++) + { + for (int y = m_Height[512 * (16 * m_CurrentChunkOffZ + z) + 16 * m_CurrentChunkOffX + x]; y >= 0; y--) + { + if (IsGround(m_BlockTypes[256 * y + 16 * z + x])) + { + m_Height[512 * (16 * m_CurrentChunkOffZ + z) + 16 * m_CurrentChunkOffX + x] = y; + break; // for y + } + } // for y + } // for x + } // for z + return true; +} + + + + + +void cHeightMap::StartNewRegion(int a_RegionX, int a_RegionZ) +{ + AString FileName; + Printf(FileName, "Height.%d.%d.bmp", m_CurrentRegionX, m_CurrentRegionZ); + cFile f; + if (!f.Open(FileName, cFile::fmWrite)) + { + LOG("Cannot open file \"%s\" for writing the height map. Data for this region lost.", FileName.c_str()); + } + else + { + f.Write(g_BMPHeader, sizeof(g_BMPHeader)); + for (int z = 0; z < 512; z++) + { + int RowData[512]; + int * HeightRow = m_Height + z * 512; + for (int x = 0; x < 512; x++) + { + RowData[x] = std::max(std::min(HeightRow[x], 255), 0) * 0x010101; + } + f.Write(RowData, sizeof(RowData)); + } // for z + } + + memset(m_Height, 0, sizeof(m_Height)); + m_CurrentRegionX = a_RegionX; + m_CurrentRegionZ = a_RegionZ; +} + + + + + +bool cHeightMap::IsGround(BLOCKTYPE a_BlockType) +{ + // Name all blocks that are NOT ground, return false for them: + switch (a_BlockType) + { + case E_BLOCK_AIR: + case E_BLOCK_BED: + case E_BLOCK_BREWING_STAND: + case E_BLOCK_BROWN_MUSHROOM: + case E_BLOCK_CACTUS: + case E_BLOCK_CAKE: + case E_BLOCK_CARROTS: + case E_BLOCK_CAULDRON: + case E_BLOCK_CHEST: + case E_BLOCK_COBBLESTONE_WALL: + case E_BLOCK_COBWEB: + case E_BLOCK_COCOA_POD: + case E_BLOCK_CROPS: + case E_BLOCK_DEAD_BUSH: + case E_BLOCK_DETECTOR_RAIL: + case E_BLOCK_DIRT: + case E_BLOCK_DRAGON_EGG: + case E_BLOCK_END_PORTAL: + case E_BLOCK_ENDER_CHEST: + case E_BLOCK_FENCE: + case E_BLOCK_FENCE_GATE: + case E_BLOCK_FIRE: + case E_BLOCK_FLOWER_POT: + case E_BLOCK_HEAD: + case E_BLOCK_IRON_BARS: + case E_BLOCK_LADDER: + case E_BLOCK_LAVA: + case E_BLOCK_LEAVES: + case E_BLOCK_LEVER: + case E_BLOCK_LILY_PAD: + case E_BLOCK_LOG: // NOTE: This block is actually solid, but we don't want it because it's the thing that trees are made of, and we're getting rid of trees + case E_BLOCK_MELON: + case E_BLOCK_MELON_STEM: + case E_BLOCK_NETHER_BRICK_FENCE: + case E_BLOCK_NETHER_PORTAL: + case E_BLOCK_POWERED_RAIL: + case E_BLOCK_PUMPKIN: + case E_BLOCK_PUMPKIN_STEM: + case E_BLOCK_RAIL: + case E_BLOCK_RED_ROSE: + case E_BLOCK_RED_MUSHROOM: + case E_BLOCK_REDSTONE_REPEATER_OFF: + case E_BLOCK_REDSTONE_REPEATER_ON: + case E_BLOCK_REDSTONE_TORCH_OFF: + case E_BLOCK_REDSTONE_TORCH_ON: + case E_BLOCK_REDSTONE_WIRE: + case E_BLOCK_REEDS: + case E_BLOCK_SAPLING: + case E_BLOCK_SIGN_POST: + case E_BLOCK_SNOW: + case E_BLOCK_STATIONARY_LAVA: + case E_BLOCK_STATIONARY_WATER: + case E_BLOCK_STONE_BUTTON: + case E_BLOCK_STONE_PRESSURE_PLATE: + case E_BLOCK_TALL_GRASS: + case E_BLOCK_TORCH: + case E_BLOCK_TRIPWIRE: + case E_BLOCK_TRIPWIRE_HOOK: + case E_BLOCK_VINES: + case E_BLOCK_WALLSIGN: + case E_BLOCK_WATER: + case E_BLOCK_WOODEN_BUTTON: + case E_BLOCK_WOODEN_PRESSURE_PLATE: + case E_BLOCK_YELLOW_FLOWER: + { + return false; + } + } + return true; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHeightMapFactory: + +cHeightMapFactory::~cHeightMapFactory() +{ + // Force all threads to save their last regions: + for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) + { + ((cHeightMap *)(*itr))->Finish(); + } + // TODO: Join all the files into one giant image file +} + + + + diff --git a/Tools/AnvilStats/HeightMap.h b/Tools/AnvilStats/HeightMap.h new file mode 100644 index 000000000..4f9e702d5 --- /dev/null +++ b/Tools/AnvilStats/HeightMap.h @@ -0,0 +1,81 @@ + +// HeightMap.h + +// Declares the cHeightMap class representing a cCallback descendant that draws a B&W map of heights for the world + + + + + +#pragma once + +#include "Callback.h" + + + + + +class cHeightMap : + public cCallback +{ +public: + cHeightMap(void); + + void Finish(void); + +protected: + int m_CurrentChunkX; // Absolute chunk coords + int m_CurrentChunkZ; + int m_CurrentChunkOffX; // Chunk offset from the start of the region + int m_CurrentChunkOffZ; + int m_CurrentRegionX; + int m_CurrentRegionZ; + bool m_IsCurrentRegionValid; + int m_Height[16 * 32 * 16 * 32]; ///< Height-map of the entire current region [x + 16 * 32 * z] + BLOCKTYPE m_BlockTypes[16 * 16 * 256]; ///< Block data of the currently processed chunk (between OnSection() and OnSectionsFinished() ) + + // cCallback overrides: + virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } + virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } + virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! + virtual bool OnBiomes(const unsigned char * a_BiomeData) { return false; } + virtual bool OnHeightMap(const int * a_HeightMapBE) override; + virtual bool OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight + ) override; + virtual bool OnSectionsFinished(void) override; + + void StartNewRegion(int a_RegionX, int a_RegionZ); + + static bool IsGround(BLOCKTYPE a_BlockType); +} ; + + + + + +class cHeightMapFactory : + public cCallbackFactory +{ +public: + virtual ~cHeightMapFactory(); + + virtual cCallback * CreateNewCallback(void) override + { + return new cHeightMap; + } + +} ; + + + + diff --git a/Tools/AnvilStats/Processor.cpp b/Tools/AnvilStats/Processor.cpp new file mode 100644 index 000000000..d6c793182 --- /dev/null +++ b/Tools/AnvilStats/Processor.cpp @@ -0,0 +1,579 @@ + +// Processor.cpp + +// Implements the cProcessor class representing the overall processor engine that manages threads, calls callbacks etc. + +#include "Globals.h" +#include "Processor.h" +#include "Callback.h" +#include "../../source/WorldStorage/FastNBT.h" +#include "zlib.h" +#include "Utils.h" + + + + + +const int CHUNK_INFLATE_MAX = 1 MiB; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cProcessor::cThread: + +cProcessor::cThread::cThread(cCallback & a_Callback, cProcessor & a_ParentProcessor) : + super("cProcessor::cThread"), + m_Callback(a_Callback), + m_ParentProcessor(a_ParentProcessor) +{ + super::Start(); +} + + + + + +void cProcessor::cThread::Execute(void) +{ + LOG("Started a new thread: %d", cIsThread::GetCurrentID()); + + m_ParentProcessor.m_ThreadsHaveStarted.Set(); + + for (;;) + { + AString FileName = m_ParentProcessor.GetOneFileName(); + if (FileName.empty()) + { + // All done, terminate the thread + break; + } + ProcessFile(FileName); + } // for-ever + + LOG("Thread %d terminated", cIsThread::GetCurrentID()); +} + + + + + +void cProcessor::cThread::ProcessFile(const AString & a_FileName) +{ + LOG("Processing file \"%s\"", a_FileName.c_str()); + + size_t idx = a_FileName.rfind("r."); + if (idx == AString::npos) + { + LOG("Cannot parse filename \"%s\", skipping file.", a_FileName.c_str()); + return; + } + int RegionX = 0, RegionZ = 0; + if (sscanf_s(a_FileName.c_str() + idx, "r.%d.%d.mca", &RegionX, &RegionZ) != 2) + { + LOG("Cannot parse filename \"%s\" into coords, skipping file.", a_FileName.c_str()); + return; + } + + cFile f; + if (!f.Open(a_FileName, cFile::fmRead)) + { + LOG("Cannot open file \"%s\", skipping file.", a_FileName.c_str()); + return; + } + + AString FileContents; + f.ReadRestOfFile(FileContents); + if (FileContents.size() < sizeof(8 KiB)) + { + LOG("Cannot read header in file \"%s\", skipping file.", a_FileName.c_str()); + return; + } + + ProcessFileData(FileContents.data(), FileContents.size(), RegionX * 32, RegionZ * 32); +} + + + + + +void cProcessor::cThread::ProcessFileData(const char * a_FileData, size_t a_Size, int a_ChunkBaseX, int a_ChunkBaseZ) +{ + int Header[2048]; + int * HeaderPtr = (int *)a_FileData; + for (int i = 0; i < ARRAYCOUNT(Header); i++) + { + Header[i] = ntohl(HeaderPtr[i]); + } + + for (int i = 0; i < 1024; i++) + { + unsigned Location = Header[i]; + unsigned Timestamp = Header[i + 1024]; + if ( + ((Location == 0) && (Timestamp == 0)) || // Official docs' "not present" + (Location >> 8 < 2) || // Logical - no chunk can start inside the header + ((Location & 0xff) == 0) || // Logical - no chunk can be zero bytes + ((Location >> 8) * 4096 > a_Size) // Logical - no chunk can start at beyond the file end + ) + { + // Chunk not present in the file + continue; + } + int ChunkX = a_ChunkBaseX + (i % 32); + int ChunkZ = a_ChunkBaseZ + (i / 32); + if (m_Callback.OnNewChunk(ChunkX, ChunkZ)) + { + continue; + } + ProcessChunk(a_FileData, ChunkX, ChunkZ, Location >> 8, Location & 0xff, Timestamp); + } // for i - chunk index +} + + + + + +void cProcessor::cThread::ProcessChunk(const char * a_FileData, int a_ChunkX, int a_ChunkZ, unsigned a_SectorStart, unsigned a_SectorSize, unsigned a_TimeStamp) +{ + if (m_Callback.OnHeader(a_SectorStart * 4096, a_SectorSize, a_TimeStamp)) + { + return; + } + + const char * ChunkStart = a_FileData + a_SectorStart * 4096; + int ByteSize = ntohl(*(int *)ChunkStart); + char CompressionMethod = ChunkStart[4]; + + if (m_Callback.OnCompressedDataSizePos(ByteSize, a_SectorStart * 4096 + 5, CompressionMethod)) + { + return; + } + + ProcessCompressedChunkData(a_ChunkX, a_ChunkZ, ChunkStart + 5, ByteSize); +} + + + + + +void cProcessor::cThread::ProcessCompressedChunkData(int a_ChunkX, int a_ChunkZ, const char * a_CompressedData, int a_CompressedSize) +{ + char Decompressed[CHUNK_INFLATE_MAX]; + z_stream strm; + strm.zalloc = (alloc_func)NULL; + strm.zfree = (free_func)NULL; + strm.opaque = NULL; + inflateInit(&strm); + strm.next_out = (Bytef *)Decompressed; + strm.avail_out = sizeof(Decompressed); + strm.next_in = (Bytef *)a_CompressedData; + strm.avail_in = a_CompressedSize; + int res = inflate(&strm, Z_FINISH); + inflateEnd(&strm); + if (res != Z_STREAM_END) + { + LOG("Decompression failed, skipping chunk [%d, %d]", a_ChunkX, a_ChunkZ); + return; + } + + if (m_Callback.OnDecompressedData(Decompressed, strm.total_out)) + { + return; + } + + // Parse the NBT data: + cParsedNBT NBT(Decompressed, strm.total_out); + if (!NBT.IsValid()) + { + LOG("NBT Parsing failed, skipping chunk [%d, %d]", a_ChunkX, a_ChunkZ); + return; + } + + ProcessParsedChunkData(a_ChunkX, a_ChunkZ, NBT); +} + + + + + +void cProcessor::cThread::ProcessParsedChunkData(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT) +{ + int LevelTag = a_NBT.FindChildByName(0, "Level"); + if (LevelTag < 0) + { + LOG("Bad logical structure of the NBT, skipping chunk [%d, %d].", a_ChunkX, a_ChunkZ); + return; + } + int XPosTag = a_NBT.FindChildByName(LevelTag, "xPos"); + int ZPosTag = a_NBT.FindChildByName(LevelTag, "zPos"); + if ((XPosTag < 0) || (ZPosTag < 0)) + { + LOG("Pos tags missing in NTB, skipping chunk [%d, %d].", a_ChunkX, a_ChunkZ); + return; + } + if (m_Callback.OnRealCoords(a_NBT.GetInt(XPosTag), a_NBT.GetInt(ZPosTag))) + { + return; + } + + int LastUpdateTag = a_NBT.FindChildByName(LevelTag, "LastUpdate"); + if (LastUpdateTag > 0) + { + if (m_Callback.OnLastUpdate(a_NBT.GetLong(LastUpdateTag))) + { + return; + } + } + + int TerrainPopulatedTag = a_NBT.FindChildByName(LevelTag, "TerrainPopulated"); + bool TerrainPopulated = (TerrainPopulatedTag < 0) ? false : (a_NBT.GetByte(TerrainPopulatedTag) != 0); + if (m_Callback.OnTerrainPopulated(TerrainPopulated)) + { + return; + } + + int BiomesTag = a_NBT.FindChildByName(LevelTag, "Biomes"); + if (BiomesTag > 0) + { + if (m_Callback.OnBiomes((const unsigned char *)(a_NBT.GetData(BiomesTag)))) + { + return; + } + } + + int HeightMapTag = a_NBT.FindChildByName(LevelTag, "HeightMap"); + if (HeightMapTag > 0) + { + if (m_Callback.OnHeightMap((const int *)(a_NBT.GetData(HeightMapTag)))) + { + return; + } + } + + if (ProcessChunkSections(a_ChunkX, a_ChunkZ, a_NBT, LevelTag)) + { + return; + } + + if (ProcessChunkEntities(a_ChunkX, a_ChunkZ, a_NBT, LevelTag)) + { + return; + } + + if (ProcessChunkTileEntities(a_ChunkX, a_ChunkZ, a_NBT, LevelTag)) + { + return; + } + + if (ProcessChunkTileTicks(a_ChunkX, a_ChunkZ, a_NBT, LevelTag)) + { + return; + } +} + + + + + +bool cProcessor::cThread::ProcessChunkSections(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag) +{ + int Sections = a_NBT.FindChildByName(a_LevelTag, "Sections"); + if (Sections < 0) + { + return false; + } + + bool SectionProcessed[16]; + memset(SectionProcessed, 0, sizeof(SectionProcessed)); + for (int Tag = a_NBT.GetFirstChild(Sections); Tag > 0; Tag = a_NBT.GetNextSibling(Tag)) + { + int YTag = a_NBT.FindChildByName(Tag, "Y"); + int BlocksTag = a_NBT.FindChildByName(Tag, "Blocks"); + int AddTag = a_NBT.FindChildByName(Tag, "Add"); + int DataTag = a_NBT.FindChildByName(Tag, "Data"); + int BlockLightTag = a_NBT.FindChildByName(Tag, "BlockLightTag"); + int SkyLightTag = a_NBT.FindChildByName(Tag, "SkyLight"); + + if ((YTag < 0) || (BlocksTag < 0) || (DataTag < 0)) + { + continue; + } + + unsigned char SectionY = a_NBT.GetByte(YTag); + if (SectionY >= 16) + { + LOG("WARNING: Section Y >= 16 (%d), high world, wtf? Skipping section!", SectionY); + continue; + } + if (m_Callback.OnSection( + SectionY, + (const BLOCKTYPE *) (a_NBT.GetData(BlocksTag)), + (AddTag > 0) ? (const NIBBLETYPE *)(a_NBT.GetData(AddTag)) : NULL, + (const NIBBLETYPE *)(a_NBT.GetData(DataTag)), + (BlockLightTag > 0) ? (const NIBBLETYPE *)(a_NBT.GetData(BlockLightTag)) : NULL, + (BlockLightTag > 0) ? (const NIBBLETYPE *)(a_NBT.GetData(BlockLightTag)) : NULL + )) + { + return true; + } + SectionProcessed[SectionY] = true; + } // for Tag - Sections[] + + // Call the callback for empty sections: + for (unsigned char y = 0; y < 16; y++) + { + if (!SectionProcessed[y]) + { + if (m_Callback.OnEmptySection(y)) + { + return true; + } + } + } + + if (m_Callback.OnSectionsFinished()) + { + return true; + } + + return false; +} + + + + + +bool cProcessor::cThread::ProcessChunkEntities(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag) +{ + int EntitiesTag = a_NBT.FindChildByName(a_LevelTag, "Entities"); + if (EntitiesTag < 0) + { + return false; + } + + for (int EntityTag = a_NBT.GetFirstChild(EntitiesTag); EntityTag > 0; EntityTag = a_NBT.GetNextSibling(EntityTag)) + { + int PosTag = a_NBT.FindChildByName(EntityTag, "Pos"); + if (PosTag < 0) + { + continue; + } + int SpeedTag = a_NBT.FindChildByName(EntityTag, "Motion"); + if (SpeedTag < 0) + { + continue; + } + int RotTag = a_NBT.FindChildByName(EntityTag, "Rotation"); + if (RotTag < 0) + { + continue; + } + double Pos[3]; + for (int i = 0, tag = a_NBT.GetFirstChild(PosTag); (i < 3) && (tag > 0); i++) + { + Pos[i] = a_NBT.GetDouble(tag); + } + double Speed[3]; + for (int i = 0, tag = a_NBT.GetFirstChild(SpeedTag); (i < 3) && (tag > 0); i++) + { + Speed[i] = a_NBT.GetDouble(tag); + } + float Rot[2]; + for (int i = 0, tag = a_NBT.GetFirstChild(RotTag); (i < 2) && (tag > 0); i++) + { + Rot[i] = a_NBT.GetFloat(tag); + } + + if (m_Callback.OnEntity( + a_NBT.GetString(a_NBT.FindChildByName(EntityTag, "id")), + Pos[0], Pos[1], Pos[2], + Speed[0], Speed[1], Speed[2], + Rot[0], Rot[1], + a_NBT.GetFloat(a_NBT.FindChildByName(EntityTag, "FallDistance")), + a_NBT.GetShort(a_NBT.FindChildByName(EntityTag, "Fire")), + a_NBT.GetShort(a_NBT.FindChildByName(EntityTag, "Air")), + a_NBT.GetByte(a_NBT.FindChildByName(EntityTag, "OnGround")), + a_NBT, EntityTag + )) + { + return true; + } + } // for EntityTag - Entities[] + return false; +} + + + + + +bool cProcessor::cThread::ProcessChunkTileEntities(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag) +{ + int TileEntitiesTag = a_NBT.FindChildByName(a_LevelTag, "TileEntities"); + if (TileEntitiesTag < 0) + { + return false; + } + + for (int TileEntityTag = a_NBT.GetFirstChild(TileEntitiesTag); TileEntityTag > 0; TileEntityTag = a_NBT.GetNextSibling(TileEntityTag)) + { + if (m_Callback.OnTileEntity( + a_NBT.GetString(a_NBT.FindChildByName(TileEntityTag, "id")), + a_NBT.GetInt(a_NBT.FindChildByName(TileEntityTag, "x")), + a_NBT.GetInt(a_NBT.FindChildByName(TileEntityTag, "y")), + a_NBT.GetInt(a_NBT.FindChildByName(TileEntityTag, "z")), + a_NBT, TileEntityTag + )) + { + return true; + } + } // for EntityTag - Entities[] + return false; +} + + + + + +bool cProcessor::cThread::ProcessChunkTileTicks(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag) +{ + int TileTicksTag = a_NBT.FindChildByName(a_LevelTag, "TileTicks"); + if (TileTicksTag < 0) + { + return false; + } + + for (int TileTickTag = a_NBT.GetFirstChild(TileTicksTag); TileTickTag > 0; TileTickTag = a_NBT.GetNextSibling(TileTickTag)) + { + int iTag = a_NBT.FindChildByName(TileTicksTag, "i"); + int tTag = a_NBT.FindChildByName(TileTicksTag, "t"); + int xTag = a_NBT.FindChildByName(TileTicksTag, "x"); + int yTag = a_NBT.FindChildByName(TileTicksTag, "y"); + int zTag = a_NBT.FindChildByName(TileTicksTag, "z"); + if ((iTag < 0) || (tTag < 0) || (xTag < 0) || (yTag < 0) || (zTag < 0)) + { + continue; + } + if (m_Callback.OnTileTick( + a_NBT.GetInt(iTag), + a_NBT.GetInt(iTag), + a_NBT.GetInt(iTag), + a_NBT.GetInt(iTag), + a_NBT.GetInt(iTag) + )) + { + return true; + } + } // for EntityTag - Entities[] + return false; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cProcessor: + +cProcessor::cProcessor(void) : + m_IsShuttingDown(false) +{ +} + + + + + +cProcessor::~cProcessor() +{ +} + + + + + +void cProcessor::ProcessWorld(const AString & a_WorldFolder, cCallbackFactory & a_CallbackFactory) +{ + PopulateFileQueue(a_WorldFolder); + + if (m_FileQueue.empty()) + { + LOG("No files to process, exitting."); + return; + } + + // Start as many threads as there are cores, plus one: + // (One more thread can be in the file-read IO block while all other threads crunch the numbers) + int NumThreads = GetNumCores() + 1; + + /* + // Limit the number of threads in DEBUG mode to 1 for easier debugging + #ifdef _DEBUG + NumThreads = 1; + #endif // _DEBUG + //*/ + + for (int i = 0; i < NumThreads; i++) + { + cCallback * Callback = a_CallbackFactory.GetNewCallback(); + m_Threads.push_back(new cThread(*Callback, *this)); + } + + // Wait for the first thread to start processing: + m_ThreadsHaveStarted.Wait(); + + // Wait for all threads to finish + // simply by calling each thread's destructor sequentially + LOG("Waiting for threads to finish"); + for (cThreads::iterator itr = m_Threads.begin(), end = m_Threads.end(); itr != end; ++itr) + { + delete *itr; + } // for itr - m_Threads[] + LOG("Processor finished"); +} + + + + + +void cProcessor::PopulateFileQueue(const AString & a_WorldFolder) +{ + LOG("Processing world in \"%s\"...", a_WorldFolder.c_str()); + + AString Path = a_WorldFolder; + if (!Path.empty() && (Path[Path.length() - 1] != cFile::PathSeparator)) + { + Path.push_back(cFile::PathSeparator); + } + AStringList AllFiles = GetDirectoryContents(Path.c_str()); + for (AStringList::iterator itr = AllFiles.begin(), end = AllFiles.end(); itr != end; ++itr) + { + if (itr->rfind(".mca") != itr->length() - 4) + { + // Not a .mca file + continue; + } + m_FileQueue.push_back(Path + *itr); + } // for itr - AllFiles[] +} + + + + + +AString cProcessor::GetOneFileName(void) +{ + cCSLock Lock(m_CS); + if (m_FileQueue.empty()) + { + return ""; + } + AString res = m_FileQueue.back(); + m_FileQueue.pop_back(); + return res; +} + + + + diff --git a/Tools/AnvilStats/Processor.h b/Tools/AnvilStats/Processor.h new file mode 100644 index 000000000..b7d3f392e --- /dev/null +++ b/Tools/AnvilStats/Processor.h @@ -0,0 +1,77 @@ + +// Processor.h + +// Interfaces to the cProcessor class representing the overall processor engine that manages threads, calls callbacks etc. + + + + +#pragma once + + + + + +// fwd: +class cCallback; +class cCallbackFactory; +class cParsedNBT; + + + + + +class cProcessor +{ + class cThread : + public cIsThread + { + typedef cIsThread super; + + cCallback & m_Callback; + cProcessor & m_ParentProcessor; + + // cIsThread override: + virtual void Execute(void) override; + + void ProcessFile(const AString & a_FileName); + void ProcessFileData(const char * a_FileData, size_t a_Size, int a_ChunkBaseX, int a_ChunkBaseZ); + void ProcessChunk(const char * a_FileData, int a_ChunkX, int a_ChunkZ, unsigned a_SectorStart, unsigned a_SectorSize, unsigned a_TimeStamp); + void ProcessCompressedChunkData(int a_ChunkX, int a_ChunkZ, const char * a_CompressedData, int a_CompressedSize); + void ProcessParsedChunkData(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT); + + // The following processing parts return true if they were interrupted by the callback, causing the processing of current chunk to abort + bool ProcessChunkSections(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag); + bool ProcessChunkEntities(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag); + bool ProcessChunkTileEntities(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag); + bool ProcessChunkTileTicks(int a_ChunkX, int a_ChunkZ, cParsedNBT & a_NBT, int a_LevelTag); + + public: + cThread(cCallback & a_Callback, cProcessor & a_ParentProcessor); + } ; + + typedef std::vector cThreads; + +public: + cProcessor(void); + ~cProcessor(); + + void ProcessWorld(const AString & a_WorldFolder, cCallbackFactory & a_CallbackFactory); + +protected: + bool m_IsShuttingDown; // If true, the threads should stop ASAP + + cCriticalSection m_CS; + AStringList m_FileQueue; + + cThreads m_Threads; + cEvent m_ThreadsHaveStarted; // This is signalled by each thread to notify the parent thread that it can start waiting for those threads + + void PopulateFileQueue(const AString & a_WorldFolder); + + AString GetOneFileName(void); +} ; + + + + diff --git a/Tools/AnvilStats/SpringStats.cpp b/Tools/AnvilStats/SpringStats.cpp new file mode 100644 index 000000000..14ede6889 --- /dev/null +++ b/Tools/AnvilStats/SpringStats.cpp @@ -0,0 +1,279 @@ + +// SpringStats.cpp + +// Implements the cSpringStats class representing a cCallback descendant that collects statistics on lava and water springs + +#include "Globals.h" +#include "SpringStats.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSpringStats::cStats + +cSpringStats::cStats::cStats(void) : + m_TotalChunks(0) +{ + memset(m_LavaSprings, 0, sizeof(m_LavaSprings)); + memset(m_WaterSprings, 0, sizeof(m_WaterSprings)); +} + + + + + +void cSpringStats::cStats::Add(const cSpringStats::cStats & a_Other) +{ + m_TotalChunks += a_Other.m_TotalChunks; + for (int Biome = 0; Biome < 256; Biome++) + { + for (int Height = 0; Height < 256; Height++) + { + m_LavaSprings[Biome][Height] += a_Other.m_LavaSprings[Biome][Height]; + m_WaterSprings[Biome][Height] += a_Other.m_WaterSprings[Biome][Height]; + } + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSpringStats: + +cSpringStats::cSpringStats(void) : + m_AreBiomesValid(false) +{ +} + + + + + +bool cSpringStats::OnNewChunk(int a_ChunkX, int a_ChunkZ) +{ + memset(m_BlockTypes, 0, sizeof(m_BlockTypes)); + m_AreBiomesValid = false; + return false; +} + + + + + +bool cSpringStats::OnBiomes(const unsigned char * a_BiomeData) +{ + memcpy(m_Biomes, a_BiomeData, sizeof(m_Biomes)); + m_AreBiomesValid = true; + return false; +} + + + + + +bool cSpringStats::OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight +) +{ + memcpy(m_BlockTypes + ((int)a_Y) * 16 * 16 * 16, a_BlockTypes, 16 * 16 * 16); + memcpy(m_BlockMetas + ((int)a_Y) * 16 * 16 * 16 / 2, a_BlockMeta, 16 * 16 * 16 / 2); + return false; +} + + + + + +bool cSpringStats::OnSectionsFinished(void) +{ + if (!m_AreBiomesValid) + { + return true; + } + + // Calc the spring stats: + for (int y = 1; y < 255; y++) + { + int BaseY = y * 16 * 16; + for (int z = 1; z < 15; z++) + { + int Base = BaseY + z * 16; + for (int x = 1; x < 15; x++) + { + if (cChunkDef::GetNibble(m_BlockMetas, Base + x) != 0) + { + // Not a source block + continue; + } + switch (m_BlockTypes[Base + x]) + { + case E_BLOCK_WATER: + case E_BLOCK_STATIONARY_WATER: + { + TestSpring(x, y, z, m_Stats.m_WaterSprings); + break; + } + case E_BLOCK_LAVA: + case E_BLOCK_STATIONARY_LAVA: + { + TestSpring(x, y, z, m_Stats.m_LavaSprings); + break; + } + } // switch (BlockType) + } // for x + } // for z + } // for y + m_Stats.m_TotalChunks += 1; + return true; +} + + + + + +void cSpringStats::TestSpring(int a_RelX, int a_RelY, int a_RelZ, cSpringStats::cStats::SpringStats & a_Stats) +{ + static const struct + { + int x, y, z; + } Coords[] = + { + {-1, 0, 0}, + { 1, 0, 0}, + { 0, -1, 0}, + { 0, 1, 0}, + { 0, 0, -1}, + { 0, 0, 1}, + } ; + bool HasFluidNextToIt = false; + for (int i = 0; i < ARRAYCOUNT(Coords); i++) + { + switch (cChunkDef::GetBlock(m_BlockTypes, a_RelX + Coords[i].x, a_RelY + Coords[i].y, a_RelZ + Coords[i].z)) + { + case E_BLOCK_WATER: + case E_BLOCK_STATIONARY_WATER: + case E_BLOCK_LAVA: + case E_BLOCK_STATIONARY_LAVA: + { + if (cChunkDef::GetNibble(m_BlockMetas, a_RelX + Coords[i].x, a_RelY + Coords[i].y, a_RelZ + Coords[i].z) == 0) + { + // There is another source block next to this, so this is not a spring + return; + } + HasFluidNextToIt = true; + } + } // switch (BlockType) + } // for i - Coords[] + + if (!HasFluidNextToIt) + { + // Surrounded by solids on all sides, this is probably not a spring, + // but rather a bedrocked lake or something similar. Dont want. + return; + } + + // No source blocks next to the specified block, so it is a spring. Add it to stats: + a_Stats[a_RelY][((unsigned char *)m_Biomes)[a_RelX + 16 * a_RelZ]] += 1; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cSpringStatsFactory: + +cSpringStatsFactory::~cSpringStatsFactory() +{ + LOG("cSpringStats:"); + LOG(" Joining results..."); + JoinResults(); + LOG(" Total %llu chunks went through", m_CombinedStats.m_TotalChunks); + + // Save statistics: + LOG(" Saving statistics into files:"); + LOG(" Springs.xls"); + SaveTotals("Springs.xls"); + LOG(" BiomeWaterSprings.xls"); + SaveStatistics(m_CombinedStats.m_WaterSprings, "BiomeWaterSprings.xls"); + LOG(" BiomeLavaSprings.xls"); + SaveStatistics(m_CombinedStats.m_LavaSprings, "BiomeLavaSprings.xls"); +} + + + + + +void cSpringStatsFactory::JoinResults(void) +{ + for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) + { + m_CombinedStats.Add(((cSpringStats *)(*itr))->GetStats()); + } // for itr - m_Callbacks[] +} + + + + + +void cSpringStatsFactory::SaveTotals(const AString & a_FileName) +{ + cFile f(a_FileName, cFile::fmWrite); + if (!f.IsOpen()) + { + LOG("Cannot open file \"%s\" for writing!", a_FileName.c_str()); + return; + } + f.Printf("Height\tWater\tLava\n"); + for (int Height = 0; Height < 256; Height++) + { + UInt64 TotalW = 0; + UInt64 TotalL = 0; + for (int Biome = 0; Biome < 256; Biome++) + { + TotalW += m_CombinedStats.m_WaterSprings[Height][Biome]; + TotalL += m_CombinedStats.m_LavaSprings[Height][Biome]; + } + f.Printf("%d\t%llu\t%llu\n", Height, TotalW, TotalL); + } + f.Printf("\n# Chunks\t%llu", m_CombinedStats.m_TotalChunks); +} + + + + + +void cSpringStatsFactory::SaveStatistics(const cSpringStats::cStats::SpringStats & a_Stats, const AString & a_FileName) +{ + cFile f(a_FileName, cFile::fmWrite); + if (!f.IsOpen()) + { + LOG("Cannot open file \"%s\" for writing!", a_FileName.c_str()); + return; + } + for (int Height = 0; Height < 256; Height++) + { + AString Line; + Line.reserve(2000); + Printf(Line, "%d\t", Height); + for (int Biome = 0; Biome < 256; Biome++) + { + AppendPrintf(Line, "%llu\t", a_Stats[Height][Biome]); + } + Line.append("\n"); + f.Write(Line.c_str(), Line.size()); + } +} + + + + diff --git a/Tools/AnvilStats/SpringStats.h b/Tools/AnvilStats/SpringStats.h new file mode 100644 index 000000000..292c5b82d --- /dev/null +++ b/Tools/AnvilStats/SpringStats.h @@ -0,0 +1,102 @@ + +// SpringStats.h + +// Declares the cSpringStats class representing a cCallback descendant that collects statistics on lava and water springs + + + + + +#pragma once + +#include "Callback.h" + + + + + +class cSpringStats : + public cCallback +{ +public: + class cStats + { + public: + /// Per-height, per-biome frequencies of springs + typedef UInt64 SpringStats[256][256]; + + SpringStats m_LavaSprings; + SpringStats m_WaterSprings; + + UInt64 m_TotalChunks; ///< Total number of chunks that are fully processed through this callback(OnSectionsFinished()) + + cStats(void); + void Add(const cStats & a_Other); + } ; + + cSpringStats(void); + + const cStats & GetStats(void) const { return m_Stats; } + +protected: + + BLOCKTYPE m_BlockTypes[16 * 16 * 256]; + NIBBLETYPE m_BlockMetas[16 * 16 * 256 / 2]; + char m_Biomes[16 * 16]; + bool m_AreBiomesValid; + + cStats m_Stats; + + // cCallback overrides: + virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } + virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } + virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! + virtual bool OnBiomes(const unsigned char * a_BiomeData) override; + virtual bool OnHeightMap(const int * a_HeightMap) override { return false; } + virtual bool OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight + ) override; + virtual bool OnSectionsFinished(void) override; + + /// Tests the specified block, if it appears to be a spring, it is added to a_Stats + void TestSpring(int a_RelX, int a_RelY, int a_RelZ, cStats::SpringStats & a_Stats); +} ; + + + + + +class cSpringStatsFactory : + public cCallbackFactory +{ +public: + virtual ~cSpringStatsFactory(); + + virtual cCallback * CreateNewCallback(void) override + { + return new cSpringStats; + } + + cSpringStats::cStats m_CombinedStats; + + void JoinResults(void); + + /// Saves total per-height data (summed through biomes) for both spring types to the file + void SaveTotals(const AString & a_FileName); + + /// Saves complete per-height, per-biome statistics for the springs to the file + void SaveStatistics(const cSpringStats::cStats::SpringStats & a_Stats, const AString & a_FileName); +} ; + + + + diff --git a/Tools/AnvilStats/Statistics.cpp b/Tools/AnvilStats/Statistics.cpp new file mode 100644 index 000000000..2f30e158a --- /dev/null +++ b/Tools/AnvilStats/Statistics.cpp @@ -0,0 +1,523 @@ + +// Statistics.cpp + +// Implements the various statistics-collecting classes + +#include "Globals.h" +#include "Statistics.h" +#include "../../source/WorldStorage/FastNBT.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStatistics::cStats: + +cStatistics::cStats::cStats(void) : + m_TotalChunks(0), + m_BiomeNumChunks(0), + m_BlockNumChunks(0), + m_NumEntities(0), + m_NumTileEntities(0), + m_NumTileTicks(0), + m_MinChunkX(0x7fffffff), + m_MaxChunkX(0x80000000), + m_MinChunkZ(0x7fffffff), + m_MaxChunkZ(0x80000000) +{ + memset(m_BiomeCounts, 0, sizeof(m_BiomeCounts)); + memset(m_BlockCounts, 0, sizeof(m_BlockCounts)); + memset(m_SpawnerEntity, 0, sizeof(m_SpawnerEntity)); +} + + + + + +void cStatistics::cStats::Add(const cStatistics::cStats & a_Stats) +{ + for (int i = 0; i <= 255; i++) + { + m_BiomeCounts[i] += a_Stats.m_BiomeCounts[i]; + } + for (int i = 0; i <= 255; i++) + { + for (int j = 0; j <= 255; j++) + { + m_BlockCounts[i][j] += a_Stats.m_BlockCounts[i][j]; + } + } + for (int i = 0; i < ARRAYCOUNT(m_SpawnerEntity); i++) + { + m_SpawnerEntity[i] += a_Stats.m_SpawnerEntity[i]; + } + m_BiomeNumChunks += a_Stats.m_BiomeNumChunks; + m_BlockNumChunks += a_Stats.m_BlockNumChunks; + m_TotalChunks += a_Stats.m_TotalChunks; + m_NumEntities += a_Stats.m_NumEntities; + m_NumTileEntities += a_Stats.m_NumTileEntities; + m_NumTileTicks += a_Stats.m_NumTileTicks; + UpdateCoordsRange(a_Stats.m_MinChunkX, a_Stats.m_MinChunkZ); + UpdateCoordsRange(a_Stats.m_MinChunkX, a_Stats.m_MinChunkZ); +} + + + + + +void cStatistics::cStats::UpdateCoordsRange(int a_ChunkX, int a_ChunkZ) +{ + if (a_ChunkX < m_MinChunkX) + { + m_MinChunkX = a_ChunkX; + } + if (a_ChunkX > m_MaxChunkX) + { + m_MaxChunkX = a_ChunkX; + } + if (a_ChunkZ < m_MinChunkZ) + { + m_MinChunkZ = a_ChunkZ; + } + if (a_ChunkZ > m_MaxChunkZ) + { + m_MaxChunkZ = a_ChunkZ; + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStatistics: + +cStatistics::cStatistics(void) +{ +} + + + + + +bool cStatistics::OnNewChunk(int a_ChunkX, int a_ChunkZ) +{ + m_Stats.m_TotalChunks++; + m_Stats.UpdateCoordsRange(a_ChunkX, a_ChunkZ); + m_IsBiomesValid = false; + m_IsFirstSectionInChunk = true; + return false; +} + + + + + +bool cStatistics::OnBiomes(const unsigned char * a_BiomeData) +{ + for (int i = 0; i < 16 * 16; i++) + { + m_Stats.m_BiomeCounts[a_BiomeData[i]] += 1; + } + m_Stats.m_BiomeNumChunks += 1; + memcpy(m_BiomeData, a_BiomeData, sizeof(m_BiomeData)); + m_IsBiomesValid = true; + return false; +} + + + + + + +bool cStatistics::OnSection +( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight +) +{ + if (!m_IsBiomesValid) + { + // The current biome data is not valid, we don't have the means for sorting the BlockTypes into per-biome arrays + return true; + } + + for (int y = 0; y < 16; y++) + { + for (int z = 0; z < 16; z++) + { + for (int x = 0; x < 16; x++) + { + unsigned char Biome = m_BiomeData[x + 16 * z]; // Cannot use cChunkDef, different datatype + unsigned char BlockType = cChunkDef::GetBlock(a_BlockTypes, x, y, z); + m_Stats.m_BlockCounts[Biome][BlockType] += 1; + } + } + } + + m_Stats.m_BlockNumChunks += m_IsFirstSectionInChunk ? 1 : 0; + m_IsFirstSectionInChunk = false; + + return false; +} + + + + + +bool cStatistics::OnEmptySection(unsigned char a_Y) +{ + if (!m_IsBiomesValid) + { + // The current biome data is not valid, we don't have the means for sorting the BlockTypes into per-biome arrays + return true; + } + + // Add air to all columns: + for (int z = 0; z < 16; z++) + { + for (int x = 0; x < 16; x++) + { + unsigned char Biome = m_BiomeData[x + 16 * z]; // Cannot use cChunkDef, different datatype + m_Stats.m_BlockCounts[Biome][0] += 16; // 16 blocks in a column, all air + } + } + + m_Stats.m_BlockNumChunks += m_IsFirstSectionInChunk ? 1 : 0; + m_IsFirstSectionInChunk = false; + + return false; +} + + + + + +bool cStatistics::OnEntity( + const AString & a_EntityType, + double a_PosX, double a_PosY, double a_PosZ, + double a_SpeedX, double a_SpeedY, double a_SpeedZ, + float a_Yaw, float a_Pitch, + float a_FallDistance, + short a_FireTicksLeft, + short a_AirTicks, + char a_IsOnGround, + cParsedNBT & a_NBT, + int a_NBTTag +) +{ + m_Stats.m_NumEntities += 1; + + // TODO + + return false; +} + + + + + +bool cStatistics::OnTileEntity( + const AString & a_EntityType, + int a_PosX, int a_PosY, int a_PosZ, + cParsedNBT & a_NBT, + int a_NBTTag +) +{ + m_Stats.m_NumTileEntities += 1; + + if (a_EntityType == "MobSpawner") + { + OnSpawner(a_NBT, a_NBTTag); + } + + return false; +} + + + + + +bool cStatistics::OnTileTick( + int a_BlockType, + int a_TicksLeft, + int a_PosX, int a_PosY, int a_PosZ +) +{ + m_Stats.m_NumTileTicks += 1; + return false; +} + + + + + +void cStatistics::OnSpawner(cParsedNBT & a_NBT, int a_TileEntityTag) +{ + int EntityIDTag = a_NBT.FindChildByName(a_TileEntityTag, "EntityId"); + if ((EntityIDTag < 0) || (a_NBT.GetType(EntityIDTag) != TAG_String)) + { + return; + } + eEntityType Ent = GetEntityType(a_NBT.GetString(EntityIDTag)); + if (Ent < ARRAYCOUNT(m_Stats.m_SpawnerEntity)) + { + m_Stats.m_SpawnerEntity[Ent] += 1; + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStatisticsFactory: + +cStatisticsFactory::cStatisticsFactory(void) : + m_BeginTick(clock()) +{ +} + + + + + +cStatisticsFactory::~cStatisticsFactory() +{ + // Join the results together: + LOG("cStatistics:"); + LOG(" Joining results..."); + JoinResults(); + LOG(" Total %llu chunks went through", m_CombinedStats.m_TotalChunks); + LOG(" Biomes processed for %llu chunks", m_CombinedStats.m_BiomeNumChunks); + + // Check the number of blocks processed + UInt64 TotalBlocks = 0; + for (int i = 0; i <= 255; i++) + { + for (int j = 0; j < 255; j++) + { + TotalBlocks += m_CombinedStats.m_BlockCounts[i][j]; + } + } + UInt64 ExpTotalBlocks = m_CombinedStats.m_BlockNumChunks * 16LL * 16LL * 256LL; + LOG(" BlockIDs processed for %llu chunks, %llu blocks (exp %llu; %s)", m_CombinedStats.m_BlockNumChunks, TotalBlocks, ExpTotalBlocks, (TotalBlocks == ExpTotalBlocks) ? "match" : "failed"); + + // Save statistics: + LOG(" Saving statistics into files:"); + LOG(" Statistics.txt"); + SaveStatistics(); + LOG(" Biomes.xls"); + SaveBiomes(); + LOG(" BlockTypes.xls"); + SaveBlockTypes(); + LOG(" BiomeBlockTypes.xls"); + SaveBiomeBlockTypes(); + LOG(" Spawners.xls"); + SaveSpawners(); +} + + + + + +void cStatisticsFactory::JoinResults(void) +{ + for (cCallbacks::iterator itr = m_Callbacks.begin(), end = m_Callbacks.end(); itr != end; ++itr) + { + m_CombinedStats.Add(((cStatistics *)(*itr))->GetStats()); + } // for itr - m_Callbacks[] +} + + + + + +void cStatisticsFactory::SaveBiomes(void) +{ + cFile f; + if (!f.Open("Biomes.xls", cFile::fmWrite)) + { + LOG("Cannot write to file Biomes.xls. Statistics not written."); + return; + } + double TotalColumns = (double)(m_CombinedStats.m_BiomeNumChunks) * 16 * 16 / 100; // Total number of columns processed; convert into percent + if (TotalColumns < 1) + { + // Avoid division by zero + TotalColumns = 1; + } + for (int i = 0; i <= 255; i++) + { + AString Line; + Printf(Line, "%s\t%d\t%llu\t%.05f\n", GetBiomeString(i), i, m_CombinedStats.m_BiomeCounts[i], ((double)(m_CombinedStats.m_BiomeCounts[i])) / TotalColumns); + f.Write(Line.c_str(), Line.length()); + } +} + + + + + +void cStatisticsFactory::SaveBlockTypes(void) +{ + cFile f; + if (!f.Open("BlockTypes.xls", cFile::fmWrite)) + { + LOG("Cannot write to file Biomes.xls. Statistics not written."); + return; + } + double TotalBlocks = ((double)(m_CombinedStats.m_BlockNumChunks)) * 16 * 16 * 256 / 100; // Total number of blocks processed; convert into percent + if (TotalBlocks < 1) + { + // Avoid division by zero + TotalBlocks = 1; + } + for (int i = 0; i <= 255; i++) + { + UInt64 Count = 0; + for (int Biome = 0; Biome <= 255; ++Biome) + { + Count += m_CombinedStats.m_BlockCounts[Biome][i]; + } + AString Line; + Printf(Line, "%s\t%d\t%llu\t%.08f\n", GetBlockTypeString(i), i, Count, ((double)Count) / TotalBlocks); + f.Write(Line.c_str(), Line.length()); + } +} + + + + + +void cStatisticsFactory::SaveBiomeBlockTypes(void) +{ + // Export as two tables: biomes 0-127 and 128-255, because OpenOffice doesn't support more than 256 columns + cFile f; + if (!f.Open("BiomeBlockTypes.xls", cFile::fmWrite)) + { + LOG("Cannot write to file BiomeBlockTypes.xls. Statistics not written."); + return; + } + + AString FileHeader("Biomes 0-127:\n"); + f.Write(FileHeader.c_str(), FileHeader.length()); + + AString Header("BlockType\tBlockType"); + for (int Biome = 0; Biome <= 127; Biome++) + { + const char * BiomeName = GetBiomeString(Biome); + if ((BiomeName != NULL) && (BiomeName[0] != 0)) + { + AppendPrintf(Header, "\t%s (%d)", BiomeName, Biome); + } + else + { + AppendPrintf(Header, "\t%d", Biome); + } + } + Header.append("\n"); + f.Write(Header.c_str(), Header.length()); + + for (int BlockType = 0; BlockType <= 255; BlockType++) + { + AString Line; + Printf(Line, "%s\t%d", GetBlockTypeString(BlockType), BlockType); + for (int Biome = 0; Biome <= 127; Biome++) + { + AppendPrintf(Line, "\t%llu", m_CombinedStats.m_BlockCounts[Biome][BlockType]); + } + Line.append("\n"); + f.Write(Line.c_str(), Line.length()); + } + + Header.assign("\n\nBiomes 127-255:\nBlockType\tBlockType"); + for (int Biome = 0; Biome <= 127; Biome++) + { + const char * BiomeName = GetBiomeString(Biome); + if ((BiomeName != NULL) && (BiomeName[0] != 0)) + { + AppendPrintf(Header, "\t%s (%d)", BiomeName, Biome); + } + else + { + AppendPrintf(Header, "\t%d", Biome); + } + } + Header.append("\n"); + f.Write(Header.c_str(), Header.length()); + + for (int BlockType = 0; BlockType <= 255; BlockType++) + { + AString Line; + Printf(Line, "%s\t%d", GetBlockTypeString(BlockType), BlockType); + for (int Biome = 128; Biome <= 255; Biome++) + { + AppendPrintf(Line, "\t%llu", m_CombinedStats.m_BlockCounts[Biome][BlockType]); + } + Line.append("\n"); + f.Write(Line.c_str(), Line.length()); + } +} + + + + + + +void cStatisticsFactory::SaveStatistics(void) +{ + cFile f; + if (!f.Open("Statistics.txt", cFile::fmWrite)) + { + LOG("Cannot write to file Statistics.txt. Statistics not written."); + return; + } + + int Elapsed = (clock() - m_BeginTick) / CLOCKS_PER_SEC; + f.Printf("Time elapsed: %d seconds (%d hours, %d minutes and %d seconds)\n", Elapsed, Elapsed / 3600, (Elapsed / 60) % 60, Elapsed % 60); + f.Printf("Total chunks processed: %llu\n", m_CombinedStats.m_TotalChunks); + if (Elapsed > 0) + { + f.Printf("Chunk processing speed: %.02f chunks per second\n", (double)(m_CombinedStats.m_TotalChunks) / Elapsed); + } + f.Printf("Biomes counted for %llu chunks.\n", m_CombinedStats.m_BiomeNumChunks); + f.Printf("Blocktypes counted for %llu chunks.\n", m_CombinedStats.m_BlockNumChunks); + f.Printf("Total blocks counted: %llu\n", m_CombinedStats.m_BlockNumChunks * 16 * 16 * 256); + f.Printf("Total biomes counted: %llu\n", m_CombinedStats.m_BiomeNumChunks * 16 * 16); + f.Printf("Total entities counted: %llu\n", m_CombinedStats.m_NumEntities); + f.Printf("Total tile entities counted: %llu\n", m_CombinedStats.m_NumTileEntities); + f.Printf("Total tile ticks counted: %llu\n", m_CombinedStats.m_NumTileTicks); + f.Printf("Chunk coord ranges:\n"); + f.Printf("\tX: %d .. %d\n", m_CombinedStats.m_MinChunkX, m_CombinedStats.m_MaxChunkX); + f.Printf("\tZ: %d .. %d\n", m_CombinedStats.m_MinChunkZ, m_CombinedStats.m_MaxChunkZ); +} + + + + + +void cStatisticsFactory::SaveSpawners(void) +{ + cFile f; + if (!f.Open("Spawners.xls", cFile::fmWrite)) + { + LOG("Cannot write to file Spawners.xls. Statistics not written."); + return; + } + + f.Printf("Entity type\tTotal count\tCount per chunk\n"); + for (int i = 0; i < entMax; i++) + { + f.Printf("%s\t%llu\t%0.4f\n", GetEntityTypeString((eEntityType)i), m_CombinedStats.m_SpawnerEntity[i], (double)(m_CombinedStats.m_SpawnerEntity[i]) / m_CombinedStats.m_BlockNumChunks); + } +} + + + + diff --git a/Tools/AnvilStats/Statistics.h b/Tools/AnvilStats/Statistics.h new file mode 100644 index 000000000..2c0a86cc1 --- /dev/null +++ b/Tools/AnvilStats/Statistics.h @@ -0,0 +1,138 @@ + +// Statistics.h + +// Interfaces to the cStatistics class representing a statistics-collecting callback + + + + + +#pragma once + +#include "Callback.h" +#include "Utils.h" + + + + + +class cStatistics : + public cCallback +{ +public: + class cStats + { + public: + UInt64 m_TotalChunks; // Total number of chunks that go through this callback (OnNewChunk()) + UInt64 m_BiomeCounts[256]; + UInt64 m_BlockCounts[256][256]; // First dimension is the biome, second dimension is BlockType + UInt64 m_BiomeNumChunks; // Num chunks that have been processed for biome stats + UInt64 m_BlockNumChunks; // Num chunks that have been processed for block stats + UInt64 m_NumEntities; + UInt64 m_NumTileEntities; + UInt64 m_NumTileTicks; + int m_MinChunkX, m_MaxChunkX; // X coords range + int m_MinChunkZ, m_MaxChunkZ; // Z coords range + + Int64 m; + UInt64 m_SpawnerEntity[entMax + 1]; + + cStats(void); + void Add(const cStats & a_Stats); + void UpdateCoordsRange(int a_ChunkX, int a_ChunkZ); + } ; + + cStatistics(void); + + const cStats & GetStats(void) const { return m_Stats; } + +protected: + cStats m_Stats; + + bool m_IsBiomesValid; // Set to true in OnBiomes(), reset to false in OnNewChunk(); if true, the m_BiomeData is valid for the current chunk + unsigned char m_BiomeData[16 * 16]; + bool m_IsFirstSectionInChunk; // True if there was no section in the chunk yet. Set by OnNewChunk(), reset by OnSection() + + // cCallback overrides: + virtual bool OnNewChunk(int a_ChunkX, int a_ChunkZ) override; + virtual bool OnHeader(int a_FileOffset, unsigned char a_NumSectors, int a_Timestamp) override { return false; } + virtual bool OnCompressedDataSizePos(int a_CompressedDataSize, int a_DataOffset, char a_CompressionMethod) override { return false; } + virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; } + virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; } + virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; } + virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it! + virtual bool OnBiomes(const unsigned char * a_BiomeData) override; + virtual bool OnHeightMap(const int * a_HeightMap) override { return false; } + virtual bool OnSection( + unsigned char a_Y, + const BLOCKTYPE * a_BlockTypes, + const NIBBLETYPE * a_BlockAdditional, + const NIBBLETYPE * a_BlockMeta, + const NIBBLETYPE * a_BlockLight, + const NIBBLETYPE * a_BlockSkyLight + ) override; + + virtual bool OnEmptySection(unsigned char a_Y) override; + + virtual bool OnEntity( + const AString & a_EntityType, + double a_PosX, double a_PosY, double a_PosZ, + double a_SpeedX, double a_SpeedY, double a_SpeedZ, + float a_Yaw, float a_Pitch, + float a_FallDistance, + short a_FireTicksLeft, + short a_AirTicks, + char a_IsOnGround, + cParsedNBT & a_NBT, + int a_NBTTag + ) override; + + virtual bool OnTileEntity( + const AString & a_EntityType, + int a_PosX, int a_PosY, int a_PosZ, + cParsedNBT & a_NBT, + int a_NBTTag + ) override; + + virtual bool OnTileTick( + int a_BlockType, + int a_TicksLeft, + int a_PosX, int a_PosY, int a_PosZ + ) override; + + void OnSpawner(cParsedNBT & a_NBT, int a_TileEntityTag); +} ; + + + + + +class cStatisticsFactory : + public cCallbackFactory +{ +public: + cStatisticsFactory(void); + virtual ~cStatisticsFactory(); + + virtual cCallback * CreateNewCallback(void) + { + return new cStatistics; + } + +protected: + // The results, combined, are stored here: + cStatistics::cStats m_CombinedStats; + + clock_t m_BeginTick; + + void JoinResults(void); + void SaveBiomes(void); + void SaveBlockTypes(void); + void SaveBiomeBlockTypes(void); + void SaveStatistics(void); + void SaveSpawners(void); +} ; + + + + diff --git a/Tools/AnvilStats/Utils.cpp b/Tools/AnvilStats/Utils.cpp new file mode 100644 index 000000000..be1f067c0 --- /dev/null +++ b/Tools/AnvilStats/Utils.cpp @@ -0,0 +1,291 @@ + +// Utils.cpp + +// Implements utility functions + +#include "Globals.h" +#include "Utils.h" + + + + + +struct +{ + eEntityType Type; + const char * String; +} g_EntityTypes[] = +{ + {entBat, "Bat"}, + {entBlaze, "Blaze"}, + {entCaveSpider, "CaveSpider"}, + {entChicken, "Chicken"}, + {entCow, "Cow"}, + {entCreeper, "Creeper"}, + {entEnderDragon, "EnderDragon"}, + {entEnderman, "Enderman"}, + {entGhast, "Ghast"}, + {entGiant, "Giant"}, + {entLavaSlime, "LavaSlime"}, + {entMushroomCow, "MushroomCow"}, + {entOzelot, "Ozelot"}, + {entPig, "Pig"}, + {entPigZombie, "PigZombie"}, + {entSheep, "Sheep"}, + {entSilverfish, "Slverfish"}, + {entSkeleton, "Skeleton"}, + {entSlime, "Slime"}, + {entSnowMan, "SnowMan"}, + {entSpider, "Spider"}, + {entSquid, "Squid"}, + {entVillager, "Villager"}, + {entVillagerGolem, "VillagerGolem"}, + {entWitch, "Witch"}, + {entWitherBoss, "WitherBoss"}, + {entWolf, "Wolf"}, + {entZombie, "Zombie"}, + {entUnknown, "Unknown"}, +} ; + + + + + +const char * GetBiomeString(unsigned char a_Biome) +{ + static const char * BiomeNames[] = // Biome names, as equivalent to their index + { + "Ocean", + "Plains", + "Desert", + "Extreme Hills", + "Forest", + "Taiga", + "Swampland", + "River", + "Hell", + "Sky", + "Frozen Ocean", + "Frozen River", + "Ice Plains", + "Ice Mountains", + "Mushroom Island", + "Mushroom Island Shore", + "Beach", + "Desert Hills", + "Forest Hills", + "Taiga Hills", + "Extreme Hills Edge", + "Jungle", + "Jungle Hills", + } ; + return (a_Biome < ARRAYCOUNT(BiomeNames)) ? BiomeNames[a_Biome] : ""; +} + + + + + +const char * GetBlockTypeString(unsigned char a_BlockType) +{ + static const char * BlockTypeNames[] = // Block type names, as equivalent to their index + { + "air", + "stone", + "grass", + "dirt", + "cobblestone", + "planks", + "sapling", + "bedrock", + "water", + "stillwater", + "lava", + "stilllava", + "sand", + "gravel", + "goldore", + "ironore", + "coalore", + "log", + "leaves", + "sponge", + "glass", + "lapisore", + "lapisblock", + "dispenser", + "sandstone", + "noteblock", + "bedblock", + "poweredrail", + "detectorrail", + "stickypiston", + "cobweb", + "tallgrass", + "deadbush", + "piston", + "pistonhead", + "wool", + "pistonmovedblock", + "flower", + "rose", + "brownmushroom", + "redmushroom", + "goldblock", + "ironblock", + "doubleslab", + "slab", + "brickblock", + "tnt", + "bookcase", + "mossycobblestone", + "obsidian", + "torch", + "fire", + "mobspawner", + "woodstairs", + "chest", + "redstonedust", + "diamondore", + "diamondblock", + "workbench", + "crops", + "soil", + "furnace", + "litfurnace", + "signblock", + "wooddoorblock", + "ladder", + "tracks", + "cobblestonestairs", + "wallsign", + "lever", + "stoneplate", + "irondoorblock", + "woodplate", + "redstoneore", + "redstoneorealt", + "redstonetorchoff", + "redstonetorchon", + "button", + "snow", + "ice", + "snowblock", + "cactus", + "clayblock", + "reedblock", + "jukebox", + "fence", + "pumpkin", + "netherrack", + "soulsand", + "glowstone", + "portal", + "jack-o-lantern", + "cakeblock", + "repeateroff", + "repeateron", + "lockedchest", + "trapdoor", + "silverfishblock", + "stonebricks", + "hugebrownmushroom", + "hugeredmushroom", + "ironbars", + "glasspane", + "melon", + "pumpkinstem", + "melonstem", + "vines", + "fencegate", + "brickstairs", + "stonebrickstairs", + "mycelium", + "lilypad", + "netherbrick", + "netherbrickfence", + "netherbrickstairs", + "netherwartblock", + "enchantmenttable", + "brewingstandblock", + "cauldronblock", + "endportal", + "endportalframe", + "endstone", + "dragonegg", + "redstonelampoff", + "redstonelampon", + "woodendoubleslab", + "woodenslab", + "cocoapod", + "sandstonestairs", /* 128 */ + "Emerald Ore", + "Ender Chest", + "Tripwire Hook", + "Tripwire", + "Block of Emerald", + "Spruce Wood Stairs", + "Birch Wood Stairs", + "Jungle Wood Stairs", + "Command Block", + "Beacon", + "Cobblestone Wall", + "Flower Pot", + "Carrots", + "Potatoes", + "Wooden Button", + "Head", + } ; + + return (a_BlockType < ARRAYCOUNT(BlockTypeNames)) ? BlockTypeNames[a_BlockType] : ""; +} + + + + + +eEntityType GetEntityType(const AString & a_EntityTypeString) +{ + for (int i = 0; i < ARRAYCOUNT(g_EntityTypes); i++) + { + if (a_EntityTypeString == g_EntityTypes[i].String) + { + return g_EntityTypes[i].Type; + } + } + return entUnknown; +} + + + + + +extern const char * GetEntityTypeString(eEntityType a_EntityType) +{ + return g_EntityTypes[a_EntityType].String; +} + + + + + +int GetNumCores(void) +{ + // Get number of cores by querying the system process affinity mask (Windows-specific) + DWORD Affinity, ProcAffinity; + GetProcessAffinityMask(GetCurrentProcess(), &ProcAffinity, &Affinity); + int NumCores = 0; + while (Affinity > 0) + { + if ((Affinity & 1) == 1) + { + ++NumCores; + } + Affinity >>= 1; + } // while (Affinity > 0) + return NumCores; +} + + + + diff --git a/Tools/AnvilStats/Utils.h b/Tools/AnvilStats/Utils.h new file mode 100644 index 000000000..095abc99e --- /dev/null +++ b/Tools/AnvilStats/Utils.h @@ -0,0 +1,61 @@ + +// Utils.h + +// Interfaces to utility functions + + + + + +#pragma once + + + + + +enum eEntityType +{ + entBat, + entBlaze, + entCaveSpider, + entChicken, + entCow, + entCreeper, + entEnderDragon, + entEnderman, + entGhast, + entGiant, + entLavaSlime, + entMushroomCow, + entOzelot, + entPig, + entPigZombie, + entSheep, + entSilverfish, + entSkeleton, + entSlime, + entSnowMan, + entSpider, + entSquid, + entVillager, + entVillagerGolem, + entWitch, + entWitherBoss, + entWolf, + entZombie, + entUnknown, + entMax = entUnknown, +} ; + + + + + +extern const char * GetBiomeString(unsigned char a_Biome); +extern const char * GetBlockTypeString(unsigned char a_BlockType); +extern eEntityType GetEntityType(const AString & a_EntityTypeString); +extern const char * GetEntityTypeString(eEntityType a_EntityType); +extern int GetNumCores(void); + + + diff --git a/Tools/AnvilStats/profile_run.cmd b/Tools/AnvilStats/profile_run.cmd new file mode 100644 index 000000000..2a9285614 --- /dev/null +++ b/Tools/AnvilStats/profile_run.cmd @@ -0,0 +1,70 @@ +@echo off +:: +:: Profiling using a MSVC standalone profiler +:: +:: See http://www.codeproject.com/Articles/144643/Profiling-of-C-Applications-in-Visual-Studio-for-F for details +:: + + + + +set pt="C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Performance Tools" +set appdir="Release profiled" +set app="Release profiled\AnvilStats.exe" +set args="0 c:\Games\MLG\world\region" + +:: outputdir is relative to appdir! +set outputdir=Profiling +set output=profile.vsp + + + + + +::Create the output directory, if it didn't exist +mkdir %outputdir% + + + + + +:: Start the profiler +%pt%\vsperfcmd /start:sample /output:%outputdir%\%output% +if errorlevel 1 goto haderror + +:: Launch the application via the profiler +%pt%\vsperfcmd /launch:%app% /args:%args% +if errorlevel 1 goto haderror + +:: Shut down the profiler (this command waits, until the application is terminated) +%pt%\vsperfcmd /shutdown +if errorlevel 1 goto haderror + + + + + +:: cd to outputdir, so that the reports are generated there +cd %outputdir% + +:: generate the report files (.csv) +%pt%\vsperfreport /summary:all %output% /symbolpath:"srv*C:\Programovani\Symbols*http://msdl.microsoft.com/download/symbols" +if errorlevel 1 goto haderror + + + + + +goto finished + + + + +:haderror +echo An error was encountered +pause + + + + +:finished -- cgit v1.2.3