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.cpp24
-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/Generating/Prefabs/PlainsVillagePrefabs.cpp15
-rw-r--r--src/Items/ItemBow.h13
-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.h9
-rw-r--r--src/World.cpp18
-rw-r--r--src/World.h8
19 files changed, 615 insertions, 247 deletions
diff --git a/src/BlockInfo.cpp b/src/BlockInfo.cpp
index 16314290a..9a953e396 100644
--- a/src/BlockInfo.cpp
+++ b/src/BlockInfo.cpp
@@ -365,7 +365,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 0fee40cac..1e80eb61b 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)
}
@@ -1641,6 +1645,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);
@@ -1852,7 +1881,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 c9fb0b59e..687c0824f 100644
--- a/src/ChunkMap.cpp
+++ b/src/ChunkMap.cpp
@@ -2674,6 +2674,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:
@@ -2788,12 +2802,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 433516490..b8870dfca 100644
--- a/src/ChunkMap.h
+++ b/src/ChunkMap.h
@@ -339,6 +339,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 47a0876fc..d59088e72 100644
--- a/src/Entities/ArrowEntity.cpp
+++ b/src/Entities/ArrowEntity.cpp
@@ -106,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();
}
@@ -136,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
- );
}
}
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/Generating/Prefabs/PlainsVillagePrefabs.cpp b/src/Generating/Prefabs/PlainsVillagePrefabs.cpp
index bba493bf1..bad8dae74 100644
--- a/src/Generating/Prefabs/PlainsVillagePrefabs.cpp
+++ b/src/Generating/Prefabs/PlainsVillagePrefabs.cpp
@@ -356,7 +356,8 @@ const cPrefab::sDef g_PlainsVillagePrefabs[] =
"e: 8: 0\n" /* water */
"f: 50: 5\n" /* torch */
"g: 59: 7\n" /* crops */
- "h:111: 0\n" /* lilypad */
+ "h: 59: 0\n" /* crops */
+ "i: 59: 1\n" /* crops */
"m: 19: 0\n" /* sponge */,
// Block data:
@@ -404,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...gghgg."
- /* 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/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 381c6e121..ebdc1aea8 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 9e855b8af..c175bf135 100644
--- a/src/Vector3.h
+++ b/src/Vector3.h
@@ -316,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 48c3448a3..f91a15260 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);
@@ -3042,6 +3033,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 32e64c8e0..f903ff5be 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);
@@ -773,6 +772,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: