1
0
Fork 0

Merge pull request #1061 from mc-server/portals

Portals and others
This commit is contained in:
Mattes D 2014-07-29 17:48:56 +02:00
commit 30e64ed4d8
42 changed files with 673 additions and 236 deletions

View File

@ -88,3 +88,4 @@
! 269:1 = 200 # 1 Wooden Shovel -> 10 sec
! 290:1 = 200 # 1 Wooden Hoe -> 10 sec
! 268:1 = 200 # 1 Wooden Sword -> 10 sec

View File

@ -447,6 +447,15 @@ bool cIniFile::SetValueI(const AString & a_KeyName, const AString & a_ValueName,
bool cIniFile::SetValueI(const AString & a_Keyname, const AString & a_ValueName, const Int64 a_Value, const bool a_CreateIfNotExists)
{
return SetValue(a_Keyname, a_ValueName, Printf("%lld", a_Value), a_CreateIfNotExists);
}
bool cIniFile::SetValueF(const AString & a_KeyName, const AString & a_ValueName, double const a_Value, const bool a_CreateIfNotExists)
{
return SetValue(a_KeyName, a_ValueName, Printf("%f", a_Value), a_CreateIfNotExists);
@ -571,6 +580,24 @@ int cIniFile::GetValueSetI(const AString & keyname, const AString & valuename, c
Int64 cIniFile::GetValueSetI(const AString & keyname, const AString & valuename, const Int64 defValue)
{
AString Data;
Printf(Data, "%lld", defValue);
AString resultstring = GetValueSet(keyname, valuename, Data);
Int64 result = defValue;
#ifdef _WIN32
sscanf_s(resultstring.c_str(), "%lld", &result);
#else
sscanf(resultstring.c_str(), "%lld", &result);
#endif
return result;
}
bool cIniFile::DeleteValueByID(const int keyID, const int valueID)
{
if ((keyID < (int)keys.size()) && (valueID < (int)keys[keyID].names.size()))

View File

@ -119,6 +119,7 @@ public:
AString GetValueSet (const AString & keyname, const AString & valuename, const AString & defValue = "");
double GetValueSetF(const AString & keyname, const AString & valuename, const double defValue = 0.0);
int GetValueSetI(const AString & keyname, const AString & valuename, const int defValue = 0);
Int64 GetValueSetI(const AString & keyname, const AString & valuename, const Int64 defValue = 0);
bool GetValueSetB(const AString & keyname, const AString & valuename, const bool defValue = false)
{
return (GetValueSetI(keyname, valuename, defValue ? 1 : 0) != 0);
@ -141,6 +142,7 @@ public:
bool SetValue (const int keyID, const int valueID, const AString & value);
bool SetValue (const AString & a_KeyName, const AString & a_ValueName, const AString & a_Value, const bool a_CreateIfNotExists = true);
bool SetValueI(const AString & a_KeyName, const AString & a_ValueName, const int a_Value, const bool a_CreateIfNotExists = true);
bool SetValueI(const AString & a_Keyname, const AString & a_ValueName, const Int64 a_Value, const bool a_CreateIfNotExists = true);
bool SetValueB(const AString & a_KeyName, const AString & a_ValueName, const bool a_Value, const bool a_CreateIfNotExists = true)
{
return SetValueI(a_KeyName, a_ValueName, int(a_Value), a_CreateIfNotExists);

View File

@ -346,6 +346,37 @@ eDimension StringToDimension(const AString & a_DimensionString)
AString DimensionToString(eDimension a_Dimension)
{
// Decode using a built-in map:
static struct
{
eDimension m_Dimension;
const char * m_String;
} DimensionMap[] =
{
{ dimOverworld, "Overworld" },
{ dimNether, "Nether" },
{ dimEnd, "End" },
};
for (size_t i = 0; i < ARRAYCOUNT(DimensionMap); i++)
{
if (DimensionMap[i].m_Dimension == a_Dimension)
{
return DimensionMap[i].m_String;
}
} // for i - DimensionMap[]
// Not found
LOGWARNING("Unknown dimension: \"%i\". Setting to Overworld", (int)a_Dimension);
return "Overworld";
}
/// Translates damage type constant to a string representation (built-in).
AString DamageTypeToString(eDamageType a_DamageType)
{

View File

@ -920,9 +920,14 @@ extern AString ItemToFullString(const cItem & a_Item);
/// Translates a mob string ("ocelot") to mobtype (E_ENTITY_TYPE_OCELOT)
extern int StringToMobType(const AString & a_MobString);
/// Translates a dimension string to dimension enum. Takes either a number or a dimension alias (built-in). Returns -1000 on failure
/// Translates a dimension string to dimension enum. Takes either a number or a dimension alias (built-in). Returns dimOverworld on failure
extern eDimension StringToDimension(const AString & a_DimensionString);
/** Translates a dimension enum to dimension string.
Takes an eDimension enum value and returns "Overworld" on failure
*/
extern AString DimensionToString(eDimension a_Dimension);
/// Translates damage type constant to a string representation (built-in).
extern AString DamageTypeToString(eDamageType a_DamageType);

View File

@ -450,6 +450,7 @@ void cBlockInfo::Initialize(cBlockInfoArray & a_Info)
a_Info[E_BLOCK_CROPS ].m_IsSolid = false;
a_Info[E_BLOCK_DANDELION ].m_IsSolid = false;
a_Info[E_BLOCK_DETECTOR_RAIL ].m_IsSolid = false;
a_Info[E_BLOCK_END_PORTAL ].m_IsSolid = false;
a_Info[E_BLOCK_FIRE ].m_IsSolid = false;
a_Info[E_BLOCK_FLOWER ].m_IsSolid = false;
a_Info[E_BLOCK_HEAVY_WEIGHTED_PRESSURE_PLATE].m_IsSolid = false;

View File

@ -108,7 +108,7 @@ void cBlockBedHandler::OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface
NIBBLETYPE Meta = a_ChunkInterface.GetBlockMeta(a_BlockX, a_BlockY, a_BlockZ);
if (Meta & 0x4)
{
a_Player->SendMessageFailure("This bed is occupied.");
a_Player->SendMessageFailure("This bed is occupied");
}
else
{
@ -133,6 +133,8 @@ void cBlockBedHandler::OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface
a_ChunkInterface.SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta | 0x4); // Where 0x4 = occupied bit
a_Player->SetIsInBed(true);
a_Player->SetBedPos(Vector3i(a_BlockX, a_BlockY, a_BlockZ));
a_Player->SendMessageSuccess("Home position set successfully");
cTimeFastForwardTester Tester;
if (a_WorldInterface.ForEachPlayer(Tester))

View File

@ -58,6 +58,24 @@ public:
{
return true;
}
virtual void OnUpdate(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cBlockPluginInterface & a_PluginInterface, cChunk & a_Chunk, int a_RelX, int a_RelY, int a_RelZ) override
{
int BlockX = a_RelX + a_Chunk.GetPosX() * cChunkDef::Width;
int BlockZ = a_RelZ + a_Chunk.GetPosZ() * cChunkDef::Width;
if (!a_WorldInterface.IsWeatherWetAt(BlockX, BlockZ) || (a_RelY != a_WorldInterface.GetHeight(BlockX, BlockZ)))
{
// It's not raining at our current location or we do not have a direct view of the sky
// We cannot eat the rain :(
return;
}
NIBBLETYPE Meta = a_Chunk.GetMeta(a_RelX, a_RelY, a_RelZ);
if (Meta < 3)
{
a_Chunk.SetMeta(a_RelX, a_RelY, a_RelZ, Meta + 1);
}
}
} ;

View File

@ -46,6 +46,12 @@ public:
virtual void SetTimeOfDay(Int64 a_TimeOfDay) = 0;
/** Returns true if it is raining, stormy or snowing at the specified location. This takes into account biomes. */
virtual bool IsWeatherWetAt(int a_BlockX, int a_BlockZ) = 0;
/** Returns the world height at the specified coords; waits for the chunk to get loaded / generated */
virtual int GetHeight(int a_BlockX, int a_BlockZ) = 0;
/** Wakes up the simulators for the specified block */
virtual void WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ) = 0;

View File

@ -577,39 +577,34 @@ void cChunk::Tick(float a_Dt)
m_IsDirty = (*itr)->Tick(a_Dt, *this) | m_IsDirty;
}
// Tick all entities in this chunk (except mobs):
for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end(); ++itr)
{
// Mobs are tickes inside MobTick (as we don't have to tick them if they are far away from players)
if (!((*itr)->IsMob()))
for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end();)
{
if (!((*itr)->IsMob())) // Mobs are ticked inside cWorld::TickMobs() (as we don't have to tick them if they are far away from players)
{
// Tick all entities in this chunk (except mobs):
(*itr)->Tick(a_Dt, *this);
}
} // for itr - m_Entitites[]
// Remove all entities that were scheduled for removal:
for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end();)
{
if ((*itr)->IsDestroyed())
{
LOGD("Destroying entity #%i (%s)", (*itr)->GetUniqueID(), (*itr)->GetClass());
cEntity * ToDelete = *itr;
itr = m_Entities.erase(itr);
delete ToDelete;
ToDelete = NULL;
continue;
}
++itr;
} // for itr - m_Entitites[]
// If any entity moved out of the chunk, move it to the neighbor:
for (cEntityList::iterator itr = m_Entities.begin(); itr != m_Entities.end();)
{
if (
if ((*itr)->IsDestroyed()) // Remove all entities that were scheduled for removal:
{
LOGD("Destroying entity #%i (%s)", (*itr)->GetUniqueID(), (*itr)->GetClass());
MarkDirty();
cEntity * ToDelete = *itr;
itr = m_Entities.erase(itr);
delete ToDelete;
}
else if ((*itr)->IsWorldTravellingFrom(m_World)) // Remove all entities that are travelling to another world:
{
MarkDirty();
(*itr)->SetWorldTravellingFrom(NULL);
itr = m_Entities.erase(itr);
}
else if ( // If any entity moved out of the chunk, move it to the neighbor:
((*itr)->GetChunkX() != m_PosX) ||
((*itr)->GetChunkZ() != m_PosZ)
)
{
MarkDirty();
MoveEntityToNewChunk(*itr);
itr = m_Entities.erase(itr);
}
@ -617,7 +612,7 @@ void cChunk::Tick(float a_Dt)
{
++itr;
}
}
} // for itr - m_Entitites[]
ApplyWeatherToTop();
}
@ -902,7 +897,6 @@ void cChunk::ApplyWeatherToTop()
}
break;
} // case (snowy biomes)
// TODO: Rainy biomes should check for farmland and cauldrons
default:
{
break;
@ -1798,7 +1792,7 @@ void cChunk::RemoveBlockEntity( cBlockEntity* a_BlockEntity)
bool cChunk::AddClient(cClientHandle* a_Client)
bool cChunk::AddClient(cClientHandle * a_Client)
{
for (cClientHandleList::iterator itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr)
{
@ -1829,7 +1823,7 @@ bool cChunk::AddClient(cClientHandle* a_Client)
void cChunk::RemoveClient( cClientHandle* a_Client)
void cChunk::RemoveClient(cClientHandle * a_Client)
{
for (cClientHandleList::iterator itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr)
{
@ -1837,7 +1831,7 @@ void cChunk::RemoveClient( cClientHandle* a_Client)
{
continue;
}
m_LoadedByClient.erase(itr);
if (!a_Client->IsDestroyed())
@ -1862,7 +1856,7 @@ void cChunk::RemoveClient( cClientHandle* a_Client)
bool cChunk::HasClient( cClientHandle* a_Client)
bool cChunk::HasClient(cClientHandle * a_Client)
{
for (cClientHandleList::const_iterator itr = m_LoadedByClient.begin(); itr != m_LoadedByClient.end(); ++itr)
{
@ -1893,9 +1887,9 @@ void cChunk::AddEntity(cEntity * a_Entity)
{
MarkDirty();
}
ASSERT(std::find(m_Entities.begin(), m_Entities.end(), a_Entity) == m_Entities.end()); // Not there already
m_Entities.push_back(a_Entity);
}
@ -1905,17 +1899,12 @@ void cChunk::AddEntity(cEntity * a_Entity)
void cChunk::RemoveEntity(cEntity * a_Entity)
{
size_t SizeBefore = m_Entities.size();
m_Entities.remove(a_Entity);
size_t SizeAfter = m_Entities.size();
if (SizeBefore != SizeAfter)
// Mark as dirty if it was a server-generated entity:
if (!a_Entity->IsPlayer())
{
// Mark as dirty if it was a server-generated entity:
if (!a_Entity->IsPlayer())
{
MarkDirty();
}
MarkDirty();
}
}

View File

@ -120,7 +120,8 @@ cClientHandle::~cClientHandle()
}
if (World != NULL)
{
World->RemovePlayer(m_Player);
m_Player->SetWorldTravellingFrom(NULL); // Make sure that the player entity is actually removed
World->RemovePlayer(m_Player); // Must be called before cPlayer::Destroy() as otherwise cChunk tries to delete the player, and then we do it again
m_Player->Destroy();
}
delete m_Player;
@ -1795,8 +1796,7 @@ void cClientHandle::RemoveFromWorld(void)
m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ);
} // for itr - Chunks[]
// StreamChunks() called in cPlayer::MoveToWorld() after new world has been set
// Meanwhile here, we set last streamed values to bogus ones so everything is resent
// Here, we set last streamed values to bogus ones so everything is resent
m_LastStreamedChunkX = 0x7fffffff;
m_LastStreamedChunkZ = 0x7fffffff;
m_HasSentPlayerChunk = false;
@ -2377,9 +2377,9 @@ void cClientHandle::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effec
void cClientHandle::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks)
void cClientHandle::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks)
{
m_Protocol->SendRespawn(a_World, a_ShouldIgnoreDimensionChecks);
m_Protocol->SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks);
}

