diff options
Diffstat (limited to 'src')
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; } |