diff options
Diffstat (limited to 'src/Entities')
-rw-r--r-- | src/Entities/Player.cpp | 106 | ||||
-rw-r--r-- | src/Entities/Player.h | 43 | ||||
-rw-r--r-- | src/Entities/ProjectileEntity.cpp | 160 | ||||
-rw-r--r-- | src/Entities/ProjectileEntity.h | 59 |
4 files changed, 298 insertions, 70 deletions
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 098417dc5..f37a23f22 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -66,7 +66,9 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) , m_EatingFinishTick(-1) , m_IsChargingBow(false) , m_BowCharge(0) - , m_XpTotal(0) + , m_CurrentXp(0) + , m_LifetimeTotalXp(0) + , m_bDirtyExperience(false) { LOGD("Created a player object for \"%s\" @ \"%s\" at %p, ID %d", a_PlayerName.c_str(), a_Client->GetIPString().c_str(), @@ -222,6 +224,12 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) { m_BowCharge += 1; } + + //handle updating experience + if (m_bDirtyExperience) + { + SendExperience(); + } if (m_bDirtyPosition) { @@ -262,7 +270,7 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk) -int cPlayer::CalcLevelFromXp(int a_XpTotal) +short cPlayer::CalcLevelFromXp(short a_XpTotal) { //level 0 to 15 if(a_XpTotal <= XP_TO_LEVEL15) @@ -273,18 +281,18 @@ int cPlayer::CalcLevelFromXp(int a_XpTotal) //level 30+ if(a_XpTotal > XP_TO_LEVEL30) { - return (int) (151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7; + return (short) (151.5 + sqrt( 22952.25 - (14 * (2220 - a_XpTotal)))) / 7; } //level 16 to 30 - return (int) ( 29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal )))) / 3; + return (short) ( 29.5 + sqrt( 870.25 - (6 * ( 360 - a_XpTotal )))) / 3; } -int cPlayer::XpForLevel(int a_Level) +short cPlayer::XpForLevel(short a_Level) { //level 0 to 15 if(a_Level <= 15) @@ -295,46 +303,51 @@ int cPlayer::XpForLevel(int a_Level) //level 30+ if(a_Level >= 31) { - return (int) ( (3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220 ); + return (short) ( (3.5 * a_Level * a_Level) - (151.5 * a_Level) + 2220 ); } //level 16 to 30 - return (int) ( (1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360 ); + return (short) ( (1.5 * a_Level * a_Level) - (29.5 * a_Level) + 360 ); } -int cPlayer::XpGetLevel() +short cPlayer::GetXpLevel() { - return CalcLevelFromXp(m_XpTotal); + return CalcLevelFromXp(m_CurrentXp); } -float cPlayer::XpGetPercentage() +float cPlayer::GetXpPercentage() { - int currentLevel = CalcLevelFromXp(m_XpTotal); + short int currentLevel = CalcLevelFromXp(m_CurrentXp); + short int currentLevel_XpBase = XpForLevel(currentLevel); - return (float)m_XpTotal / (float)XpForLevel(1+currentLevel); + return (float)(m_CurrentXp - currentLevel_XpBase) / + (float)(XpForLevel(1+currentLevel) - currentLevel_XpBase); } -bool cPlayer::SetExperience(int a_XpTotal) +bool cPlayer::SetCurrentExperience(short int a_CurrentXp) { - if(!(a_XpTotal >= 0) || (a_XpTotal > (INT_MAX - m_XpTotal))) + if(!(a_CurrentXp >= 0) || (a_CurrentXp > (SHRT_MAX - m_LifetimeTotalXp))) { - LOGWARNING("Tried to update experiece with an invalid Xp value: %d", a_XpTotal); + LOGWARNING("Tried to update experiece with an invalid Xp value: %d", a_CurrentXp); return false; //oops, they gave us a dodgey number } - m_XpTotal = a_XpTotal; + m_CurrentXp = a_CurrentXp; + + // Set experience to be updated + m_bDirtyExperience = true; return true; } @@ -343,21 +356,37 @@ bool cPlayer::SetExperience(int a_XpTotal) -int cPlayer::AddExperience(int a_Xp_delta) +short cPlayer::DeltaExperience(short a_Xp_delta) { - if(a_Xp_delta < 0) + if (a_Xp_delta > (SHRT_MAX - m_CurrentXp)) { - //value was negative, abort and report - LOGWARNING("Attempt was made to increment Xp by %d, must be positive", + // Value was bad, abort and report + LOGWARNING("Attempt was made to increment Xp by %d, which overflowed the short datatype. Ignoring.", a_Xp_delta); - return -1; //should we instead just return the current Xp? + return -1; // Should we instead just return the current Xp? + } + + m_CurrentXp += a_Xp_delta; + + // Make sure they didn't subtract too much + if (m_CurrentXp < 0) + { + m_CurrentXp = 0; + } + + // Update total for score calculation + if (a_Xp_delta > 0) + { + m_LifetimeTotalXp += a_Xp_delta; } - - LOGD("Player \"%s\" earnt %d experience", m_PlayerName.c_str(), a_Xp_delta); - m_XpTotal += a_Xp_delta; + LOGD("Player \"%s\" gained/lost %d experience, total is now: %d", + m_PlayerName.c_str(), a_Xp_delta, m_CurrentXp); - return m_XpTotal; + // Set experience to be updated + m_bDirtyExperience = true; + + return m_CurrentXp; } @@ -607,6 +636,19 @@ void cPlayer::SendHealth(void) +void cPlayer::SendExperience(void) +{ + if (m_ClientHandle != NULL) + { + m_ClientHandle->SendExperience(); + m_bDirtyExperience = false; + } +} + + + + + void cPlayer::ClearInventoryPaintSlots(void) { // Clear the list of slots that are being inventory-painted. Used by cWindow only @@ -759,6 +801,11 @@ void cPlayer::Respawn(void) m_FoodLevel = MAX_FOOD_LEVEL; m_FoodSaturationLevel = 5; + // Reset Experience + m_CurrentXp = 0; + m_LifetimeTotalXp = 0; + // ToDo: send score to client? How? + m_ClientHandle->SendRespawn(); // Extinguish the fire: @@ -1411,14 +1458,16 @@ bool cPlayer::LoadFromDisk() SetRoll ((float)JSON_PlayerRotation[(unsigned int)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(); m_FoodTickTimer = root.get("foodTickTimer", 0).asInt(); m_FoodExhaustionLevel = root.get("foodExhaustion", 0).asDouble(); + m_LifetimeTotalXp = (short) root.get("xpTotal", 0).asInt(); + m_CurrentXp = (short) root.get("xpCurrent", 0).asInt(); - SetExperience(root.get("experience", 0).asInt()); + //SetExperience(root.get("experience", 0).asInt()); m_GameMode = (eGameMode) root.get("gamemode", eGameMode_NotSet).asInt(); @@ -1460,7 +1509,8 @@ bool cPlayer::SaveToDisk() root["rotation"] = JSON_PlayerRotation; root["inventory"] = JSON_Inventory; root["health"] = m_Health; - root["experience"] = m_XpTotal; + root["xpTotal"] = m_LifetimeTotalXp; + root["xpCurrent"] = m_CurrentXp; root["air"] = m_AirLevel; root["food"] = m_FoodLevel; root["foodSaturation"] = m_FoodSaturationLevel; diff --git a/src/Entities/Player.h b/src/Entities/Player.h index ab2f94d4c..44cab7d74 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -71,21 +71,31 @@ public: Returns true on success "should" really only be called at init or player death, plugins excepted */ - bool SetExperience(int a_XpTotal); + bool SetCurrentExperience(short a_XpTotal); - /* Adds Xp, "should" not inc more than MAX_EXPERIENCE_ORB_SIZE unless you're a plugin being funny, *cough* cheating - Returns the new total experience, -1 on error + /* changes Xp by Xp_delta, you "shouldn't" inc more than MAX_EXPERIENCE_ORB_SIZE + Wont't allow xp to go negative + Returns the new current experience, -1 on error */ - int AddExperience(int a_Xp_delta); + short DeltaExperience(short a_Xp_delta); - /// Gets the experience total - XpTotal - inline int XpGetTotal(void) { return m_XpTotal; } + /// Gets the experience total - XpTotal for score on death + inline short GetXpLifetimeTotal(void) { return m_LifetimeTotalXp; } + + /// Gets the currrent experience + inline short GetCurrentXp(void) { return m_CurrentXp; } /// Gets the current level - XpLevel - int XpGetLevel(void); + short GetXpLevel(void); /// Gets the experience bar percentage - XpP - float XpGetPercentage(void); + float GetXpPercentage(void); + + /// Caculates the amount of XP needed for a given level, ref: http://minecraft.gamepedia.com/XP + static short XpForLevel(short int a_Level); + + /// inverse of XpForLevel, ref: http://minecraft.gamepedia.com/XP values are as per this with pre-calculations + static short CalcLevelFromXp(short int a_CurrentXp); // tolua_end @@ -269,6 +279,8 @@ public: void UseEquippedItem(void); void SendHealth(void); + + void SendExperience(void); // In UI windows, the item that the player is dragging: bool IsDraggingItem(void) const { return !m_DraggingItem.IsEmpty(); } @@ -319,9 +331,7 @@ public: virtual bool IsCrouched (void) const { return m_IsCrouched; } virtual bool IsSprinting(void) const { return m_IsSprinting; } virtual bool IsRclking (void) const { return IsEating(); } - - - + protected: typedef std::map< std::string, bool > PermissionMap; PermissionMap m_ResolvedPermissions; @@ -413,17 +423,16 @@ protected: Int64 m_EatingFinishTick; /// Player Xp level - int m_XpTotal; + short int m_LifetimeTotalXp; + short int m_CurrentXp; - /// Caculates the Xp needed for a given level, ref: http://minecraft.gamepedia.com/XP - static int XpForLevel(int a_Level); + // flag saying we need to send a xp update to client + bool m_bDirtyExperience; - /// inverse of XpAtLevel, ref: http://minecraft.gamepedia.com/XP values are as per this with pre-calculations - static int CalcLevelFromXp(int a_XpTotal); - bool m_IsChargingBow; int m_BowCharge; + virtual void Destroyed(void); /// Filters out damage for creative mode diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp index c63b9523b..fb25aea35 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -230,6 +230,8 @@ cProjectileEntity * cProjectileEntity::Create(eKind a_Kind, cEntity * a_Creator, case pkSnowball: return new cThrownSnowballEntity (a_Creator, a_X, a_Y, a_Z, Speed); case pkGhastFireball: return new cGhastFireballEntity (a_Creator, a_X, a_Y, a_Z, Speed); case pkFireCharge: return new cFireChargeEntity (a_Creator, a_X, a_Y, a_Z, Speed); + case pkExpBottle: return new cExpBottleEntity (a_Creator, a_X, a_Y, a_Z, Speed); + case pkFirework: return new cFireworkEntity (a_Creator, a_X, a_Y, a_Z ); // TODO: the rest } @@ -287,7 +289,11 @@ AString cProjectileEntity::GetMCAClassName(void) const void cProjectileEntity::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); - BroadcastMovementUpdate(); + + if (GetProjectileKind() != pkArrow) // See cArrow::Tick + { + BroadcastMovementUpdate(); + } } @@ -391,7 +397,8 @@ cArrowEntity::cArrowEntity(cEntity * a_Creator, double a_X, double a_Y, double a m_IsCritical(false), m_Timer(0), m_bIsCollected(false), - m_HitBlockPos(Vector3i(0, 0, 0)) + m_HitBlockPos(Vector3i(0, 0, 0)), + m_HitGroundTimer(0) { SetSpeed(a_Speed); SetMass(0.1); @@ -414,7 +421,8 @@ cArrowEntity::cArrowEntity(cPlayer & a_Player, double a_Force) : m_IsCritical((a_Force >= 1)), m_Timer(0), m_bIsCollected(false), - m_HitBlockPos(0, 0, 0) + m_HitBlockPos(0, 0, 0), + m_HitGroundTimer(0) { } @@ -440,37 +448,25 @@ bool cArrowEntity::CanPickup(const cPlayer & a_Player) const void cArrowEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) { - if (a_HitFace == BLOCK_FACE_NONE) - { - return; - } + 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; - - if (a_HitFace != BLOCK_FACE_YP) - { - AddFaceDirection(a_X, a_Y, a_Z, a_HitFace); - } - else if (a_HitFace == BLOCK_FACE_YP) // These conditions because xoft got a little confused on block face directions, so AddFace works with all but YP & YM - { - a_Y--; - } - else + + switch (a_HitFace) { - a_Y++; + 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); } m_HitBlockPos = Vector3i(a_X, a_Y, a_Z); // Broadcast arrow hit sound m_World->BroadcastSoundEffect("random.bowhit", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 0.5, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); - - // Broadcast the position and speed packets before teleporting: - BroadcastMovementUpdate(); - - // Teleport the entity to the exact hit coords: - m_World->BroadcastTeleportEntity(*this); } @@ -542,6 +538,24 @@ void cArrowEntity::Tick(float a_Dt, cChunk & a_Chunk) if (m_IsInGround) { + // When an arrow hits, the client doesn't think its in the ground and keeps on moving, IF BroadcastMovementUpdate() and TeleportEntity was called during flight, AT ALL + // Fix is to simply not sync with the client and send a teleport to confirm pos after arrow has stabilised (around 1 sec after landing) + // We can afford to do this because xoft's algorithm for trajectory is near perfect, so things are pretty close anyway without sync + // Besides, this seems to be what the vanilla server does, note how arrows teleport half a second after they hit to the server position + + if (m_HitGroundTimer != -1) // Sent a teleport already, don't do again + { + if (m_HitGroundTimer > 1000.f) // Send after a second, could be less, but just in case + { + m_World->BroadcastTeleportEntity(*this); + m_HitGroundTimer = -1; + } + else + { + m_HitGroundTimer += a_Dt; + } + } + int RelPosX = m_HitBlockPos.x - a_Chunk.GetPosX() * cChunkDef::Width; int RelPosZ = m_HitBlockPos.z - a_Chunk.GetPosZ() * cChunkDef::Width; cChunk * Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelPosX, RelPosZ); @@ -651,6 +665,104 @@ void cThrownSnowballEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_Hi /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBottleOEnchantingEntity : + +cExpBottleEntity::cExpBottleEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : +super(pkExpBottle, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) +{ + SetSpeed(a_Speed); +} + + + + + +void cExpBottleEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +{ + // TODO: Spawn experience orbs + + Destroy(); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFireworkEntity : + +cFireworkEntity::cFireworkEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z) : +super(pkFirework, a_Creator, a_X, a_Y, a_Z, 0.25, 0.25) +{ +} + + + + + +void cFireworkEntity::OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) +{ + if ((a_HitFace != BLOCK_FACE_BOTTOM) && (a_HitFace != BLOCK_FACE_NONE)) + { + return; + } + + SetSpeed(0, 0, 0); + SetPosition(GetPosX(), GetPosY() - 0.5, GetPosZ()); + + std::cout << a_HitPos.x << " " << a_HitPos.y << " " << a_HitPos.z << std::endl; + + m_IsInGround = true; + + BroadcastMovementUpdate(); +} + + + + + +void cFireworkEntity::HandlePhysics(float a_Dt, cChunk & a_Chunk) +{ + if (m_IsInGround) + { + if (a_Chunk.GetBlock((int)GetPosX(), (int)GetPosY() + 1, (int)GetPosZ()) == E_BLOCK_AIR) + { + m_IsInGround = false; + } + else + { + 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 + + // Update the position: + SetPosition(NextPos); + + // Add slowdown and gravity effect to the speed: + Vector3d NewSpeed(GetSpeed()); + NewSpeed.y += 2; + NewSpeed *= TracerCallback.GetSlowdownCoeff(); + SetSpeed(NewSpeed); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cGhastFireballEntity : cGhastFireballEntity::cGhastFireballEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed) : diff --git a/src/Entities/ProjectileEntity.h b/src/Entities/ProjectileEntity.h index 28dd76935..959e81ae5 100644 --- a/src/Entities/ProjectileEntity.h +++ b/src/Entities/ProjectileEntity.h @@ -34,6 +34,7 @@ public: pkEnderPearl = 65, pkExpBottle = 75, pkSplashPotion = 73, + pkFirework = 76, pkWitherSkull = 66, pkFishingFloat = 90, } ; @@ -83,7 +84,7 @@ protected: /// True if the projectile has hit the ground and is stuck there bool m_IsInGround; - + // cEntity overrides: virtual void Tick(float a_Dt, cChunk & a_Chunk) override; virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk) override; @@ -159,6 +160,9 @@ protected: /// Timer for pickup collection animation or five minute timeout float m_Timer; + /// Timer for client arrow position confirmation via TeleportEntity + float m_HitGroundTimer; + /// If true, the arrow is in the process of being collected - don't go to anyone else bool m_bIsCollected; @@ -260,6 +264,59 @@ protected: +class cExpBottleEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cExpBottleEntity); + + cExpBottleEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z, const Vector3d & a_Speed); + +protected: + + // cProjectileEntity overrides: + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + + // tolua_begin + +}; + + + + + +class cFireworkEntity : + public cProjectileEntity +{ + typedef cProjectileEntity super; + +public: + + // tolua_end + + CLASS_PROTODEF(cFireworkEntity); + + cFireworkEntity(cEntity * a_Creator, double a_X, double a_Y, double a_Z); + +protected: + + // cProjectileEntity overrides: + virtual void OnHitSolidBlock(const Vector3d & a_HitPos, char a_HitFace) override; + virtual void HandlePhysics(float a_Dt, cChunk & a_Chunk) override; + + // tolua_begin + +}; + + + + + class cGhastFireballEntity : public cProjectileEntity { |