Skip to content
54 changes: 54 additions & 0 deletions steel-core/src/behavior/items/fishing_rod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use crate::behavior::{InteractionResult, ItemBehavior, UseItemContext};
use crate::entity::projectile::fishing_hook::{FishingHook, FishingHookState};
use crate::entity::{Entity, Projectile};
use crate::world::World;
use glam::DVec3;
use rand::{RngExt, rng};
use std::sync::Weak;
use steel_macros::item_behavior;
use steel_registry::sound_events::{ENTITY_FISHING_BOBBER_RETRIEVE, ENTITY_FISHING_BOBBER_THROW};
use steel_registry::vanilla_entities;
use steel_utils::locks::SyncMutex;

/// literally self-explanatory
#[item_behavior]
pub struct FishingRodItem;

impl ItemBehavior for FishingRodItem {
fn use_item(&self, context: &mut UseItemContext) -> InteractionResult {
let player = context.player;
let infinite_materials = context.player.has_infinite_materials();
if let Some(fishing) = &player.fishing {
context.inv.with_item(|item| {
let damage = fishing.retrieve(item);
item.hurt_and_break(damage, infinite_materials);
});

player.play_sound(
&ENTITY_FISHING_BOBBER_RETRIEVE,
1.0,
0.4 / (rng().random::<f32>() * 0.4 + 0.8),
);
// TODO: vibration
} else {
player.play_sound(
&ENTITY_FISHING_BOBBER_THROW,
0.5,
0.4 / (rng().random::<f32>() * 0.4 + 0.8),
);

let hook = FishingHook::new(
&vanilla_entities::FISHING_BOBBER,
1,
DVec3::ZERO,
Weak::<World>::new(),
SyncMutex::new(FishingHookState::new(0, 0)),
);

hook.shoot(DVec3::new(0.0, 0.0, 1.0), 1.5, 1.0);
// TODO: award stat
// TODO: vibration
}
InteractionResult::Success
}
}
2 changes: 2 additions & 0 deletions steel-core/src/behavior/items/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod shovel;
mod sign_item;
mod standing_and_wall_block_item;

mod fishing_rod;
mod flint_and_steel;

pub use axe::AxeItem;
Expand All @@ -28,6 +29,7 @@ pub use bucket::BucketItem;
pub use default::DefaultItemBehavior;
pub use ender_eye::EnderEyeItem;
pub use ender_pearl::EnderPearlItem;
pub use fishing_rod::FishingRodItem;
pub use flint_and_steel::{FireChargeItem, FlintAndSteelItem};
pub use food_on_a_stick::FoodOnAStickItem;
pub use hoe::HoeItem;
Expand Down
9 changes: 9 additions & 0 deletions steel-core/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4586,6 +4586,15 @@ pub trait Entity: EntityEventSource + ErasedType + Send + Sync {
};
change_non_player_entity_world(entity, teleport_transition);
}

// This already exists for structures and AABB whatever that might be, but I also need it for entities, I hope this is the right spot to put it.
fn distance_to_sqr(&self, pos: DVec3) -> f64 {
let dx = self.position().x - pos.x;
let dy = self.position().y - pos.y;
let dz = self.position().z - pos.z;

dx * dx + dy * dy + dz * dz
}
}

/// A trait for living entities that can take damage, heal, and die.
Expand Down
191 changes: 191 additions & 0 deletions steel-core/src/entity/projectile/fishing_hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use crate::entity::entities::ItemEntity;
use crate::entity::{Entity, EntityBase, Projectile, ProjectileBase, RemovalReason, SharedEntity};
use crate::player::Player;
use crate::world::World;
use glam::DVec3;
use std::ops::Add;
use std::sync::{Arc, Weak};
use steel_macros::entity_behavior;
use steel_registry::entity_type::EntityTypeRef;
use steel_registry::item_stack::ItemStack;
use steel_registry::vanilla_entity_data::FishingBobberEntityData;
use steel_registry::vanilla_items;
use steel_utils::locks::SyncMutex;
use steel_utils::types::InteractionHand;
use steel_utils::{Downcast, DowncastType, DowncastTypeKey};

#[entity_behavior]
pub struct FishingHook {
base: EntityBase,
entity_type: EntityTypeRef,
entity_data: SyncMutex<FishingBobberEntityData>,
projectile_base: ProjectileBase,
hook_state: SyncMutex<FishingHookState>,
}

pub(crate) struct FishingHookState {
out_of_water_time: i32,
life: i32,
nibble: i32,
time_until_lured: i32,
time_until_hooked: i32,
fish_angle: f32,
open_water: bool,
current_state: FishHookState,
hooked_in: Option<SharedEntity>,
luck: i32,
lure_speed: i32,
}

impl FishingHookState {
pub fn new(lure_speed: i32, luck: i32) -> Self {
Self {
out_of_water_time: 0,
life: 0,
nibble: 0,
time_until_lured: 0,
time_until_hooked: 0,
fish_angle: 0.0,
open_water: false,
current_state: FishHookState::Flying,
hooked_in: None,
luck: luck.max(0),
lure_speed: lure_speed.max(0),
}
}
}

