Commit is being made to allow additions of GPL3+ code previously un-addable. With these changes, contributions back to cuberite are possible with the backporting exemtion, as well as adding stuff in minetest with minetest code properly being read through and implimented to upgrade it to GPL3 from GPL2. project still has Apache2.0 license and credits to all its contributers, but now has the freedom of GPL3+ and all the code that can be implimented and shared with it.
653 lines
15 KiB
C++
653 lines
15 KiB
C++
|
|
/*
|
|
* Copyright 2011-2022 Cuberite Contributors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// Statistics.cpp
|
|
|
|
// Implements the various statistics-collecting classes
|
|
|
|
#include "Globals.h"
|
|
#include "Statistics.h"
|
|
#include "../../src/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_PerHeightBlockCounts, 0, sizeof(m_PerHeightBlockCounts));
|
|
memset(m_PerHeightSpawners, 0, sizeof(m_PerHeightSpawners));
|
|
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];
|
|
m_PerHeightBlockCounts[i][j] += a_Stats.m_PerHeightBlockCounts[i][j];
|
|
}
|
|
for (int j = 0; j < ARRAYCOUNT(m_PerHeightSpawners[0]); j++)
|
|
{
|
|
m_PerHeightSpawners[i][j] += a_Stats.m_PerHeightSpawners[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++)
|
|
{
|
|
int Height = (int)a_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_PerHeightBlockCounts[Height][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)
|
|
{
|
|
// Get the spawned entity type:
|
|
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))
|
|
{
|
|
return;
|
|
}
|
|
m_Stats.m_SpawnerEntity[Ent] += 1;
|
|
|
|
// Get the spawner pos:
|
|
int PosYTag = a_NBT.FindChildByName(a_TileEntityTag, "y");
|
|
if ((PosYTag < 0) || (a_NBT.GetType(PosYTag) != TAG_Int))
|
|
{
|
|
return;
|
|
}
|
|
int BlockY = Clamp(a_NBT.GetInt(PosYTag), 0, 255);
|
|
m_Stats.m_PerHeightSpawners[BlockY][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(" PerHeightBlockTypes.xls");
|
|
SavePerHeightBlockTypes();
|
|
LOG(" BiomeBlockTypes.xls");
|
|
SaveBiomeBlockTypes();
|
|
LOG(" Spawners.xls");
|
|
SaveSpawners();
|
|
LOG(" PerHeightSpawners.xls");
|
|
SavePerHeightSpawners();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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::SavePerHeightBlockTypes(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("PerHeightBlockTypes.xls", cFile::fmWrite))
|
|
{
|
|
LOG("Cannot write to file PerHeightBlockTypes.xls. Statistics not written.");
|
|
return;
|
|
}
|
|
|
|
// Write header:
|
|
f.Printf("Blocks 0 - 127:\nHeight");
|
|
for (int i = 0; i < 128; i++)
|
|
{
|
|
f.Printf("\t%s(%d)", GetBlockTypeString(i), i);
|
|
}
|
|
f.Printf("\n");
|
|
|
|
// Write first half:
|
|
for (int y = 0; y < 256; y++)
|
|
{
|
|
f.Printf("%d", y);
|
|
for (int BlockType = 0; BlockType < 128; BlockType++)
|
|
{
|
|
f.Printf("\t%llu", m_CombinedStats.m_PerHeightBlockCounts[y][BlockType]);
|
|
} // for BlockType
|
|
f.Printf("\n");
|
|
} // for y - height (0 - 127)
|
|
f.Printf("\n");
|
|
|
|
// Write second header:
|
|
f.Printf("Blocks 128 - 255:\nHeight");
|
|
for (int i = 128; i < 256; i++)
|
|
{
|
|
f.Printf("\t%s(%d)", GetBlockTypeString(i), i);
|
|
}
|
|
f.Printf("\n");
|
|
|
|
// Write second half:
|
|
for (int y = 0; y < 256; y++)
|
|
{
|
|
f.Printf("%d", y);
|
|
for (int BlockType = 128; BlockType < 256; BlockType++)
|
|
{
|
|
f.Printf("\t%llu", m_CombinedStats.m_PerHeightBlockCounts[y][BlockType]);
|
|
} // for BlockType
|
|
f.Printf("\n");
|
|
} // for y - height (0 - 127)
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cStatisticsFactory::SavePerHeightSpawners(void)
|
|
{
|
|
cFile f;
|
|
if (!f.Open("PerHeightSpawners.xls", cFile::fmWrite))
|
|
{
|
|
LOG("Cannot write to file PerHeightSpawners.xls. Statistics not written.");
|
|
return;
|
|
}
|
|
|
|
// Write header:
|
|
f.Printf("Height\tTotal");
|
|
for (int i = 0; i < entMax; i++)
|
|
{
|
|
f.Printf("\t%s", GetEntityTypeString((eEntityType)i));
|
|
}
|
|
f.Printf("\n");
|
|
|
|
// Write individual lines:
|
|
for (int y = 0; y < 256; y++)
|
|
{
|
|
UInt64 Total = 0;
|
|
for (int i = 0; i < entMax; i++)
|
|
{
|
|
Total += m_CombinedStats.m_PerHeightSpawners[y][i];
|
|
}
|
|
f.Printf("%d\t%llu", y, Total);
|
|
for (int i = 0; i < entMax; i++)
|
|
{
|
|
f.Printf("\t%llu", m_CombinedStats.m_PerHeightSpawners[y][i]);
|
|
}
|
|
f.Printf("\n");
|
|
}
|
|
}
|