summaryrefslogtreecommitdiffstats
path: root/Tools
diff options
context:
space:
mode:
authormadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2013-07-06 21:56:03 +0200
committermadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2013-07-06 21:56:03 +0200
commit8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d (patch)
tree786de8ccf882511206a8113eb3cad5b7e19a80b3 /Tools
parentProtoProxy: moved into the Tools folder (diff)
downloadcuberite-8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d.tar
cuberite-8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d.tar.gz
cuberite-8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d.tar.bz2
cuberite-8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d.tar.lz
cuberite-8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d.tar.xz
cuberite-8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d.tar.zst
cuberite-8f18510dec4eab5f00e0ff311cf31ae2ce4f2d4d.zip
Diffstat (limited to 'Tools')
-rw-r--r--Tools/AnvilStats/AnvilStats.cpp69
-rw-r--r--Tools/AnvilStats/AnvilStats.sln34
-rw-r--r--Tools/AnvilStats/AnvilStats.txt27
-rw-r--r--Tools/AnvilStats/AnvilStats.vcproj452
-rw-r--r--Tools/AnvilStats/BiomeMap.cpp172
-rw-r--r--Tools/AnvilStats/BiomeMap.h69
-rw-r--r--Tools/AnvilStats/Callback.h165
-rw-r--r--Tools/AnvilStats/ChunkExtract.cpp104
-rw-r--r--Tools/AnvilStats/ChunkExtract.h66
-rw-r--r--Tools/AnvilStats/Globals.cpp10
-rw-r--r--Tools/AnvilStats/Globals.h228
-rw-r--r--Tools/AnvilStats/HeightMap.cpp265
-rw-r--r--Tools/AnvilStats/HeightMap.h81
-rw-r--r--Tools/AnvilStats/Processor.cpp579
-rw-r--r--Tools/AnvilStats/Processor.h77
-rw-r--r--Tools/AnvilStats/SpringStats.cpp279
-rw-r--r--Tools/AnvilStats/SpringStats.h102
-rw-r--r--Tools/AnvilStats/Statistics.cpp523
-rw-r--r--Tools/AnvilStats/Statistics.h138
-rw-r--r--Tools/AnvilStats/Utils.cpp291
-rw-r--r--Tools/AnvilStats/Utils.h61
-rw-r--r--Tools/AnvilStats/profile_run.cmd70
22 files changed, 3862 insertions, 0 deletions
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 <method number> [<world folder>]", 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 @@
+<?xml version="1.0" encoding="windows-1250"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9,00"
+ Name="AnvilStats"
+ ProjectGUID="{CF996A5E-0A86-4004-9710-682B06B5AEBA}"
+ RootNamespace="AnvilStats"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="196613"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="&quot;..\..\zlib-1.2.7&quot;"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="Globals.h"
+ WarningLevel="3"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ StackReserveSize="16777216"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="2"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories="&quot;..\..\zlib-1.2.7&quot;"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="0"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="Globals.h"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="1"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ StackReserveSize="16777216"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release profiled|Win32"
+ OutputDirectory="$(SolutionDir)$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ CharacterSet="2"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ EnableIntrinsicFunctions="true"
+ AdditionalIncludeDirectories="&quot;..\..\zlib-1.2.7&quot;"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="0"
+ EnableFunctionLevelLinking="true"
+ UsePrecompiledHeader="2"
+ PrecompiledHeaderThrough="Globals.h"
+ WarningLevel="3"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalDependencies="ws2_32.lib"
+ LinkIncremental="1"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ StackReserveSize="16777216"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ Profile="true"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\AnvilStats.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\BiomeMap.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\BiomeMap.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Callback.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ChunkExtract.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\ChunkExtract.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Globals.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release profiled|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\Globals.h"
+ >
+ </File>
+ <File
+ RelativePath=".\HeightMap.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\HeightMap.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Processor.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Processor.h"
+ >
+ </File>
+ <File
+ RelativePath=".\SpringStats.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\SpringStats.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Statistics.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Statistics.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Utils.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Utils.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="shared"
+ >
+ <File
+ RelativePath="..\..\source\OSSupport\CriticalSection.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\CriticalSection.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\Endianness.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\Event.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\Event.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\WorldStorage\FastNBT.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="NBT_RESERVE_SIZE=10000"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="NBT_RESERVE_SIZE=10000"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release profiled|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="NBT_RESERVE_SIZE=10000"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\..\source\WorldStorage\FastNBT.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\File.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\File.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\GZipFile.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\GZipFile.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\IsThread.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\OSSupport\IsThread.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\StringUtils.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\source\StringUtils.h"
+ >
+ </File>
+ </Filter>
+ <File
+ RelativePath=".\AnvilStats.txt"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
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<cCallback *> 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 <Windows.h>
+ #include <winsock2.h>
+
+ // 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 <sys/types.h>
+ #include <sys/stat.h> // for mkdir
+ #include <sys/time.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+ #include <arpa/inet.h>
+ #include <netdb.h>
+ #include <time.h>
+ #include <dirent.h>
+ #include <errno.h>
+ #include <iostream>
+
+ #include <cstdio>
+ #include <cstring>
+ #include <pthread.h>
+ #include <semaphore.h>
+ #include <errno.h>
+ #include <fcntl.h>
+#endif
+
+
+
+
+
+#define FILE_IO_PREFIX ""
+
+
+
+
+
+// CRT stuff:
+#include <assert.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdarg.h>
+
+
+
+
+
+// STL stuff:
+#include <vector>
+#include <list>
+#include <deque>
+#include <string>
+#include <map>
+#include <algorithm>
+#include <memory>
+#include <ctime>
+
+
+
+
+
+// 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 <typename Type> 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<cThread *> 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