Obsidian pillars, end fountain, not Ender dragon spawning (#4993)
* added generator for obsidian pillars and central fountain which then is used for the exit portal * checkstyle * checkstyle the second * fixed clang removed magic number added Clamp * make the pillars configurable * fixed clang added warning if there was a unknown value if the tower should have a cage or not * forgot to cancel on unkwon value * fixed clang this time maybe * added new generator to generator test * fixed test * added prefab generation for end fountain * fixed checkstyle and updated the prefab * added ender dragon spawning made the fountain positioning dynamic removed fountain placement functions * added enderdragon stuff to testing * pls compile * added changes suggested by @peterbell10 * fixed clang * added debug for further research on the ARM build * ok - it wasn't my tower placement * checking in setup * readded the fountain schematic * removed finisher * readded generator * removed generator trigger - kept ini file access * using cChunkDef function to calculate abs pos of endercrystal * yes, I know it's unused... * commented everything in the ComposableGenerator.cpp - so only the new class in compiled in but not called at all * don't compile in the new generator at all (removed from CMakeLists.txt) * readded the new generator * readded the new generator * removed debug output * made the towers generate acrocc chunk borders * fixed bad merge * fixed clang * fixed clang * generate the dragon 20 blocks above terrain * trying to fixed weird undefined reference * maybe this fixes the weird behaviour * takes chunk width as parameter now * added new comments with info to generated structures removed ender dragon spawning removed chunkwidth from parameter * fixed linker * maybe fixed linking. tried with gc and clang * fixed ender crystal * fixed test * updated output strings * fixed build * fixed up test * fixed test compile * fixed test - cant get the tests to show up * removed the semicolon * maybe this is the fix? * at this point i have no idea - in MVSC it works * removed the ender dragon Co-authored-by: 12xx12 <12xx12100@gmail.com>
This commit is contained in:
parent
b2eddbed84
commit
d78bcac150
BIN
Server/Prefabs/SinglePieceStructures/EndFountain.schematic
Normal file
BIN
Server/Prefabs/SinglePieceStructures/EndFountain.schematic
Normal file
Binary file not shown.
@ -11,6 +11,7 @@ target_sources(
|
|||||||
DistortedHeightmap.cpp
|
DistortedHeightmap.cpp
|
||||||
DungeonRoomsFinisher.cpp
|
DungeonRoomsFinisher.cpp
|
||||||
EndGen.cpp
|
EndGen.cpp
|
||||||
|
EnderDragonFightStructuresGen.cpp
|
||||||
FinishGen.cpp
|
FinishGen.cpp
|
||||||
GridStructGen.cpp
|
GridStructGen.cpp
|
||||||
HeiGen.cpp
|
HeiGen.cpp
|
||||||
@ -43,6 +44,7 @@ target_sources(
|
|||||||
DistortedHeightmap.h
|
DistortedHeightmap.h
|
||||||
DungeonRoomsFinisher.h
|
DungeonRoomsFinisher.h
|
||||||
EndGen.h
|
EndGen.h
|
||||||
|
EnderDragonFightStructuresGen.h
|
||||||
FinishGen.h
|
FinishGen.h
|
||||||
GridStructGen.h
|
GridStructGen.h
|
||||||
HeiGen.h
|
HeiGen.h
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "DistortedHeightmap.h"
|
#include "DistortedHeightmap.h"
|
||||||
#include "DungeonRoomsFinisher.h"
|
#include "DungeonRoomsFinisher.h"
|
||||||
#include "EndGen.h"
|
#include "EndGen.h"
|
||||||
|
#include "EnderDragonFightStructuresGen.h"
|
||||||
#include "MineShafts.h"
|
#include "MineShafts.h"
|
||||||
#include "Noise3DGenerator.h"
|
#include "Noise3DGenerator.h"
|
||||||
#include "Ravines.h"
|
#include "Ravines.h"
|
||||||
@ -262,7 +263,7 @@ void cComposableGenerator::InitializeGeneratorDefaults(cIniFile & a_IniFile, eDi
|
|||||||
a_IniFile.GetValueSet("Generator", "ConstantBiome", "End");
|
a_IniFile.GetValueSet("Generator", "ConstantBiome", "End");
|
||||||
a_IniFile.GetValueSet("Generator", "ShapeGen", "End");
|
a_IniFile.GetValueSet("Generator", "ShapeGen", "End");
|
||||||
a_IniFile.GetValueSet("Generator", "CompositionGen", "End");
|
a_IniFile.GetValueSet("Generator", "CompositionGen", "End");
|
||||||
a_IniFile.GetValueSet("Generator", "Finishers", "");
|
a_IniFile.GetValueSet("Generator", "Finishers", "EnderDragonFightStructures");
|
||||||
break;
|
break;
|
||||||
} // dimEnd
|
} // dimEnd
|
||||||
|
|
||||||
@ -450,6 +451,19 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile)
|
|||||||
AString HeightDistrib = a_IniFile.GetValueSet ("Generator", "DungeonRoomsHeightDistrib", "0, 0; 10, 10; 11, 500; 40, 500; 60, 40; 90, 1");
|
AString HeightDistrib = a_IniFile.GetValueSet ("Generator", "DungeonRoomsHeightDistrib", "0, 0; 10, 10; 11, 500; 40, 500; 60, 40; 90, 1");
|
||||||
m_FinishGens.push_back(std::make_unique<cDungeonRoomsFinisher>(*m_ShapeGen, m_Seed, GridSize, MaxSize, MinSize, HeightDistrib));
|
m_FinishGens.push_back(std::make_unique<cDungeonRoomsFinisher>(*m_ShapeGen, m_Seed, GridSize, MaxSize, MinSize, HeightDistrib));
|
||||||
}
|
}
|
||||||
|
else if (NoCaseCompare(finisher, "EnderDragonFightStructures") == 0)
|
||||||
|
{
|
||||||
|
AString Pillars = a_IniFile.GetValueSet("Generator", "ObsidianPillars",
|
||||||
|
"76|3|false; 79|3|true; 82|3|true; "
|
||||||
|
"85|4|false; 88|4|false; 91|4|false; "
|
||||||
|
"94|5|false; 97|5|false; 100|5|false; "
|
||||||
|
"103|6|false");
|
||||||
|
int Radius = a_IniFile.GetValueSetI("Generator", "ObsidianPillarsRadius", 43);
|
||||||
|
// The init method is called manually because the linker can't access the cChunkDef::Width in the constructor
|
||||||
|
auto Gen = new cEnderDragonFightStructuresGen(m_Seed);
|
||||||
|
Gen->Init(Pillars, Radius);
|
||||||
|
m_FinishGens.push_back(cFinishGenPtr(Gen));
|
||||||
|
}
|
||||||
else if (NoCaseCompare(finisher, "GlowStone") == 0)
|
else if (NoCaseCompare(finisher, "GlowStone") == 0)
|
||||||
{
|
{
|
||||||
m_FinishGens.push_back(std::make_unique<cFinishGenGlowStone>(m_Seed));
|
m_FinishGens.push_back(std::make_unique<cFinishGenGlowStone>(m_Seed));
|
||||||
|
285
src/Generating/EnderDragonFightStructuresGen.cpp
Normal file
285
src/Generating/EnderDragonFightStructuresGen.cpp
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
|
||||||
|
#include "Globals.h"
|
||||||
|
|
||||||
|
#include "EnderDragonFightStructuresGen.h"
|
||||||
|
#include "../Chunk.h"
|
||||||
|
#include "../Entities/EnderCrystal.h"
|
||||||
|
#include "../WorldStorage/SchematicFileSerializer.h"
|
||||||
|
|
||||||
|
const std::array<Vector3i, 48> cEnderDragonFightStructuresGen::m_CagePos =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// First layer
|
||||||
|
{-2, -1, -2}, {-2, -1, -1}, {-2, -1, 0}, {-2, -1, 1}, {-2, -1, 2},
|
||||||
|
{2, -1, -2}, {2, -1, -1}, {2, -1, 0}, {2, -1, 1}, {2, -1, 2},
|
||||||
|
{-1, -1, -2}, {0, -1, -2}, {1, -1, -2},
|
||||||
|
{-1, -1, 2}, {0, -1, 2}, {1, -1, 2},
|
||||||
|
|
||||||
|
// Second layer
|
||||||
|
{-2, 0, -2}, {-2, 0, -1}, {-2, 0, 0}, {-2, 0, 1}, {-2, 0, 2},
|
||||||
|
{2, 0, -2}, {2, 0, -1}, {2, 0, 0}, {2, 0, 1}, {2, 0, 2},
|
||||||
|
{-1, 0, -2}, {0, 0, -2}, {1, 0, -2},
|
||||||
|
{-1, 0, 2}, {0, 0, 2}, {1, 0, 2},
|
||||||
|
|
||||||
|
// Third layer
|
||||||
|
{-2, 1, -2}, {-2, 1, -1}, {-2, 1, 0}, {-2, 1, 1}, {-2, 1, 2},
|
||||||
|
{2, 1, -2}, {2, 1, -1}, {2, 1, 0}, {2, 1, 1}, {2, 1, 2},
|
||||||
|
{-1, 1, -2}, {0, 1, -2}, {1, 1, -2},
|
||||||
|
{-1, 1, 2}, {0, 1, 2}, {1, 1, 2},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const std::array<Vector3i, 26> cEnderDragonFightStructuresGen::m_CageAir =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// First layer
|
||||||
|
{-1, -1, -1}, {0, -1, -1}, {1, -1, -1},
|
||||||
|
{-1, -1, 1}, {0, -1, 1}, {1, -1, 1},
|
||||||
|
{-1, -1, 0}, {1, -1, 0},
|
||||||
|
|
||||||
|
// Second layer
|
||||||
|
{-1, 0, -1}, {0, 0, -1}, {1, 0, -1},
|
||||||
|
{-1, 0, 1}, {0, 0, 1}, {1, 0, 1},
|
||||||
|
{-1, 0, 0}, {1, 0, 0}, {0, 0, 0},
|
||||||
|
|
||||||
|
// Third layer
|
||||||
|
{-1, 1, -1}, {0, 1, -1}, {1, 1, -1},
|
||||||
|
{-1, 1, 1}, {0, 1, 1}, {1, 1, 1},
|
||||||
|
{-1, 1, 0}, {1, 1, 0}, {0, 1, 0},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cEnderDragonFightStructuresGen::cEnderDragonFightStructuresGen(int a_Seed) :
|
||||||
|
m_Noise(a_Seed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEnderDragonFightStructuresGen::Init(const AString & a_TowerProperties, int a_Radius)
|
||||||
|
{
|
||||||
|
const auto ChunkWidth = cChunkDef::Width;
|
||||||
|
// Loads the fountain schematic
|
||||||
|
if (!cFile::IsFile(AString("Prefabs") + cFile::GetPathSeparator() + "SinglePieceStructures" + cFile::GetPathSeparator() + "EndFountain.schematic"))
|
||||||
|
{
|
||||||
|
LOGWARNING("EnderDragonFightStructuresGen is missing its end fountain prefab, please update your Cuberite server files! There will be no end fountain!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cSchematicFileSerializer::LoadFromSchematicFile(m_Fountain, AString("Prefabs") + cFile::GetPathSeparator() + "SinglePieceStructures" + cFile::GetPathSeparator() + "EndFountain.schematic");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the given tower properties
|
||||||
|
const auto TowerPropertiesVector = StringSplitAndTrim(a_TowerProperties, ";");
|
||||||
|
std::vector<sTowerProperties> TowerProperties;
|
||||||
|
for (const auto & TowerProperty : TowerPropertiesVector)
|
||||||
|
{
|
||||||
|
const auto TowerPropertyVector = StringSplitAndTrim(TowerProperty, "|");
|
||||||
|
if (TowerPropertyVector.size() != 3)
|
||||||
|
{
|
||||||
|
LOGWARNING("Got unknown parameters on generating obsidian pillars: %s, Please use \"Height|Radius|HasCage\"; ...", TowerProperty);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int Height = std::min(std::stoi(TowerPropertyVector[0]), cChunkDef::Height - 2); // The highest block placed is two blocks above the given height (the cage above some towers)
|
||||||
|
int Radius = std::stoi(TowerPropertyVector[1]);
|
||||||
|
bool HasCage;
|
||||||
|
if (NoCaseCompare(TowerPropertyVector[2], "true") == 0)
|
||||||
|
{
|
||||||
|
HasCage = true;
|
||||||
|
}
|
||||||
|
else if (NoCaseCompare(TowerPropertyVector[2], "false") == 0)
|
||||||
|
{
|
||||||
|
HasCage = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGWARNING("Got unknown value for boolean of the tower: %s should have a cage! %s. Tower will not be generated!", TowerProperty, TowerPropertyVector[2]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TowerProperties.push_back({Vector3d(), Height, Radius, HasCage});
|
||||||
|
}
|
||||||
|
// A random angle in radian
|
||||||
|
double Angle = m_Noise.IntNoise1D(m_Noise.GetSeed()) * M_PI + M_PI;
|
||||||
|
// Shuffles the order of the towers
|
||||||
|
std::shuffle(TowerProperties.begin(), TowerProperties.end(), std::default_random_engine(static_cast<size_t>(m_Noise.GetSeed())));
|
||||||
|
// Generate Positions in a circle
|
||||||
|
for (size_t I = 0; I < TowerProperties.size(); I++)
|
||||||
|
{
|
||||||
|
auto TowerPos = Vector3i(FloorC(a_Radius * cos(Angle)), 0, FloorC(a_Radius * sin(Angle)));
|
||||||
|
TowerProperties[I].m_Pos = TowerPos;
|
||||||
|
|
||||||
|
// Check all crossed chunks
|
||||||
|
for (int X = -TowerProperties[I].m_Radius - ChunkWidth; X <= TowerProperties[I].m_Radius + ChunkWidth; X+=std::min(TowerProperties[I].m_Radius, ChunkWidth))
|
||||||
|
{
|
||||||
|
for (int Z = -TowerProperties[I].m_Radius - ChunkWidth; Z <= TowerProperties[I].m_Radius + ChunkWidth; Z+=std::min(TowerProperties[I].m_Radius, ChunkWidth))
|
||||||
|
{
|
||||||
|
auto Chunk = cChunkDef::BlockToChunk({TowerPos.x + X, 0, TowerPos.z + Z});
|
||||||
|
// Update limits
|
||||||
|
m_MinX = std::min(Chunk.m_ChunkX, m_MinX);
|
||||||
|
m_MinZ = std::min(Chunk.m_ChunkZ, m_MinZ);
|
||||||
|
|
||||||
|
m_MaxX = std::max(Chunk.m_ChunkX, m_MaxX);
|
||||||
|
m_MaxZ = std::max(Chunk.m_ChunkZ, m_MaxZ);
|
||||||
|
// If the tower is not in chunk put it in
|
||||||
|
bool ShouldPlace = true;
|
||||||
|
for (const auto & Property : m_TowerPos[Chunk])
|
||||||
|
{
|
||||||
|
ShouldPlace &= !(TowerPos == Property.m_Pos);
|
||||||
|
}
|
||||||
|
if (ShouldPlace)
|
||||||
|
{
|
||||||
|
m_TowerPos[Chunk].emplace_back(TowerProperties[I]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Generate angle of next tower
|
||||||
|
Angle = fmod(Angle + (2.0 * M_PI / static_cast<double>(TowerProperties.size())), 2.0 * M_PI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEnderDragonFightStructuresGen::GenFinish(cChunkDesc &a_ChunkDesc)
|
||||||
|
{
|
||||||
|
auto Coords = a_ChunkDesc.GetChunkCoords();
|
||||||
|
// If not in the chunks to write
|
||||||
|
if ((Coords.m_ChunkX > m_MaxX) ||
|
||||||
|
(Coords.m_ChunkX < m_MinX) ||
|
||||||
|
(Coords.m_ChunkZ > m_MaxZ) ||
|
||||||
|
(Coords.m_ChunkZ < m_MinZ))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Places the exit portal
|
||||||
|
if (Coords == cChunkCoords({0, 0}))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
auto EnderDragon = std::make_unique<cEnderDragon>();
|
||||||
|
EnderDragon->SetPosition({0.0, static_cast<double>(a_ChunkDesc.GetHeight(0, 0) + 20), 0.0}); // Spawns the dragon 20 blocks above the terrain at (0, 0)
|
||||||
|
a_ChunkDesc.GetEntities().emplace_back(std::move(EnderDragon));
|
||||||
|
*/ // Todo: 25.10.20 - Add the ender dragon spawning when the dragon behaves properly - 12xx12
|
||||||
|
a_ChunkDesc.WriteBlockArea(m_Fountain,
|
||||||
|
static_cast<int>(FloorC(-m_Fountain.GetSizeX() / 2)),
|
||||||
|
62,
|
||||||
|
static_cast<int>(FloorC(-m_Fountain.GetSizeX() / 2)),
|
||||||
|
cBlockArea::msSpongePrint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (Coords == cChunkCoords({-1, 0}))
|
||||||
|
{
|
||||||
|
a_ChunkDesc.WriteBlockArea(m_Fountain,
|
||||||
|
cChunkDef::Width - static_cast<int>(FloorC(m_Fountain.GetSizeX() / 2)),
|
||||||
|
62,
|
||||||
|
static_cast<int>(FloorC(-m_Fountain.GetSizeZ() / 2)),
|
||||||
|
cBlockArea::msSpongePrint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (Coords == cChunkCoords({0, -1}))
|
||||||
|
{
|
||||||
|
a_ChunkDesc.WriteBlockArea(m_Fountain,
|
||||||
|
static_cast<int>(FloorC(-m_Fountain.GetSizeX() / 2)),
|
||||||
|
62,
|
||||||
|
cChunkDef::Width - static_cast<int>(FloorC(m_Fountain.GetSizeZ() / 2)),
|
||||||
|
cBlockArea::msSpongePrint);
|
||||||
|
}
|
||||||
|
else if (Coords == cChunkCoords({-1, -1}))
|
||||||
|
{
|
||||||
|
a_ChunkDesc.WriteBlockArea(m_Fountain,
|
||||||
|
cChunkDef::Width - static_cast<int>(FloorC(m_Fountain.GetSizeX() / 2)),
|
||||||
|
62,
|
||||||
|
cChunkDef::Width - static_cast<int>(FloorC(m_Fountain.GetSizeZ() / 2)),
|
||||||
|
cBlockArea::msSpongePrint);
|
||||||
|
}
|
||||||
|
auto It = m_TowerPos.find(Coords);
|
||||||
|
if (It == m_TowerPos.end())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto & Property : It->second)
|
||||||
|
{
|
||||||
|
PlaceTower(a_ChunkDesc, Property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEnderDragonFightStructuresGen::PlaceTower(cChunkDesc &a_ChunkDesc, const sTowerProperties & a_Properties)
|
||||||
|
{
|
||||||
|
auto Pos = cChunkDef::AbsoluteToRelative(a_Properties.m_Pos, a_ChunkDesc.GetChunkCoords());
|
||||||
|
// Place obsidian pillar
|
||||||
|
for (int X = -a_Properties.m_Radius; X < a_Properties.m_Radius; X++)
|
||||||
|
{
|
||||||
|
for (int Z = -a_Properties.m_Radius; Z < a_Properties.m_Radius; Z++)
|
||||||
|
{
|
||||||
|
Vector3i NewPos = {Pos.x + X, 1, Pos.z + Z};
|
||||||
|
if (cChunkDef::IsValidRelPos(NewPos))
|
||||||
|
{
|
||||||
|
// The 3 was achieved by trial and error till the shape matched the notchian implementation
|
||||||
|
if ((NewPos - Vector3i(Pos.x, 1, Pos.z)).SqrLength() < a_Properties.m_Radius * a_Properties.m_Radius - 3)
|
||||||
|
{
|
||||||
|
for (int Y = 0; Y <= a_Properties.m_Height - 2; Y++)
|
||||||
|
{
|
||||||
|
a_ChunkDesc.SetBlockType(NewPos.x, Y, NewPos.z, E_BLOCK_OBSIDIAN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn the iron bars if there are some
|
||||||
|
if (a_Properties.m_HasCage)
|
||||||
|
{
|
||||||
|
// Place walls
|
||||||
|
for (const auto & Offset : m_CagePos)
|
||||||
|
{
|
||||||
|
if (cChunkDef::IsValidRelPos(Vector3d(Pos.x, a_Properties.m_Height, Pos.z) + Offset))
|
||||||
|
{
|
||||||
|
a_ChunkDesc.SetBlockType(Pos.x + Offset.x, a_Properties.m_Height + Offset.y, Pos.z + Offset.z, E_BLOCK_IRON_BARS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove any block that may generate inside the cage
|
||||||
|
for (const auto & Offset : m_CageAir)
|
||||||
|
{
|
||||||
|
if (cChunkDef::IsValidRelPos(Pos + Offset))
|
||||||
|
{
|
||||||
|
a_ChunkDesc.SetBlockType(Pos.x + Offset.x, a_Properties.m_Height + Offset.y, Pos.z + Offset.z, E_BLOCK_AIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Place roof
|
||||||
|
for (int X = -2; X <= 2; ++X)
|
||||||
|
{
|
||||||
|
for (int Z = -2; Z <= 2; ++Z)
|
||||||
|
{
|
||||||
|
if (cChunkDef::IsValidRelPos({Pos.x + X, 1, Pos.z + Z}))
|
||||||
|
{
|
||||||
|
a_ChunkDesc.SetBlockType(Pos.x + X, a_Properties.m_Height + 2, Pos.z + Z, E_BLOCK_IRON_BARS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Place the top decoration if the origin is in this chunk
|
||||||
|
if (cChunkDef::IsValidRelPos(Pos))
|
||||||
|
{
|
||||||
|
// Spawn the bedrock
|
||||||
|
a_ChunkDesc.SetBlockType(Pos.x, a_Properties.m_Height - 1, Pos.z, E_BLOCK_BEDROCK);
|
||||||
|
// Spawn the fire
|
||||||
|
a_ChunkDesc.SetBlockType(Pos.x, a_Properties.m_Height, Pos.z, E_BLOCK_FIRE);
|
||||||
|
// Spawn the ender crystal of the origin position is in this chunk
|
||||||
|
auto EnderCrystal = std::make_unique<cEnderCrystal>(Vector3d(0.5, a_Properties.m_Height, 0.5) + a_Properties.m_Pos, true);
|
||||||
|
a_ChunkDesc.GetEntities().emplace_back(std::move(EnderCrystal));
|
||||||
|
}
|
||||||
|
}
|
38
src/Generating/EnderDragonFightStructuresGen.h
Normal file
38
src/Generating/EnderDragonFightStructuresGen.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
https://minecraft.gamepedia.com/End_spike
|
||||||
|
https://minecraft.gamepedia.com/End_Crystal
|
||||||
|
https://minecraft.gamepedia.com/Ender_Dragon
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FinishGen.h"
|
||||||
|
|
||||||
|
class cEnderDragonFightStructuresGen :
|
||||||
|
public cFinishGen
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
cEnderDragonFightStructuresGen(int a_Seed);
|
||||||
|
void Init(const AString & a_TowerProperties, int a_Radius);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct sTowerProperties
|
||||||
|
{
|
||||||
|
Vector3i m_Pos;
|
||||||
|
int m_Height;
|
||||||
|
int m_Radius;
|
||||||
|
bool m_HasCage;
|
||||||
|
};
|
||||||
|
|
||||||
|
cNoise m_Noise;
|
||||||
|
std::map<cChunkCoords, std::vector<sTowerProperties>> m_TowerPos;
|
||||||
|
static const std::array<Vector3i, 48> m_CagePos;
|
||||||
|
static const std::array<Vector3i, 26> m_CageAir;
|
||||||
|
cBlockArea m_Fountain;
|
||||||
|
|
||||||
|
int m_MinX = -1, m_MaxX = 1, m_MinZ = -1, m_MaxZ = 1;
|
||||||
|
|
||||||
|
void GenFinish(cChunkDesc &a_ChunkDesc) override;
|
||||||
|
void PlaceTower(cChunkDesc & a_ChunkDesc, const sTowerProperties & a_TowerProperties);
|
||||||
|
};
|
@ -44,6 +44,7 @@ set (GENERATING_SRCS
|
|||||||
${PROJECT_SOURCE_DIR}/src/Generating/DistortedHeightmap.cpp
|
${PROJECT_SOURCE_DIR}/src/Generating/DistortedHeightmap.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/Generating/DungeonRoomsFinisher.cpp
|
${PROJECT_SOURCE_DIR}/src/Generating/DungeonRoomsFinisher.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/Generating/EndGen.cpp
|
${PROJECT_SOURCE_DIR}/src/Generating/EndGen.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/src/Generating/EnderDragonFightStructuresGen.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/Generating/FinishGen.cpp
|
${PROJECT_SOURCE_DIR}/src/Generating/FinishGen.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/Generating/GridStructGen.cpp
|
${PROJECT_SOURCE_DIR}/src/Generating/GridStructGen.cpp
|
||||||
${PROJECT_SOURCE_DIR}/src/Generating/HeiGen.cpp
|
${PROJECT_SOURCE_DIR}/src/Generating/HeiGen.cpp
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
#include "Generating/ChunkDesc.h"
|
#include "Generating/ChunkDesc.h"
|
||||||
#include "DeadlockDetect.h"
|
#include "DeadlockDetect.h"
|
||||||
#include "Entities/Entity.h"
|
#include "Entities/Entity.h"
|
||||||
|
#include "Entities/EnderCrystal.h"
|
||||||
#include "Mobs/Monster.h"
|
#include "Mobs/Monster.h"
|
||||||
|
#include "Mobs/EnderDragon.h"
|
||||||
#include "Simulator/FluidSimulator.h"
|
#include "Simulator/FluidSimulator.h"
|
||||||
#include "Simulator/FireSimulator.h"
|
#include "Simulator/FireSimulator.h"
|
||||||
#include "MobSpawner.h"
|
#include "MobSpawner.h"
|
||||||
@ -334,6 +336,15 @@ std::unique_ptr<cMonster> cMonster::NewMonsterFromType(eMonsterType a_Type)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::CheckEventLostPlayer(std::chrono::milliseconds a_Dt)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool cFluidSimulator::CanWashAway(BLOCKTYPE a_BlockType)
|
bool cFluidSimulator::CanWashAway(BLOCKTYPE a_BlockType)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -395,3 +406,637 @@ cItem::cItem(
|
|||||||
void cItem::Empty()
|
void cItem::Empty()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cEnderCrystal::cEnderCrystal(Vector3d a_Pos, bool a_ShowBottom):
|
||||||
|
Super(etEnderCrystal, a_Pos, 1.0, 1.0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEnderCrystal::SpawnOn(class cClientHandle & a_ClientHandle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEnderCrystal::Tick(std::chrono::milliseconds a_Dt, class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEnderCrystal::KilledBy(struct TakeDamageInfo & a_TakeDamageInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cEntity::cEntity(enum cEntity::eEntityType a_EntityType, class Vector3<double> a_Pos, double a_Height, double a_Width)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cEntity::Initialize(class std::unique_ptr<class cEntity,struct std::default_delete<class cEntity> > a_Entity,class cWorld & a_World)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::OnAddToWorld(class cWorld & a_World)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::OnRemoveFromWorld(class cWorld & a_World)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cEntity::IsA(char const * a_Type)const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
char const * cEntity::GetParentClass(void) const
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::HandleSpeedFromAttachee(float a_SpeedX,float a_SpeedZ)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::Destroy(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cEntity::DoTakeDamage(struct TakeDamageInfo & a_TakeDamageInfo)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int cEntity::GetRawDamageAgainst(class cEntity const & a_Entity)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cEntity::ArmorCoversAgainst(enum eDamageType a_DamageType)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
float cEntity::GetArmorCoverAgainst(class cEntity const * a_Entity,enum eDamageType a_DamageType,int a_Value)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
char const * cEntity::GetClass(void) const
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
float cEntity::GetEnchantmentCoverAgainst(class cEntity const * a_Entity,enum eDamageType a_DamageType,int a_Value)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
float cEntity::GetEnchantmentBlastKnockbackReduction(void)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
double cEntity::GetKnockbackAmountAgainst(class cEntity const & a_Entity)
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::ApplyArmorDamage(int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::KilledBy(struct TakeDamageInfo & a_TakeDamageInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::Heal(int a_Value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::Tick(std::chrono::milliseconds a_Dt, class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::HandlePhysics(std::chrono::milliseconds a_Dt, class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::TickBurning(class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::DetectCacti(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cEntity::DetectPortal(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::TickInVoid(class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::OnStartedBurning(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::OnFinishedBurning(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::TeleportToEntity(class cEntity & a_Entity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::TeleportToCoords(double a_XPos, double a_YPos, double a_ZPos)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::BroadcastMovementUpdate(class cClientHandle const * a_ClientHandle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::AttachTo(class cEntity * a_Entity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::Detach(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::SetSpeed(double a_XSpeed, double a_YSpeed, double a_ZSpeed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::DetectMagma(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cEntity::MoveToWorld(cWorld & a_World, Vector3d a_NewPosition, bool a_SetPortalCooldown, bool a_ShouldSendRespawn)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::HandleAir(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::SetSwimState(class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cEntity::ResetPosition(class Vector3<double> a_Pos)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cPawn::cPawn(enum cEntity::eEntityType,double a_Width, double a_Height) :
|
||||||
|
cEntity(etMonster, Vector3d(), a_Height, a_Width)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cPawn::IsFireproof(void) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cPawn::IsInvisible(void) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPawn::HandleAir(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPawn::HandleFalling()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPawn::KilledBy(struct TakeDamageInfo & a_TakeDamageInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPawn::OnRemoveFromWorld(cWorld & a_World)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPawn::ResetPosition(class Vector3<double>)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cPawn::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cMonster::cMonster(const AString & a_StringA, enum eMonsterType a_MonsterType, const AString & a_StringB, const AString & a_StringC, const AString & a_StringD, double a_Width, double a_Height) :
|
||||||
|
cPawn(etMonster, a_Width, a_Height),
|
||||||
|
m_PathFinder(a_Width, a_Height)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cPathFinder::cPathFinder(double a_Width, double a_Height)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::OnRemoveFromWorld(class cWorld & a_World)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::Tick(std::chrono::milliseconds a_Dt, class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::InStateChasing(std::chrono::milliseconds a_Dt, class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::SpawnOn(class cClientHandle & a_ClientHandle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cMonster::DoTakeDamage(struct TakeDamageInfo & a_TakeDamageInfo)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::KilledBy(struct TakeDamageInfo & a_TakeDamageInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::OnRightClicked(class cPlayer & a_Player)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::HandleFalling(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::MoveToPosition(class Vector3<double> const & a_Pos)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::CheckEventSeePlayer(class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cMonster::IsUndead(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::EventLosePlayer(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::InStateIdle(std::chrono::milliseconds a_Dt ,class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMonster::InStateEscaping(std::chrono::milliseconds a_Dt ,class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cAggressiveMonster::cAggressiveMonster(const AString & a_StringA, enum eMonsterType a_MonsterType, const AString & a_StringB, const AString & a_StringC, const AString & a_StringD, double a_Width, double a_Height) :
|
||||||
|
cMonster(a_StringA, a_MonsterType, a_StringB, a_StringC, a_StringD, a_Width, a_Height)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cAggressiveMonster::Tick(std::chrono::milliseconds a_Dt, class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt, class cChunk & a_Chunk)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cAggressiveMonster::EventSeePlayer(class cPlayer *,class cChunk &)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cAggressiveMonster::Attack(std::chrono::milliseconds a_Dt)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user