1
0
cuberite-2a/src/Generating/Prefab.cpp
Mattes D 01b8ed5295
Pulled the BlockID and BlockInfo headers from Globals.h. (#4591)
The BlockID.h file was removed from Globals.h and renamed to BlockType.h (main change)
The BlockInfo.h file was removed from Globals.h (main change)
The ENUM_BLOCK_ID and ENUM_ITEM_ID enum names were replaced with ENUM_BLOCK_TYPE and ENUM_ITEM_TYPE (cosmetics)
The various enums, such as eDimension, eDamageType and eExplosionSource were moved from BlockType.h to Defines.h, together with the helper functions for converting between them and strings (StringToDimension et al.) (minor)
Many inline functions were moved from headers to their respective cpp files, so that BlockType.h could be included only into the cpp file, rather than the header.
That broke our tests a bit, since they pick bits and pieces out of the main code and provide stubs for the rest; they had to be re-stubbed and re-verified.
eMonsterType values are no longer tied to E_ITEM_SPAWN_EGG_META_* values
2020-04-03 08:57:01 +02:00

515 lines
12 KiB
C++

// Prefab.cpp
/*
Implements the cPrefab class, representing a cPiece descendant for the cPieceGenerator that
uses a prefabricate in a cBlockArea for drawing itself.
*/
#include "Globals.h"
#include "Prefab.h"
#include "../WorldStorage/SchematicFileSerializer.h"
#include "ChunkDesc.h"
#include "../BlockInfo.h"
cPrefab::cPrefab(const cPrefab::sDef & a_Def) :
m_Size(a_Def.m_SizeX, a_Def.m_SizeY, a_Def.m_SizeZ),
m_HitBox(
{a_Def.m_HitboxMinX, a_Def.m_HitboxMinY, a_Def.m_HitboxMinZ},
{a_Def.m_HitboxMaxX, a_Def.m_HitboxMaxY, a_Def.m_HitboxMaxZ}
),
m_AllowedRotations(a_Def.m_AllowedRotations),
m_MergeStrategy(a_Def.m_MergeStrategy),
m_ExtendFloorStrategy(a_Def.m_ExtendFloorStrategy),
m_DefaultWeight(a_Def.m_DefaultWeight),
m_AddWeightIfSame(a_Def.m_AddWeightIfSame),
m_MoveToGround(a_Def.m_MoveToGround)
{
m_BlockArea[0].Create(m_Size);
CharMap cm;
ParseCharMap(cm, a_Def.m_CharMap);
ParseBlockImage(cm, a_Def.m_Image);
ParseConnectors(a_Def.m_Connectors);
ParseDepthWeight(a_Def.m_DepthWeight);
AddRotatedBlockAreas();
}
cPrefab::cPrefab(const cBlockArea & a_Image, int a_AllowedRotations) :
m_Size(a_Image.GetSize()),
m_AllowedRotations(a_AllowedRotations),
m_MergeStrategy(cBlockArea::msOverwrite),
m_ExtendFloorStrategy(efsNone),
m_DefaultWeight(1),
m_AddWeightIfSame(0),
m_MoveToGround(false)
{
m_HitBox.p1.Set(0, 0, 0);
m_HitBox.p2.Set(m_Size.x - 1, m_Size.y - 1, m_Size.z - 1);
m_BlockArea[0].CopyFrom(a_Image);
AddRotatedBlockAreas();
}
cPrefab::cPrefab(const cBlockArea & a_Image) :
m_Size(a_Image.GetSize()),
m_AllowedRotations(0),
m_MergeStrategy(cBlockArea::msOverwrite),
m_ExtendFloorStrategy(efsNone),
m_DefaultWeight(1),
m_AddWeightIfSame(0),
m_MoveToGround(false)
{
m_HitBox.p1.Set(0, 0, 0);
m_HitBox.p2.Set(m_Size.x - 1, m_Size.y - 1, m_Size.z - 1);
m_BlockArea[0].CopyFrom(a_Image);
}
cPrefab::cPrefab(const AString & a_BlockDefinitions, const AString & a_BlockData, int a_SizeX, int a_SizeY, int a_SizeZ) :
m_Size(a_SizeX, a_SizeY, a_SizeZ),
m_AllowedRotations(0),
m_MergeStrategy(cBlockArea::msOverwrite),
m_ExtendFloorStrategy(efsNone),
m_DefaultWeight(1),
m_AddWeightIfSame(0),
m_MoveToGround(false)
{
m_HitBox.p1.Set(0, 0, 0);
m_HitBox.p2.Set(m_Size.x - 1, m_Size.y - 1, m_Size.z - 1);
m_BlockArea[0].Create(m_Size);
CharMap cm;
ParseCharMap(cm, a_BlockDefinitions.c_str());
ParseBlockImage(cm, a_BlockData.c_str());
}
void cPrefab::AddRotatedBlockAreas(void)
{
// 1 CCW rotation:
if ((m_AllowedRotations & 0x01) != 0)
{
m_BlockArea[1].CopyFrom(m_BlockArea[0]);
m_BlockArea[1].RotateCCW();
}
// 2 rotations are the same as mirroring twice; mirroring is faster because it has no reallocations
if ((m_AllowedRotations & 0x02) != 0)
{
m_BlockArea[2].CopyFrom(m_BlockArea[0]);
m_BlockArea[2].MirrorXY();
m_BlockArea[2].MirrorYZ();
}
// 3 CCW rotations = 1 CW rotation:
if ((m_AllowedRotations & 0x04) != 0)
{
m_BlockArea[3].CopyFrom(m_BlockArea[0]);
m_BlockArea[3].RotateCW();
}
}
void cPrefab::Draw(cChunkDesc & a_Dest, const cPlacedPiece * a_Placement) const
{
Draw(a_Dest, a_Placement->GetCoords(), a_Placement->GetNumCCWRotations());
}
void cPrefab::Draw(cChunkDesc & a_Dest, const Vector3i & a_Placement, int a_NumRotations) const
{
// Draw the basic image:
Vector3i Placement(a_Placement);
int ChunkStartX = a_Dest.GetChunkX() * cChunkDef::Width;
int ChunkStartZ = a_Dest.GetChunkZ() * cChunkDef::Width;
Placement.Move(-ChunkStartX, 0, -ChunkStartZ);
const cBlockArea & Image = m_BlockArea[a_NumRotations];
// If the placement is outside this chunk, bail out:
if (
(Placement.x > cChunkDef::Width) || (Placement.x + Image.GetSizeX() < 0) ||
(Placement.z > cChunkDef::Width) || (Placement.z + Image.GetSizeZ() < 0)
)
{
return;
}
// Write the image:
a_Dest.WriteBlockArea(Image, Placement.x, Placement.y, Placement.z, m_MergeStrategy);
// If requested, draw the floor (from the bottom of the prefab down to the nearest non-air)
switch (m_ExtendFloorStrategy)
{
case efsNone: break; // Nothing needed
case efsRepeatBottomTillNonAir:
{
int MaxX = Image.GetSizeX();
int MaxZ = Image.GetSizeZ();
for (int z = 0; z < MaxZ; z++)
{
int RelZ = Placement.z + z;
if ((RelZ < 0) || (RelZ >= cChunkDef::Width))
{
// Z coord outside the chunk
continue;
}
for (int x = 0; x < MaxX; x++)
{
int RelX = Placement.x + x;
if ((RelX < 0) || (RelX >= cChunkDef::Width))
{
// X coord outside the chunk
continue;
}
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
Image.GetRelBlockTypeMeta(x, 0, z, BlockType, BlockMeta);
if ((BlockType == E_BLOCK_AIR) || (BlockType == E_BLOCK_SPONGE))
{
// Do not expand air nor sponge blocks
continue;
}
for (int y = Placement.y - 1; y >= 0; y--)
{
BLOCKTYPE ExistingBlock = a_Dest.GetBlockType(RelX, y, RelZ);
if (ExistingBlock != E_BLOCK_AIR)
{
// End the expansion for this column, reached the end
break;
}
a_Dest.SetBlockTypeMeta(RelX, y, RelZ, BlockType, BlockMeta);
} // for y
} // for x
} // for z
break;
} // efsRepeatBottomTillNonAir
case efsRepeatBottomTillSolid:
{
int MaxX = Image.GetSizeX();
int MaxZ = Image.GetSizeZ();
for (int z = 0; z < MaxZ; z++)
{
int RelZ = Placement.z + z;
if ((RelZ < 0) || (RelZ >= cChunkDef::Width))
{
// Z coord outside the chunk
continue;
}
for (int x = 0; x < MaxX; x++)
{
int RelX = Placement.x + x;
if ((RelX < 0) || (RelX >= cChunkDef::Width))
{
// X coord outside the chunk
continue;
}
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
Image.GetRelBlockTypeMeta(x, 0, z, BlockType, BlockMeta);
if ((BlockType == E_BLOCK_AIR) || (BlockType == E_BLOCK_SPONGE))
{
// Do not expand air nor sponge blocks
continue;
}
for (int y = Placement.y - 1; y >= 0; y--)
{
BLOCKTYPE ExistingBlock = a_Dest.GetBlockType(RelX, y, RelZ);
if (cBlockInfo::IsSolid(ExistingBlock))
{
// End the expansion for this column, reached the end
break;
}
a_Dest.SetBlockTypeMeta(RelX, y, RelZ, BlockType, BlockMeta);
} // for y
} // for x
} // for z
break;
} // efsRepeatBottomTillSolid
}
}
bool cPrefab::HasConnectorType(int a_ConnectorType) const
{
for (cConnectors::const_iterator itr = m_Connectors.begin(), end = m_Connectors.end(); itr != end; ++itr)
{
if (itr->m_Type == a_ConnectorType)
{
return true;
}
} // for itr - m_Connectors[]
return false;
}
int cPrefab::GetPieceWeight(const cPlacedPiece & a_PlacedPiece, const cPiece::cConnector & a_ExistingConnector) const
{
// Use the default or per-depth weight:
cDepthWeight::const_iterator itr = m_DepthWeight.find(a_PlacedPiece.GetDepth() + 1);
int res = (itr == m_DepthWeight.end()) ? m_DefaultWeight : itr->second;
// If the piece is the same as the parent, apply the m_AddWeightIfSame modifier:
const cPiece * ParentPiece = &a_PlacedPiece.GetPiece();
const cPiece * ThisPiece = this;
if (ThisPiece == ParentPiece)
{
res += m_AddWeightIfSame;
}
return res;
}
void cPrefab::SetDefaultWeight(int a_DefaultWeight)
{
m_DefaultWeight = a_DefaultWeight;
}
void cPrefab::AddConnector(int a_RelX, int a_RelY, int a_RelZ, cPiece::cConnector::eDirection a_Direction, int a_Type)
{
m_Connectors.push_back(cConnector(a_RelX, a_RelY, a_RelZ, a_Type, a_Direction));
}
void cPrefab::SetAllowedRotations(int a_AllowedRotations)
{
m_AllowedRotations = a_AllowedRotations;
AddRotatedBlockAreas();
}
void cPrefab::ParseCharMap(CharMap & a_CharMapOut, const char * a_CharMapDef)
{
ASSERT(a_CharMapDef != nullptr);
// Initialize the charmap to all-invalid values:
for (size_t i = 0; i < ARRAYCOUNT(a_CharMapOut); i++)
{
a_CharMapOut[i].m_BlockType = 0;
a_CharMapOut[i].m_BlockMeta = 16; // Mark unassigned entries with a meta that is impossible otherwise
}
// Process the lines in the definition:
AStringVector Lines = StringSplitAndTrim(a_CharMapDef, "\n");
for (AStringVector::const_iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr)
{
AStringVector CharDef = StringSplitAndTrim(*itr, ":");
size_t NumElements = CharDef.size();
if ((NumElements < 2) || CharDef[0].empty() || CharDef[1].empty())
{
LOGWARNING("Bad prefab CharMap definition line: \"%s\", skipping.", itr->c_str());
continue;
}
unsigned char Src = static_cast<unsigned char>(CharDef[0][0]);
ASSERT(a_CharMapOut[Src].m_BlockMeta == 16); // This letter has not been assigned yet?
a_CharMapOut[Src].m_BlockType = static_cast<BLOCKTYPE>(atoi(CharDef[1].c_str()));
NIBBLETYPE BlockMeta = 0;
if ((NumElements >= 3) && !CharDef[2].empty())
{
BlockMeta = static_cast<NIBBLETYPE>(atoi(CharDef[2].c_str()));
ASSERT((BlockMeta <= 15));
}
a_CharMapOut[Src].m_BlockMeta = BlockMeta;
} // for itr - Lines[]
}
void cPrefab::ParseBlockImage(const CharMap & a_CharMap, const char * a_BlockImage)
{
// Map each letter in the a_BlockImage (from the in-source definition) to real blocktype / blockmeta:
for (int y = 0; y < m_Size.y; y++)
{
for (int z = 0; z < m_Size.z; z++)
{
const unsigned char * BlockImage = reinterpret_cast<const unsigned char *>(a_BlockImage + y * m_Size.x * m_Size.z + z * m_Size.x);
for (int x = 0; x < m_Size.x; x++)
{
const sBlockTypeDef & MappedValue = a_CharMap[BlockImage[x]];
ASSERT(MappedValue.m_BlockMeta != 16); // Using a letter not defined in the CharMap?
m_BlockArea[0].SetRelBlockTypeMeta(x, y, z, MappedValue.m_BlockType, MappedValue.m_BlockMeta);
}
}
}
}
void cPrefab::ParseConnectors(const char * a_ConnectorsDef)
{
ASSERT(a_ConnectorsDef != nullptr);
AStringVector Lines = StringSplitAndTrim(a_ConnectorsDef, "\n");
for (AStringVector::const_iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr)
{
if (itr->empty())
{
continue;
}
// Split into components: "Type: X, Y, Z: Direction":
AStringVector Defs = StringSplitAndTrim(*itr, ":");
if (Defs.size() != 3)
{
LOGWARNING("Bad prefab Connector definition line: \"%s\", skipping.", itr->c_str());
continue;
}
AStringVector Coords = StringSplitAndTrim(Defs[1], ",");
if (Coords.size() != 3)
{
LOGWARNING("Bad prefab Connector coords definition: \"%s\", skipping.", Defs[1].c_str());
continue;
}
// Check that the Direction is valid:
cPiece::cConnector::eDirection Direction;
if (!cPiece::cConnector::StringToDirection(Defs[2], Direction))
{
LOGWARNING("Bad prefab Connector direction: \"%s\", skipping.", Defs[2].c_str());
continue;
}
// Add the connector:
m_Connectors.push_back(cPiece::cConnector(
atoi(Coords[0].c_str()), atoi(Coords[1].c_str()), atoi(Coords[2].c_str()), // Connector pos
atoi(Defs[0].c_str()), // Connector type
Direction
));
} // for itr - Lines[]
}
void cPrefab::ParseDepthWeight(const char * a_DepthWeightDef)
{
// The member needn't be defined at all, if so, skip:
if (a_DepthWeightDef == nullptr)
{
return;
}
// Split into individual records: "Record | Record | Record"
AStringVector Defs = StringSplitAndTrim(a_DepthWeightDef, "|");
// Add each record's contents:
for (AStringVector::const_iterator itr = Defs.begin(), end = Defs.end(); itr != end; ++itr)
{
// Split into components: "Depth : Weight"
AStringVector Components = StringSplitAndTrim(*itr, ":");
if (Components.size() != 2)
{
LOGWARNING("Bad prefab DepthWeight record: \"%s\", skipping.", itr->c_str());
continue;
}
// Parse depth:
int Depth = atoi(Components[0].c_str());
if ((Depth == 0) && (Components[0] != "0"))
{
LOGWARNING("Bad prefab DepthWeight record, cannot parse depth \"%s\", skipping.", Components[0].c_str());
continue;
}
// Parse weight:
int Weight = atoi(Components[1].c_str());
if ((Weight == 0) && (Components[1] != "0"))
{
LOGWARNING("Bad prefab DepthWeight record, cannot parse weight \"%s\", skipping.", Components[1].c_str());
continue;
}
// Save to map:
ASSERT(m_DepthWeight.find(Depth) == m_DepthWeight.end()); // Not a duplicate
m_DepthWeight[Depth] = Weight;
} // for itr - Defs[]
}
cPiece::cConnectors cPrefab::GetConnectors(void) const
{
return m_Connectors;
}
Vector3i cPrefab::GetSize(void) const
{
return m_Size;
}
cCuboid cPrefab::GetHitBox(void) const
{
return m_HitBox;
}
bool cPrefab::CanRotateCCW(int a_NumRotations) const
{
// Either zero rotations
// Or the proper bit in m_AllowedRotations is set
return (a_NumRotations == 0) || ((m_AllowedRotations & (1 << ((a_NumRotations + 3) % 4))) != 0);
}