View File

@ -161,7 +161,7 @@ public:
void SendPlayerSpawn (const cPlayer & a_Player);
void SendPluginMessage (const AString & a_Channel, const AString & a_Message); // Exported in ManualBindings.cpp
void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID);
void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false);
void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks = false);
void SendExperience (void);
void SendExperienceOrb (const cExpOrb & a_ExpOrb);
void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode);

View File

@ -38,6 +38,7 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d
, m_Gravity(-9.81f)
, m_LastPos(a_X, a_Y, a_Z)
, m_IsInitialized(false)
, m_WorldTravellingFrom(NULL)
, m_EntityType(a_EntityType)
, m_World(NULL)
, m_IsFireproof(false)
@ -617,9 +618,12 @@ void cEntity::Tick(float a_Dt, cChunk & a_Chunk)
// Handle drowning
HandleAir();
}
// None of the above functions change position, we remain in the chunk of NextChunk
HandlePhysics(a_Dt, *NextChunk);
if (!DetectPortal()) // Our chunk is invalid if we have moved to another world
{
// None of the above functions changed position, we remain in the chunk of NextChunk
HandlePhysics(a_Dt, *NextChunk);
}
}
}
@ -856,7 +860,7 @@ void cEntity::TickBurning(cChunk & a_Chunk)
// Remember the current burning state:
bool HasBeenBurning = (m_TicksLeftBurning > 0);
if (m_World->IsWeatherWet())
if (GetWorld()->IsWeatherWetAt(POSX_TOINT, POSZ_TOINT))
{
if (POSY_TOINT > m_World->GetHeight(POSX_TOINT, POSZ_TOINT))
{
@ -1027,6 +1031,184 @@ void cEntity::DetectCacti(void)
bool cEntity::DetectPortal()
{
if (GetWorld()->GetDimension() == dimOverworld)
{
if (GetWorld()->GetNetherWorldName().empty() && GetWorld()->GetEndWorldName().empty())
{
// Teleportation to either dimension not enabled, don't bother proceeding
return false;
}
}
else if (GetWorld()->GetLinkedOverworldName().empty())
{
// Overworld teleportation disabled, abort
return false;
}
int X = POSX_TOINT, Y = POSY_TOINT, Z = POSZ_TOINT;
if ((Y > 0) && (Y < cChunkDef::Height))
{
switch (GetWorld()->GetBlock(X, Y, Z))
{
case E_BLOCK_NETHER_PORTAL:
{
if (m_PortalCooldownData.m_ShouldPreventTeleportation)
{
// Just exited a portal, don't teleport again
return false;
}
if (IsPlayer() && !((cPlayer *)this)->IsGameModeCreative() && m_PortalCooldownData.m_TicksDelayed != 80)
{
// Delay teleportation for four seconds if the entity is a non-creative player
m_PortalCooldownData.m_TicksDelayed++;
return false;
}
m_PortalCooldownData.m_TicksDelayed = 0;
switch (GetWorld()->GetDimension())
{
case dimNether:
{
if (GetWorld()->GetLinkedOverworldName().empty())
{
return false;
}
m_PortalCooldownData.m_ShouldPreventTeleportation = true; // Stop portals from working on respawn
if (IsPlayer())
{
((cPlayer *)this)->GetClientHandle()->SendRespawn(dimOverworld); // Send a respawn packet before world is loaded/generated so the client isn't left in limbo
}
return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false);
}
case dimOverworld:
{
if (GetWorld()->GetNetherWorldName().empty())
{
return false;
}
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer())
{
((cPlayer *)this)->AwardAchievement(achEnterPortal);
((cPlayer *)this)->GetClientHandle()->SendRespawn(dimNether);
}
return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetNetherWorldName(), dimNether, GetWorld()->GetName()), false);
}
default: return false;
}
}
case E_BLOCK_END_PORTAL:
{
if (m_PortalCooldownData.m_ShouldPreventTeleportation)
{
return false;
}
switch (GetWorld()->GetDimension())
{
case dimEnd:
{
if (GetWorld()->GetLinkedOverworldName().empty())
{
return false;
}
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer())
{
cPlayer * Player = (cPlayer *)this;
Player->TeleportToCoords(Player->GetLastBedPos().x, Player->GetLastBedPos().y, Player->GetLastBedPos().z);
Player->GetClientHandle()->SendRespawn(dimOverworld);
}
return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false);
}
case dimOverworld:
{
if (GetWorld()->GetEndWorldName().empty())
{
return false;
}
m_PortalCooldownData.m_ShouldPreventTeleportation = true;
if (IsPlayer())
{
((cPlayer *)this)->AwardAchievement(achEnterTheEnd);
((cPlayer *)this)->GetClientHandle()->SendRespawn(dimEnd);
}
return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetEndWorldName(), dimEnd, GetWorld()->GetName()), false);
}
default: return false;
}
}
default: break;
}
}
// Allow portals to work again
m_PortalCooldownData.m_ShouldPreventTeleportation = false;
m_PortalCooldownData.m_TicksDelayed = 0;
return false;
}
bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn)
{
UNUSED(a_ShouldSendRespawn);
ASSERT(a_World != NULL);
if (GetWorld() == a_World)
{
// Don't move to same world
return false;
}
// Remove all links to the old world
SetWorldTravellingFrom(GetWorld()); // cChunk::Tick() handles entity removal
GetWorld()->BroadcastDestroyEntity(*this);
// Queue add to new world
a_World->AddEntity(this);
SetWorld(a_World);
return true;
}
bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn)
{
cWorld * World = cRoot::Get()->GetWorld(a_WorldName);
if (World == NULL)
{
LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName.c_str());
return false;
}
return DoMoveToWorld(World, a_ShouldSendRespawn);
}
void cEntity::SetSwimState(cChunk & a_Chunk)
{
int RelY = (int)floor(GetPosY() + 0.1);

View File

@ -336,6 +336,11 @@ public:
/** Detects the time for application of cacti damage */
virtual void DetectCacti(void);
/** Detects whether we are in a portal block and begins teleportation procedures if so
Returns true if MoveToWorld() was called, false if not
*/
virtual bool DetectPortal(void);
/// Handles when the entity is in the void
virtual void TickInVoid(cChunk & a_Chunk);
@ -378,8 +383,22 @@ public:
/// Teleports to the coordinates specified
virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ);
/** Moves entity to specified world, taking a world pointer */
bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true) { return DoMoveToWorld(a_World, a_ShouldSendRespawn); }
/** Moves entity to specified world, taking a world name */
bool MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn = true);
// tolua_end
virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn);
/** Returns if the entity is travelling away from a specified world */
bool IsWorldTravellingFrom(cWorld * a_World) const { return (m_WorldTravellingFrom == a_World); }
/** Sets the world the entity will be leaving */
void SetWorldTravellingFrom(cWorld * a_World) { m_WorldTravellingFrom = a_World; }
/// Updates clients of changes in the entity.
virtual void BroadcastMovementUpdate(const cClientHandle * a_Exclude = NULL);
@ -482,6 +501,12 @@ protected:
/** True when entity is initialised (Initialize()) and false when destroyed pending deletion (Destroy()) */
bool m_IsInitialized;
/** World entity is travelling from
Set to a valid world pointer by MoveToWorld; reset to NULL when the entity is removed from the old world
Can't be a simple boolean as context switches between worlds may leave the new chunk processing (and therefore immediately removing) the entity before the old chunk could remove it
*/
cWorld * m_WorldTravellingFrom;
eEntityType m_EntityType;
cWorld * m_World;
@ -503,7 +528,6 @@ protected:
/// Time, in ticks, since the last damage dealt by the void. Reset to zero when moving out of the void.
int m_TicksSinceLastVoidDamage;
/** Does the actual speed-setting. The default implementation just sets the member variable value;
overrides can provide further processing, such as forcing players to move at the given speed. */
@ -523,6 +547,21 @@ protected:
/** Air level of a mobile */
int m_AirLevel;
int m_AirTickTimer;
/** Structure storing the portal delay timer and cooldown boolean */
struct sPortalCooldownData
{
/** Ticks since entry of portal, used to delay teleportation */
unsigned short m_TicksDelayed;
/** Whether the entity has just exited the portal, and should therefore not be teleported again
This prevents teleportation loops, and is reset when the entity has moved out of the portal
*/
bool m_ShouldPreventTeleportation;
};
/** Portal delay timer and cooldown boolean data */
sPortalCooldownData m_PortalCooldownData;
/** The number of ticks this entity has been alive for */
long int m_TicksAlive;

