summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/BlockInfo.cpp1
-rw-r--r--src/Chunk.cpp151
-rw-r--r--src/Chunk.h34
-rw-r--r--src/ChunkMap.cpp20
-rw-r--r--src/ChunkMap.h7
-rw-r--r--src/Entities/ArrowEntity.cpp64
-rw-r--r--src/Entities/ArrowEntity.h6
-rw-r--r--src/Entities/Entity.cpp12
-rw-r--r--src/Entities/Entity.h4
-rw-r--r--src/Entities/Pickup.cpp2
-rw-r--r--src/Entities/Player.cpp216
-rw-r--r--src/Entities/Player.h43
-rw-r--r--src/Entities/ProjectileEntity.cpp71
-rw-r--r--src/Entities/ProjectileEntity.h34
-rw-r--r--src/Entities/ThrownEggEntity.cpp7
-rw-r--r--src/Entities/ThrownEggEntity.h21
-rw-r--r--src/Entities/ThrownEnderPearlEntity.cpp40
-rw-r--r--src/Entities/ThrownEnderPearlEntity.h25
-rw-r--r--src/Entities/ThrownSnowballEntity.cpp7
-rw-r--r--src/Entities/ThrownSnowballEntity.h21
-rw-r--r--src/Generating/Prefabs/AlchemistVillagePrefabs.cpp10
-rw-r--r--src/Generating/Prefabs/JapaneseVillagePrefabs.cpp2
-rw-r--r--src/Generating/Prefabs/PlainsVillagePrefabs.cpp14
-rw-r--r--src/Items/ItemBow.h13
-rw-r--r--src/Items/ItemBucket.h73
-rw-r--r--src/Mobs/Creeper.cpp22
-rw-r--r--src/Mobs/Skeleton.cpp5
-rw-r--r--src/Mobs/Zombie.cpp5
-rw-r--r--src/Protocol/Protocol17x.cpp2
-rw-r--r--src/Server.cpp3
-rw-r--r--src/Server.h20
-rw-r--r--src/UI/SlotArea.cpp237
-rw-r--r--src/UI/SlotArea.h19
-rw-r--r--src/UI/Window.cpp22
-rw-r--r--src/Vector3.h30
-rw-r--r--src/World.cpp18
-rw-r--r--src/World.h8
-rw-r--r--src/WorldStorage/NBTChunkSerializer.cpp28
-rw-r--r--src/WorldStorage/WSSAnvil.cpp11
39 files changed, 943 insertions, 385 deletions
diff --git a/src/BlockInfo.cpp b/src/BlockInfo.cpp
index 6ceb2c697..4fc1d602c 100644
--- a/src/BlockInfo.cpp
+++ b/src/BlockInfo.cpp
@@ -368,7 +368,6 @@ void cBlockInfo::Initialize(cBlockInfoArray & a_Info)
a_Info[E_BLOCK_LIGHT_WEIGHTED_PRESSURE_PLATE].m_IsSolid = false;
a_Info[E_BLOCK_MELON_STEM ].m_IsSolid = false;
a_Info[E_BLOCK_NETHER_PORTAL ].m_IsSolid = false;
- a_Info[E_BLOCK_PISTON_EXTENSION ].m_IsSolid = false;
a_Info[E_BLOCK_POTATOES ].m_IsSolid = false;
a_Info[E_BLOCK_POWERED_RAIL ].m_IsSolid = false;
a_Info[E_BLOCK_RAIL ].m_IsSolid = false;
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index 3c63a2dc9..c004a6408 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -87,7 +87,8 @@ cChunk::cChunk(
m_NeighborZM(a_NeighborZM),
m_NeighborZP(a_NeighborZP),
m_WaterSimulatorData(a_World->GetWaterSimulator()->CreateChunkData()),
- m_LavaSimulatorData (a_World->GetLavaSimulator ()->CreateChunkData())
+ m_LavaSimulatorData (a_World->GetLavaSimulator ()->CreateChunkData()),
+ m_AlwaysTicked(0)
{
if (a_NeighborXM != NULL)
{
@@ -463,7 +464,7 @@ void cChunk::CollectMobCensus(cMobCensus& toFill)
-void cChunk::getThreeRandomNumber(int& a_X, int& a_Y, int& a_Z,int a_MaxX, int a_MaxY, int a_MaxZ)
+void cChunk::GetThreeRandomNumbers(int & a_X, int & a_Y, int & a_Z,int a_MaxX, int a_MaxY, int a_MaxZ)
{
ASSERT(a_MaxX * a_MaxY * a_MaxZ * 8 < 0x00ffffff);
int Random = m_World->GetTickRandomNumber(0x00ffffff);
@@ -479,12 +480,12 @@ void cChunk::getThreeRandomNumber(int& a_X, int& a_Y, int& a_Z,int a_MaxX, int a
-void cChunk::getRandomBlockCoords(int& a_X, int& a_Y, int& a_Z)
+void cChunk::GetRandomBlockCoords(int & a_X, int & a_Y, int & a_Z)
{
// MG TODO : check if this kind of optimization (only one random call) is still needed
// MG TODO : if so propagate it
- getThreeRandomNumber(a_X, a_Y, a_Z, Width, Height-2, Width);
+ GetThreeRandomNumbers(a_X, a_Y, a_Z, Width, Height - 2, Width);
a_Y++;
}
@@ -494,65 +495,68 @@ void cChunk::getRandomBlockCoords(int& a_X, int& a_Y, int& a_Z)
void cChunk::SpawnMobs(cMobSpawner& a_MobSpawner)
{
- int Center_X,Center_Y,Center_Z;
- getRandomBlockCoords(Center_X,Center_Y,Center_Z);
-
- BLOCKTYPE PackCenterBlock = GetBlock(Center_X, Center_Y, Center_Z);
- if (a_MobSpawner.CheckPackCenter(PackCenterBlock))
- {
- a_MobSpawner.NewPack();
- int NumberOfTries = 0;
- int NumberOfSuccess = 0;
- int MaxNbOfSuccess = 4; // this can be changed during the process for Wolves and Ghass
- while (NumberOfTries < 12 && NumberOfSuccess < MaxNbOfSuccess)
- {
- const int HorizontalRange = 20; // MG TODO : relocate
- const int VerticalRange = 0; // MG TODO : relocate
- int Try_X, Try_Y, Try_Z;
- getThreeRandomNumber(Try_X, Try_Y, Try_Z, 2*HorizontalRange+1 , 2*VerticalRange+1 , 2*HorizontalRange+1);
- Try_X -= HorizontalRange;
- Try_Y -= VerticalRange;
- Try_Z -= HorizontalRange;
- Try_X += Center_X;
- Try_Y += Center_Y;
- Try_Z += Center_Z;
-
- ASSERT(Try_Y > 0);
- ASSERT(Try_Y < cChunkDef::Height-1);
-
- EMCSBiome Biome = m_ChunkMap->GetBiomeAt (Try_X, Try_Z);
- // MG TODO :
- // Moon cycle (for slime)
- // check player and playerspawn presence < 24 blocks
- // check mobs presence on the block
-
- // MG TODO : check that "Level" really means Y
-
- /*
- NIBBLETYPE SkyLight = 0;
-
- NIBBLETYPE BlockLight = 0;
- */
+ int CenterX, CenterY, CenterZ;
+ GetRandomBlockCoords(CenterX, CenterY, CenterZ);
- if (IsLightValid())
- {
- cEntity* newMob = a_MobSpawner.TryToSpawnHere(this, Try_X, Try_Y, Try_Z, Biome, MaxNbOfSuccess);
- if (newMob)
- {
- int WorldX, WorldY, WorldZ;
- PositionToWorldPosition(Try_X, Try_Y, Try_Z, WorldX, WorldY, WorldZ);
- double ActualX = WorldX + 0.5;
- double ActualZ = WorldZ + 0.5;
- newMob->SetPosition(ActualX, WorldY, ActualZ);
- LOGD("Spawning %s #%i at %d,%d,%d",newMob->GetClass(),newMob->GetUniqueID(),WorldX, WorldY, WorldZ);
- NumberOfSuccess++;
- }
- }
-
- NumberOfTries++;
- }
+ BLOCKTYPE PackCenterBlock = GetBlock(CenterX, CenterY, CenterZ);
+ if (!a_MobSpawner.CheckPackCenter(PackCenterBlock))
+ {
+ return;
}
+
+ a_MobSpawner.NewPack();
+ int NumberOfTries = 0;
+ int NumberOfSuccess = 0;
+ int MaxNbOfSuccess = 4; // This can be changed during the process for Wolves and Ghasts
+ while ((NumberOfTries < 12) && (NumberOfSuccess < MaxNbOfSuccess))
+ {
+ const int HorizontalRange = 20; // MG TODO : relocate
+ const int VerticalRange = 0; // MG TODO : relocate
+ int TryX, TryY, TryZ;
+ GetThreeRandomNumbers(TryX, TryY, TryZ, 2 * HorizontalRange + 1, 2 * VerticalRange + 1, 2 * HorizontalRange + 1);
+ TryX -= HorizontalRange;
+ TryY -= VerticalRange;
+ TryZ -= HorizontalRange;
+ TryX += CenterX;
+ TryY += CenterY;
+ TryZ += CenterZ;
+
+ ASSERT(TryY > 0);
+ ASSERT(TryY < cChunkDef::Height - 1);
+
+ EMCSBiome Biome = m_ChunkMap->GetBiomeAt(TryX, TryZ);
+ // MG TODO :
+ // Moon cycle (for slime)
+ // check player and playerspawn presence < 24 blocks
+ // check mobs presence on the block
+
+ // MG TODO : check that "Level" really means Y
+
+ /*
+ NIBBLETYPE SkyLight = 0;
+
+ NIBBLETYPE BlockLight = 0;
+ */
+ NumberOfTries++;
+ if (!IsLightValid())
+ {
+ continue;
+ }
+
+ cEntity * newMob = a_MobSpawner.TryToSpawnHere(this, TryX, TryY, TryZ, Biome, MaxNbOfSuccess);
+ if (newMob == NULL)
+ {
+ continue;
+ }
+ int WorldX, WorldY, WorldZ;
+ PositionToWorldPosition(TryX, TryY, TryZ, WorldX, WorldY, WorldZ);
+ double ActualX = WorldX + 0.5;
+ double ActualZ = WorldZ + 0.5;
+ newMob->SetPosition(ActualX, WorldY, ActualZ);
+ LOGD("Spawning %s #%i at {%d, %d, %d}", newMob->GetClass(), newMob->GetUniqueID(), WorldX, WorldY, WorldZ);
+ NumberOfSuccess++;
+ } // while (retry)
}
@@ -1643,6 +1647,31 @@ cBlockEntity * cChunk::GetBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ)
+bool cChunk::ShouldBeTicked(void) const
+{
+ return (HasAnyClients() || (m_AlwaysTicked > 0));
+}
+
+
+
+
+
+void cChunk::SetAlwaysTicked(bool a_AlwaysTicked)
+{
+ if (a_AlwaysTicked)
+ {
+ m_AlwaysTicked += 1;
+ }
+ else
+ {
+ m_AlwaysTicked -= 1;
+ }
+}
+
+
+
+
+
void cChunk::UseBlockEntity(cPlayer * a_Player, int a_X, int a_Y, int a_Z)
{
cBlockEntity * be = GetBlockEntity(a_X, a_Y, a_Z);
@@ -1854,7 +1883,7 @@ bool cChunk::HasClient( cClientHandle* a_Client )
-bool cChunk::HasAnyClients(void)
+bool cChunk::HasAnyClients(void) const
{
return !m_LoadedByClient.empty();
}
diff --git a/src/Chunk.h b/src/Chunk.h
index e9d964e05..90664b513 100644
--- a/src/Chunk.h
+++ b/src/Chunk.h
@@ -200,11 +200,16 @@ public:
void SendBlockTo(int a_RelX, int a_RelY, int a_RelZ, cClientHandle * a_Client);
/** Adds a client to the chunk; returns true if added, false if already there */
- bool AddClient (cClientHandle* a_Client );
+ bool AddClient(cClientHandle * a_Client);
- void RemoveClient (cClientHandle* a_Client );
- bool HasClient (cClientHandle* a_Client );
- bool HasAnyClients(void); // Returns true if theres any client in the chunk; false otherwise
+ /** Removes the specified client from the chunk; ignored if client not in chunk. */
+ void RemoveClient(cClientHandle * a_Client);
+
+ /** Returns true if the specified client is present in this chunk. */
+ bool HasClient(cClientHandle * a_Client);
+
+ /** Returns true if theres any client in the chunk; false otherwise */
+ bool HasAnyClients(void) const;
void AddEntity(cEntity * a_Entity);
void RemoveEntity(cEntity * a_Entity);
@@ -390,6 +395,17 @@ public:
cBlockEntity * GetBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ);
cBlockEntity * GetBlockEntity(const Vector3i & a_BlockPos) { return GetBlockEntity(a_BlockPos.x, a_BlockPos.y, a_BlockPos.z); }
+
+ /** Returns true if the chunk should be ticked in the tick-thread.
+ Checks if there are any clients and if the always-tick flag is set */
+ bool ShouldBeTicked(void) const;
+
+ /** Increments (a_AlwaysTicked == true) or decrements (false) the m_AlwaysTicked counter.
+ If the m_AlwaysTicked counter is greater than zero, the chunk is ticked in the tick-thread regardless of
+ whether it has any clients or not.
+ This function allows nesting and task-concurrency (multiple separate tasks can request ticking and as long
+ as at least one requests is active the chunk will be ticked). */
+ void SetAlwaysTicked(bool a_AlwaysTicked);
private:
@@ -462,10 +478,16 @@ private:
/** Indicates if simulate-once blocks should be updated by the redstone simulator */
bool m_IsRedstoneDirty;
+
+ /** If greater than zero, the chunk is ticked even if it has no clients.
+ Manipulated by the SetAlwaysTicked() function, allows for nested calls of the function.
+ This is the support for plugin-accessible chunk tick forcing. */
+ int m_AlwaysTicked;
+
// Pick up a random block of this chunk
- void getRandomBlockCoords(int& a_X, int& a_Y, int& a_Z);
- void getThreeRandomNumber(int& a_X, int& a_Y, int& a_Z,int a_MaxX, int a_MaxY, int a_MaxZ);
+ void GetRandomBlockCoords(int & a_X, int & a_Y, int & a_Z);
+ void GetThreeRandomNumbers(int & a_X, int & a_Y, int & a_Z, int a_MaxX, int a_MaxY, int a_MaxZ);
void RemoveBlockEntity(cBlockEntity * a_BlockEntity);
void AddBlockEntity (cBlockEntity * a_BlockEntity);
diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp
index c53211b6b..35d55d396 100644
--- a/src/ChunkMap.cpp
+++ b/src/ChunkMap.cpp
@@ -2693,6 +2693,20 @@ void cChunkMap::QueueTickBlock(int a_BlockX, int a_BlockY, int a_BlockZ)
+void cChunkMap::SetChunkAlwaysTicked(int a_ChunkX, int a_ChunkZ, bool a_AlwaysTicked)
+{
+ cCSLock Lock(m_CSLayers);
+ cChunkPtr Chunk = GetChunkNoLoad(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ);
+ if (Chunk != NULL)
+ {
+ Chunk->SetAlwaysTicked(a_AlwaysTicked);
+ }
+}
+
+
+
+
+
////////////////////////////////////////////////////////////////////////////////
// cChunkMap::cChunkLayer:
@@ -2807,12 +2821,14 @@ void cChunkMap::cChunkLayer::SpawnMobs(cMobSpawner& a_MobSpawner)
+
+
void cChunkMap::cChunkLayer::Tick(float a_Dt)
{
for (size_t i = 0; i < ARRAYCOUNT(m_Chunks); i++)
{
- // Only tick chunks that are valid and have clients:
- if ((m_Chunks[i] != NULL) && m_Chunks[i]->IsValid() && m_Chunks[i]->HasAnyClients())
+ // Only tick chunks that are valid and should be ticked:
+ if ((m_Chunks[i] != NULL) && m_Chunks[i]->IsValid() && m_Chunks[i]->ShouldBeTicked())
{
m_Chunks[i]->Tick(a_Dt);
}
diff --git a/src/ChunkMap.h b/src/ChunkMap.h
index 9453f090f..4daa85ebd 100644
--- a/src/ChunkMap.h
+++ b/src/ChunkMap.h
@@ -340,6 +340,13 @@ public:
/** Returns the CS for locking the chunkmap; only cWorld::cLock may use this function! */
cCriticalSection & GetCS(void) { return m_CSLayers; }
+
+ /** Increments (a_AlwaysTicked == true) or decrements (false) the m_AlwaysTicked counter for the specified chunk.
+ If the m_AlwaysTicked counter is greater than zero, the chunk is ticked in the tick-thread regardless of
+ whether it has any clients or not.
+ This function allows nesting and task-concurrency (multiple separate tasks can request ticking and as long
+ as at least one requests is active the chunk will be ticked). */
+ void SetChunkAlwaysTicked(int a_ChunkX, int a_ChunkZ, bool a_AlwaysTicked);
private:
diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp
index 4727f67a9..d59088e72 100644
--- a/src/Entities/ArrowEntity.cpp
+++ b/src/Entities/ArrowEntity.cpp
@@ -72,26 +72,24 @@ bool cArrowEntity::CanPickup(const cPlayer & a_Player) const
void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace)
-{
- if (a_HitFace == BLOCK_FACE_NONE) { return; }
-
- super::OnHitSolidBlock(a_HitPos, a_HitFace);
- int a_X = (int)a_HitPos.x, a_Y = (int)a_HitPos.y, a_Z = (int)a_HitPos.z;
-
- switch (a_HitFace)
+{
+ if (GetSpeed().EqualsEps(Vector3d(0, 0, 0), 0.0000001))
{
- case BLOCK_FACE_XM: // Strangely, bounding boxes / block tracers return the actual block for these two directions, so AddFace not needed
- case BLOCK_FACE_YM:
- {
- break;
- }
- default: AddFaceDirection(a_X, a_Y, a_Z, a_HitFace, true);
+ SetSpeed(GetLookVector().NormalizeCopy() * 0.1); // Ensure that no division by zero happens later
}
-
- m_HitBlockPos = Vector3i(a_X, a_Y, a_Z);
+
+ Vector3d Hit = a_HitPos;
+ Vector3d SinkMovement = (GetSpeed() / 800);
+ Hit += (SinkMovement * 0.01) / SinkMovement.Length(); // Make arrow sink into block a centimetre so it lodges (but not to far so it goes black clientside)
+
+ super::OnHitSolidBlock(Hit, a_HitFace);
+ Vector3i BlockHit = Hit.Floor();
+
+ int X = BlockHit.x, Y = BlockHit.y, Z = BlockHit.z;
+ m_HitBlockPos = Vector3i(X, Y, Z);
// Broadcast arrow hit sound
- m_World->BroadcastSoundEffect("random.bowhit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
+ m_World->BroadcastSoundEffect("random.bowhit", X * 8, Y * 8, Z * 8, 0.5f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
}
@@ -99,13 +97,7 @@ void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFa
void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos)
-{
- if (!a_EntityHit.IsMob() && !a_EntityHit.IsMinecart() && !a_EntityHit.IsPlayer() && !a_EntityHit.IsBoat())
- {
- // Not an entity that interacts with an arrow
- return;
- }
-
+{
int Damage = (int)(GetSpeed().Length() / 20 * m_DamageCoeff + 0.5);
if (m_IsCritical)
{
@@ -114,14 +106,7 @@ void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos)
a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, 1);
// Broadcast successful hit sound
- m_World->BroadcastSoundEffect(
- "random.successful_hit",
- (int)std::floor(GetPosX() * 8.0),
- (int)std::floor(GetPosY() * 8.0),
- (int)std::floor(GetPosZ() * 8.0),
- 0.5f,
- 0.75f + ((float)((GetUniqueID() * 23) % 32)) / 64.0f
- );
+ GetWorld()->BroadcastSoundEffect("random.successful_hit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
Destroy();
}
@@ -144,21 +129,10 @@ void cArrowEntity::CollectedBy(cPlayer * a_Dest)
return;
}
}
-
- // TODO: BroadcastCollectPickup needs a cPickup, which we don't have
- // m_World->BroadcastCollectPickup(*this, *a_Dest);
+ GetWorld()->BroadcastCollectEntity(*this, *a_Dest);
+ GetWorld()->BroadcastSoundEffect("random.pop", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
m_bIsCollected = true;
-
- cFastRandom Random;
- m_World->BroadcastSoundEffect(
- "random.pop",
- (int)std::floor(GetPosX() * 8.0),
- (int)std::floor(GetPosY() * 8),
- (int)std::floor(GetPosZ() * 8),
- 0.2F,
- ((Random.NextFloat(1.0F) - Random.NextFloat(1.0F)) * 0.7F + 1.0F) * 2.0F
- );
}
}
@@ -194,7 +168,7 @@ void cArrowEntity::Tick(float a_Dt, cChunk & a_Chunk)
if (!m_HasTeleported) // Sent a teleport already, don't do again
{
- if (m_HitGroundTimer > 1000.f) // Send after a second, could be less, but just in case
+ if (m_HitGroundTimer > 500.f) // Send after half a second, could be less, but just in case
{
m_World->BroadcastTeleportEntity(*this);
m_HasTeleported = true;
diff --git a/src/Entities/ArrowEntity.h b/src/Entities/ArrowEntity.h
index 1fe3032ee..76cb24449 100644
--- a/src/Entities/ArrowEntity.h
+++ b/src/Entities/ArrowEntity.h
@@ -58,8 +58,14 @@ public:
/// Sets the IsCritical flag
void SetIsCritical(bool a_IsCritical) { m_IsCritical = a_IsCritical; }
+
+ /** Gets the block arrow is in */
+ Vector3i GetBlockHit(void) const { return m_HitBlockPos; }
// tolua_end
+
+ /** Sets the block arrow is in. To be used by the MCA loader only! */
+ void SetBlockHit(const Vector3i & a_BlockHit) { m_HitBlockPos = a_BlockHit; }
protected:
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index 2b256e766..26823924f 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -1479,8 +1479,7 @@ void cEntity::SetWidth(double a_Width)
void cEntity::AddPosX(double a_AddPosX)
{
- m_Pos.x += a_AddPosX;
-
+ m_Pos.x += a_AddPosX;
}
@@ -1488,8 +1487,7 @@ void cEntity::AddPosX(double a_AddPosX)
void cEntity::AddPosY(double a_AddPosY)
{
- m_Pos.y += a_AddPosY;
-
+ m_Pos.y += a_AddPosY;
}
@@ -1497,8 +1495,7 @@ void cEntity::AddPosY(double a_AddPosY)
void cEntity::AddPosZ(double a_AddPosZ)
{
- m_Pos.z += a_AddPosZ;
-
+ m_Pos.z += a_AddPosZ;
}
@@ -1508,8 +1505,7 @@ void cEntity::AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ)
{
m_Pos.x += a_AddPosX;
m_Pos.y += a_AddPosY;
- m_Pos.z += a_AddPosZ;
-
+ m_Pos.z += a_AddPosZ;
}
diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h
index 2df66e353..f4080f8aa 100644
--- a/src/Entities/Entity.h
+++ b/src/Entities/Entity.h
@@ -237,9 +237,9 @@ public:
void AddPosY (double a_AddPosY);
void AddPosZ (double a_AddPosZ);
void AddPosition(double a_AddPosX, double a_AddPosY, double a_AddPosZ);
- void AddPosition(const Vector3d & a_AddPos) { AddPosition(a_AddPos.x,a_AddPos.y,a_AddPos.z);}
+ void AddPosition(const Vector3d & a_AddPos) { AddPosition(a_AddPos.x, a_AddPos.y, a_AddPos.z); }
void AddSpeed (double a_AddSpeedX, double a_AddSpeedY, double a_AddSpeedZ);
- void AddSpeed (const Vector3d & a_AddSpeed) { AddSpeed(a_AddSpeed.x,a_AddSpeed.y,a_AddSpeed.z);}
+ void AddSpeed (const Vector3d & a_AddSpeed) { AddSpeed(a_AddSpeed.x, a_AddSpeed.y, a_AddSpeed.z); }
void AddSpeedX (double a_AddSpeedX);
void AddSpeedY (double a_AddSpeedY);
void AddSpeedZ (double a_AddSpeedZ);
diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp
index 10b6bbd5c..24fa591da 100644
--- a/src/Entities/Pickup.cpp
+++ b/src/Entities/Pickup.cpp
@@ -224,7 +224,7 @@ bool cPickup::CollectedBy(cPlayer * a_Dest)
}
m_Item.m_ItemCount -= NumAdded;
- m_World->BroadcastCollectPickup(*this, *a_Dest);
+ m_World->BroadcastCollectEntity(*this, *a_Dest);
// Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)
m_World->BroadcastSoundEffect("random.pop",(int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64));
if (m_Item.m_ItemCount <= 0)
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index dbb8cd26c..944ed643e 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -34,52 +34,48 @@
-cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
- : super(etPlayer, 0.6, 1.8)
- , m_bVisible(true)
- , m_FoodLevel(MAX_FOOD_LEVEL)
- , m_FoodSaturationLevel(5.0)
- , m_FoodTickTimer(0)
- , m_FoodExhaustionLevel(0.0)
- , m_FoodPoisonedTicksRemaining(0)
- , m_LastJumpHeight(0)
- , m_LastGroundHeight(0)
- , m_bTouchGround(false)
- , m_Stance(0.0)
- , m_Inventory(*this)
- , m_EnderChestContents(9, 3)
- , m_CurrentWindow(NULL)
- , m_InventoryWindow(NULL)
- , m_Color('-')
- , m_GameMode(eGameMode_NotSet)
- , m_IP("")
- , m_ClientHandle(a_Client)
- , m_NormalMaxSpeed(1.0)
- , m_SprintingMaxSpeed(1.3)
- , m_FlyingMaxSpeed(1.0)
- , m_IsCrouched(false)
- , m_IsSprinting(false)
- , m_IsFlying(false)
- , m_IsSwimming(false)
- , m_IsSubmerged(false)
- , m_IsFishing(false)
- , m_CanFly(false)
- , m_EatingFinishTick(-1)
- , m_LifetimeTotalXp(0)
- , m_CurrentXp(0)
- , m_bDirtyExperience(false)
- , m_IsChargingBow(false)
- , m_BowCharge(0)
- , m_FloaterID(-1)
- , m_Team(NULL)
- , m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL)
- , m_bIsTeleporting(false)
-{
- LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d",
- a_PlayerName.c_str(), a_Client->GetIPString().c_str(),
- this, GetUniqueID()
- );
-
+cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) :
+ super(etPlayer, 0.6, 1.8),
+ m_bVisible(true),
+ m_FoodLevel(MAX_FOOD_LEVEL),
+ m_FoodSaturationLevel(5.0),
+ m_FoodTickTimer(0),
+ m_FoodExhaustionLevel(0.0),
+ m_FoodPoisonedTicksRemaining(0),
+ m_LastJumpHeight(0),
+ m_LastGroundHeight(0),
+ m_bTouchGround(false),
+ m_Stance(0.0),
+ m_Inventory(*this),
+ m_EnderChestContents(9, 3),
+ m_CurrentWindow(NULL),
+ m_InventoryWindow(NULL),
+ m_Color('-'),
+ m_GameMode(eGameMode_NotSet),
+ m_IP(""),
+ m_ClientHandle(a_Client),
+ m_NormalMaxSpeed(1.0),
+ m_SprintingMaxSpeed(1.3),
+ m_FlyingMaxSpeed(1.0),
+ m_IsCrouched(false),
+ m_IsSprinting(false),
+ m_IsFlying(false),
+ m_IsSwimming(false),
+ m_IsSubmerged(false),
+ m_IsFishing(false),
+ m_CanFly(false),
+ m_EatingFinishTick(-1),
+ m_LifetimeTotalXp(0),
+ m_CurrentXp(0),
+ m_bDirtyExperience(false),
+ m_IsChargingBow(false),
+ m_BowCharge(0),
+ m_FloaterID(-1),
+ m_Team(NULL),
+ m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL),
+ m_bIsTeleporting(false),
+ m_UUID((a_Client != NULL) ? a_Client->GetUUID() : "")
+{
m_InventoryWindow = new cInventoryWindow(*this);
m_CurrentWindow = m_InventoryWindow;
m_InventoryWindow->OpenedByPlayer(*this);
@@ -1690,53 +1686,98 @@ void cPlayer::LoadPermissionsFromDisk()
-bool cPlayer::LoadFromDisk()
+
+bool cPlayer::LoadFromDisk(void)
{
LoadPermissionsFromDisk();
- AString SourceFile;
- Printf(SourceFile, "players/%s.json", GetName().c_str() );
+ // Load from the UUID file:
+ if (LoadFromFile(GetUUIDFileName(m_UUID)))
+ {
+ return true;
+ }
+
+ // Load from the offline UUID file, if allowed:
+ AString OfflineUUID = cClientHandle::GenerateOfflineUUID(GetName());
+ if (cRoot::Get()->GetServer()->ShouldLoadOfflinePlayerData())
+ {
+ if (LoadFromFile(GetUUIDFileName(OfflineUUID)))
+ {
+ return true;
+ }
+ }
+
+ // Load from the old-style name-based file, if allowed:
+ if (cRoot::Get()->GetServer()->ShouldLoadNamedPlayerData())
+ {
+ AString OldStyleFileName = Printf("players/%s.json", GetName().c_str());
+ if (LoadFromFile(OldStyleFileName))
+ {
+ // Save in new format and remove the old file
+ if (SaveToDisk())
+ {
+ cFile::Delete(OldStyleFileName);
+ }
+ return true;
+ }
+ }
+
+ // None of the files loaded successfully
+ 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()
+ );
+ return false;
+}
+
+
+
+
+bool cPlayer::LoadFromFile(const AString & a_FileName)
+{
+ // Load the data from the file:
cFile f;
- if (!f.Open(SourceFile, cFile::fmRead))
+ if (!f.Open(a_FileName, cFile::fmRead))
{
// This is a new player whom we haven't seen yet, bail out, let them have the defaults
return false;
}
-
AString buffer;
if (f.ReadRestOfFile(buffer) != f.GetSize())
{
- LOGWARNING("Cannot read player data from file \"%s\"", SourceFile.c_str());
+ LOGWARNING("Cannot read player data from file \"%s\"", a_FileName.c_str());
return false;
}
- f.Close(); //cool kids play nice
+ f.Close();
+ // Parse the JSON format:
Json::Value root;
Json::Reader reader;
if (!reader.parse(buffer, root, false))
{
- LOGWARNING("Cannot parse player data in file \"%s\", player will be reset", SourceFile.c_str());
+ LOGWARNING("Cannot parse player data in file \"%s\"", a_FileName.c_str());
+ return false;
}
+ // Load the player data:
Json::Value & JSON_PlayerPosition = root["position"];
if (JSON_PlayerPosition.size() == 3)
{
- SetPosX(JSON_PlayerPosition[(unsigned int)0].asDouble());
- SetPosY(JSON_PlayerPosition[(unsigned int)1].asDouble());
- SetPosZ(JSON_PlayerPosition[(unsigned int)2].asDouble());
+ SetPosX(JSON_PlayerPosition[(unsigned)0].asDouble());
+ SetPosY(JSON_PlayerPosition[(unsigned)1].asDouble());
+ SetPosZ(JSON_PlayerPosition[(unsigned)2].asDouble());
m_LastPos = GetPosition();
}
Json::Value & JSON_PlayerRotation = root["rotation"];
if (JSON_PlayerRotation.size() == 3)
{
- SetYaw ((float)JSON_PlayerRotation[(unsigned int)0].asDouble());
- SetPitch ((float)JSON_PlayerRotation[(unsigned int)1].asDouble());
- SetRoll ((float)JSON_PlayerRotation[(unsigned int)2].asDouble());
+ SetYaw ((float)JSON_PlayerRotation[(unsigned)0].asDouble());
+ SetPitch ((float)JSON_PlayerRotation[(unsigned)1].asDouble());
+ SetRoll ((float)JSON_PlayerRotation[(unsigned)2].asDouble());
}
- m_Health = root.get("health", 0).asInt();
+ m_Health = root.get("health", 0).asInt();
m_AirLevel = root.get("air", MAX_AIR_LEVEL).asInt();
m_FoodLevel = root.get("food", MAX_FOOD_LEVEL).asInt();
m_FoodSaturationLevel = root.get("foodSaturation", MAX_FOOD_LEVEL).asDouble();
@@ -1763,8 +1804,8 @@ bool cPlayer::LoadFromDisk()
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), &m_Stats);
StatSerializer.Load();
- LOGD("Player \"%s\" was read from file, spawning at {%.2f, %.2f, %.2f} in world \"%s\"",
- GetName().c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str()
+ 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()
);
return true;
@@ -1777,6 +1818,7 @@ bool cPlayer::LoadFromDisk()
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
Json::Value JSON_PlayerPosition;
@@ -1808,33 +1850,45 @@ bool cPlayer::SaveToDisk()
root["foodSaturation"] = m_FoodSaturationLevel;
root["foodTickTimer"] = m_FoodTickTimer;
root["foodExhaustion"] = m_FoodExhaustionLevel;
- root["world"] = GetWorld()->GetName();
root["isflying"] = IsFlying();
-
- if (m_GameMode == GetWorld()->GetGameMode())
+ root["lastknownname"] = GetName();
+ if (m_World != NULL)
{
- root["gamemode"] = (int) eGameMode_NotSet;
+ root["world"] = m_World->GetName();
+ if (m_GameMode == m_World->GetGameMode())
+ {
+ root["gamemode"] = (int) eGameMode_NotSet;
+ }
+ else
+ {
+ root["gamemode"] = (int) m_GameMode;
+ }
}
else
{
- root["gamemode"] = (int) m_GameMode;
+ // This happens if the player is saved to new format after loading from the old format
+ root["world"] = m_LoadedWorldName;
+ root["gamemode"] = (int) eGameMode_NotSet;
}
Json::StyledWriter writer;
std::string JsonData = writer.write(root);
- AString SourceFile;
- Printf(SourceFile, "players/%s.json", GetName().c_str() );
+ AString SourceFile = GetUUIDFileName(m_UUID);
cFile f;
if (!f.Open(SourceFile, cFile::fmWrite))
{
- LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", GetName().c_str(), SourceFile.c_str());
+ LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot open file. Player will lose their progress.",
+ GetName().c_str(), SourceFile.c_str()
+ );
return false;
}
if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size())
{
- LOGERROR("ERROR WRITING PLAYER JSON TO FILE \"%s\"", SourceFile.c_str());
+ LOGWARNING("Error writing player \"%s\" to file \"%s\" - cannot save data. Player will lose their progress. ",
+ GetName().c_str(), SourceFile.c_str()
+ );
return false;
}
@@ -1843,7 +1897,7 @@ bool cPlayer::SaveToDisk()
cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), &m_Stats);
if (!StatSerializer.Save())
{
- LOGERROR("Could not save stats for player %s", GetName().c_str());
+ LOGWARNING("Could not save stats for player %s", GetName().c_str());
return false;
}
@@ -2156,3 +2210,19 @@ void cPlayer::Detach()
+
+AString cPlayer::GetUUIDFileName(const AString & a_UUID)
+{
+ ASSERT(a_UUID.size() == 36);
+
+ AString res("players/");
+ res.append(a_UUID, 0, 2);
+ res.push_back('/');
+ res.append(a_UUID, 2, AString::npos);
+ res.append(".json");
+ return res;
+}
+
+
+
+
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index f247ac2f9..8f9b46e0f 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -41,6 +41,7 @@ public:
cPlayer(cClientHandle * a_Client, const AString & a_PlayerName);
+
virtual ~cPlayer();
virtual void SpawnOn(cClientHandle & a_Client) override;
@@ -337,7 +338,15 @@ public:
bool MoveToWorld(const char * a_WorldName); // tolua_export
bool SaveToDisk(void);
+
+ /** Loads the player data from the disk file.
+ Returns true on success, false on failure. */
bool LoadFromDisk(void);
+
+ /** Loads the player data from the specified file.
+ Returns true on success, false on failure. */
+ bool LoadFromFile(const AString & a_FileName);
+
void LoadPermissionsFromDisk(void); // tolua_export
const AString & GetLoadedWorldName() { return m_LoadedWorldName; }
@@ -519,6 +528,24 @@ protected:
cStatManager m_Stats;
+ /** Flag representing whether the player is currently in a bed
+ Set by a right click on unoccupied bed, unset by a time fast forward or teleport */
+ bool m_bIsInBed;
+
+ /** How long till the player's inventory will be saved
+ Default save interval is #defined in PLAYER_INVENTORY_SAVE_INTERVAL */
+ unsigned int m_TicksUntilNextSave;
+
+ /** Flag used by food handling system to determine whether a teleport has just happened
+ Will not apply food penalties if found to be true; will set to false after processing
+ */
+ bool m_bIsTeleporting;
+
+ /** The UUID of the player, as read from the ClientHandle.
+ 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;
@@ -545,19 +572,9 @@ protected:
/** Adds food exhaustion based on the difference between Pos and LastPos, sprinting status and swimming (in water block) */
void ApplyFoodExhaustionFromMovement();
- /** Flag representing whether the player is currently in a bed
- Set by a right click on unoccupied bed, unset by a time fast forward or teleport */
- bool m_bIsInBed;
-
- /** How long till the player's inventory will be saved
- Default save interval is #defined in PLAYER_INVENTORY_SAVE_INTERVAL */
- unsigned int m_TicksUntilNextSave;
-
- /** Flag used by food handling system to determine whether a teleport has just happened
- Will not apply food penalties if found to be true; will set to false after processing
- */
- bool m_bIsTeleporting;
-
+ /** Returns the filename for the player data based on the UUID given.
+ This can be used both for online and offline UUIDs. */
+ AString GetUUIDFileName(const AString & a_UUID);
} ; // tolua_export
diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp
index 95c494569..0bb34019e 100644
--- a/src/Entities/ProjectileEntity.cpp
+++ b/src/Entities/ProjectileEntity.cpp
@@ -21,6 +21,7 @@
#include "FireChargeEntity.h"
#include "FireworkEntity.h"
#include "GhastFireballEntity.h"
+#include "Player.h"
@@ -67,16 +68,17 @@ protected:
if (cBlockInfo::IsSolid(a_BlockType))
{
- // The projectile hit a solid block
- // Calculate the exact hit coords:
- cBoundingBox bb(a_BlockX, a_BlockX + 1, a_BlockY, a_BlockY + 1, a_BlockZ, a_BlockZ + 1);
- Vector3d Line1 = m_Projectile->GetPosition();
- Vector3d Line2 = Line1 + m_Projectile->GetSpeed();
- double LineCoeff = 0;
- eBlockFace Face;
- if (bb.CalcLineIntersection(Line1, Line2, LineCoeff, Face))
+ // The projectile hit a solid block, calculate the exact hit coords:
+ cBoundingBox bb(a_BlockX, a_BlockX + 1, a_BlockY, a_BlockY + 1, a_BlockZ, a_BlockZ + 1); // Bounding box of the block hit
+ const Vector3d LineStart = m_Projectile->GetPosition(); // Start point for the imaginary line that goes through the block hit
+ const Vector3d LineEnd = LineStart + m_Projectile->GetSpeed(); // End point for the imaginary line that goes through the block hit
+ double LineCoeff = 0; // Used to calculate where along the line an intersection with the bounding box occurs
+ eBlockFace Face; // Face hit
+
+ if (bb.CalcLineIntersection(LineStart, LineEnd, LineCoeff, Face))
{
- Vector3d Intersection = Line1 + m_Projectile->GetSpeed() * LineCoeff;
+ Vector3d Intersection = LineStart + m_Projectile->GetSpeed() * LineCoeff; // Point where projectile goes into the hit block
+
if (cPluginManager::Get()->CallHookProjectileHitBlock(*m_Projectile, a_BlockX, a_BlockY, a_BlockZ, Face, &Intersection))
{
return false;
@@ -140,7 +142,7 @@ public:
{
if (
(a_Entity == m_Projectile) || // Do not check collisions with self
- (a_Entity == m_Projectile->GetCreator()) // Do not check whoever shot the projectile
+ (a_Entity->GetUniqueID() == m_Projectile->GetCreatorUniqueID()) // Do not check whoever shot the projectile
)
{
// TODO: Don't check creator only for the first 5 ticks
@@ -161,7 +163,12 @@ public:
return false;
}
- // TODO: Some entities don't interact with the projectiles (pickups, falling blocks)
+ if (!a_Entity->IsMob() && !a_Entity->IsMinecart() && !a_Entity->IsPlayer() && !a_Entity->IsBoat())
+ {
+ // Not an entity that interacts with a projectile
+ return false;
+ }
+
if (cPluginManager::Get()->CallHookProjectileHitEntity(*m_Projectile, *a_Entity))
{
// A plugin disagreed.
@@ -209,7 +216,10 @@ protected:
cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, double a_X, double a_Y, double a_Z, double a_Width, double a_Height) :
super(etProjectile, a_X, a_Y, a_Z, a_Width, a_Height),
m_ProjectileKind(a_Kind),
- m_Creator(a_Creator),
+ m_CreatorData(
+ ((a_Creator != NULL) ? a_Creator->GetUniqueID() : -1),
+ ((a_Creator != NULL) ? (a_Creator->IsPlayer() ? ((cPlayer *)a_Creator)->GetName() : "") : "")
+ ),
m_IsInGround(false)
{
}
@@ -221,7 +231,7 @@ cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, double a
cProjectileEntity::cProjectileEntity(eKind a_Kind, cEntity * a_Creator, const Vector3d & a_Pos, const Vector3d & a_Speed, double a_Width, double a_Height) :
super(etProjectile, a_Pos.x, a_Pos.y, a_Pos.z, a_Width, a_Height),
m_ProjectileKind(a_Kind),
- m_Creator(a_Creator),
+ m_CreatorData(a_Creator->GetUniqueID(), a_Creator->IsPlayer() ? ((cPlayer *)a_Creator)->GetName() : ""),
m_IsInGround(false)
{
SetSpeed(a_Speed);
@@ -298,7 +308,7 @@ AString cProjectileEntity::GetMCAClassName(void) const
case pkEgg: return "Egg";
case pkGhastFireball: return "Fireball";
case pkFireCharge: return "SmallFireball";
- case pkEnderPearl: return "ThrownEnderPearl";
+ case pkEnderPearl: return "ThrownEnderpearl";
case pkExpBottle: return "ThrownExpBottle";
case pkSplashPotion: return "ThrownPotion";
case pkWitherSkull: return "WitherSkull";
@@ -316,8 +326,9 @@ AString cProjectileEntity::GetMCAClassName(void) const
void cProjectileEntity::Tick(float a_Dt, cChunk & a_Chunk)
{
super::Tick(a_Dt, a_Chunk);
-
- if (GetProjectileKind() != pkArrow) // See cArrow::Tick
+
+ // TODO: see BroadcastMovementUpdate; RelativeMove packet jerkiness affects projectiles too (cause of sympton described in cArrowEntity::Tick())
+ if (GetProjectileKind() != pkArrow)
{
BroadcastMovementUpdate();
}
@@ -335,19 +346,10 @@ void cProjectileEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk)
return;
}
- Vector3d PerTickSpeed = GetSpeed() / 20;
- Vector3d Pos = GetPosition();
-
- // Trace the tick's worth of movement as a line:
- Vector3d NextPos = Pos + PerTickSpeed;
- cProjectileTracerCallback TracerCallback(this);
- if (!cLineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos))
- {
- // Something has been hit, abort all other processing
- return;
- }
- // The tracer also checks the blocks for slowdown blocks - water and lava - and stores it for later in its SlowdownCoeff
-
+ const Vector3d PerTickSpeed = GetSpeed() / 20;
+ const Vector3d Pos = GetPosition();
+ const Vector3d NextPos = Pos + PerTickSpeed;
+
// Test for entity collisions:
cProjectileEntityCollisionCallback EntityCollisionCallback(this, Pos, NextPos);
a_Chunk.ForEachEntity(EntityCollisionCallback);
@@ -364,10 +366,19 @@ void cProjectileEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk)
HitPos.x, HitPos.y, HitPos.z,
EntityCollisionCallback.GetMinCoeff()
);
-
+
OnHitEntity(*(EntityCollisionCallback.GetHitEntity()), HitPos);
}
// TODO: Test the entities in the neighboring chunks, too
+
+ // Trace the tick's worth of movement as a line:
+ cProjectileTracerCallback TracerCallback(this);
+ if (!cLineBlockTracer::Trace(*m_World, TracerCallback, Pos, NextPos))
+ {
+ // Something has been hit, abort all other processing
+ return;
+ }
+ // The tracer also checks the blocks for slowdown blocks - water and lava - and stores it for later in its SlowdownCoeff
// Update the position:
SetPosition(NextPos);
diff --git a/src/Entities/ProjectileEntity.h b/src/Entities/ProjectileEntity.h
index ae06b072f..7b38169e2 100644
--- a/src/Entities/ProjectileEntity.h
+++ b/src/Entities/ProjectileEntity.h
@@ -66,8 +66,15 @@ public:
/// Returns the kind of the projectile (fast class identification)
eKind GetProjectileKind(void) const { return m_ProjectileKind; }
- /// Returns the entity who created this projectile; may be NULL
- cEntity * GetCreator(void) { return m_Creator; }
+ /** Returns the unique ID of the entity who created this projectile
+ May return an ID <0
+ */
+ int GetCreatorUniqueID(void) { return m_CreatorData.m_UniqueID; }
+
+ /** Returns the name of the player that created the projectile
+ Will be empty for non-player creators
+ */
+ AString GetCreatorName(void) const { return m_CreatorData.m_Name; }
/// Returns the string that is used as the entity type (class name) in MCA files
AString GetMCAClassName(void) const;
@@ -81,10 +88,29 @@ public:
void SetIsInGround(bool a_IsInGround) { m_IsInGround = a_IsInGround; }
protected:
+
+ /** A structure that stores the Entity ID and Playername of the projectile's creator
+ Used to migitate invalid pointers caused by the creator being destroyed
+ */
+ struct CreatorData
+ {
+ CreatorData(int a_UniqueID, const AString & a_Name) :
+ m_UniqueID(a_UniqueID),
+ m_Name(a_Name)
+ {
+ }
+
+ const int m_UniqueID;
+ AString m_Name;
+ };
+
+ /** The type of projectile I am */
eKind m_ProjectileKind;
- /// The entity who has created this projectile; may be NULL (e. g. for dispensers)
- cEntity * m_Creator;
+ /** The structure for containing the entity ID and name who has created this projectile
+ The ID and/or name may be NULL (e.g. for dispensers/mobs)
+ */
+ CreatorData m_CreatorData;
/// True if the projectile has hit the ground and is stuck there
bool m_IsInGround;
diff --git a/src/Entities/ThrownEggEntity.cpp b/src/Entities/ThrownEggEntity.cpp
index 224019091..456083108 100644
--- a/src/Entities/ThrownEggEntity.cpp
+++ b/src/Entities/ThrownEggEntity.cpp
@@ -8,7 +8,8 @@
cThrownEggEntity::cThrownEggEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) :
- super(pkEgg, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25)
+ super(pkEgg, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25),
+ m_DestroyTimer(-1)
{
SetSpeed(a_Speed);
}
@@ -21,7 +22,7 @@ void cThrownEggEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_H
{
TrySpawnChicken(a_HitPos);
- Destroy();
+ m_DestroyTimer = 2;
}
@@ -36,7 +37,7 @@ void cThrownEggEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_Hit
TrySpawnChicken(a_HitPos);
a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1);
- Destroy(true);
+ m_DestroyTimer = 5;
}
diff --git a/src/Entities/ThrownEggEntity.h b/src/Entities/ThrownEggEntity.h
index 5ba8f051b..dc72c279f 100644
--- a/src/Entities/ThrownEggEntity.h
+++ b/src/Entities/ThrownEggEntity.h
@@ -30,8 +30,29 @@ protected:
// cProjectileEntity overrides:
virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override;
virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override;
+ virtual void Tick (float a_Dt, cChunk & a_Chunk) override
+ {
+ if (m_DestroyTimer > 0)
+ {
+ m_DestroyTimer--;
+ if (m_DestroyTimer == 0)
+ {
+ Destroy();
+ return;
+ }
+ }
+ else
+ {
+ super::Tick(a_Dt, a_Chunk);
+ }
+ }
// Randomly decides whether to spawn a chicken where the egg lands.
void TrySpawnChicken(const Vector3d & a_HitPos);
+
+private:
+
+ /** Time in ticks to wait for the hit animation to begin before destroying */
+ int m_DestroyTimer;
} ; // tolua_export
diff --git a/src/Entities/ThrownEnderPearlEntity.cpp b/src/Entities/ThrownEnderPearlEntity.cpp
index c37161145..c7407e6ae 100644
--- a/src/Entities/ThrownEnderPearlEntity.cpp
+++ b/src/Entities/ThrownEnderPearlEntity.cpp
@@ -1,13 +1,15 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "ThrownEnderPearlEntity.h"
+#include "Player.h"
cThrownEnderPearlEntity::cThrownEnderPearlEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) :
- super(pkEnderPearl, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25)
+ super(pkEnderPearl, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25),
+ m_DestroyTimer(-1)
{
SetSpeed(a_Speed);
}
@@ -21,7 +23,7 @@ void cThrownEnderPearlEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockF
// TODO: Tweak a_HitPos based on block face.
TeleportCreator(a_HitPos);
- Destroy();
+ m_DestroyTimer = 2;
}
@@ -36,7 +38,7 @@ void cThrownEnderPearlEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d
TeleportCreator(a_HitPos);
a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1);
- Destroy(true);
+ m_DestroyTimer = 5;
}
@@ -45,10 +47,34 @@ void cThrownEnderPearlEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d
void cThrownEnderPearlEntity::TeleportCreator(const Vector3d & a_HitPos)
{
- // Teleport the creator here, make them take 5 damage:
- if (m_Creator != NULL)
+ if (m_CreatorData.m_Name.empty())
{
- m_Creator->TeleportToCoords(a_HitPos.x + 0.5, a_HitPos.y + 1.7, a_HitPos.z + 0.5);
- m_Creator->TakeDamage(dtEnderPearl, this, 5, 0);
+ return;
}
+
+ class cProjectileCreatorCallbackForPlayers : public cPlayerListCallback
+ {
+ public:
+ cProjectileCreatorCallbackForPlayers(cEntity * a_Attacker, Vector3i a_HitPos) :
+ m_Attacker(a_Attacker),
+ m_HitPos(a_HitPos)
+ {
+ }
+
+ virtual bool Item(cPlayer * a_Entity) override
+ {
+ // Teleport the creator here, make them take 5 damage:
+ a_Entity->TeleportToCoords(m_HitPos.x, m_HitPos.y + 0.2, m_HitPos.z);
+ a_Entity->TakeDamage(dtEnderPearl, m_Attacker, 5, 0);
+ return true;
+ }
+
+ private:
+
+ cEntity * m_Attacker;
+ Vector3i m_HitPos;
+ };
+
+ cProjectileCreatorCallbackForPlayers PCCFP(this, a_HitPos);
+ GetWorld()->FindAndDoWithPlayer(m_CreatorData.m_Name, PCCFP);
}
diff --git a/src/Entities/ThrownEnderPearlEntity.h b/src/Entities/ThrownEnderPearlEntity.h
index ddee5babe..1cea5f7d9 100644
--- a/src/Entities/ThrownEnderPearlEntity.h
+++ b/src/Entities/ThrownEnderPearlEntity.h
@@ -30,8 +30,29 @@ protected:
// cProjectileEntity overrides:
virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override;
virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override;
-
- // Teleports the creator where the ender pearl lands.
+ virtual void Tick (float a_Dt, cChunk & a_Chunk) override
+ {
+ if (m_DestroyTimer > 0)
+ {
+ m_DestroyTimer--;
+ if (m_DestroyTimer == 0)
+ {
+ Destroy();
+ return;
+ }
+ }
+ else
+ {
+ super::Tick(a_Dt, a_Chunk);
+ }
+ }
+
+ /** Teleports the creator where the ender pearl lands */
void TeleportCreator(const Vector3d & a_HitPos);
+
+private:
+
+ /** Time in ticks to wait for the hit animation to begin before destroying */
+ int m_DestroyTimer;
} ; // tolua_export
diff --git a/src/Entities/ThrownSnowballEntity.cpp b/src/Entities/ThrownSnowballEntity.cpp
index cefc3433c..d94e75898 100644
--- a/src/Entities/ThrownSnowballEntity.cpp
+++ b/src/Entities/ThrownSnowballEntity.cpp
@@ -8,7 +8,8 @@
cThrownSnowballEntity::cThrownSnowballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) :
- super(pkSnowball, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25)
+ super(pkSnowball, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25),
+ m_DestroyTimer(-1)
{
SetSpeed(a_Speed);
}
@@ -19,7 +20,7 @@ cThrownSnowballEntity::cThrownSnowballEntity(cEntity * a_Creator, double a_X, do
void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace)
{
- Destroy();
+ m_DestroyTimer = 2;
}
@@ -40,5 +41,5 @@ void cThrownSnowballEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d &
// TODO: If entity is Ender Crystal, destroy it
a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1);
- Destroy(true);
+ m_DestroyTimer = 5;
}
diff --git a/src/Entities/ThrownSnowballEntity.h b/src/Entities/ThrownSnowballEntity.h
index a09512e37..9a8770379 100644
--- a/src/Entities/ThrownSnowballEntity.h
+++ b/src/Entities/ThrownSnowballEntity.h
@@ -30,5 +30,26 @@ protected:
// cProjectileEntity overrides:
virtual void OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_HitFace) override;
virtual void OnHitEntity (cEntity & a_EntityHit, const Vector3d & a_HitPos) override;
+ virtual void Tick (float a_Dt, cChunk & a_Chunk) override
+ {
+ if (m_DestroyTimer > 0)
+ {
+ m_DestroyTimer--;
+ if (m_DestroyTimer == 0)
+ {
+ Destroy();
+ return;
+ }
+ }
+ else
+ {
+ super::Tick(a_Dt, a_Chunk);
+ }
+ }
+
+private:
+
+ /** Time in ticks to wait for the hit animation to begin before destroying */
+ int m_DestroyTimer;
} ; // tolua_export
diff --git a/src/Generating/Prefabs/AlchemistVillagePrefabs.cpp b/src/Generating/Prefabs/AlchemistVillagePrefabs.cpp
index 5cb864d53..976f8490e 100644
--- a/src/Generating/Prefabs/AlchemistVillagePrefabs.cpp
+++ b/src/Generating/Prefabs/AlchemistVillagePrefabs.cpp
@@ -58,9 +58,9 @@ const cPrefab::sDef g_AlchemistVillagePrefabs[] =
"o: 50: 4\n" /* torch */
"p: 13: 0\n" /* gravel */
"q: 5: 0\n" /* wood */
- "r: 96:12\n" /* trapdoor */
+ "r: 96: 8\n" /* trapdoor */
"s:128: 5\n" /* sandstonestairs */
- "t:107: 0\n" /* fencegate */
+ "t:107: 2\n" /* fencegate */
"u:128: 4\n" /* sandstonestairs */
"v:134: 3\n" /* 134 */
"w: 85: 0\n" /* fence */
@@ -288,7 +288,7 @@ const cPrefab::sDef g_AlchemistVillagePrefabs[] =
"d: 13: 0\n" /* gravel */
"e: 5: 0\n" /* wood */
"f:128: 5\n" /* sandstonestairs */
- "g:107: 0\n" /* fencegate */
+ "g:107: 2\n" /* fencegate */
"h:128: 4\n" /* sandstonestairs */
"i:134: 1\n" /* 134 */
"j:134: 3\n" /* 134 */
@@ -298,7 +298,7 @@ const cPrefab::sDef g_AlchemistVillagePrefabs[] =
"n:134: 5\n" /* 134 */
"o:134: 7\n" /* 134 */
"p:134: 4\n" /* 134 */
- "q:107: 5\n" /* fencegate */
+ "q:107: 1\n" /* fencegate */
"r: 64: 1\n" /* wooddoorblock */
"s: 65: 3\n" /* ladder */
"t: 50: 3\n" /* torch */
@@ -2195,7 +2195,7 @@ const cPrefab::sDef g_AlchemistVillagePrefabs[] =
0,
// MoveToGround:
- false,
+ true,
}, // LittleHouse8
diff --git a/src/Generating/Prefabs/JapaneseVillagePrefabs.cpp b/src/Generating/Prefabs/JapaneseVillagePrefabs.cpp
index 79f7a5272..d22153d87 100644
--- a/src/Generating/Prefabs/JapaneseVillagePrefabs.cpp
+++ b/src/Generating/Prefabs/JapaneseVillagePrefabs.cpp
@@ -294,7 +294,7 @@ const cPrefab::sDef g_JapaneseVillagePrefabs[] =
0,
// MoveToGround:
- false,
+ true,
}, // Farm
diff --git a/src/Generating/Prefabs/PlainsVillagePrefabs.cpp b/src/Generating/Prefabs/PlainsVillagePrefabs.cpp
index 9e5e06769..bad8dae74 100644
--- a/src/Generating/Prefabs/PlainsVillagePrefabs.cpp
+++ b/src/Generating/Prefabs/PlainsVillagePrefabs.cpp
@@ -356,6 +356,8 @@ const cPrefab::sDef g_PlainsVillagePrefabs[] =
"e: 8: 0\n" /* water */
"f: 50: 5\n" /* torch */
"g: 59: 7\n" /* crops */
+ "h: 59: 0\n" /* crops */
+ "i: 59: 1\n" /* crops */
"m: 19: 0\n" /* sponge */,
// Block data:
@@ -403,12 +405,12 @@ const cPrefab::sDef g_PlainsVillagePrefabs[] =
/* * 012345678901234 */
/* 0 */ "f.....f.f.....f"
/* 1 */ ".gg.gg...gg.gg."
- /* 2 */ ".g...g...gg.gg."
- /* 3 */ ".g.......gg.gg."
- /* 4 */ ".gg..g...gg.gg."
- /* 5 */ ".gg..g...gg.gg."
- /* 6 */ "..g..g...gg.gg."
- /* 7 */ "..g.g....gg.gg."
+ /* 2 */ ".gh.hg...gg.gg."
+ /* 3 */ ".gh.ih...gg.gg."
+ /* 4 */ ".gg.hg...gg.gg."
+ /* 5 */ ".gg.hg...gg.gg."
+ /* 6 */ ".ig.hg...gg.gg."
+ /* 7 */ ".hg.gh...gg.gg."
/* 8 */ "f.....f.f.....f"
// Level 4
diff --git a/src/Items/ItemBow.h b/src/Items/ItemBow.h
index 940338d0f..185f17fee 100644
--- a/src/Items/ItemBow.h
+++ b/src/Items/ItemBow.h
@@ -55,7 +55,7 @@ public:
// Too little force, ignore the shot
return;
}
- Force = std::max(Force, 1.0);
+ Force = std::min(Force, 1.0);
// Create the arrow entity:
cArrowEntity * Arrow = new cArrowEntity(*a_Player, Force * 2);
@@ -70,16 +70,7 @@ public:
return;
}
- cFastRandom Random;
- a_Player->GetWorld()->BroadcastSoundEffect(
- "random.bow",
- (int)std::floor(a_Player->GetPosX() * 8.0),
- (int)std::floor(a_Player->GetPosY() * 8.0),
- (int)std::floor(a_Player->GetPosZ() * 8.0),
- 1.0F,
- 1.0F / (Random.NextFloat(1.0F) * 0.4F + 1.2F) + (float)Force * 0.5F
- );
-
+ a_Player->GetWorld()->BroadcastSoundEffect("random.bow", (int)a_Player->GetPosX() * 8, (int)a_Player->GetPosY() * 8, (int)a_Player->GetPosZ() * 8, 0.5, (float)Force);
if (!a_Player->IsGameModeCreative())
{
a_Player->UseEquippedItem();
diff --git a/src/Items/ItemBucket.h b/src/Items/ItemBucket.h
index 68c89dd85..5529b4e36 100644
--- a/src/Items/ItemBucket.h
+++ b/src/Items/ItemBucket.h
@@ -41,7 +41,7 @@ public:
bool ScoopUpFluid(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace)
{
- if (a_BlockFace > 0)
+ if (a_BlockFace != BLOCK_FACE_NONE)
{
return false;
}
@@ -95,29 +95,15 @@ public:
bool PlaceFluid(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, BLOCKTYPE a_FluidBlock)
{
- if (a_BlockFace < 0)
+ if (a_BlockFace != BLOCK_FACE_NONE)
{
return false;
}
- BLOCKTYPE CurrentBlock = a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ);
- bool CanWashAway = cFluidSimulator::CanWashAway(CurrentBlock);
- if (!CanWashAway)
+ BLOCKTYPE CurrentBlock;
+ Vector3i BlockPos;
+ if (!GetPlacementCoordsFromTrace(a_World, a_Player, BlockPos, CurrentBlock))
{
- // The block pointed at cannot be washed away, so put fluid on top of it / on its sides
- AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
- CurrentBlock = a_World->GetBlock(a_BlockX, a_BlockY, a_BlockZ);
- }
- if (
- !CanWashAway &&
- (CurrentBlock != E_BLOCK_AIR) &&
- (CurrentBlock != E_BLOCK_WATER) &&
- (CurrentBlock != E_BLOCK_STATIONARY_WATER) &&
- (CurrentBlock != E_BLOCK_LAVA) &&
- (CurrentBlock != E_BLOCK_STATIONARY_LAVA)
- )
- {
- // Cannot place water here
return false;
}
@@ -138,7 +124,7 @@ public:
}
// Wash away anything that was there prior to placing:
- if (CanWashAway)
+ if (cFluidSimulator::CanWashAway(CurrentBlock))
{
cBlockHandler * Handler = BlockHandler(CurrentBlock);
if (Handler->DoesDropOnUnsuitable())
@@ -149,13 +135,13 @@ public:
}
}
- a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, a_FluidBlock, 0);
+ a_World->SetBlock(BlockPos.x, BlockPos.y, BlockPos.z, a_FluidBlock, 0);
return true;
}
- bool GetBlockFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & BlockPos)
+ bool GetBlockFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & a_BlockPos)
{
class cCallbacks :
public cBlockTracer::cCallbacks
@@ -198,8 +184,49 @@ public:
}
- BlockPos.Set(Callbacks.m_Pos.x, Callbacks.m_Pos.y, Callbacks.m_Pos.z);
+ a_BlockPos = Callbacks.m_Pos;
return true;
}
+
+
+ bool GetPlacementCoordsFromTrace(cWorld * a_World, cPlayer * a_Player, Vector3i & a_BlockPos, BLOCKTYPE & a_BlockType)
+ {
+ class cCallbacks :
+ public cBlockTracer::cCallbacks
+ {
+ public:
+ Vector3i m_Pos;
+ BLOCKTYPE m_ReplacedBlock;
+
+ virtual bool OnNextBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, char a_EntryFace) override
+ {
+ if (a_BlockType != E_BLOCK_AIR)
+ {
+ m_ReplacedBlock = a_BlockType;
+ if (!cFluidSimulator::CanWashAway(a_BlockType))
+ {
+ AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, (eBlockFace)a_EntryFace); // Was an unwashawayable block, can't overwrite it!
+ }
+ m_Pos.Set(a_BlockX, a_BlockY, a_BlockZ); // (Block could be washed away, replace it)
+ return true; // Abort tracing
+ }
+ return false;
+ }
+ } Callbacks;
+
+ cLineBlockTracer Tracer(*a_World, Callbacks);
+ Vector3d Start(a_Player->GetEyePosition() + a_Player->GetLookVector());
+ Vector3d End(a_Player->GetEyePosition() + a_Player->GetLookVector() * 5);
+
+ // cTracer::Trace returns true when whole line was traversed. By returning true when we hit something, we ensure that this never happens if liquid could be placed
+ // Use this to judge whether the position is valid
+ if (!Tracer.Trace(Start.x, Start.y, Start.z, End.x, End.y, End.z))
+ {
+ a_BlockPos = Callbacks.m_Pos;
+ a_BlockType = Callbacks.m_ReplacedBlock;
+ return true;
+ }
+ return false;
+ }
};
diff --git a/src/Mobs/Creeper.cpp b/src/Mobs/Creeper.cpp
index a7b97f604..b9041bd5a 100644
--- a/src/Mobs/Creeper.cpp
+++ b/src/Mobs/Creeper.cpp
@@ -67,9 +67,27 @@ void cCreeper::GetDrops(cItems & a_Drops, cEntity * a_Killer)
}
AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_GUNPOWDER);
- if ((a_Killer != NULL) && (a_Killer->IsProjectile()))
+ if ((a_Killer != NULL) && a_Killer->IsProjectile() && (((cProjectileEntity *)a_Killer)->GetCreatorUniqueID() >= 0))
{
- if (((cMonster *)((cProjectileEntity *)a_Killer)->GetCreator())->GetMobType() == mtSkeleton)
+ class cProjectileCreatorCallback : public cEntityCallback
+ {
+ public:
+ cProjectileCreatorCallback(void)
+ {
+ }
+
+ virtual bool Item(cEntity * a_Entity) override
+ {
+ if (a_Entity->IsMob() && ((cMonster *)a_Entity)->GetMobType() == mtSkeleton)
+ {
+ return true;
+ }
+ return false;
+ }
+ };
+
+ cProjectileCreatorCallback PCC;
+ if (GetWorld()->DoWithEntityByID(((cProjectileEntity *)a_Killer)->GetCreatorUniqueID(), PCC))
{
// 12 music discs. TickRand starts from 0 to 11. Disk IDs start at 2256, so add that. There.
AddRandomDropItem(a_Drops, 1, 1, (short)m_World->GetTickRandomNumber(11) + 2256);
diff --git a/src/Mobs/Skeleton.cpp b/src/Mobs/Skeleton.cpp
index e3357185d..0641a3d57 100644
--- a/src/Mobs/Skeleton.cpp
+++ b/src/Mobs/Skeleton.cpp
@@ -49,11 +49,10 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer)
void cSkeleton::MoveToPosition(const Vector3f & a_Position)
{
- // If the destination is in the sun and if it is not night AND the skeleton isn't on fire then block the movement.
+ // If the destination is sufficiently skylight challenged AND the skeleton isn't on fire then block the movement
if (
!IsOnFire() &&
- (m_World->GetTimeOfDay() < 13187) &&
- (m_World->GetBlockSkyLight((int) a_Position.x, (int) a_Position.y, (int) a_Position.z) == 15)
+ (m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8)
)
{
m_bMovingToDestination = false;
diff --git a/src/Mobs/Zombie.cpp b/src/Mobs/Zombie.cpp
index f19e096ee..725790ed9 100644
--- a/src/Mobs/Zombie.cpp
+++ b/src/Mobs/Zombie.cpp
@@ -44,11 +44,10 @@ void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer)
void cZombie::MoveToPosition(const Vector3f & a_Position)
{
- // If the destination is in the sun and if it is not night AND the zombie isn't on fire then block the movement.
+ // If the destination is sufficiently skylight challenged AND the skeleton isn't on fire then block the movement
if (
!IsOnFire() &&
- (m_World->GetTimeOfDay() < 13187) &&
- (m_World->GetBlockSkyLight((int)a_Position.x, (int)a_Position.y, (int)a_Position.z) == 15)
+ (m_World->GetBlockSkyLight((int)floor(a_Position.x), (int)floor(a_Position.y), (int)floor(a_Position.z)) - m_World->GetSkyDarkness() > 8)
)
{
m_bMovingToDestination = false;
diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp
index df9c6b50d..855687269 100644
--- a/src/Protocol/Protocol17x.cpp
+++ b/src/Protocol/Protocol17x.cpp
@@ -2333,7 +2333,7 @@ void cProtocol172::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata)
for (int loretag = NBT.GetFirstChild(displaytag); loretag >= 0; loretag = NBT.GetNextSibling(loretag)) // Loop through array of strings
{
- AppendPrintf(Lore, "%s`", NBT.GetString(loretag).c_str()); // Append the lore with a newline, used internally by MCS to display a new line in the client; don't forget to c_str ;)
+ AppendPrintf(Lore, "%s`", NBT.GetString(loretag).c_str()); // Append the lore with a grave accent/backtick, used internally by MCS to display a new line in the client; don't forget to c_str ;)
}
a_Item.m_Lore = Lore;
diff --git a/src/Server.cpp b/src/Server.cpp
index 2c695cc70..5d1e51036 100644
--- a/src/Server.cpp
+++ b/src/Server.cpp
@@ -258,6 +258,9 @@ bool cServer::InitServer(cIniFile & a_SettingsIni)
m_ServerID.resize(16, '0');
}
+ m_ShouldLoadOfflinePlayerData = a_SettingsIni.GetValueSetB("PlayerData", "LoadOfflinePlayerData", false);
+ m_ShouldLoadNamedPlayerData = a_SettingsIni.GetValueSetB("PlayerData", "LoadNamedPlayerData", true);
+
m_ClientViewDistance = a_SettingsIni.GetValueSetI("Server", "DefaultViewDistance", cClientHandle::DEFAULT_VIEW_DISTANCE);
if (m_ClientViewDistance < cClientHandle::MIN_VIEW_DISTANCE)
{
diff --git a/src/Server.h b/src/Server.h
index 3d76c8ccf..5227799e8 100644
--- a/src/Server.h
+++ b/src/Server.h
@@ -112,8 +112,18 @@ public: // tolua_export
cRsaPrivateKey & GetPrivateKey(void) { return m_PrivateKey; }
const AString & GetPublicKeyDER(void) const { return m_PublicKeyDER; }
+ /** Returns true if authentication has been turned on in server settings. */
bool ShouldAuthenticate(void) const { return m_ShouldAuthenticate; }
+ /** Returns true if offline UUIDs should be used to load data for players whose normal UUIDs cannot be found.
+ Loaded from the settings.ini [PlayerData].LoadOfflinePlayerData setting. */
+ bool ShouldLoadOfflinePlayerData(void) const { return m_ShouldLoadOfflinePlayerData; }
+
+ /** Returns true if old-style playernames should be used to load data for players whose regular datafiles cannot be found.
+ This allows a seamless transition from name-based to UUID-based player storage.
+ Loaded from the settings.ini [PlayerData].LoadNamedPlayerData setting. */
+ bool ShouldLoadNamedPlayerData(void) const { return m_ShouldLoadNamedPlayerData; }
+
private:
friend class cRoot; // so cRoot can create and destroy cServer
@@ -204,6 +214,16 @@ private:
This setting is the same as the "online-mode" setting in Vanilla. */
bool m_ShouldAuthenticate;
+ /** True if offline UUIDs should be used to load data for players whose normal UUIDs cannot be found.
+ This allows transitions from an offline (no-auth) server to an online one.
+ Loaded from the settings.ini [PlayerData].LoadOfflinePlayerData setting. */
+ bool m_ShouldLoadOfflinePlayerData;
+
+ /** True if old-style playernames should be used to load data for players whose regular datafiles cannot be found.
+ This allows a seamless transition from name-based to UUID-based player storage.
+ Loaded from the settings.ini [PlayerData].LoadNamedPlayerData setting. */
+ bool m_ShouldLoadNamedPlayerData;
+
cServer(void);
diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp
index 4df04c9e2..e220960ff 100644
--- a/src/UI/SlotArea.cpp
+++ b/src/UI/SlotArea.cpp
@@ -60,12 +60,35 @@ void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickA
ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
return;
}
-
case caDblClick:
{
DblClicked(a_Player, a_SlotNum);
return;
}
+ case caMiddleClick:
+ {
+ MiddleClicked(a_Player, a_SlotNum);
+ return;
+ }
+ case caDropKey:
+ case caCtrlDropKey:
+ {
+ DropClicked(a_Player, a_SlotNum, (a_ClickAction == caCtrlDropKey));
+ return;
+ }
+ case caNumber1:
+ case caNumber2:
+ case caNumber3:
+ case caNumber4:
+ case caNumber5:
+ case caNumber6:
+ case caNumber7:
+ case caNumber8:
+ case caNumber9:
+ {
+ NumberClicked(a_Player, a_SlotNum, a_ClickAction);
+ return;
+ }
default:
{
break;
@@ -226,6 +249,77 @@ void cSlotArea::DblClicked(cPlayer & a_Player, int a_SlotNum)
+void cSlotArea::MiddleClicked(cPlayer & a_Player, int a_SlotNum)
+{
+ cItem Slot(*GetSlot(a_SlotNum, a_Player));
+ cItem & DraggingItem = a_Player.GetDraggingItem();
+
+ if (!a_Player.IsGameModeCreative() || Slot.IsEmpty() || !DraggingItem.IsEmpty())
+ {
+ return;
+ }
+
+ DraggingItem = Slot;
+ DraggingItem.m_ItemCount = DraggingItem.GetMaxStackSize();
+}
+
+
+
+
+
+void cSlotArea::DropClicked(cPlayer & a_Player, int a_SlotNum, bool a_DropStack)
+{
+ cItem Slot(*GetSlot(a_SlotNum, a_Player));
+ if (Slot.IsEmpty())
+ {
+ return;
+ }
+
+ cItem ItemToDrop = Slot.CopyOne();
+ if (a_DropStack)
+ {
+ ItemToDrop.m_ItemCount = Slot.m_ItemCount;
+ }
+
+ Slot.m_ItemCount -= ItemToDrop.m_ItemCount;
+ if (Slot.m_ItemCount <= 0)
+ {
+ Slot.Empty();
+ }
+ SetSlot(a_SlotNum, a_Player, Slot);
+
+ a_Player.TossPickup(ItemToDrop);
+}
+
+
+
+
+
+void cSlotArea::NumberClicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction)
+{
+ if ((a_ClickAction < caNumber1) || (a_ClickAction > caNumber9))
+ {
+ return;
+ }
+
+ int HotbarSlot = (int)a_ClickAction - (int)caNumber1;
+ cItem ItemInHotbar(a_Player.GetInventory().GetHotbarSlot(HotbarSlot));
+ cItem ItemInSlot(*GetSlot(a_SlotNum, a_Player));
+
+ // The items are equal. Do nothing.
+ if (ItemInHotbar.IsEqual(ItemInSlot))
+ {
+ return;
+ }
+
+ a_Player.GetInventory().SetHotbarSlot(HotbarSlot, ItemInSlot);
+ SetSlot(a_SlotNum, a_Player, ItemInHotbar);
+}
+
+
+
+
+
void cSlotArea::OnPlayerAdded(cPlayer & a_Player)
{
UNUSED(a_Player);
@@ -410,6 +504,12 @@ cSlotAreaCrafting::cSlotAreaCrafting(int a_GridSize, cWindow & a_ParentWindow) :
void cSlotAreaCrafting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction, const cItem & a_ClickedItem)
{
+ if (a_ClickAction == caMiddleClick)
+ {
+ MiddleClicked(a_Player, a_SlotNum);
+ return;
+ }
+
// Override for craft result slot
if (a_SlotNum == 0)
{
@@ -417,12 +517,17 @@ void cSlotAreaCrafting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction
{
ShiftClickedResult(a_Player);
}
+ else if ((a_ClickAction == caDropKey) || (a_ClickAction == caCtrlDropKey))
+ {
+ DropClickedResult(a_Player);
+ }
else
{
ClickedResult(a_Player);
}
return;
}
+
super::Clicked(a_Player, a_SlotNum, a_ClickAction, a_ClickedItem);
UpdateRecipe(a_Player);
}
@@ -582,6 +687,27 @@ void cSlotAreaCrafting::ShiftClickedResult(cPlayer & a_Player)
+void cSlotAreaCrafting::DropClickedResult(cPlayer & a_Player)
+{
+ // Get the current recipe:
+ cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player);
+ const cItem & Result = Recipe.GetResult();
+
+ cItem * PlayerSlots = GetPlayerSlots(a_Player) + 1;
+ cCraftingGrid Grid(PlayerSlots, m_GridSize, m_GridSize);
+
+ a_Player.TossPickup(Result);
+ Recipe.ConsumeIngredients(Grid);
+ Grid.CopyToItems(PlayerSlots);
+
+ HandleCraftItem(Result, a_Player);
+ UpdateRecipe(a_Player);
+}
+
+
+
+
+
void cSlotAreaCrafting::UpdateRecipe(cPlayer & a_Player)
{
cCraftingGrid Grid(GetPlayerSlots(a_Player) + 1, m_GridSize, m_GridSize);
@@ -669,15 +795,37 @@ void cSlotAreaAnvil::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_C
return;
}
- if (a_ClickAction == caDblClick)
- {
- return;
- }
-
- if ((a_ClickAction == caShiftLeftClick) || (a_ClickAction == caShiftRightClick))
+ switch (a_ClickAction)
{
- ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
- return;
+ case caDblClick:
+ {
+ return;
+ }
+ case caShiftLeftClick:
+ case caShiftRightClick:
+ {
+ ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
+ return;
+ }
+ case caMiddleClick:
+ {
+ MiddleClicked(a_Player, a_SlotNum);
+ return;
+ }
+ case caDropKey:
+ case caCtrlDropKey:
+ {
+ if (CanTakeResultItem(a_Player))
+ {
+ DropClicked(a_Player, a_SlotNum, true);
+ OnTakeResult(a_Player);
+ }
+ return;
+ }
+ default:
+ {
+ break;
+ }
}
cItem Slot(*GetSlot(a_SlotNum, a_Player));
@@ -1075,12 +1223,16 @@ void cSlotAreaEnchanting::Clicked(cPlayer & a_Player, int a_SlotNum, eClickActio
ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
return;
}
-
case caDblClick:
{
DblClicked(a_Player, a_SlotNum);
return;
}
+ case caMiddleClick:
+ {
+ MiddleClicked(a_Player, a_SlotNum);
+ return;
+ }
default:
{
break;
@@ -1425,11 +1577,32 @@ void cSlotAreaFurnace::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a
bAsync = true;
}
- if ((a_ClickAction == caShiftLeftClick) || (a_ClickAction == caShiftRightClick))
+ switch (a_ClickAction)
{
- HandleSmeltItem(Slot, a_Player);
- ShiftClicked(a_Player, a_SlotNum, Slot);
- return;
+ case caShiftLeftClick:
+ case caShiftRightClick:
+ {
+ HandleSmeltItem(Slot, a_Player);
+ ShiftClicked(a_Player, a_SlotNum, Slot);
+ return;
+ }
+ case caMiddleClick:
+ {
+ MiddleClicked(a_Player, a_SlotNum);
+ return;
+ }
+ case caDropKey:
+ case caCtrlDropKey:
+ {
+ DropClicked(a_Player, a_SlotNum, (a_SlotNum == caCtrlDropKey));
+ Slot.m_ItemCount = Slot.m_ItemCount - GetSlot(a_SlotNum, a_Player)->m_ItemCount;
+ HandleSmeltItem(Slot, a_Player);
+ return;
+ }
+ default:
+ {
+ break;
+ }
}
cItem & DraggingItem = a_Player.GetDraggingItem();
@@ -1607,6 +1780,12 @@ void cSlotAreaInventoryBase::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAc
{
if (a_Player.IsGameModeCreative() && (m_ParentWindow.GetWindowType() == cWindow::wtInventory))
{
+ if ((a_ClickAction == caDropKey) || (a_ClickAction == caCtrlDropKey))
+ {
+ DropClicked(a_Player, a_SlotNum, (a_ClickAction == caCtrlDropKey));
+ return;
+ }
+
// Creative inventory must treat a_ClickedItem as a DraggedItem instead, replacing the inventory slot with it
SetSlot(a_SlotNum, a_Player, a_ClickedItem);
return;
@@ -1694,16 +1873,28 @@ void cSlotAreaArmor::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_C
return;
}
- if ((a_ClickAction == caShiftLeftClick) || (a_ClickAction == caShiftRightClick))
- {
- ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
- return;
- }
-
- // Armors haven't a dbl click
- if (a_ClickAction == caDblClick)
+ switch (a_ClickAction)
{
- return;
+ case caDblClick:
+ {
+ // Armors haven't a dbl click
+ return;
+ }
+ case caShiftLeftClick:
+ case caShiftRightClick:
+ {
+ ShiftClicked(a_Player, a_SlotNum, a_ClickedItem);
+ return;
+ }
+ case caMiddleClick:
+ {
+ MiddleClicked(a_Player, a_SlotNum);
+ return;
+ }
+ default:
+ {
+ break;
+ }
}
cItem Slot(*GetSlot(a_SlotNum, a_Player));
diff --git a/src/UI/SlotArea.h b/src/UI/SlotArea.h
index e271ea454..3dc5c3849 100644
--- a/src/UI/SlotArea.h
+++ b/src/UI/SlotArea.h
@@ -46,10 +46,19 @@ public:
/// Called from Clicked when the action is a shiftclick (left or right)
virtual void ShiftClicked(cPlayer & a_Player, int a_SlotNum, const cItem & a_ClickedItem);
-
+
/// Called from Clicked when the action is a caDblClick
virtual void DblClicked(cPlayer & a_Player, int a_SlotNum);
-
+
+ /** Called from Clicked when the action is a middleclick */
+ virtual void MiddleClicked(cPlayer & a_Player, int a_SlotNum);
+
+ /** Called from Clicked when the action is a drop click. */
+ virtual void DropClicked(cPlayer & a_Player, int a_SlotNum, bool a_DropStack);
+
+ /** Called from Clicked when the action is a number click. */
+ virtual void NumberClicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickAction);
+
/// Called when a new player opens the same parent window. The window already tracks the player. CS-locked.
virtual void OnPlayerAdded(cPlayer & a_Player);
@@ -237,6 +246,7 @@ public:
// Distributing items into this area is completely disabled
virtual void DistributeStack(cItem & a_ItemStack, cPlayer & a_Player, bool a_ShouldApply, bool a_KeepEmptySlots) override;
+
protected:
/// Maps player's EntityID -> current recipe; not a std::map because cCraftingGrid needs proper constructor params
typedef std::list<std::pair<int, cCraftingRecipe> > cRecipeMap;
@@ -249,7 +259,10 @@ protected:
/// Handles a shift-click in the result slot. Crafts using the current recipe until it changes or no more space for result.
void ShiftClickedResult(cPlayer & a_Player);
-
+
+ /** Handles a drop-click in the result slot. */
+ void DropClickedResult(cPlayer & a_Player);
+
/// Updates the current recipe and result slot based on the ingredients currently in the crafting grid of the specified player
void UpdateRecipe(cPlayer & a_Player);
diff --git a/src/UI/Window.cpp b/src/UI/Window.cpp
index 37d3c6bd6..43d923fa5 100644
--- a/src/UI/Window.cpp
+++ b/src/UI/Window.cpp
@@ -178,6 +178,7 @@ void cWindow::Clicked(
switch (a_ClickAction)
{
+ case caLeftClickOutside:
case caRightClickOutside:
{
if (PlgMgr->CallHookPlayerTossingItem(a_Player))
@@ -190,25 +191,16 @@ void cWindow::Clicked(
a_Player.TossPickup(a_ClickedItem);
}
- // Toss one of the dragged items:
- a_Player.TossHeldItem();
- return;
- }
- case caLeftClickOutside:
- {
- if (PlgMgr->CallHookPlayerTossingItem(a_Player))
+ if (a_ClickAction == caLeftClickOutside)
{
- // A plugin doesn't agree with the tossing. The plugin itself is responsible for handling the consequences (possible inventory mismatch)
- return;
+ // Toss all dragged items:
+ a_Player.TossHeldItem(a_Player.GetDraggingItem().m_ItemCount);
}
-
- if (a_Player.IsGameModeCreative())
+ else
{
- a_Player.TossPickup(a_ClickedItem);
+ // Toss one of the dragged items:
+ a_Player.TossHeldItem();
}
-
- // Toss all dragged items:
- a_Player.TossHeldItem(a_Player.GetDraggingItem().m_ItemCount);
return;
}
case caLeftClickOutsideHoldNothing:
diff --git a/src/Vector3.h b/src/Vector3.h
index 5faac1457..c175bf135 100644
--- a/src/Vector3.h
+++ b/src/Vector3.h
@@ -134,6 +134,16 @@ public:
z += a_Diff.z;
}
+ /** Runs each value of the vector through std::floor() */
+ inline Vector3<int> Floor(void) const
+ {
+ return Vector3<int>(
+ (int)floor(x),
+ (int)floor(y),
+ (int)floor(z)
+ );
+ }
+
// tolua_end
inline bool operator != (const Vector3<T> & a_Rhs) const
@@ -146,6 +156,16 @@ public:
return Equals(a_Rhs);
}
+ inline bool operator > (const Vector3<T> & a_Rhs) const
+ {
+ return (SqrLength() > a_Rhs.SqrLength());
+ }
+
+ inline bool operator < (const Vector3<T> & a_Rhs) const
+ {
+ return (SqrLength() < a_Rhs.SqrLength());
+ }
+
inline void operator += (const Vector3<T> & a_Rhs)
{
x += a_Rhs.x;
@@ -288,6 +308,7 @@ protected:
{
return (a_Value < 0) ? -a_Value : a_Value;
}
+
};
// tolua_end
@@ -295,6 +316,15 @@ protected:
+template <> inline Vector3<int> Vector3<int>::Floor(void) const
+{
+ return *this;
+}
+
+
+
+
+
template <typename T>
const double Vector3<T>::EPS = 0.000001;
diff --git a/src/World.cpp b/src/World.cpp
index bc30f16e4..64a629233 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -1886,15 +1886,6 @@ void cWorld::BroadcastCollectEntity(const cEntity & a_Entity, const cPlayer & a_
-void cWorld::BroadcastCollectPickup(const cPickup & a_Pickup, const cPlayer & a_Player, const cClientHandle * a_Exclude)
-{
- m_ChunkMap->BroadcastCollectEntity(a_Pickup, a_Player, a_Exclude);
-}
-
-
-
-
-
void cWorld::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastDestroyEntity(a_Entity, a_Exclude);
@@ -3051,6 +3042,15 @@ void cWorld::TabCompleteUserName(const AString & a_Text, AStringVector & a_Resul
+void cWorld::SetChunkAlwaysTicked(int a_ChunkX, int a_ChunkZ, bool a_AlwaysTicked)
+{
+ m_ChunkMap->SetChunkAlwaysTicked(a_ChunkX, a_ChunkZ, a_AlwaysTicked);
+}
+
+
+
+
+
cRedstoneSimulator * cWorld::InitializeRedstoneSimulator(cIniFile & a_IniFile)
{
AString SimulatorName = a_IniFile.GetValueSet("Physics", "RedstoneSimulator", "");
diff --git a/src/World.h b/src/World.h
index 4f39ce231..27dd81be3 100644
--- a/src/World.h
+++ b/src/World.h
@@ -207,7 +207,6 @@ public:
void BroadcastChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer, const cClientHandle * a_Exclude = NULL);
void BroadcastCollectEntity (const cEntity & a_Pickup, const cPlayer & a_Player, const cClientHandle * a_Exclude = NULL);
- void BroadcastCollectPickup (const cPickup & a_Pickup, const cPlayer & a_Player, const cClientHandle * a_Exclude = NULL);
void BroadcastDestroyEntity (const cEntity & a_Entity, const cClientHandle * a_Exclude = NULL);
void BroadcastEntityEffect (const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude = NULL);
void BroadcastEntityEquipment (const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item, const cClientHandle * a_Exclude = NULL);
@@ -762,6 +761,13 @@ public:
/** Get the current darkness level based on the time */
NIBBLETYPE GetSkyDarkness() { return m_SkyDarkness; }
+
+ /** Increments (a_AlwaysTicked == true) or decrements (false) the m_AlwaysTicked counter for the specified chunk.
+ If the m_AlwaysTicked counter is greater than zero, the chunk is ticked in the tick-thread regardless of
+ whether it has any clients or not.
+ This function allows nesting and task-concurrency (multiple separate tasks can request ticking and as long
+ as at least one requests is active the chunk will be ticked). */
+ void SetChunkAlwaysTicked(int a_ChunkX, int a_ChunkZ, bool a_AlwaysTicked = true); // tolua_export
private:
diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp
index f5ce06fcb..3ccbceb7a 100644
--- a/src/WorldStorage/NBTChunkSerializer.cpp
+++ b/src/WorldStorage/NBTChunkSerializer.cpp
@@ -589,20 +589,19 @@ void cNBTChunkSerializer::AddProjectileEntity(cProjectileEntity * a_Projectile)
m_Writer.BeginCompound("");
AddBasicEntity(a_Projectile, a_Projectile->GetMCAClassName());
Vector3d Pos = a_Projectile->GetPosition();
- m_Writer.AddShort("xTile", (Int16)floor(Pos.x));
- m_Writer.AddShort("yTile", (Int16)floor(Pos.y));
- m_Writer.AddShort("zTile", (Int16)floor(Pos.z));
- m_Writer.AddShort("inTile", 0); // TODO: Query the block type
- m_Writer.AddShort("shake", 0); // TODO: Any shake?
- m_Writer.AddByte ("inGround", a_Projectile->IsInGround() ? 1 : 0);
+ m_Writer.AddByte("inGround", a_Projectile->IsInGround() ? 1 : 0);
switch (a_Projectile->GetProjectileKind())
{
case cProjectileEntity::pkArrow:
{
- m_Writer.AddByte("inData", 0); // TODO: Query the block meta (is it needed?)
- m_Writer.AddByte("pickup", ((cArrowEntity *)a_Projectile)->GetPickupState());
- m_Writer.AddDouble("damage", ((cArrowEntity *)a_Projectile)->GetDamageCoeff());
+ cArrowEntity * Arrow = (cArrowEntity *)a_Projectile;
+
+ m_Writer.AddInt("xTile", (Int16)Arrow->GetBlockHit().x);
+ m_Writer.AddInt("yTile", (Int16)Arrow->GetBlockHit().y);
+ m_Writer.AddInt("zTile", (Int16)Arrow->GetBlockHit().z);
+ m_Writer.AddByte("pickup", Arrow->GetPickupState());
+ m_Writer.AddDouble("damage", Arrow->GetDamageCoeff());
break;
}
case cProjectileEntity::pkGhastFireball:
@@ -620,14 +619,11 @@ void cNBTChunkSerializer::AddProjectileEntity(cProjectileEntity * a_Projectile)
{
ASSERT(!"Unsaved projectile entity!");
}
- } // switch (ProjectileKind)
- cEntity * Creator = a_Projectile->GetCreator();
- if (Creator != NULL)
+ } // switch (ProjectileKind)
+
+ if (!a_Projectile->GetCreatorName().empty())
{
- if (Creator->GetEntityType() == cEntity::etPlayer)
- {
- m_Writer.AddString("ownerName", ((cPlayer *)Creator)->GetName());
- }
+ m_Writer.AddString("ownerName", a_Projectile->GetCreatorName());
}
m_Writer.EndCompound();
}
diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp
index 1e9fc651d..fb2aebbf5 100644
--- a/src/WorldStorage/WSSAnvil.cpp
+++ b/src/WorldStorage/WSSAnvil.cpp
@@ -1646,6 +1646,15 @@ void cWSSAnvil::LoadArrowFromNBT(cEntityList & a_Entities, const cParsedNBT & a_
Arrow->SetDamageCoeff(a_NBT.GetDouble(DamageIdx));
}
+ // Load block hit:
+ int InBlockXIdx = a_NBT.FindChildByName(a_TagIdx, "xTile");
+ int InBlockYIdx = a_NBT.FindChildByName(a_TagIdx, "yTile");
+ int InBlockZIdx = a_NBT.FindChildByName(a_TagIdx, "zTile");
+ if ((InBlockXIdx > 0) && (InBlockYIdx > 0) && (InBlockZIdx > 0))
+ {
+ Arrow->SetBlockHit(Vector3i(a_NBT.GetInt(InBlockXIdx), a_NBT.GetInt(InBlockYIdx), a_NBT.GetInt(InBlockZIdx)));
+ }
+
// Store the new arrow in the entities list:
a_Entities.push_back(Arrow.release());
}
@@ -2478,8 +2487,6 @@ bool cWSSAnvil::LoadProjectileBaseFromNBT(cProjectileEntity & a_Entity, const cP
}
a_Entity.SetIsInGround(IsInGround);
- // TODO: Load inTile, TileCoords
-
return true;
}