#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Horse.h" #include "../World.h" #include "../EffectID.h" #include "../Entities/Player.h" #include "../UI/HorseWindow.h" cHorse::cHorse(int Type, int Color, int Style, int TameTimes) : Super("Horse", mtHorse, "entity.horse.hurt", "entity.horse.death", "entity.horse.ambient", 1.4f, 1.6f), cEntityWindowOwner(this), m_bHasChest(false), m_bIsEating(false), m_bIsRearing(false), m_bIsMouthOpen(false), m_bIsTame(false), m_Type(Type), m_Color(Color), m_Style(Style), m_TimesToTame(TameTimes), m_TameAttemptTimes(0), m_RearTickCount(0), m_MaxSpeed(14.0) { } void cHorse::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { Super::Tick(a_Dt, a_Chunk); if (!IsTicking()) { // The base class tick destroyed us return; } bool MetadataDirty = false; auto & Random = GetRandomProvider(); if (!m_bIsMouthOpen) { if (Random.RandBool(0.02)) { MetadataDirty = true; m_bIsMouthOpen = true; } } else { if (Random.RandBool(0.10)) { MetadataDirty = true; m_bIsMouthOpen = false; } } if ((m_Attachee != nullptr) && (!m_bIsTame)) { if (m_TameAttemptTimes < m_TimesToTame) { if (Random.RandBool(0.02)) { m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, GetPosition().Floor(), int(SmokeDirection::SOUTH_EAST)); m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, GetPosition().Floor(), int(SmokeDirection::SOUTH_WEST)); m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, GetPosition().Floor(), int(SmokeDirection::NORTH_EAST)); m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_SMOKE, GetPosition().Floor(), int(SmokeDirection::NORTH_WEST)); m_World->BroadcastSoundEffect("entity.horse.angry", GetPosition(), 1.0f, 1.0f); m_Attachee->Detach(); MetadataDirty = true; m_bIsRearing = true; } } else { m_World->BroadcastParticleEffect("heart", static_cast(GetPosition()), Vector3f{}, 0, 5); MetadataDirty = true; m_bIsTame = true; } } if (m_bIsRearing) { if (m_RearTickCount == 20) { MetadataDirty = true; m_bIsRearing = false; m_RearTickCount = 0; } else { m_RearTickCount++; } } if (MetadataDirty) { m_World->BroadcastEntityMetadata(*this); } } void cHorse::OnRemoveFromWorld(cWorld & a_World) { const auto Window = GetWindow(); if (Window != nullptr) { Window->OwnerDestroyed(); } Super::OnRemoveFromWorld(a_World); } void cHorse::OnRightClicked(cPlayer & a_Player) { Super::OnRightClicked(a_Player); if (m_bIsTame) { if (a_Player.IsCrouched()) { PlayerOpenWindow(a_Player); return; } auto EquipedItemType = a_Player.GetEquippedItem().m_ItemType; if ( !IsSaddled() && ( (EquipedItemType == E_ITEM_SADDLE) || ItemCategory::IsHorseArmor(EquipedItemType) ) ) { // Player is holding a horse inventory item, open the window: PlayerOpenWindow(a_Player); } else { a_Player.AttachTo(*this); } } else if (a_Player.GetEquippedItem().IsEmpty()) { // Check if leashed / unleashed to player before try to ride if (!m_IsLeashActionJustDone) { if (m_Attachee != nullptr) { if (m_Attachee->GetUniqueID() == a_Player.GetUniqueID()) { a_Player.Detach(); return; } if (m_Attachee->IsPlayer()) { return; } m_Attachee->Detach(); } m_TameAttemptTimes++; a_Player.AttachTo(*this); } } else { m_bIsRearing = true; m_RearTickCount = 0; m_World->BroadcastSoundEffect("entity.horse.angry", GetPosition(), 1.0f, 0.8f); } } void cHorse::SetHorseSaddle(cItem a_Saddle) { if (a_Saddle.m_ItemType == E_ITEM_SADDLE) { m_World->BroadcastSoundEffect("entity.horse.saddle", GetPosition(), 1.0f, 0.8f); } else if (!a_Saddle.IsEmpty()) { return; // Invalid item } m_Saddle = std::move(a_Saddle); m_World->BroadcastEntityMetadata(*this); } void cHorse::SetHorseArmor(cItem a_Armor) { if (ItemCategory::IsHorseArmor(a_Armor.m_ItemType)) { m_World->BroadcastSoundEffect("entity.horse.armor", GetPosition(), 1.0f, 0.8f); } else if (!a_Armor.IsEmpty()) { return; // Invalid item } m_Armor = std::move(a_Armor); m_World->BroadcastEntityMetadata(*this); } int cHorse::GetHorseArmour(void) const { switch (m_Armor.m_ItemType) { case E_ITEM_EMPTY: return 0; case E_ITEM_IRON_HORSE_ARMOR: return 1; case E_ITEM_GOLD_HORSE_ARMOR: return 2; case E_ITEM_DIAMOND_HORSE_ARMOR: return 3; default: { LOGWARN("cHorse::GetHorseArmour: Invalid armour item (%d)", m_Armor.m_ItemType); return 0; } } } void cHorse::GetDrops(cItems & a_Drops, cEntity * a_Killer) { if (IsBaby()) { return; // Babies don't drop items } unsigned int LootingLevel = 0; if (a_Killer != nullptr) { LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); } AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_LEATHER); if (IsSaddled()) { a_Drops.push_back(m_Saddle); } if (!m_Armor.IsEmpty()) { a_Drops.push_back(m_Armor); } } void cHorse::InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { // If horse is tame and someone is sitting on it, don't walk around if ((!m_bIsTame) || (m_Attachee == nullptr)) { Super::InStateIdle(a_Dt, a_Chunk); } } void cHorse::HandleSpeedFromAttachee(float a_Forward, float a_Sideways) { if ((m_bIsTame) && IsSaddled()) { Super::HandleSpeedFromAttachee(a_Forward * m_MaxSpeed, a_Sideways * m_MaxSpeed); } } void cHorse::PlayerOpenWindow(cPlayer & a_Player) { auto Window = GetWindow(); if (Window == nullptr) { Window = new cHorseWindow(*this); OpenWindow(Window); } a_Player.OpenWindow(*Window); }