View File

@ -88,13 +88,14 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) :
m_PlayerName = a_PlayerName;
if (!LoadFromDisk())
cWorld * World = NULL;
if (!LoadFromDisk(World))
{
m_Inventory.Clear();
cWorld * DefaultWorld = cRoot::Get()->GetDefaultWorld();
SetPosX(DefaultWorld->GetSpawnX());
SetPosY(DefaultWorld->GetSpawnY());
SetPosZ(DefaultWorld->GetSpawnZ());
SetPosX(World->GetSpawnX());
SetPosY(World->GetSpawnY());
SetPosZ(World->GetSpawnZ());
SetBedPos(Vector3i((int)World->GetSpawnX(), (int)World->GetSpawnY(), (int)World->GetSpawnZ()));
LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}",
a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ()
@ -107,11 +108,6 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) :
if (m_GameMode == gmNotSet)
{
cWorld * World = cRoot::Get()->GetWorld(GetLoadedWorldName());
if (World == NULL)
{
World = cRoot::Get()->GetDefaultWorld();
}
if (World->IsGameModeCreative())
{
m_CanFly = true;
@ -140,8 +136,6 @@ cPlayer::~cPlayer(void)
SaveToDisk();
m_World->RemovePlayer( this);
m_ClientHandle = NULL;
delete m_InventoryWindow;
@ -157,8 +151,6 @@ cPlayer::~cPlayer(void)
void cPlayer::Destroyed()
{
CloseWindow(false);
m_ClientHandle = NULL;
}
@ -983,12 +975,12 @@ void cPlayer::Respawn(void)
m_LifetimeTotalXp = 0;
// ToDo: send score to client? How?
m_ClientHandle->SendRespawn(*m_World, true);
m_ClientHandle->SendRespawn(GetWorld()->GetDimension(), true);
// Extinguish the fire:
StopBurning();
TeleportToCoords(GetWorld()->GetSpawnX(), GetWorld()->GetSpawnY(), GetWorld()->GetSpawnZ());
TeleportToCoords(GetLastBedPos().x, GetLastBedPos().y, GetLastBedPos().z);
SetVisible(true);
}
@ -1617,29 +1609,29 @@ void cPlayer::TossItems(const cItems & a_Items)
bool cPlayer::MoveToWorld(const char * a_WorldName)
bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn)
{
cWorld * World = cRoot::Get()->GetWorld(a_WorldName);
if (World == NULL)
ASSERT(a_World != NULL);
if (GetWorld() == a_World)
{
LOG("%s: Couldn't find world \"%s\".", __FUNCTION__, a_WorldName);
// Don't move to same world
return false;
}
// Send the respawn packet:
if (m_ClientHandle != NULL)
if (a_ShouldSendRespawn && (m_ClientHandle != NULL))
{
m_ClientHandle->SendRespawn(*World);
m_ClientHandle->SendRespawn(a_World->GetDimension());
}
// Remove all links to the old world
m_World->RemovePlayer(this);
// If the dimension is different, we can send the respawn packet
// http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02
// Remove player from the old world
SetWorldTravellingFrom(GetWorld()); // cChunk handles entity removal
GetWorld()->RemovePlayer(this);
// Queue adding player to the new world, including all the necessary adjustments to the object
World->AddPlayer(this);
a_World->AddPlayer(this);
SetWorld(a_World); // Chunks may be streamed before cWorld::AddPlayer() sets the world to the new value
return true;
}
@ -1687,13 +1679,12 @@ void cPlayer::LoadPermissionsFromDisk()
bool cPlayer::LoadFromDisk(void)
bool cPlayer::LoadFromDisk(cWorldPtr & a_World)
{
LoadPermissionsFromDisk();
// Load from the UUID file:
if (LoadFromFile(GetUUIDFileName(m_UUID)))
if (LoadFromFile(GetUUIDFileName(m_UUID), a_World))
{
return true;
}
@ -1702,7 +1693,7 @@ bool cPlayer::LoadFromDisk(void)
AString OfflineUUID = cClientHandle::GenerateOfflineUUID(GetName());
if (cRoot::Get()->GetServer()->ShouldLoadOfflinePlayerData())
{
if (LoadFromFile(GetUUIDFileName(OfflineUUID)))
if (LoadFromFile(GetUUIDFileName(OfflineUUID), a_World))
{
return true;
}
@ -1712,7 +1703,7 @@ bool cPlayer::LoadFromDisk(void)
if (cRoot::Get()->GetServer()->ShouldLoadNamedPlayerData())
{
AString OldStyleFileName = Printf("players/%s.json", GetName().c_str());
if (LoadFromFile(OldStyleFileName))
if (LoadFromFile(OldStyleFileName, a_World))
{
// Save in new format and remove the old file
if (SaveToDisk())
@ -1727,6 +1718,11 @@ bool cPlayer::LoadFromDisk(void)
LOG("Player data file not found for %s (%s, offline %s), will be reset to defaults.",
GetName().c_str(), m_UUID.c_str(), OfflineUUID.c_str()
);
if (a_World == NULL)
{
a_World = cRoot::Get()->GetDefaultWorld();
}
return false;
}
@ -1734,7 +1730,7 @@ bool cPlayer::LoadFromDisk(void)
bool cPlayer::LoadFromFile(const AString & a_FileName)
bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
{
// Load the data from the file:
cFile f;
@ -1799,6 +1795,11 @@ bool cPlayer::LoadFromFile(const AString & a_FileName)
cEnderChestEntity::LoadFromJson(root["enderchestinventory"], m_EnderChestContents);
m_LoadedWorldName = root.get("world", "world").asString();
a_World = cRoot::Get()->GetWorld(GetLoadedWorldName(), true);
m_LastBedPos.x = root.get("SpawnX", a_World->GetSpawnX()).asInt();
m_LastBedPos.y = root.get("SpawnY", a_World->GetSpawnY()).asInt();
m_LastBedPos.z = root.get("SpawnZ", a_World->GetSpawnZ()).asInt();
// Load the player stats.
// We use the default world name (like bukkit) because stats are shared between dimensions/worlds.
@ -1806,7 +1807,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName)
StatSerializer.Load();
LOGD("Player %s was read from file \"%s\", spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
GetName().c_str(), a_FileName.c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str()
GetName().c_str(), a_FileName.c_str(), GetPosX(), GetPosY(), GetPosZ(), a_World->GetName().c_str()
);
return true;
@ -1818,7 +1819,6 @@ bool cPlayer::LoadFromFile(const AString & a_FileName)
bool cPlayer::SaveToDisk()
{
cFile::CreateFolder(FILE_IO_PREFIX + AString("players"));
cFile::CreateFolder(FILE_IO_PREFIX + AString("players/") + m_UUID.substr(0, 2));
// create the JSON data
@ -1853,6 +1853,10 @@ bool cPlayer::SaveToDisk()
root["foodExhaustion"] = m_FoodExhaustionLevel;
root["isflying"] = IsFlying();
root["lastknownname"] = GetName();
root["SpawnX"] = GetLastBedPos().x;
root["SpawnY"] = GetLastBedPos().y;
root["SpawnZ"] = GetLastBedPos().z;
if (m_World != NULL)
{
root["world"] = m_World->GetName();

View File

@ -131,7 +131,7 @@ public:
inline const cItem & GetEquippedItem(void) const { return GetInventory().GetEquippedItem(); } // tolua_export
/** Returns whether the player is climbing (ladders, vines e.t.c). */
/** Returns whether the player is climbing (ladders, vines etc.) */
bool IsClimbing(void) const;
virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) override;
@ -325,7 +325,7 @@ public:
virtual void KilledBy(TakeDamageInfo & a_TDI) override;
virtual void Killed(cEntity * a_Victim) override;
void Respawn(void); // tolua_export
void SetVisible( bool a_bVisible); // tolua_export
@ -333,17 +333,24 @@ public:
/** Moves the player to the specified world.
Returns true if successful, false on failure (world not found). */
bool MoveToWorld(const char * a_WorldName); // tolua_export
virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) override;
/** Saves all player data, such as inventory, to JSON */
bool SaveToDisk(void);
typedef cWorld * cWorldPtr;
/** Loads the player data from the disk file.
Returns true on success, false on failure. */
bool LoadFromDisk(void);
/** Loads the player data from the disk file
Sets a_World to the world where the player will spawn, based on the stored world name or the default world by calling LoadFromFile()
Returns true on success, false on failure
*/
bool LoadFromDisk(cWorldPtr & a_World);
/** Loads the player data from the specified file.
Returns true on success, false on failure. */
bool LoadFromFile(const AString & a_FileName);
/** Loads the player data from the specified file
Sets a_World to the world where the player will spawn, based on the stored world name or the default world
Returns true on success, false on failure
*/
bool LoadFromFile(const AString & a_FileName, cWorldPtr & a_World);
void LoadPermissionsFromDisk(void); // tolua_export
@ -355,8 +362,7 @@ public:
void SendExperience(void);
// In UI windows, the item that the player is dragging:
bool IsDraggingItem(void) const { return !m_DraggingItem.IsEmpty(); }
/** In UI windows, get the item that the player is dragging */
cItem & GetDraggingItem(void) {return m_DraggingItem; }
// In UI windows, when inventory-painting:
@ -404,11 +410,20 @@ public:
/** If true the player can fly even when he's not in creative. */
void SetCanFly(bool a_CanFly);
/** Gets the last position that the player slept in
This is initialised to the world spawn point if the player has not slept in a bed as of yet
*/
Vector3i GetLastBedPos(void) const { return m_LastBedPos; }
/** Sets the player's bed (home) position */
void SetBedPos(const Vector3i & a_Pos) { m_LastBedPos = a_Pos; }
/** Update movement-related statistics. */
void UpdateMovementStats(const Vector3d & a_DeltaPos);
/** Returns wheter the player can fly or not. */
virtual bool CanFly(void) const { return m_CanFly; }
// tolua_end
// cEntity overrides:
@ -466,6 +481,9 @@ protected:
cWindow * m_CurrentWindow;
cWindow * m_InventoryWindow;
/** The player's last saved bed position */
Vector3i m_LastBedPos;
char m_Color;
eGameMode m_GameMode;
@ -540,7 +558,6 @@ protected:
If no ClientHandle is given, the UUID is initialized to empty. */
AString m_UUID;
/** Sets the speed and sends it to the client, so that they are forced to move so. */
virtual void DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) override;

View File

@ -95,7 +95,7 @@ void cBioGenConstant::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap
void cBioGenConstant::InitializeBiomeGen(cIniFile & a_IniFile)
{
AString Biome = a_IniFile.GetValueSet("Generator", "ConstantBiome", "Plains");
AString Biome = a_IniFile.GetValueSet("Generator", "ConstantBiome", "");
m_Biome = StringToBiome(Biome);
if (m_Biome == biInvalidBiome)
{

View File

@ -20,7 +20,6 @@ cChunkDesc::cChunkDesc(int a_ChunkX, int a_ChunkZ) :
m_bUseDefaultBiomes(true),
m_bUseDefaultHeight(true),
m_bUseDefaultComposition(true),
m_bUseDefaultStructures(true),
m_bUseDefaultFinish(true)
{
m_BlockArea.Create(cChunkDef::Width, cChunkDef::Height, cChunkDef::Width);
@ -207,26 +206,6 @@ bool cChunkDesc::IsUsingDefaultComposition(void) const
void cChunkDesc::SetUseDefaultStructures(bool a_bUseDefaultStructures)
{
LOGWARNING("%s: Structures are no longer accounted for, use Finishers instead", __FUNCTION__);
m_bUseDefaultStructures = a_bUseDefaultStructures;
}
bool cChunkDesc::IsUsingDefaultStructures(void) const
{
LOGWARNING("%s: Structures are no longer accounted for, use Finishers instead", __FUNCTION__);
return m_bUseDefaultStructures;
}
void cChunkDesc::SetUseDefaultFinish(bool a_bUseDefaultFinish)
{
m_bUseDefaultFinish = a_bUseDefaultFinish;

View File

@ -68,8 +68,6 @@ public:
bool IsUsingDefaultHeight(void) const;
void SetUseDefaultComposition(bool a_bUseDefaultComposition);
bool IsUsingDefaultComposition(void) const;
void SetUseDefaultStructures(bool a_bUseDefaultStructures);
bool IsUsingDefaultStructures(void) const;
void SetUseDefaultFinish(bool a_bUseDefaultFinish);
bool IsUsingDefaultFinish(void) const;
@ -214,7 +212,6 @@ private:
bool m_bUseDefaultBiomes;
bool m_bUseDefaultHeight;
bool m_bUseDefaultComposition;
bool m_bUseDefaultStructures;
bool m_bUseDefaultFinish;
} ; // tolua_export

View File

@ -52,7 +52,7 @@ bool cChunkGenerator::Start(cPluginInterface & a_PluginInterface, cChunkSink & a
m_ChunkSink = &a_ChunkSink;
MTRand rnd;
m_Seed = a_IniFile.GetValueSetI("Seed", "Seed", rnd.randInt());
m_Seed = a_IniFile.GetValueSetI("Seed", "Seed", (int)rnd.randInt());
AString GeneratorName = a_IniFile.GetValueSet("Generator", "Generator", "Composable");
if (NoCaseCompare(GeneratorName, "Noise3D") == 0)

View File

@ -45,7 +45,6 @@ cTerrainCompositionGen * cTerrainCompositionGen::CreateCompositionGen(cIniFile &
{
LOGWARN("[Generator] CompositionGen value not set in world.ini, using \"Biomal\".");
CompoGenName = "Biomal";
a_IniFile.SetValue("Generator", "CompositionGen", CompoGenName);
}
cTerrainCompositionGen * res = NULL;
@ -99,7 +98,6 @@ cTerrainCompositionGen * cTerrainCompositionGen::CreateCompositionGen(cIniFile &
else
{
LOGWARN("Unknown CompositionGen \"%s\", using \"Biomal\" instead.", CompoGenName.c_str());
a_IniFile.DeleteValue("Generator", "CompositionGen");
a_IniFile.SetValue("Generator", "CompositionGen", "Biomal");
return CreateCompositionGen(a_IniFile, a_BiomeGen, a_HeightGen, a_Seed);
}
@ -297,19 +295,7 @@ void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile)
int Seed = m_ChunkGenerator.GetSeed();
eDimension Dimension = StringToDimension(a_IniFile.GetValue("General", "Dimension", "Overworld"));
// Older configuration used "Structures" in addition to "Finishers"; we don't distinguish between the two anymore (#398)
// Therefore, we load Structures from the ini file for compatibility, but move its contents over to Finishers:
AString Structures = a_IniFile.GetValue("Generator", "Structures", "");
AString Finishers = a_IniFile.GetValueSet("Generator", "Finishers", "Ravines, WormNestCaves, WaterLakes, LavaLakes, OreNests, Trees, SprinkleFoliage, Ice, Snow, Lilypads, BottomLava, DeadBushes, PreSimulator");
if (!Structures.empty())
{
LOGINFO("[Generator].Structures is deprecated, moving the contents to [Generator].Finishers.");
// Structures used to generate before Finishers, so place them first:
Structures.append(", ");
Finishers = Structures + Finishers;
a_IniFile.SetValue("Generator", "Finishers", Finishers);
}
a_IniFile.DeleteValue("Generator", "Structures");
AString Finishers = a_IniFile.GetValueSet("Generator", "Finishers", "");
// Create all requested finishers:
AStringVector Str = StringSplitAndTrim(Finishers, ",");

View File

@ -50,7 +50,7 @@ cEndGen::cEndGen(int a_Seed) :
void cEndGen::Initialize(cIniFile & a_IniFile)
void cEndGen::InitializeCompoGen(cIniFile & a_IniFile)
{
m_IslandSizeX = a_IniFile.GetValueSetI("Generator", "EndGenIslandSizeX", m_IslandSizeX);
m_IslandSizeY = a_IniFile.GetValueSetI("Generator", "EndGenIslandSizeY", m_IslandSizeY);

View File

@ -23,8 +23,6 @@ class cEndGen :
public:
cEndGen(int a_Seed);
void Initialize(cIniFile & a_IniFile);
protected:
/// Seed for the noise
@ -66,4 +64,5 @@ protected:
// cTerrainCompositionGen overrides:
virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override;
virtual void InitializeCompoGen(cIniFile & a_IniFile) override;
} ;

View File

@ -135,6 +135,15 @@ void cFinishGenNetherClumpFoliage::TryPlaceClump(cChunkDesc & a_ChunkDesc, int a
int zz = a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z;
for (int y = MinY; y < MaxY; y++)
{
if (
((x < 0) || (x >= cChunkDef::Width)) ||
((y < 0) || (y >= cChunkDef::Height)) ||
((z < 0) || (z >= cChunkDef::Width))
)
{
continue;
}
if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR) // Don't replace non air blocks.
{
continue;

View File

@ -241,7 +241,8 @@ bool cMobSpawner::CanSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_R
(m_Random.NextInt(2, a_Biome) == 0)
);
}
case cMonster::mtMagmaCube:
case cMonster::mtSlime:
{
return (

View File

@ -1015,7 +1015,7 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
(a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand
(GetWorld()->GetTimeOfDay() < (12000 + 1000)) && // It is nighttime
!IsOnFire() && // Not already burning
(GetWorld()->GetWeather() != eWeather_Rain) // Not raining
GetWorld()->IsWeatherWetAt(POSX_TOINT, POSZ_TOINT) // Not raining
)
{
// Burn for 100 ticks, then decide again

View File

@ -100,7 +100,7 @@ public:
virtual void SendPlayerSpawn (const cPlayer & a_Player) = 0;
virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) = 0;
virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) = 0;
virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) = 0;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) = 0;
virtual void SendExperience (void) = 0;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) = 0;
virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) = 0;

View File

@ -833,23 +833,23 @@ void cProtocol125::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effect
void cProtocol125::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks)
void cProtocol125::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks)
{
cCSLock Lock(m_CSPacket);
if ((m_LastSentDimension == a_World.GetDimension()) && !a_ShouldIgnoreDimensionChecks)
if ((m_LastSentDimension == a_Dimension) && !a_ShouldIgnoreDimensionChecks)
{
// Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do (unless we are respawning from death)
return;
}
cPlayer * Player = m_Client->GetPlayer();
WriteByte (PACKET_RESPAWN);
WriteInt (a_World.GetDimension());
WriteInt ((int)(a_Dimension));
WriteByte (2); // TODO: Difficulty; 2 = Normal
WriteChar ((char)Player->GetGameMode());
WriteShort (256); // Current world height
WriteString("default");
Flush();
m_LastSentDimension = a_World.GetDimension();
m_LastSentDimension = a_Dimension;
}

View File

@ -72,7 +72,7 @@ public:
virtual void SendPlayerSpawn (const cPlayer & a_Player) override;
virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override;
virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override;
virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) override;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override;
virtual void SendExperience (void) override;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override;
virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override;

View File

@ -158,10 +158,10 @@ void cProtocol161::SendPlayerMaxSpeed(void)
void cProtocol161::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks)
void cProtocol161::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks)
{
// Besides sending the respawn, we need to also send the player max speed, otherwise the client reverts to super-fast
super::SendRespawn(a_World, a_ShouldIgnoreDimensionChecks);
super::SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks);
SendPlayerMaxSpeed();
}

