#pragma once
#include "../Entities/Floater.h"
#include "../Entities/Entity.h"
#include "../Item.h"
#include "../Root.h"
////////////////////////////////////////////////////////////////////////////////
// cFloaterCallback
class cFloaterCallback
{
public:
cFloaterCallback(void) :
m_CanPickup(false), m_AttachedMobID(cEntity::INVALID_ID)
{
}
bool operator()(cEntity & a_Entity)
{
auto & Floater = static_cast<cFloater &>(a_Entity);
m_CanPickup = Floater.CanPickup();
m_Pos = Floater.GetPosition();
m_BitePos = Floater.GetBitePos();
m_AttachedMobID = Floater.GetAttachedMobID();
Floater.Destroy();
return true;
}
bool CanPickup(void) const { return m_CanPickup; }
bool IsAttached(void) const { return (m_AttachedMobID != cEntity::INVALID_ID); }
UInt32 GetAttachedMobID(void) const { return m_AttachedMobID; }
Vector3d GetPos(void) const { return m_Pos; }
Vector3d GetBitePos(void) const { return m_BitePos; }
protected:
bool m_CanPickup;
UInt32 m_AttachedMobID;
Vector3d m_Pos;
Vector3d m_BitePos;
};
class cItemFishingRodHandler final : public cItemHandler
{
using Super = cItemHandler;
public:
using Super::Super;
virtual bool OnItemUse(
cWorld * a_World,
cPlayer * a_Player,
cBlockPluginInterface & a_PluginInterface,
const cItem & a_HeldItem,
const Vector3i a_ClickedBlockPos,
eBlockFace a_ClickedBlockFace
) const override
{
if (a_ClickedBlockFace != BLOCK_FACE_NONE)
{
return false;
}
if (a_Player->IsFishing())
{
ReelIn(*a_World, *a_Player);
}
else
{
// Cast a hook:
auto & Random = GetRandomProvider();
auto CountDownTime = Random.RandInt(100, 900) -
static_cast<int>(a_Player->GetEquippedItem().m_Enchantments.GetLevel(cEnchantments::enchLure) * 100);
auto Floater = std::make_unique<cFloater>(
a_Player->GetEyePosition(),
a_Player->GetLookVector() * 15,
a_Player->GetUniqueID(),
CountDownTime
);
auto FloaterPtr = Floater.get();
if (!FloaterPtr->Initialize(std::move(Floater), *a_World))
{
return false;
}
a_Player->SetIsFishing(true, FloaterPtr->GetUniqueID());
}
return true;
}
/** Reels back the fishing line, reeling any attached mob, or creating fished loot, or just breaking the fishing
* rod. */
void ReelIn(cWorld & a_World, cPlayer & a_Player) const
{
cFloaterCallback FloaterInfo;
a_World.DoWithEntityByID(a_Player.GetFloaterID(), FloaterInfo);
a_Player.SetIsFishing(false);
// If attached to an entity, reel it in:
if (FloaterInfo.IsAttached())
{
ReelInEntity(a_World, a_Player, FloaterInfo.GetAttachedMobID());
return;
}
// If loot can be caught, get it:
if (FloaterInfo.CanPickup())
{
ReelInLoot(a_World, a_Player, FloaterInfo.GetBitePos());
return;
}
// Empty fishing rod, just damage it:
auto BlockType = a_World.GetBlock(FloaterInfo.GetPos() - Vector3d(0, 0.1, 0));
if ((BlockType != E_BLOCK_AIR) && !IsBlockWater(BlockType))
{
a_Player.UseEquippedItem(2);
}
}
/** Reels back the entity, specified by the ID, and damages the fishing rod accordingly. */
void ReelInEntity(cWorld & a_World, cPlayer & a_Player, UInt32 a_EntityID) const
{
auto PlayerPos = a_Player.GetPosition();
a_World.DoWithEntityByID(
a_EntityID,
[=](cEntity & a_Entity)
{
auto Speed = PlayerPos - a_Entity.GetPosition();
a_Entity.AddSpeed(Speed);
return true;
}
);
a_Player.UseEquippedItem(5);
}
void ReelInLoot(cWorld & a_World, cPlayer & a_Player, const Vector3d a_FloaterBitePos) const
{
auto LotSLevel =
std::min(a_Player.GetEquippedItem().m_Enchantments.GetLevel(cEnchantments::enchLuckOfTheSea), 3u);
// Chances for getting an item from the category for each level of Luck of the Sea (0 - 3)
const int TreasureChances[] = {50, 71, 92, 113}; // 5% | 7.1% | 9.2% | 11.3%
const int JunkChances[] = {100, 81, 61, 42}; // 10% | 8.1% | 6.1% | 4.2%
cItems Drops;
auto & Random = GetRandomProvider();
int ItemCategory = Random.RandInt(999);
if (ItemCategory < TreasureChances[LotSLevel])
{
switch (Random.RandInt(5)) // Each piece of treasure has an equal chance of 1 / 6
{
case 0:
{
cItem Bow(E_ITEM_BOW, 1, Random.RandInt<short>(50));
Bow.EnchantByXPLevels(Random.RandInt(22U, 30U), GetRandomProvider());
Drops.Add(Bow);
break;
}
case 1:
{
cItem Book(E_ITEM_BOOK);
Book.EnchantByXPLevels(30, GetRandomProvider());
Drops.Add(Book);
break;
}
case 2:
{
cItem Rod(E_ITEM_FISHING_ROD, 1, Random.RandInt<short>(50));
Rod.EnchantByXPLevels(Random.RandInt(22U, 30U), GetRandomProvider());
Drops.Add(Rod);
break;
}
case 3:
{
Drops.Add(cItem(E_ITEM_NAME_TAG));
break;
}
case 4:
{
Drops.Add(cItem(E_ITEM_SADDLE));
break;
}
case 5:
{
Drops.Add(cItem(E_BLOCK_LILY_PAD));
break;
}
}
a_Player.GetStatistics().Custom[CustomStatistic::TreasureFished]++;
}
else if (ItemCategory < JunkChances[LotSLevel])
{
int Junk = Random.RandInt(82);
if (Junk < 10) // 10 / 83 chance of spawning a bowl
{
Drops.Add(cItem(E_ITEM_BOWL));
}
else if (Junk < 12) // 2 / 83 chance of spawning a fishing rod
{
// Fishing Rods caught from the Junk category will be 10% .. 100% damaged, and always unenchanted.
Drops.Add(cItem(E_ITEM_FISHING_ROD, 1, Random.RandInt<short>(7, 65)));
}
else if (Junk < 22) // 10 / 83 chance of spawning leather
{
Drops.Add(cItem(E_ITEM_LEATHER));
}
else if (Junk < 32) // 10 / 83 chance of spawning leather boots
{
// Leather boots caught from the Junk category will be 10% .. 100% damaged, and always unenchanted.
Drops.Add(cItem(E_ITEM_LEATHER_BOOTS, 1, Random.RandInt<short>(7, 66)));
}
else if (Junk < 42) // 10 / 83 chance of spawning rotten flesh
{
Drops.Add(cItem(E_ITEM_ROTTEN_FLESH));
}
else if (Junk < 47) // 5 / 83 chance of spawning a stick
{
Drops.Add(cItem(E_ITEM_STICK));
}
else if (Junk < 52) // 5 / 83 chance of spawning string
{
Drops.Add(cItem(E_ITEM_STRING));
}
else if (Junk < 62) // 10 / 83 chance of spawning a water bottle
{
Drops.Add(cItem(E_ITEM_POTION));
}
else if (Junk < 72) // 10 / 83 chance of spawning a bone
{
Drops.Add(cItem(E_ITEM_BONE));
}
else if (Junk < 73) // 1 / 83 chance of spawning an ink sac
{
Drops.Add(cItem(E_ITEM_DYE));
}
else // 10 / 83 chance of spawning a tripwire hook
{
Drops.Add(cItem(E_BLOCK_TRIPWIRE_HOOK));
}
a_Player.GetStatistics().Custom[CustomStatistic::JunkFished]++;
}
else
{
int FishType = Random.RandInt(99);
if (FishType <= 1) // Clownfish has a 2% chance of spawning
{
Drops.Add(cItem(E_ITEM_RAW_FISH, 1, E_META_RAW_FISH_CLOWNFISH));
}
else if (FishType <= 12) // Pufferfish has a 13% chance of spawning
{
Drops.Add(cItem(E_ITEM_RAW_FISH, 1, E_META_RAW_FISH_PUFFERFISH));
}
else if (FishType <= 24) // Raw salmon has a 25% chance of spawning
{
Drops.Add(cItem(E_ITEM_RAW_FISH, 1, E_META_RAW_FISH_SALMON));
}
else // Raw fish has a 60% chance of spawning
{
Drops.Add(cItem(E_ITEM_RAW_FISH, 1, E_META_RAW_FISH_FISH));
}
a_Player.GetStatistics().Custom[CustomStatistic::FishCaught]++;
}
auto Experience = Random.RandInt(1, 6);
// Check with plugins if this loot is acceptable:
if (cRoot::Get()->GetPluginManager()->CallHookPlayerFishing(a_Player, Drops, Experience))
{
return;
}
// Spawn the loot and the experience orb:
auto FloaterPos = a_FloaterBitePos.addedY(0.5);
const float FISH_SPEED_MULT = 2.25f;
Vector3d FlyDirection = (a_Player.GetEyePosition() - FloaterPos).addedY(1.0f) * FISH_SPEED_MULT;
a_World.SpawnItemPickups(Drops, FloaterPos, FlyDirection);
a_World.SpawnExperienceOrb(a_Player.GetPosition(), Experience);
a_Player.UseEquippedItem(1);
// Notify plugins
cRoot::Get()->GetPluginManager()->CallHookPlayerFished(a_Player, Drops, Experience);
}
};