unsafe impl DowncastType for FishingHook {
const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/fishing_hook");
}

impl FishingHook {
pub const MAX_OUT_OF_WATER_TIME: i32 = 10;
pub(crate) fn new(
entity_type: EntityTypeRef,
id: i32,
position: DVec3,
world: Weak<World>,
hook_state: SyncMutex<FishingHookState>,
) -> Self {
Self {
base: EntityBase::new(id, position, entity_type.dimensions, world),
entity_type,
entity_data: SyncMutex::new(FishingBobberEntityData::new()),
projectile_base: ProjectileBase::new(),
hook_state: hook_state,
}
}

fn should_stop_fishing(&self, owner: &Player) -> bool {
if !owner.can_interact_with_level() {
self.set_removed(RemovalReason::Discarded);
return true;
}

let inventory = owner.inventory.lock();

let mainhand_item = inventory.get_item_in_hand(InteractionHand::MainHand);
let offhand_item = inventory.get_offhand_item();

let mainhand_fishing = mainhand_item.is(&vanilla_items::ITEMS.fishing_rod);
let offhand_fishing = offhand_item.is(&vanilla_items::ITEMS.fishing_rod);

if (mainhand_fishing || offhand_fishing) && self.distance_to_sqr(owner.position()) <= 1024.0
{
return false;
}

self.set_removed(RemovalReason::Discarded);
true
}

fn check_collision() {}
fn set_hooked_entity() {}
fn catching_fish() {}
fn calculate_open_water() {}
fn get_open_water_type_for_area() {}
fn get_open_water_type_for_block() {}
// fn is_open_water_fishing(){}

// TODO: `rod` is needed for advancements and loot params
pub fn retrieve(&self, _rod: &ItemStack) -> i32 {
let mut damage = 0;

if let Some(owner) = self.projectile_owner()
&& let Some(player) = owner.as_player()
&& !Self::should_stop_fishing(self, player)
{
let hooked_in = {
let hook_state = self.hook_state.lock();
hook_state.hooked_in.clone()
};

if let Some(hooked_in) = hooked_in {
self.pull_entity(&hooked_in);
// TODO: criteria triggers (advancements)
damage = if hooked_in.as_ref().is::<ItemEntity>() {
3
} else {
5
};
} else if self.hook_state.lock().nibble > 0 {
// TODO: Looting
// TODO: criteria triggers (advancements)
// TODO: award stat when catching fish
}

if self.base.on_ground() {
damage = 2
}
} else {
damage = 0
}
damage
}

fn pull_entity(&self, entity: &Arc<dyn Entity>) {
if let Some(owner) = self.get_owner() {
let base = owner.base();
let delta = DVec3::new(
base.position().x - self.base.position().x,
base.position().y - self.base.position().y,
base.position().z - self.base.position().z,
);
entity.set_velocity(entity.velocity().add(delta));
}
}

fn update_owner_info() {}
// fn get_player_owner(){}
// fn get_hooked_in(){}
}

impl Entity for FishingHook {
fn base(&self) -> &EntityBase {
&self.base
}

fn entity_type(&self) -> EntityTypeRef {
self.entity_type
}
}

impl Projectile for FishingHook {
fn projectile_base(&self) -> &ProjectileBase {
&self.projectile_base
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FishHookState {
Flying,
HookedInEntity,
Bobbing,
}

enum OpenWaterType {
AboveWater,
InsideWater,
Invalid,
}
1 change: 1 addition & 0 deletions steel-core/src/entity/projectile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//!
//! The block + entity move-vector raycast mirrors `ProjectileUtil`.

pub(crate) mod fishing_hook;
mod throwable;
mod throwable_item;

Expand Down
4 changes: 4 additions & 0 deletions steel-core/src/player/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ pub enum PlayerConnection {
}

use crate::chunk::player_chunk_view::PlayerChunkView;
use crate::entity::projectile::fishing_hook::FishingHook;
use crate::player::chunk_sender::ChunkSender;
use crate::player::networking::JavaConnection;
use crate::portal::{
Expand Down Expand Up @@ -296,6 +297,8 @@ pub struct Player {
/// In-flight ender pearls thrown by this player, kept weakly so they persist
/// with the player and re-spawn on login (vanilla `ServerPlayer.enderPearls`).
ender_pearls: SyncMutex<Vec<Weak<dyn Entity>>>,

pub fishing: Option<FishingHook>,
}

// SAFETY: This key is owned by Steel and uniquely identifies `Player`.
Expand Down Expand Up @@ -553,6 +556,7 @@ impl Player {
pending_root_vehicle: SyncMutex::new(None),
pending_ender_pearls: SyncMutex::new(Vec::new()),
ender_pearls: SyncMutex::new(Vec::new()),
fishing: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions steel-utils/src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl Space for BlockLocal {
const ZERO_SPAN_IS_EMPTY: bool = true;
}

/// Marker type for world-space AABBs.
/// Marker type for world-space AABBs.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct World;
Expand Down
Loading