View File

@ -42,7 +42,7 @@ protected:
virtual void SendGameMode (eGameMode a_GameMode) override;
virtual void SendHealth (void) override;
virtual void SendPlayerMaxSpeed(void) override;
virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) override;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override;
virtual void SendWindowOpen (const cWindow & a_Window) override;
virtual int ParseEntityAction (void) override;

View File

@ -986,9 +986,9 @@ void cProtocol172::SendRemoveEntityEffect(const cEntity & a_Entity, int a_Effect
void cProtocol172::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks)
void cProtocol172::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks)
{
if ((m_LastSentDimension == a_World.GetDimension()) && !a_ShouldIgnoreDimensionChecks)
if ((m_LastSentDimension == a_Dimension) && !a_ShouldIgnoreDimensionChecks)
{
// Must not send a respawn for the world with the same dimension, the client goes cuckoo if we do (unless we are respawning from death)
return;
@ -996,11 +996,11 @@ void cProtocol172::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimens
cPacketizer Pkt(*this, 0x07); // Respawn packet
cPlayer * Player = m_Client->GetPlayer();
Pkt.WriteInt(a_World.GetDimension());
Pkt.WriteInt((int)a_Dimension);
Pkt.WriteByte(2); // TODO: Difficulty (set to Normal)
Pkt.WriteByte((Byte)Player->GetEffectiveGameMode());
Pkt.WriteString("default");
m_LastSentDimension = a_World.GetDimension();
m_LastSentDimension = a_Dimension;
}

View File

@ -104,7 +104,7 @@ public:
virtual void SendPlayerSpawn (const cPlayer & a_Player) override;
virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override;
virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override;
virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) override;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override;
virtual void SendSoundEffect (const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch) override;
virtual void SendExperience (void) override;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override;

View File

@ -556,10 +556,10 @@ void cProtocolRecognizer::SendRemoveEntityEffect(const cEntity & a_Entity, int a
void cProtocolRecognizer::SendRespawn(const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks)
void cProtocolRecognizer::SendRespawn(eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks)
{
ASSERT(m_Protocol != NULL);
m_Protocol->SendRespawn(a_World, a_ShouldIgnoreDimensionChecks);
m_Protocol->SendRespawn(a_Dimension, a_ShouldIgnoreDimensionChecks);
}

View File

@ -107,7 +107,7 @@ public:
virtual void SendPlayerSpawn (const cPlayer & a_Player) override;
virtual void SendPluginMessage (const AString & a_Channel, const AString & a_Message) override;
virtual void SendRemoveEntityEffect (const cEntity & a_Entity, int a_EffectID) override;
virtual void SendRespawn (const cWorld & a_World, bool a_ShouldIgnoreDimensionChecks = false) override;
virtual void SendRespawn (eDimension a_Dimension, bool a_ShouldIgnoreDimensionChecks) override;
virtual void SendExperience (void) override;
virtual void SendExperienceOrb (const cExpOrb & a_ExpOrb) override;
virtual void SendScoreboardObjective (const AString & a_Name, const AString & a_DisplayName, Byte a_Mode) override;

View File

@ -312,13 +312,15 @@ void cRoot::LoadWorlds(cIniFile & IniFile)
cWorld * cRoot::CreateAndInitializeWorld(const AString & a_WorldName)
cWorld * cRoot::CreateAndInitializeWorld(const AString & a_WorldName, eDimension a_Dimension, const AString & a_OverworldName)
{
if (m_WorldsByName[a_WorldName] != NULL)
cWorld * World = m_WorldsByName[a_WorldName];
if (World != NULL)
{
return NULL;
return World;
}
cWorld* NewWorld = new cWorld(a_WorldName.c_str());
cWorld * NewWorld = new cWorld(a_WorldName.c_str(), a_Dimension, a_OverworldName);
m_WorldsByName[a_WorldName] = NewWorld;
NewWorld->Start();
NewWorld->InitializeSpawn();
@ -370,7 +372,7 @@ void cRoot::UnloadWorlds(void)
cWorld* cRoot::GetDefaultWorld()
cWorld * cRoot::GetDefaultWorld()
{
return m_pDefaultWorld;
}
@ -379,12 +381,19 @@ cWorld* cRoot::GetDefaultWorld()
cWorld* cRoot::GetWorld( const AString & a_WorldName)
cWorld * cRoot::GetWorld(const AString & a_WorldName, bool a_SearchForFolder)
{
WorldMap::iterator itr = m_WorldsByName.find( a_WorldName);
WorldMap::iterator itr = m_WorldsByName.find(a_WorldName);
if (itr != m_WorldsByName.end())
{
return itr->second;
return 0;
}
if (a_SearchForFolder && cFile::IsFolder(FILE_IO_PREFIX + a_WorldName))
{
return CreateAndInitializeWorld(a_WorldName);
}
return NULL;
}
@ -396,9 +405,12 @@ bool cRoot::ForEachWorld(cWorldListCallback & a_Callback)
for (WorldMap::iterator itr = m_WorldsByName.begin(), itr2 = itr; itr != m_WorldsByName.end(); itr = itr2)
{
++itr2;
if (a_Callback.Item(itr->second))
if (itr->second != NULL)
{
return false;
if (a_Callback.Item(itr->second))
{
return false;
}
}
}
return true;

View File

@ -53,8 +53,17 @@ public:
// tolua_begin
cServer * GetServer(void) { return m_Server; }
cWorld * GetDefaultWorld(void);
cWorld * GetWorld(const AString & a_WorldName);
cWorld * CreateAndInitializeWorld(const AString & a_WorldName);
/** Returns a pointer to the world specified
If no world of that name was currently loaded and a_SearchForFolder was true, it will consult cFile::IsFolder() to see if a world folder of that name exists and if so, initialise a world based on that name
*/
cWorld * GetWorld(const AString & a_WorldName, bool a_SearchForFolder = false);
/** Returns a pointer to a world of specified name - will search loaded worlds first, then create anew if not found
The dimension parameter is used to create a world with a specific dimension
a_OverworldName should be set for non-overworld dimensions if one wishes that world to link back to an overworld via portals
*/
cWorld * CreateAndInitializeWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_OverworldName = "");
// tolua_end
/// Calls the callback for each world; returns true if the callback didn't abort (return true)

View File

@ -274,7 +274,7 @@ void cWindow::OpenedByPlayer(cPlayer & a_Player)
bool cWindow::ClosedByPlayer(cPlayer & a_Player, bool a_CanRefuse)
{
// Checks whether the player is still holding an item
if (a_Player.IsDraggingItem())
if (!a_Player.GetDraggingItem().IsEmpty())
{
LOGD("Player holds item! Dropping it...");
a_Player.TossHeldItem(a_Player.GetDraggingItem().m_ItemCount);

View File

@ -230,8 +230,9 @@ void cWorld::cTickThread::Execute(void)
////////////////////////////////////////////////////////////////////////////////
// cWorld:
cWorld::cWorld(const AString & a_WorldName) :
cWorld::cWorld(const AString & a_WorldName, eDimension a_Dimension, const AString & a_OverworldName) :
m_WorldName(a_WorldName),
m_OverworldName(a_OverworldName),
m_IniFileName(m_WorldName + "/world.ini"),
m_StorageSchema("Default"),
#ifdef __arm__
@ -239,6 +240,7 @@ cWorld::cWorld(const AString & a_WorldName) :
#else
m_StorageCompressionFactor(6),
#endif
m_Dimension(a_Dimension),
m_IsSpawnExplicitlySet(false),
m_WorldAgeSecs(0),
m_TimeOfDaySecs(0),
@ -518,9 +520,15 @@ void cWorld::Start(void)
if (!IniFile.ReadFile(m_IniFileName))
{
LOGWARNING("Cannot read world settings from \"%s\", defaults will be used.", m_IniFileName.c_str());
// TODO: More descriptions for each key
IniFile.AddHeaderComment(" This is the per-world configuration file, managing settings such as generators, simulators, and spawn points");
IniFile.AddKeyComment(" LinkedWorlds", "This section governs portal world linkage; leave a value blank to disabled that associated method of teleportation");
}
AString Dimension = IniFile.GetValueSet("General", "Dimension", "Overworld");
m_Dimension = StringToDimension(Dimension);
// The presence of a configuration value overrides everything
// If no configuration value is found, GetDimension() is written to file and the variable is written to again to ensure that cosmic rays haven't sneakily changed its value
m_Dimension = StringToDimension(IniFile.GetValueSet("General", "Dimension", DimensionToString(GetDimension())));
// Try to find the "SpawnPosition" key and coord values in the world configuration, set the flag if found
int KeyNum = IniFile.FindKey("SpawnPosition");
@ -528,8 +536,8 @@ void cWorld::Start(void)
(
(KeyNum >= 0) &&
(
(IniFile.FindValue(KeyNum, "X") >= 0) ||
(IniFile.FindValue(KeyNum, "Y") >= 0) ||
(IniFile.FindValue(KeyNum, "X") >= 0) &&
(IniFile.FindValue(KeyNum, "Y") >= 0) &&
(IniFile.FindValue(KeyNum, "Z") >= 0)
)
);
@ -565,36 +573,26 @@ void cWorld::Start(void)
m_bUseChatPrefixes = IniFile.GetValueSetB("Mechanics", "UseChatPrefixes", true);
m_VillagersShouldHarvestCrops = IniFile.GetValueSetB("Monsters", "VillagersShouldHarvestCrops", true);
int GameMode = IniFile.GetValueSetI("General", "Gamemode", (int)m_GameMode);
int Weather = IniFile.GetValueSetI("General", "Weather", (int)m_Weather);
m_TimeOfDay = IniFile.GetValueSetI("General", "TimeInTicks", m_TimeOfDay);
if (GetDimension() == dimOverworld)
{
m_NetherWorldName = IniFile.GetValueSet("LinkedWorlds", "NetherWorldName", GetName() + "_nether");
m_EndWorldName = IniFile.GetValueSet("LinkedWorlds", "EndWorldName", GetName() + "_end");
}
else
{
m_OverworldName = IniFile.GetValueSet("LinkedWorlds", "OverworldName", GetLinkedOverworldName());
}
// Adjust the enum-backed variables into their respective bounds:
m_GameMode = (eGameMode) Clamp(GameMode, (int)gmSurvival, (int)gmAdventure);
m_TNTShrapnelLevel = (eShrapnelLevel)Clamp(TNTShrapnelLevel, (int)slNone, (int)slAll);
m_Weather = (eWeather) Clamp(Weather, (int)wSunny, (int)wStorm);
// Load allowed mobs:
AString DefaultMonsters;
switch (m_Dimension)
{
case dimOverworld: DefaultMonsters = "bat, cavespider, chicken, cow, creeper, enderman, horse, mooshroom, ocelot, pig, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie"; break;
case dimNether: DefaultMonsters = "blaze, ghast, magmacube, skeleton, zombie, zombiepigman"; break;
case dimEnd: DefaultMonsters = "enderman"; break;
case dimNotSet: break;
}
m_bAnimals = IniFile.GetValueSetB("Monsters", "AnimalsOn", true);
AString AllMonsters = IniFile.GetValueSet("Monsters", "Types", DefaultMonsters);
AStringVector SplitList = StringSplitAndTrim(AllMonsters, ",");
for (AStringVector::const_iterator itr = SplitList.begin(), end = SplitList.end(); itr != end; ++itr)
{
cMonster::eType ToAdd = cMonster::StringToMobType(*itr);
if (ToAdd != cMonster::mtInvalidType)
{
m_AllowedMobs.insert(ToAdd);
LOGD("Allowed mob: %s", itr->c_str());
}
else
{
LOG("World \"%s\": Unknown mob type: %s", m_WorldName.c_str(), itr->c_str());
}
}
InitialiseGeneratorDefaults(IniFile);
InitialiseAndLoadMobSpawningValues(IniFile);
m_ChunkMap = new cChunkMap(this);
@ -691,6 +689,82 @@ eWeather cWorld::ChooseNewWeather()
void cWorld::InitialiseGeneratorDefaults(cIniFile & a_IniFile)
{
switch (GetDimension())
{
case dimEnd:
{
a_IniFile.GetValueSet("Generator", "BiomeGen", "Constant");
a_IniFile.GetValueSet("Generator", "ConstantBiome", "End");
a_IniFile.GetValueSet("Generator", "HeightGen", "Biomal");
a_IniFile.GetValueSet("Generator", "CompositionGen", "End");
break;
}
case dimOverworld:
{
a_IniFile.GetValueSet("Generator", "BiomeGen", "MultiStepMap");
a_IniFile.GetValueSet("Generator", "HeightGen", "DistortedHeightmap");
a_IniFile.GetValueSet("Generator", "CompositionGen", "DistortedHeightmap");
a_IniFile.GetValueSet("Generator", "Finishers", "Ravines, WormNestCaves, WaterLakes, WaterSprings, LavaLakes, LavaSprings, OreNests, Mineshafts, Trees, SprinkleFoliage, Ice, Snow, Lilypads, BottomLava, DeadBushes, PreSimulator");
break;
}
case dimNether:
{
a_IniFile.GetValueSet("Generator", "BiomeGen", "Constant");
a_IniFile.GetValueSet("Generator", "ConstantBiome", "Nether");
a_IniFile.GetValueSet("Generator", "HeightGen", "Flat");
a_IniFile.GetValueSet("Generator", "FlatHeight", "128");
a_IniFile.GetValueSet("Generator", "CompositionGen", "Nether");
a_IniFile.GetValueSet("Generator", "Finishers", "WormNestCaves, BottomLava, LavaSprings, NetherClumpFoliage, NetherForts, PreSimulator");
a_IniFile.GetValueSet("Generator", "BottomLavaHeight", "30");
break;
}
}
}
void cWorld::InitialiseAndLoadMobSpawningValues(cIniFile & a_IniFile)
{
AString DefaultMonsters;
switch (m_Dimension)
{
case dimOverworld: DefaultMonsters = "bat, cavespider, chicken, cow, creeper, enderman, horse, mooshroom, ocelot, pig, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie"; break;
case dimNether: DefaultMonsters = "blaze, ghast, magmacube, skeleton, zombie, zombiepigman"; break;
case dimEnd: DefaultMonsters = "enderman"; break;
}
m_bAnimals = a_IniFile.GetValueSetB("Monsters", "AnimalsOn", true);
AString AllMonsters = a_IniFile.GetValueSet("Monsters", "Types", DefaultMonsters);
if (!m_bAnimals)
{
return;
}
AStringVector SplitList = StringSplitAndTrim(AllMonsters, ",");
for (AStringVector::const_iterator itr = SplitList.begin(), end = SplitList.end(); itr != end; ++itr)
{
cMonster::eType ToAdd = cMonster::StringToMobType(*itr);
if (ToAdd != cMonster::mtInvalidType)
{
m_AllowedMobs.insert(ToAdd);
LOGD("Allowed mob: %s", itr->c_str());
}
else
{
LOG("World \"%s\": Unknown mob type: %s", m_WorldName.c_str(), itr->c_str());
}
}
}
void cWorld::Stop(void)
{
// Delete the clients that have been in this world:
@ -703,6 +777,25 @@ void cWorld::Stop(void)
} // for itr - m_Clients[]
m_Clients.clear();
}
// Write settings to file; these are all plugin changeable values - keep updated!
cIniFile IniFile;
IniFile.ReadFile(m_IniFileName);
if (GetDimension() == dimOverworld)
{
IniFile.SetValue("LinkedWorlds", "NetherWorldName", m_NetherWorldName);
IniFile.SetValue("LinkedWorlds", "EndWorldName", m_EndWorldName);
}
else
{
IniFile.SetValue("LinkedWorlds", "OverworldName", m_OverworldName);
}
IniFile.SetValueI("Physics", "TNTShrapnelLevel", (int)m_TNTShrapnelLevel);
IniFile.SetValueB("Mechanics", "CommandBlocksEnabled", m_bCommandBlocksEnabled);
IniFile.SetValueB("Mechanics", "UseChatPrefixes", m_bUseChatPrefixes);
IniFile.SetValueI("General", "Weather", (int)m_Weather);
IniFile.SetValueI("General", "TimeInTicks", m_TimeOfDay);
IniFile.WriteFile(m_IniFileName);
m_TickThread.Stop();
m_Lighting.Stop();
@ -955,11 +1048,7 @@ void cWorld::TickClients(float a_Dt)
// Add clients scheduled for adding:
for (cClientHandleList::iterator itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
{
if (std::find(m_Clients.begin(), m_Clients.end(), *itr) != m_Clients.end())
{
ASSERT(!"Adding a client that is already in the clientlist");
continue;
}
ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end());
m_Clients.push_back(*itr);
} // for itr - m_ClientsToRemove[]
m_ClientsToAdd.clear();
@ -2378,8 +2467,10 @@ void cWorld::AddPlayer(cPlayer * a_Player)
void cWorld::RemovePlayer(cPlayer * a_Player)
{
m_ChunkMap->RemoveEntity(a_Player);
if (!a_Player->IsWorldTravellingFrom(this))
{
m_ChunkMap->RemoveEntity(a_Player);
}
{
cCSLock Lock(m_CSPlayersToAdd);
m_PlayersToAdd.remove(a_Player);
@ -2882,15 +2973,6 @@ bool cWorld::HasEntity(int a_UniqueID)
void cWorld::RemoveEntity(cEntity * a_Entity)
{
m_ChunkMap->RemoveEntity(a_Entity);
}
/*
unsigned int cWorld::GetNumPlayers(void)
{
@ -3197,7 +3279,8 @@ void cWorld::AddQueuedPlayers(void)
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr)
{
ASSERT(std::find(m_Players.begin(), m_Players.end(), *itr) == m_Players.end()); // Is it already in the list? HOW?
ASSERT(std::find(m_Players.begin(), m_Players.end(), *itr) == m_Players.end()); // Is it already in the list? HOW?
LOGD("Adding player %s to world \"%s\".", (*itr)->GetName().c_str(), m_WorldName.c_str());
m_Players.push_back(*itr);
(*itr)->SetWorld(this);
@ -3227,6 +3310,9 @@ void cWorld::AddQueuedPlayers(void)
if (Client != NULL)
{
Client->StreamChunks();
Client->SendPlayerMoveLook();
Client->SendHealth();
Client->SendWholeInventory(*(*itr)->GetWindow());
}
} // for itr - PlayersToAdd[]
}

View File

@ -185,7 +185,7 @@ public:
virtual eDimension GetDimension(void) const { return m_Dimension; }
/** Returns the world height at the specified coords; waits for the chunk to get loaded / generated */
int GetHeight(int a_BlockX, int a_BlockZ);
virtual int GetHeight(int a_BlockX, int a_BlockZ);
// tolua_end
@ -304,9 +304,6 @@ public:
bool HasEntity(int a_UniqueID);
/** Removes the entity, the entity ptr ownership is assumed taken by the caller */
void RemoveEntity(cEntity * a_Entity);
/** Calls the callback for each entity in the entire world; returns true if all entities processed, false if the callback aborted by returning true */
bool ForEachEntity(cEntityCallback & a_Callback); // Exported in ManualBindings.cpp
@ -622,6 +619,15 @@ public:
bool ShouldUseChatPrefixes(void) const { return m_bUseChatPrefixes; }
void SetShouldUseChatPrefixes(bool a_Flag) { m_bUseChatPrefixes = a_Flag; }
AString GetNetherWorldName(void) const { return m_NetherWorldName; }
void SetNetherWorldName(const AString & a_Name) { m_NetherWorldName = a_Name; }
AString GetEndWorldName(void) const { return m_EndWorldName; }
void SetEndWorldName(const AString & a_Name) { m_EndWorldName = a_Name; }
AString GetLinkedOverworldName(void) const { return m_OverworldName; }
void SetLinkedOverworldName(const AString & a_Name) { m_OverworldName = a_Name; }
// tolua_end
@ -705,7 +711,7 @@ public:
/** Returns true if the current weather is stormy */
bool IsWeatherStorm(void) const { return (m_Weather == wStorm); }
/** Returns true if the weather is stormy at the specified location. This takes into account biomes. */
bool IsWeatherStormAt(int a_BlockX, int a_BlockZ)
{
@ -716,10 +722,11 @@ public:
bool IsWeatherWet(void) const { return !IsWeatherSunny(); }
/** Returns true if it is raining, stormy or snowing at the specified location. This takes into account biomes. */
bool IsWeatherWetAt(int a_BlockX, int a_BlockZ)
virtual bool IsWeatherWetAt(int a_BlockX, int a_BlockZ)
{
return (IsWeatherWet() && !IsBiomeNoDownfall(GetBiomeAt(a_BlockX, a_BlockZ)));
}
// tolua_end
cChunkGenerator & GetGenerator(void) { return m_Generator; }
@ -824,6 +831,12 @@ private:
AString m_WorldName;
/** The name of the world that a portal in this world should link to
Only has effect if this world is a nether or end world, as it is used by entities to see which world to teleport to when in a portal
*/
AString m_OverworldName;
AString m_IniFileName;
/** Name of the storage schema used to load and save chunks */
@ -908,6 +921,12 @@ private:
See the eShrapnelLevel enumeration for details
*/
eShrapnelLevel m_TNTShrapnelLevel;
/** Name of the nether world */
AString m_NetherWorldName;
/** Name of the end world */
AString m_EndWorldName;
cChunkGenerator m_Generator;
@ -967,7 +986,7 @@ private:
cSetChunkDataPtrs m_SetChunkDataQueue;
cWorld(const AString & a_WorldName);
cWorld(const AString & a_WorldName, eDimension a_Dimension = dimOverworld, const AString & a_OverworldName = "");
virtual ~cWorld();
void Tick(float a_Dt, int a_LastTickDurationMSec);
@ -1008,9 +1027,16 @@ private:
Assumes it is called from the Tick thread. */
void AddQueuedPlayers(void);
/** Sets generator values to dimension specific defaults, if those values do not exist */
void InitialiseGeneratorDefaults(cIniFile & a_IniFile);
/** Sets mob spawning values if nonexistant to their dimension specific defaults */
void InitialiseAndLoadMobSpawningValues(cIniFile & a_IniFile);
/** Sets the specified chunk data into the chunkmap. Called in the tick thread.
Modifies the a_SetChunkData - moves the entities contained in it into the chunk. */
void SetChunkData(cSetChunkData & a_SetChunkData);
}; // tolua_export

View File

@ -486,6 +486,7 @@ void cNBTChunkSerializer::AddMonsterEntity(cMonster * a_Monster)
m_Writer.AddFloat("", a_Monster->GetDropChanceBoots());
m_Writer.EndList();
m_Writer.AddByte("CanPickUpLoot", (char)a_Monster->CanPickUpLoot());
m_Writer.AddShort("Health", (short)a_Monster->GetHealth());
switch (a_Monster->GetMobType())
{
case cMonster::mtBat:

View File

@ -2499,8 +2499,16 @@ bool cWSSAnvil::LoadMonsterBaseFromNBT(cMonster & a_Monster, const cParsedNBT &
a_Monster.SetDropChanceChestplate(DropChance[2]);
a_Monster.SetDropChanceLeggings(DropChance[3]);
a_Monster.SetDropChanceBoots(DropChance[4]);
bool CanPickUpLoot = (a_NBT.GetByte(a_NBT.FindChildByName(a_TagIdx, "CanPickUpLoot")) == 1);
a_Monster.SetCanPickUpLoot(CanPickUpLoot);
int LootTag = a_NBT.FindChildByName(a_TagIdx, "CanPickUpLoot");
if (LootTag > 0)
{
bool CanPickUpLoot = (a_NBT.GetByte(LootTag) == 1);
a_Monster.SetCanPickUpLoot(CanPickUpLoot);
}
int HealthTag = a_NBT.FindChildByName(a_TagIdx, "Health");
a_Monster.SetHealth(HealthTag > 0 ? a_NBT.GetShort(HealthTag) : a_Monster.GetMaxHealth());
return true;
}