diff options
Diffstat (limited to '')
-rw-r--r-- | src/Entities/Player.cpp | 368 |
1 files changed, 286 insertions, 82 deletions
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 863aaa799..0dfdcfd8b 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -16,6 +16,9 @@ #include "../Items/ItemHandler.h" #include "../Vector3.h" +#include "../WorldStorage/StatSerializer.h" +#include "../CompositeChat.h" + #include "inifile/iniFile.h" #include "json/json.h" @@ -76,18 +79,16 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) cTimer t1; m_LastPlayerListTime = t1.GetNowTime(); - - m_TimeLastTeleportPacket = 0; m_PlayerName = a_PlayerName; - m_bDirtyPosition = true; // So chunks are streamed to player at spawn if (!LoadFromDisk()) { m_Inventory.Clear(); - SetPosX(cRoot::Get()->GetDefaultWorld()->GetSpawnX()); - SetPosY(cRoot::Get()->GetDefaultWorld()->GetSpawnY()); - SetPosZ(cRoot::Get()->GetDefaultWorld()->GetSpawnZ()); + cWorld * DefaultWorld = cRoot::Get()->GetDefaultWorld(); + SetPosX(DefaultWorld->GetSpawnX()); + SetPosY(DefaultWorld->GetSpawnY()); + SetPosZ(DefaultWorld->GetSpawnZ()); LOGD("Player \"%s\" is connecting for the first time, spawning at default world spawn {%.2f, %.2f, %.2f}", a_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ() @@ -193,6 +194,8 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) return; } } + + m_Stats.AddValue(statMinutesPlayed, 1); if (!a_Chunk.IsValid()) { @@ -208,25 +211,22 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) m_BowCharge += 1; } - //handle updating experience + // Handle updating experience if (m_bDirtyExperience) { SendExperience(); } - if (m_bDirtyPosition) + if (GetPosition() != m_LastPos) // Change in position from last tick? { // Apply food exhaustion from movement: ApplyFoodExhaustionFromMovement(); cRoot::Get()->GetPluginManager()->CallHookPlayerMoving(*this); - BroadcastMovementUpdate(m_ClientHandle); m_ClientHandle->StreamChunks(); } - else - { - BroadcastMovementUpdate(m_ClientHandle); - } + + BroadcastMovementUpdate(m_ClientHandle); if (m_Health > 0) // make sure player is alive { @@ -377,7 +377,7 @@ short cPlayer::DeltaExperience(short a_Xp_delta) } LOGD("Player \"%s\" gained/lost %d experience, total is now: %d", - m_PlayerName.c_str(), a_Xp_delta, m_CurrentXp); + GetName().c_str(), a_Xp_delta, m_CurrentXp); // Set experience to be updated m_bDirtyExperience = true; @@ -391,7 +391,7 @@ short cPlayer::DeltaExperience(short a_Xp_delta) void cPlayer::StartChargingBow(void) { - LOGD("Player \"%s\" started charging their bow", m_PlayerName.c_str()); + LOGD("Player \"%s\" started charging their bow", GetName().c_str()); m_IsChargingBow = true; m_BowCharge = 0; } @@ -402,7 +402,7 @@ void cPlayer::StartChargingBow(void) int cPlayer::FinishChargingBow(void) { - LOGD("Player \"%s\" finished charging their bow at a charge of %d", m_PlayerName.c_str(), m_BowCharge); + LOGD("Player \"%s\" finished charging their bow at a charge of %d", GetName().c_str(), m_BowCharge); int res = m_BowCharge; m_IsChargingBow = false; m_BowCharge = 0; @@ -415,7 +415,7 @@ int cPlayer::FinishChargingBow(void) void cPlayer::CancelChargingBow(void) { - LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", m_PlayerName.c_str(), m_BowCharge); + LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge); m_IsChargingBow = false; m_BowCharge = 0; } @@ -437,7 +437,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) cWorld * World = GetWorld(); if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height)) { - BLOCKTYPE BlockType = World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())); + BLOCKTYPE BlockType = World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT); if (BlockType != E_BLOCK_AIR) { m_bTouchGround = true; @@ -456,8 +456,18 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) else { float Dist = (float)(m_LastGroundHeight - floor(GetPosY())); + + if (Dist >= 2.0) // At least two blocks - TODO: Use m_LastJumpHeight instead of m_LastGroundHeight above + { + // Increment statistic + m_Stats.AddValue(statDistFallen, (StatValue)floor(Dist * 100 + 0.5)); + } + int Damage = (int)(Dist - 3.f); - if (m_LastJumpHeight > m_LastGroundHeight) Damage++; + if (m_LastJumpHeight > m_LastGroundHeight) + { + Damage++; + } m_LastJumpHeight = (float)GetPosY(); if (Damage > 0) @@ -466,7 +476,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround) TakeDamage(dtFalling, NULL, Damage, Damage, 0); // Fall particles - GetWorld()->BroadcastSoundParticleEffect(2006, (int)floor(GetPosX()), (int)GetPosY() - 1, (int)floor(GetPosZ()), Damage /* Used as particle effect speed modifier */); + GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, (int)GetPosY() - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); } m_LastGroundHeight = (float)GetPosY(); @@ -590,7 +600,7 @@ void cPlayer::FinishEating(void) m_EatingFinishTick = -1; // Send the packets: - m_ClientHandle->SendEntityStatus(*this, ENTITY_STATUS_EATING_ACCEPTED); + m_ClientHandle->SendEntityStatus(*this, esPlayerEatingAccepted); m_World->BroadcastEntityAnimation(*this, 0); m_World->BroadcastEntityMetadata(*this); @@ -807,37 +817,41 @@ void cPlayer::SetFlying(bool a_IsFlying) -void cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) +bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI) { if ((a_TDI.DamageType != dtInVoid) && (a_TDI.DamageType != dtPlugin)) { if (IsGameModeCreative()) { // No damage / health in creative mode if not void or plugin damage - return; + return false; } } if ((a_TDI.Attacker != NULL) && (a_TDI.Attacker->IsPlayer())) { - cPlayer* Attacker = (cPlayer*) a_TDI.Attacker; + cPlayer * Attacker = (cPlayer *)a_TDI.Attacker; if ((m_Team != NULL) && (m_Team == Attacker->m_Team)) { if (!m_Team->AllowsFriendlyFire()) { // Friendly fire is disabled - return; + return false; } } } - super::DoTakeDamage(a_TDI); - - // Any kind of damage adds food exhaustion - AddFoodExhaustion(0.3f); - - SendHealth(); + if (super::DoTakeDamage(a_TDI)) + { + // Any kind of damage adds food exhaustion + AddFoodExhaustion(0.3f); + SendHealth(); + + m_Stats.AddValue(statDamageTaken, (StatValue)floor(a_TDI.FinalDamage * 10 + 0.5)); + return true; + } + return false; } @@ -865,6 +879,8 @@ void cPlayer::KilledBy(cEntity * a_Killer) Pickups.Add(cItem(E_ITEM_RED_APPLE)); } + m_Stats.AddValue(statItemsDropped, Pickups.Size()); + m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10); SaveToDisk(); // Save it, yeah the world is a tough place ! @@ -874,9 +890,9 @@ void cPlayer::KilledBy(cEntity * a_Killer) } else if (a_Killer->IsPlayer()) { - GetWorld()->BroadcastChatDeath(Printf("%s was killed by %s", GetName().c_str(), ((cPlayer *)a_Killer)->GetName().c_str())); + cPlayer * Killer = (cPlayer *)a_Killer; - m_World->GetScoreBoard().AddPlayerScore(((cPlayer *)a_Killer)->GetName(), cObjective::otPlayerKillCount, 1); + GetWorld()->BroadcastChatDeath(Printf("%s was killed by %s", GetName().c_str(), Killer->GetName().c_str())); } else { @@ -886,6 +902,8 @@ void cPlayer::KilledBy(cEntity * a_Killer) GetWorld()->BroadcastChatDeath(Printf("%s was killed by a %s", GetName().c_str(), KillerClass.c_str())); } + m_Stats.AddValue(statDeaths); + m_World->GetScoreBoard().AddPlayerScore(GetName(), cObjective::otDeathCount, 1); } @@ -893,9 +911,37 @@ void cPlayer::KilledBy(cEntity * a_Killer) +void cPlayer::Killed(cEntity * a_Victim) +{ + cScoreboard & ScoreBoard = m_World->GetScoreBoard(); + + if (a_Victim->IsPlayer()) + { + m_Stats.AddValue(statPlayerKills); + + ScoreBoard.AddPlayerScore(GetName(), cObjective::otPlayerKillCount, 1); + } + else if (a_Victim->IsMob()) + { + if (((cMonster *)a_Victim)->GetMobFamily() == cMonster::mfHostile) + { + AwardAchievement(achKillMonster); + } + + m_Stats.AddValue(statMobKills); + } + + ScoreBoard.AddPlayerScore(GetName(), cObjective::otTotalKillCount, 1); +} + + + + + void cPlayer::Respawn(void) { m_Health = GetMaxHealth(); + SetInvulnerableTicks(20); // Reset food level: m_FoodLevel = MAX_FOOD_LEVEL; @@ -1110,6 +1156,47 @@ void cPlayer::SetIP(const AString & a_IP) +unsigned int cPlayer::AwardAchievement(const eStatistic a_Ach) +{ + eStatistic Prerequisite = cStatInfo::GetPrerequisite(a_Ach); + + // Check if the prerequisites are met + if (Prerequisite != statInvalid) + { + if (m_Stats.GetValue(Prerequisite) == 0) + { + return 0; + } + } + + StatValue Old = m_Stats.GetValue(a_Ach); + + if (Old > 0) + { + return m_Stats.AddValue(a_Ach); + } + else + { + // First time, announce it + cCompositeChat Msg; + Msg.SetMessageType(mtSuccess); + Msg.AddShowAchievementPart(GetName(), cStatInfo::GetName(a_Ach)); + m_World->BroadcastChat(Msg); + + // Increment the statistic + StatValue New = m_Stats.AddValue(a_Ach); + + // Achievement Get! + m_ClientHandle->SendStatistics(m_Stats); + + return New; + } +} + + + + + void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) { SetPosition(a_PosX, a_PosY, a_PosZ); @@ -1124,6 +1211,17 @@ void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ) +void cPlayer::SendRotation(double a_YawDegrees, double a_PitchDegrees) +{ + SetYaw(a_YawDegrees); + SetPitch(a_PitchDegrees); + m_ClientHandle->SendPlayerMoveLook(); +} + + + + + Vector3d cPlayer::GetThrowStartPos(void) const { Vector3d res = GetEyePosition(); @@ -1154,9 +1252,9 @@ Vector3d cPlayer::GetThrowSpeed(double a_SpeedCoeff) const -void cPlayer::ForceSetSpeed(Vector3d a_Direction) +void cPlayer::ForceSetSpeed(const Vector3d & a_Speed) { - SetSpeed(a_Direction); + SetSpeed(a_Speed); m_ClientHandle->SendEntityVelocity(*this); } @@ -1183,6 +1281,9 @@ void cPlayer::MoveTo( const Vector3d & a_NewPos ) // TODO: should do some checks to see if player is not moving through terrain // TODO: Official server refuses position packets too far away from each other, kicking "hacked" clients; we should, too + + Vector3d DeltaPos = a_NewPos - GetPosition(); + UpdateMovementStats(DeltaPos); SetPosition( a_NewPos ); SetStance(a_NewPos.y + 1.62); @@ -1214,7 +1315,7 @@ void cPlayer::AddToGroup( const AString & a_GroupName ) { cGroup* Group = cRoot::Get()->GetGroupManager()->GetGroup( a_GroupName ); m_Groups.push_back( Group ); - LOGD("Added %s to group %s", m_PlayerName.c_str(), a_GroupName.c_str() ); + LOGD("Added %s to group %s", GetName().c_str(), a_GroupName.c_str() ); ResolveGroups(); ResolvePermissions(); } @@ -1238,13 +1339,13 @@ void cPlayer::RemoveFromGroup( const AString & a_GroupName ) if( bRemoved ) { - LOGD("Removed %s from group %s", m_PlayerName.c_str(), a_GroupName.c_str() ); + LOGD("Removed %s from group %s", GetName().c_str(), a_GroupName.c_str() ); ResolveGroups(); ResolvePermissions(); } else { - LOGWARN("Tried to remove %s from group %s but was not in that group", m_PlayerName.c_str(), a_GroupName.c_str() ); + LOGWARN("Tried to remove %s from group %s but was not in that group", GetName().c_str(), a_GroupName.c_str() ); } } @@ -1350,7 +1451,7 @@ void cPlayer::ResolveGroups() if( AllGroups.find( CurrentGroup ) != AllGroups.end() ) { LOGWARNING("ERROR: Player \"%s\" is in the group multiple times (\"%s\"). Please fix your settings in users.ini!", - m_PlayerName.c_str(), CurrentGroup->GetName().c_str() + GetName().c_str(), CurrentGroup->GetName().c_str() ); } else @@ -1362,7 +1463,7 @@ void cPlayer::ResolveGroups() { if( AllGroups.find( *itr ) != AllGroups.end() ) { - LOGERROR("ERROR: Player %s is in the same group multiple times due to inheritance (%s). FIX IT!", m_PlayerName.c_str(), (*itr)->GetName().c_str() ); + LOGERROR("ERROR: Player %s is in the same group multiple times due to inheritance (%s). FIX IT!", GetName().c_str(), (*itr)->GetName().c_str() ); continue; } ToIterate.push_back( *itr ); @@ -1413,10 +1514,7 @@ void cPlayer::TossEquippedItem(char a_Amount) Drops.push_back(DroppedItem); } - double vX = 0, vY = 0, vZ = 0; - EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); - vY = -vY * 2 + 1.f; - m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player + TossItems(Drops); } @@ -1432,6 +1530,7 @@ void cPlayer::TossHeldItem(char a_Amount) char OriginalItemAmount = Item.m_ItemCount; Item.m_ItemCount = std::min(OriginalItemAmount, a_Amount); Drops.push_back(Item); + if (OriginalItemAmount > a_Amount) { Item.m_ItemCount = OriginalItemAmount - a_Amount; @@ -1442,10 +1541,7 @@ void cPlayer::TossHeldItem(char a_Amount) } } - double vX = 0, vY = 0, vZ = 0; - EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); - vY = -vY * 2 + 1.f; - m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player + TossItems(Drops); } @@ -1457,10 +1553,21 @@ void cPlayer::TossPickup(const cItem & a_Item) cItems Drops; Drops.push_back(a_Item); + TossItems(Drops); +} + + + + + +void cPlayer::TossItems(const cItems & a_Items) +{ + m_Stats.AddValue(statItemsDropped, a_Items.Size()); + double vX = 0, vY = 0, vZ = 0; EulerToVector(-GetYaw(), GetPitch(), vZ, vX, vY); vY = -vY * 2 + 1.f; - m_World->SpawnItemPickups(Drops, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player + m_World->SpawnItemPickups(a_Items, GetPosX(), GetEyeHeight(), GetPosZ(), vX * 3, vY * 3, vZ * 3, true); // 'true' because created by player } @@ -1489,6 +1596,7 @@ bool cPlayer::MoveToWorld(const char * a_WorldName) // Add player to all the necessary parts of the new world SetWorld(World); + m_ClientHandle->StreamChunks(); World->AddEntity(this); World->AddPlayer(this); @@ -1507,25 +1615,19 @@ void cPlayer::LoadPermissionsFromDisk() cIniFile IniFile; if (IniFile.ReadFile("users.ini")) { - std::string Groups = IniFile.GetValue(m_PlayerName, "Groups", ""); - if (!Groups.empty()) + AString Groups = IniFile.GetValueSet(GetName(), "Groups", "Default"); + AStringVector Split = StringSplitAndTrim(Groups, ","); + + for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr) { - AStringVector Split = StringSplitAndTrim(Groups, ","); - for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr) + if (!cRoot::Get()->GetGroupManager()->ExistsGroup(*itr)) { - if (!cRoot::Get()->GetGroupManager()->ExistsGroup(*itr)) - { - LOGWARNING("The group %s for player %s was not found!", itr->c_str(), m_PlayerName.c_str()); - } - AddToGroup(*itr); + LOGWARNING("The group %s for player %s was not found!", itr->c_str(), GetName().c_str()); } - } - else - { - AddToGroup("Default"); + AddToGroup(*itr); } - AString Color = IniFile.GetValue(m_PlayerName, "Color", "-"); + AString Color = IniFile.GetValue(GetName(), "Color", "-"); if (!Color.empty()) { m_Color = Color[0]; @@ -1534,8 +1636,10 @@ void cPlayer::LoadPermissionsFromDisk() else { cGroupManager::GenerateDefaultUsersIni(IniFile); + IniFile.AddValue("Groups", GetName(), "Default"); AddToGroup("Default"); } + IniFile.WriteFile("users.ini"); ResolvePermissions(); } @@ -1547,14 +1651,14 @@ bool cPlayer::LoadFromDisk() LoadPermissionsFromDisk(); // Log player permissions, cause it's what the cool kids do - LOGINFO("Player %s has permissions:", m_PlayerName.c_str() ); + LOGINFO("Player %s has permissions:", GetName().c_str() ); for( PermissionMap::iterator itr = m_ResolvedPermissions.begin(); itr != m_ResolvedPermissions.end(); ++itr ) { if( itr->second ) LOG(" - %s", itr->first.c_str() ); } AString SourceFile; - Printf(SourceFile, "players/%s.json", m_PlayerName.c_str() ); + Printf(SourceFile, "players/%s.json", GetName().c_str() ); cFile f; if (!f.Open(SourceFile, cFile::fmRead)) @@ -1584,10 +1688,7 @@ bool cPlayer::LoadFromDisk() SetPosX(JSON_PlayerPosition[(unsigned int)0].asDouble()); SetPosY(JSON_PlayerPosition[(unsigned int)1].asDouble()); SetPosZ(JSON_PlayerPosition[(unsigned int)2].asDouble()); - m_LastPosX = GetPosX(); - m_LastPosY = GetPosY(); - m_LastPosZ = GetPosZ(); - m_LastFoodPos = GetPosition(); + m_LastPos = GetPosition(); } Json::Value & JSON_PlayerRotation = root["rotation"]; @@ -1618,9 +1719,14 @@ bool cPlayer::LoadFromDisk() m_Inventory.LoadFromJson(root["inventory"]); m_LoadedWorldName = root.get("world", "world").asString(); + + // Load the player stats. + // We use the default world name (like bukkit) because stats are shared between dimensions/worlds. + 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\"", - m_PlayerName.c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str() + GetName().c_str(), GetPosX(), GetPosY(), GetPosZ(), m_LoadedWorldName.c_str() ); return true; @@ -1676,12 +1782,12 @@ bool cPlayer::SaveToDisk() std::string JsonData = writer.write(root); AString SourceFile; - Printf(SourceFile, "players/%s.json", m_PlayerName.c_str() ); + Printf(SourceFile, "players/%s.json", GetName().c_str() ); cFile f; if (!f.Open(SourceFile, cFile::fmWrite)) { - LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", m_PlayerName.c_str(), SourceFile.c_str()); + LOGERROR("ERROR WRITING PLAYER \"%s\" TO FILE \"%s\" - cannot open file", GetName().c_str(), SourceFile.c_str()); return false; } if (f.Write(JsonData.c_str(), JsonData.size()) != (int)JsonData.size()) @@ -1689,6 +1795,16 @@ bool cPlayer::SaveToDisk() LOGERROR("ERROR WRITING PLAYER JSON TO FILE \"%s\"", SourceFile.c_str()); return false; } + + // Save the player stats. + // We use the default world name (like bukkit) because stats are shared between dimensions/worlds. + cStatSerializer StatSerializer(cRoot::Get()->GetDefaultWorld()->GetName(), GetName(), &m_Stats); + if (!StatSerializer.Save()) + { + LOGERROR("Could not save stats for player %s", GetName().c_str()); + return false; + } + return true; } @@ -1703,7 +1819,10 @@ cPlayer::StringList cPlayer::GetResolvedPermissions() const PermissionMap& ResolvedPermissions = m_ResolvedPermissions; for( PermissionMap::const_iterator itr = ResolvedPermissions.begin(); itr != ResolvedPermissions.end(); ++itr ) { - if( itr->second ) Permissions.push_back( itr->first ); + if (itr->second) + { + Permissions.push_back( itr->first ); + } } return Permissions; @@ -1842,23 +1961,108 @@ void cPlayer::HandleFloater() +bool cPlayer::IsClimbing(void) const +{ + int PosX = POSX_TOINT; + int PosY = POSY_TOINT; + int PosZ = POSZ_TOINT; + + if ((PosY < 0) || (PosY >= cChunkDef::Height)) + { + return false; + } + + BLOCKTYPE Block = m_World->GetBlock(PosX, PosY, PosZ); + switch (Block) + { + case E_BLOCK_LADDER: + case E_BLOCK_VINES: + { + return true; + } + default: return false; + } +} + + + + + +void cPlayer::UpdateMovementStats(const Vector3d & a_DeltaPos) +{ + StatValue Value = (StatValue)floor(a_DeltaPos.Length() * 100 + 0.5); + + if (m_AttachedTo == NULL) + { + if (IsClimbing()) + { + if (a_DeltaPos.y > 0.0) // Going up + { + m_Stats.AddValue(statDistClimbed, (StatValue)floor(a_DeltaPos.y * 100 + 0.5)); + } + } + else if (IsSubmerged()) + { + m_Stats.AddValue(statDistDove, Value); + } + else if (IsSwimming()) + { + m_Stats.AddValue(statDistSwum, Value); + } + else if (IsOnGround()) + { + m_Stats.AddValue(statDistWalked, Value); + } + else + { + if (Value >= 25) // Ignore small/slow movement + { + m_Stats.AddValue(statDistFlown, Value); + } + } + } + else + { + switch (m_AttachedTo->GetEntityType()) + { + case cEntity::etMinecart: m_Stats.AddValue(statDistMinecart, Value); break; + case cEntity::etBoat: m_Stats.AddValue(statDistBoat, Value); break; + case cEntity::etMonster: + { + cMonster * Monster = (cMonster *)m_AttachedTo; + switch (Monster->GetMobType()) + { + case cMonster::mtPig: m_Stats.AddValue(statDistPig, Value); break; + case cMonster::mtHorse: m_Stats.AddValue(statDistHorse, Value); break; + default: break; + } + break; + } + default: break; + } + } +} + + + + + void cPlayer::ApplyFoodExhaustionFromMovement() { if (IsGameModeCreative()) { return; } - - // Calculate the distance travelled, update the last pos: - Vector3d Movement(GetPosition() - m_LastFoodPos); - Movement.y = 0; // Only take XZ movement into account - m_LastFoodPos = GetPosition(); - + // If riding anything, apply no food exhaustion if (m_AttachedTo != NULL) { return; } + + // Calculate the distance travelled, update the last pos: + Vector3d Movement(GetPosition() - m_LastPos); + Movement.y = 0; // Only take XZ movement into account // Apply the exhaustion based on distance travelled: double BaseExhaustion = Movement.Length(); @@ -1887,9 +2091,9 @@ void cPlayer::ApplyFoodExhaustionFromMovement() void cPlayer::Detach() { super::Detach(); - int PosX = (int)floor(GetPosX()); - int PosY = (int)floor(GetPosY()); - int PosZ = (int)floor(GetPosZ()); + int PosX = POSX_TOINT; + int PosY = POSY_TOINT; + int PosZ = POSZ_TOINT; // Search for a position within an area to teleport player after detachment // Position must be solid land, and occupied by a nonsolid block |