diff --git a/code/__DEFINES/do_afters.dm b/code/__DEFINES/do_afters.dm
index dcedbbe010b..cba02d6cf67 100644
--- a/code/__DEFINES/do_afters.dm
+++ b/code/__DEFINES/do_afters.dm
@@ -7,3 +7,4 @@
#define DOAFTER_SOURCE_HEAL_TOUCH "doafter_heal_touch"
#define DOAFTER_SOURCE_PLANTING_DEVICE "doafter_planting_device"
#define DOAFTER_SOURCE_CHARGE_CRANKRECHARGE "doafter_charge_crank_recharge"
+#define DOAFTER_SOURCE_REMOVING_HOOK "doafter_removing_hook"
diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm
index 89b7963d91d..52eb9152730 100644
--- a/code/__DEFINES/fish.dm
+++ b/code/__DEFINES/fish.dm
@@ -18,6 +18,11 @@
#define FISH_AI_ZIPPY "zippy"
#define FISH_AI_SLOW "slow"
+///Slot defines for the fishing rod and its equipment
+#define ROD_SLOT_BAIT "bait"
+#define ROD_SLOT_LINE "line"
+#define ROD_SLOT_HOOK "hook"
+
#define ADDITIVE_FISHING_MOD "additive"
#define MULTIPLICATIVE_FISHING_MOD "multiplicative"
@@ -45,19 +50,21 @@
#define FISHING_LINE_REINFORCED (1 << 1)
/// Much like FISHING_HOOK_ENSNARE but for the fishing line.
#define FISHING_LINE_BOUNCY (1 << 2)
+/// The sorta opposite of FISHING_LINE_BOUNCY. It makes it slower to gain completion and faster to lose it.
+#define FISHING_LINE_STIFF (1 << 3)
///Keeps the bait from falling from gravity, instead allowing the player to move the bait down with right click.
-#define FISHING_MINIGAME_RULE_BIDIRECTIONAL (1 << 2)
+#define FISHING_MINIGAME_RULE_BIDIRECTIONAL (1 << 0)
///Prevents the player from losing the minigame when the completion reaches 0
-#define FISHING_MINIGAME_RULE_NO_ESCAPE (1 << 3)
+#define FISHING_MINIGAME_RULE_NO_ESCAPE (1 << 1)
///Automatically kills the fish after a while, at the cost of killing it
-#define FISHING_MINIGAME_RULE_KILL (1 << 4)
+#define FISHING_MINIGAME_RULE_KILL (1 << 2)
///Prevents the fishing skill from having an effect on the minigame and experience from being awarded
-#define FISHING_MINIGAME_RULE_NO_EXP (1 << 5)
+#define FISHING_MINIGAME_RULE_NO_EXP (1 << 3)
///If enabled, the minigame will occasionally screw around and invert the velocity of the bait
-#define FISHING_MINIGAME_RULE_ANTIGRAV (1 << 6)
+#define FISHING_MINIGAME_RULE_ANTIGRAV (1 << 4)
///Will filp the minigame hud for the duration of the effect
-#define FISHING_MINIGAME_RULE_FLIP (1 << 7)
+#define FISHING_MINIGAME_RULE_FLIP (1 << 5)
///all the effects that are active and will last for a few seconds before triggering a cooldown
#define FISHING_MINIGAME_ACTIVE_EFFECTS (FISHING_MINIGAME_RULE_ANTIGRAV|FISHING_MINIGAME_RULE_FLIP)
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index b3025c4ccc1..8301a9fb107 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -172,3 +172,4 @@ GLOBAL_LIST_INIT(announcer_keys, list(
#define SFX_TREE_CHOP "tree_chop"
#define SFX_ROCK_TAP "rock_tap"
#define SFX_SEAR "sear"
+#define SFX_REEL "reel"
diff --git a/code/datums/components/crafting/tools.dm b/code/datums/components/crafting/tools.dm
index f126bdff538..8b4b00d0060 100644
--- a/code/datums/components/crafting/tools.dm
+++ b/code/datums/components/crafting/tools.dm
@@ -55,3 +55,26 @@
)
result = /obj/item/stack/medical/bandage/makeshift
category = CAT_TOOLS
+
+/datum/crafting_recipe/bone_rod
+ name = "Bone Fishing Rod"
+ result = /obj/item/fishing_rod/bone
+ time = 5 SECONDS
+ reqs = list(/obj/item/stack/sheet/leather = 1,
+ /obj/item/stack/sheet/sinew = 2,
+ /obj/item/stack/sheet/bone = 2)
+ category = CAT_TOOLS
+
+/datum/crafting_recipe/sinew_line
+ name = "Sinew Fishing Line Reel"
+ result = /obj/item/fishing_line/sinew
+ reqs = list(/obj/item/stack/sheet/sinew = 2)
+ time = 2 SECONDS
+ category = CAT_TOOLS
+
+/datum/crafting_recipe/bone_hook
+ name = "Goliath Bone Hook"
+ result = /obj/item/fishing_hook/bone
+ reqs = list(/obj/item/stack/sheet/bone = 1)
+ time = 2 SECONDS
+ category = CAT_TOOLS
diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm
index 05456380235..c8a00ce74cd 100644
--- a/code/datums/components/fishing_spot.dm
+++ b/code/datums/components/fishing_spot.dm
@@ -33,7 +33,7 @@
var/obj/item/fishing_rod/rod = possibly_rod
if(!istype(rod))
return
- if(HAS_TRAIT(user,TRAIT_GONE_FISHING) || rod.currently_hooked_item)
+ if(HAS_TRAIT(user,TRAIT_GONE_FISHING) || rod.fishing_line)
user.balloon_alert(user, "already fishing")
return COMPONENT_NO_AFTERATTACK
var/denial_reason = fish_source.reason_we_cant_fish(rod, user, parent)
diff --git a/code/datums/status_effects/debuffs/hooked.dm b/code/datums/status_effects/debuffs/hooked.dm
new file mode 100644
index 00000000000..9280303209a
--- /dev/null
+++ b/code/datums/status_effects/debuffs/hooked.dm
@@ -0,0 +1,63 @@
+///Status effect applied when casting a fishing rod at someone, provided the attached fishing hook allows it.
+/datum/status_effect/grouped/hooked
+ id = "hooked"
+ duration = -1
+ tick_interval = -1
+ status_type = STATUS_EFFECT_MULTIPLE
+ alert_type = /atom/movable/screen/alert/status_effect/hooked
+
+/datum/status_effect/grouped/hooked/proc/try_unhook()
+ return do_after(owner, 2 SECONDS, timed_action_flags = IGNORE_USER_LOC_CHANGE, extra_checks = CALLBACK(src, PROC_REF(still_exists)), interaction_key = DOAFTER_SOURCE_REMOVING_HOOK)
+
+/datum/status_effect/grouped/hooked/proc/still_exists()
+ return !QDELETED(src)
+
+/datum/status_effect/grouped/hooked/on_creation(mob/living/new_owner, datum/beam/fishing_line/source)
+ . = ..()
+ if(!.) //merged with an existing effect
+ return
+ RegisterSignal(source, COMSIG_QDELETING, PROC_REF(on_fishing_line_deleted))
+
+/datum/status_effect/grouped/hooked/merge_with_existing(datum/status_effect/grouped/hooked/existing, datum/beam/fishing_line/source)
+ existing.RegisterSignal(source, COMSIG_QDELETING, PROC_REF(on_fishing_line_deleted))
+
+/datum/status_effect/grouped/hooked/proc/on_fishing_line_deleted(datum/source)
+ SIGNAL_HANDLER
+ owner.remove_status_effect(type, source)
+
+/atom/movable/screen/alert/status_effect/hooked
+ name = "Snagged By Hook"
+ desc = "You're being caught like a fish by some asshat! Click to safely remove the hook or move away far enough to snap it off."
+ icon_state = "hooked"
+
+/atom/movable/screen/alert/status_effect/hooked/Click()
+ if(!owner.can_resist())
+ return
+ owner.balloon_alert(owner, "removing hook...")
+ var/datum/status_effect/grouped/hooked/effect = owner.has_status_effect(attached_effect.type)
+ if(!effect.try_unhook())
+ return
+ owner.balloon_alert(owner, "hook removed")
+ var/datum/beam/fishing_line/rand_source = pick(effect.sources)
+ qdel(rand_source)
+
+///Version used by the jawed fishing hook, which also applies slowdown
+/datum/status_effect/grouped/hooked/jaws
+ id = "hooked_jaws"
+ alert_type = /atom/movable/screen/alert/status_effect/hooked/jaws
+
+/datum/status_effect/grouped/hooked/jaws/on_apply()
+ . = ..()
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/hook_jawed)
+
+/datum/status_effect/grouped/hooked/jaws/on_remove()
+ . = ..()
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/hook_jawed)
+
+/datum/status_effect/grouped/hooked/jaws/try_unhook()
+ return do_after(owner, 10 SECONDS, extra_checks = CALLBACK(src, PROC_REF(still_exists)), interaction_key = DOAFTER_SOURCE_REMOVING_HOOK)
+
+/atom/movable/screen/alert/status_effect/hooked/jaws
+ name = "Snagged By Jaws"
+ desc = "You've been snagged by some sort of beartrap-slash-fishing-hook-gizmo! Click to safely remove the hook or move away far enough to snap it off."
+ icon_state = "hooked_jaws"
diff --git a/code/datums/status_effects/grouped_effect.dm b/code/datums/status_effects/grouped_effect.dm
index ade0a187e0d..601945b83aa 100644
--- a/code/datums/status_effects/grouped_effect.dm
+++ b/code/datums/status_effects/grouped_effect.dm
@@ -9,12 +9,16 @@
var/datum/status_effect/grouped/existing = new_owner.has_status_effect(type)
if(existing)
existing.sources |= source
+ merge_with_existing(existing, source)
qdel(src)
return FALSE
sources |= source
return ..()
+/datum/status_effect/grouped/proc/merge_with_existing(datum/status_effect/grouped/existing, source)
+ return
+
/datum/status_effect/grouped/before_remove(source)
sources -= source
return !length(sources)
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 02ee2954fd7..3c6a62eb781 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -416,4 +416,12 @@
soundin = pick('sound/effects/rocktap1.ogg', 'sound/effects/rocktap2.ogg', 'sound/effects/rocktap3.ogg')
if(SFX_SEAR)
soundin = 'sound/weapons/sear.ogg'
+ if(SFX_REEL)
+ soundin = pick(
+ 'sound/items/reel1.ogg',
+ 'sound/items/reel2.ogg',
+ 'sound/items/reel3.ogg',
+ 'sound/items/reel4.ogg',
+ 'sound/items/reel5.ogg',
+ )
return soundin
diff --git a/code/modules/cargo/bounties/assistant.dm b/code/modules/cargo/bounties/assistant.dm
index 5885ad58252..7d345802eef 100644
--- a/code/modules/cargo/bounties/assistant.dm
+++ b/code/modules/cargo/bounties/assistant.dm
@@ -215,7 +215,7 @@
description = "We need fish to populate our aquariums with. Fishes that are dead or bought from cargo will only be paid half as much."
reward = CARGO_CRATE_VALUE * 9
required_count = 4
- wanted_types = list(/obj/item/fish = TRUE)
+ wanted_types = list(/obj/item/fish = TRUE, /obj/item/storage/fish_case = TRUE)
///the penalty for shipping dead/bought fish, which can subtract up to half the reward in total.
var/shipping_penalty
@@ -223,11 +223,27 @@
..()
shipping_penalty = reward * 0.5 / required_count
+/datum/bounty/item/assistant/fish/applies_to(obj/shipped)
+ . = ..()
+ if(!.)
+ return
+ var/obj/item/fish/fishie = shipped
+ if(istype(shipped, /obj/item/storage/fish_case))
+ fishie = locate() in shipped
+ if(!fishie || !is_type_in_typecache(fishie, wanted_types))
+ return FALSE
+ return can_ship_fish(fishie)
+
+/datum/bounty/item/assistant/fish/proc/can_ship_fish(obj/item/fish/fishie)
+ return TRUE
+
/datum/bounty/item/assistant/fish/ship(obj/shipped)
. = ..()
if(!.)
return
var/obj/item/fish/fishie = shipped
+ if(istype(shipped, /obj/item/storage/fish_case))
+ fishie = locate() in shipped
if(fishie.status == FISH_DEAD || HAS_TRAIT(fishie, TRAIT_FISH_FROM_CASE))
reward -= shipping_penalty
@@ -243,11 +259,7 @@
name = "[fluid_type] Fish"
description = "We need [lowertext(fluid_type)] fish to populate our aquariums with. Fishes that are dead or bought from cargo will only be paid half as much."
-/datum/bounty/item/assistant/fish/fluid/applies_to(obj/shipped)
- . = ..()
- if(!.)
- return
- var/obj/item/fish/fishie = shipped
+/datum/bounty/item/assistant/fish/fluid/can_ship_fish(obj/item/fish/fishie)
return compatible_fluid_type(fishie.required_fluid_type, fluid_type)
///A subtype of the fish bounty that requires specific fish types. The higher their rarity, the better the pay.
@@ -255,7 +267,7 @@
description = "Our prestigious fish collection is currently lacking a few specific species. Fishes that are dead or bought from cargo will only be paid half as much."
reward = CARGO_CRATE_VALUE * 16
required_count = 3
- wanted_types = list()
+ wanted_types = list(/obj/item/storage/fish_case = TRUE)
/datum/bounty/item/assistant/fish/specific/New()
var/static/list/choosable_fishes
diff --git a/code/modules/fishing/admin.dm b/code/modules/fishing/admin.dm
index ad97ab890b4..d1d2c0ae2c5 100644
--- a/code/modules/fishing/admin.dm
+++ b/code/modules/fishing/admin.dm
@@ -28,10 +28,7 @@
.["rod_types"] = typesof(/obj/item/fishing_rod)
.["hook_types"] = typesof(/obj/item/fishing_hook)
.["line_types"] = typesof(/obj/item/fishing_line)
- var/list/spot_keys = list()
- for(var/key in GLOB.preset_fish_sources)
- spot_keys += key
- .["spot_types"] = subtypesof(/datum/fish_source) + spot_keys
+ .["spot_types"] = subtypesof(/datum/fish_source)
/datum/fishing_calculator/ui_data(mob/user)
return list("info" = current_table)
@@ -45,22 +42,20 @@
var/bait_type = text2path(params["bait"])
var/hook_type = text2path(params["hook"])
var/line_type = text2path(params["line"])
- var/spot_type = text2path(params["spot"]) || params["spot"] //can be also key from presets
-
- //validate here against nonsense values
- var/datum/fish_source/spot
- if(ispath(spot_type))
- spot = new spot_type
- else
- spot = GLOB.preset_fish_sources[spot_type]
+ var/datum/fish_source/spot = GLOB.preset_fish_sources[text2path(params["spot"])]
var/obj/item/fishing_rod/temporary_rod = new rod_type
+ qdel(temporary_rod.bait)
+ qdel(temporary_rod.line)
+ qdel(temporary_rod.hook)
+
if(bait_type)
- temporary_rod.bait = new bait_type
+ temporary_rod.set_slot(new bait_type(temporary_rod), ROD_SLOT_BAIT)
if(hook_type)
- temporary_rod.hook = new hook_type
+ temporary_rod.set_slot(new hook_type(temporary_rod), ROD_SLOT_HOOK)
if(line_type)
- temporary_rod.line = new line_type
+ temporary_rod.set_slot(new line_type(temporary_rod), ROD_SLOT_HOOK)
+
var/result_table = list()
var/modified_table = spot.get_modified_fish_table(temporary_rod,user)
for(var/result_type in spot.fish_table) // through this not modified to display 0 chance ones too
@@ -71,5 +66,6 @@
info["count"] = spot.fish_counts[result_type] || "Infinite"
result_table += list(info)
current_table = result_table
+
qdel(temporary_rod)
return TRUE
diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm
index 3f34222ad87..2a18b1c46e8 100644
--- a/code/modules/fishing/fishing_equipment.dm
+++ b/code/modules/fishing/fishing_equipment.dm
@@ -8,9 +8,10 @@
/obj/item/fishing_line
name = "fishing line reel"
- desc = "Simple fishing line."
+ desc = "A fishing line. In spite of its simplicity, the added length will make fishing a speck easier."
icon = 'icons/obj/fishing.dmi'
icon_state = "reel_blue"
+ w_class = WEIGHT_CLASS_SMALL
///A list of traits that this fishing line has, checked by fish traits and the minigame.
var/list/fishing_line_traits
/// Color of the fishing line
@@ -39,23 +40,18 @@
/obj/item/fishing_line/sinew
name = "fishing sinew"
- desc = "An all-natural fishing line made of stretched out sinew."
+ desc = "An all-natural fishing line made of stretched out sinew. A bit stiff, but usable to fish in extreme enviroments."
icon = 'icons/obj/fishing.dmi'
icon_state = "reel_sinew"
+ icon_state = "reel_green"
+ fishing_line_traits = FISHING_LINE_REINFORCED|FISHING_LINE_STIFF
line_color = "#d1cca3"
-/datum/crafting_recipe/sinew_line
- name = "Sinew Fishing Line Reel"
- result = /obj/item/fishing_line/sinew
- reqs = list(/obj/item/stack/sheet/sinew = 2)
- time = 2 SECONDS
- category = CAT_TOOLS
-
// Hooks
/obj/item/fishing_hook
name = "simple fishing hook"
- desc = "A simple fishing hook."
+ desc = "A simple fishing hook. Don't expect to hook onto anything without one."
icon = 'icons/obj/fishing.dmi'
icon_state = "hook"
w_class = WEIGHT_CLASS_TINY
@@ -85,6 +81,13 @@
/obj/item/fishing_hook/proc/get_hook_bonus_multiplicative(fish_type)
return FISHING_DEFAULT_HOOK_BONUS_MULTIPLICATIVE
+///Check if tha target can be caught by the hook
+/obj/item/fishing_hook/proc/can_be_hooked(atom/target)
+ return isitem(target)
+
+///Any special effect when hooking a target that's not managed by the fishing rod.
+/obj/item/fishing_hook/proc/hook_attached(atom/target, obj/item/fishing_rod/rod)
+ return
/**
* Is there a reason why this hook couldn't fish in target_fish_source?
@@ -133,6 +136,13 @@
rod_overlay_icon_state = "hook_rescue_overlay"
chasm_detritus_type = /datum/chasm_detritus/restricted/bodies
+/obj/item/fishing_hook/rescue/can_be_hooked(atom/target)
+ return ..() || isliving(target)
+
+/obj/item/fishing_hook/rescue/hook_attached(atom/target, obj/item/fishing_rod/rod)
+ if(isliving(target))
+ var/mob/living/living_target = target
+ living_target.apply_status_effect(/datum/status_effect/grouped/hooked, rod.fishing_line)
// This hook can only fish in chasms.
/obj/item/fishing_hook/rescue/reason_we_cant_fish(datum/fish_source/target_fish_source)
@@ -155,13 +165,6 @@
desc = "A simple hook carved from sharpened bone"
icon_state = "hook_bone"
-/datum/crafting_recipe/bone_hook
- name = "Goliath Bone Hook"
- result = /obj/item/fishing_hook/bone
- reqs = list(/obj/item/stack/sheet/bone = 1)
- time = 2 SECONDS
- category = CAT_TOOLS
-
/obj/item/fishing_hook/stabilized
name = "gyro-stabilized hook"
desc = "A quirky hook that grants the user a better control of the tool, allowing them to move the bait both and up and down when reeling in, otherwise keeping it in place."
@@ -177,9 +180,18 @@
name = "jawed hook"
desc = "Despite hints of rust, this gritty beartrap-like hook hybrid manages to look even more threating than the real thing. May neptune have mercy of whatever gets caught in its jaws."
icon_state = "jaws"
+ w_class = WEIGHT_CLASS_NORMAL
fishing_hook_traits = FISHING_HOOK_NO_ESCAPE|FISHING_HOOK_NO_ESCAPE|FISHING_HOOK_KILL
rod_overlay_icon_state = "hook_jaws_overlay"
+/obj/item/fishing_hook/jaws/can_be_hooked(atom/target)
+ return ..() || isliving(target)
+
+/obj/item/fishing_hook/jaws/hook_attached(atom/target, obj/item/fishing_rod/rod)
+ if(isliving(target))
+ var/mob/living/living_target = target
+ living_target.apply_status_effect(/datum/status_effect/grouped/hooked/jaws, rod.fishing_line)
+
/obj/item/storage/toolbox/fishing
name = "fishing toolbox"
desc = "Contains everything you need for your fishing trip."
@@ -198,7 +210,7 @@
/obj/item/storage/toolbox/fishing/PopulateContents()
new /obj/item/bait_can/worm(src)
- new /obj/item/fishing_rod(src)
+ new /obj/item/fishing_rod/unslotted(src)
new /obj/item/fishing_hook(src)
new /obj/item/fishing_line(src)
@@ -214,7 +226,7 @@
atom_storage.max_specific_storage = WEIGHT_CLASS_SMALL //It can still hold a fishing rod
/obj/item/storage/toolbox/fishing/small/PopulateContents()
- new /obj/item/fishing_rod(src)
+ new /obj/item/fishing_rod/unslotted(src)
new /obj/item/fishing_hook(src)
new /obj/item/fishing_line(src)
diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm
index 81e8b3a6943..a5e1dd142b1 100644
--- a/code/modules/fishing/fishing_minigame.dm
+++ b/code/modules/fishing/fishing_minigame.dm
@@ -156,8 +156,12 @@
trait.minigame_mod(rod, user, src)
/// Enable special parameters
if(rod.line)
+ completion_gain += 1 // Any fishing line will provide a small boost by default
if(rod.line.fishing_line_traits & FISHING_LINE_BOUNCY)
completion_loss -= 2
+ if(rod.line.fishing_line_traits & FISHING_LINE_STIFF)
+ completion_loss += 1
+ completion_gain -= 1
if(rod.hook)
if(rod.hook.fishing_hook_traits & FISHING_HOOK_WEIGHTED)
bait_bounce_mult = 0.1
diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm
index 1abba8e414c..8a3035a9cc9 100644
--- a/code/modules/fishing/fishing_rod.dm
+++ b/code/modules/fishing/fishing_rod.dm
@@ -1,6 +1,4 @@
-#define ROD_SLOT_BAIT "bait"
-#define ROD_SLOT_LINE "line"
-#define ROD_SLOT_HOOK "hook"
+#define FISHING_ROD_REEL_CAST_RANGE 2
/obj/item/fishing_rod
name = "fishing rod"
@@ -16,18 +14,18 @@
w_class = WEIGHT_CLASS_HUGE
/// How far can you cast this
- var/cast_range = 5
+ var/cast_range = 3
/// Fishing minigame difficulty modifier (additive)
var/difficulty_modifier = 0
/// Explaination of rod functionality shown in the ui
var/ui_description = "A classic fishing rod, with no special qualities."
var/obj/item/bait
- var/obj/item/fishing_line/line
- var/obj/item/fishing_hook/hook
+ var/obj/item/fishing_line/line = /obj/item/fishing_line
+ var/obj/item/fishing_hook/hook = /obj/item/fishing_hook
/// Currently hooked item for item reeling
- var/obj/item/currently_hooked_item
+ var/atom/movable/currently_hooked
/// Fishing line visual for the hooked item
var/datum/beam/fishing_line/fishing_line
@@ -45,11 +43,19 @@
. = ..()
register_context()
register_item_context()
+
+ if(ispath(bait))
+ set_slot(new bait(src), ROD_SLOT_BAIT)
+ if(ispath(hook))
+ set_slot(new hook(src), ROD_SLOT_HOOK)
+ if(ispath(line))
+ set_slot(new line(src), ROD_SLOT_LINE)
+
update_appearance()
/obj/item/fishing_rod/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(src == held_item)
- if(currently_hooked_item)
+ if(currently_hooked)
context[SCREENTIP_CONTEXT_LMB] = "Reel in"
context[SCREENTIP_CONTEXT_RMB] = "Modify"
return CONTEXTUAL_SCREENTIP_SET
@@ -57,14 +63,11 @@
/obj/item/fishing_rod/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
. = ..()
- if(currently_hooked_item)
+ if(currently_hooked)
context[SCREENTIP_CONTEXT_LMB] = "Reel in"
return CONTEXTUAL_SCREENTIP_SET
return NONE
-/obj/item/fishing_rod/Destroy(force)
- return ..()
-
/obj/item/fishing_rod/examine(mob/user)
. = ..()
var/list/equipped_stuff = list()
@@ -72,35 +75,12 @@
equipped_stuff += "[icon2html(line, user)] [line.name]"
if(hook)
equipped_stuff += "[icon2html(hook, user)] [hook.name]"
+ if(bait)
+ equipped_stuff += "[icon2html(bait, user)] [bait] as bait."
if(length(equipped_stuff))
. += span_notice("It has \a [english_list(equipped_stuff)] equipped.")
- if(bait)
- . += span_notice("\a [icon2html(bait, user)] [bait] is being used as bait.")
- else
- . += span_warning("It doesn't have any bait attached. Fishing will be more tedious!")
- . += span_notice("Right-Click in your active hand to access its slots UI")
-
-/**
- * Catch weight modifier for the given fish_type (or FISHING_DUD)
- * and source, multiplicative. Called before `additive_fish_bonus()`.
- */
-/obj/item/fishing_rod/proc/multiplicative_fish_bonus(fish_type, datum/fish_source/source)
- if(!hook)
- return FISHING_DEFAULT_HOOK_BONUS_MULTIPLICATIVE
-
- return hook.get_hook_bonus_multiplicative(fish_type)
-
-
-/**
- * Catch weight modifier for the given fish_type (or FISHING_DUD)
- * and source, additive. Called after `multiplicative_fish_bonus()`.
- */
-/obj/item/fishing_rod/proc/additive_fish_bonus(fish_type, datum/fish_source/source)
- if(!hook)
- return FISHING_DEFAULT_HOOK_BONUS_ADDITIVE
-
- return hook.get_hook_bonus_additive(fish_type)
-
+ if(!bait)
+ . += span_warning("It doesn't have a bait attached to it. Fishing will be more tedious!")
/**
* Is there a reason why this fishing rod couldn't fish in target_fish_source?
@@ -126,16 +106,34 @@
update_icon()
/obj/item/fishing_rod/interact(mob/user)
- if(currently_hooked_item)
+ if(currently_hooked)
reel(user)
/obj/item/fishing_rod/proc/reel(mob/user)
- //Could use sound here for feedback
- if(do_after(user, 1 SECONDS, currently_hooked_item))
- // Should probably respect and used force move later
- step_towards(currently_hooked_item, get_turf(src))
- if(get_dist(currently_hooked_item,get_turf(src)) < 1)
+ if(DOING_INTERACTION_WITH_TARGET(user, currently_hooked))
+ return
+ playsound(src, SFX_REEL, 50, vary = FALSE)
+ if(!do_after(user, 0.8 SECONDS, currently_hooked, timed_action_flags = IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE, extra_checks = CALLBACK(src, PROC_REF(fishing_line_check))))
+ return
+ if(currently_hooked.anchored || currently_hooked.move_resist >= MOVE_FORCE_STRONG)
+ balloon_alert(user, "[currently_hooked.p_they()] won't budge!")
+ return
+ //Try to move it 'till it's under the user's feet, then try to pick it up
+ if(isitem(currently_hooked))
+ step_towards(currently_hooked, get_turf(src))
+ if(currently_hooked.loc == user.loc)
+ user.put_in_inactive_hand(currently_hooked)
QDEL_NULL(fishing_line)
+ //Not an item, so just delete the line if it's adjacent to the user.
+ else if(get_dist(currently_hooked,get_turf(src)) > 1)
+ step_towards(currently_hooked, get_turf(src))
+ if(get_dist(currently_hooked,get_turf(src)) <= 1)
+ QDEL_NULL(fishing_line)
+ else
+ QDEL_NULL(fishing_line)
+
+/obj/item/fishing_rod/proc/fishing_line_check()
+ return !QDELETED(fishing_line)
/obj/item/fishing_rod/attack_self_secondary(mob/user, modifiers)
. = ..()
@@ -144,9 +142,11 @@
/obj/item/fishing_rod/pre_attack(atom/targeted_atom, mob/living/user, params)
. = ..()
/// Reel in if able
- if(currently_hooked_item)
+ if(currently_hooked)
reel(user)
return TRUE
+ if(!hook)
+ balloon_alert(user, "install a hook first!")
SEND_SIGNAL(targeted_atom, COMSIG_PRE_FISHING)
/// Generates the fishing line visual from the current user to the target and updates inhands
@@ -171,7 +171,7 @@
var/mob/user = loc
user.update_held_items()
fishing_line = null
- currently_hooked_item = null
+ currently_hooked = null
/obj/item/fishing_rod/dropped(mob/user, silent)
. = ..()
@@ -179,19 +179,15 @@
/// Hooks the item
/obj/item/fishing_rod/proc/hook_item(mob/user, atom/target_atom)
- if(currently_hooked_item)
+ if(currently_hooked)
return
- if(!can_be_hooked(target_atom))
+ if(!hook.can_be_hooked(target_atom))
return
- currently_hooked_item = target_atom
+ currently_hooked = target_atom
create_fishing_line(target_atom)
+ hook.hook_attached(target_atom, src)
SEND_SIGNAL(src, COMSIG_FISHING_ROD_HOOKED_ITEM, target_atom, user)
-/// Checks what can be hooked
-/obj/item/fishing_rod/proc/can_be_hooked(atom/movable/target)
- // Could be made dependent on actual hook, ie magnet to hook metallic items
- return isitem(target)
-
// Checks fishing line for interruptions and range
/obj/item/fishing_rod/proc/check_los(datum/beam/source)
SIGNAL_HANDLER
@@ -206,7 +202,7 @@
. |= AFTERATTACK_PROCESSED_ITEM
/// Reel in if able
- if(currently_hooked_item)
+ if(currently_hooked)
reel(user)
return .
@@ -216,7 +212,13 @@
///Called by afterattack(). If the line to whatever that is is clear and we're not already busy, try fishing in it
/obj/item/fishing_rod/proc/cast_line(atom/target, mob/user, proximity_flag)
- if(casting || currently_hooked_item || proximity_flag || !CheckToolReach(user, target, cast_range))
+ if(casting || currently_hooked || proximity_flag)
+ return
+ if(!hook)
+ balloon_alert(user, "install a hook first!")
+ return
+ if(!CheckToolReach(user, target, cast_range))
+ balloon_alert(user, "cannot reach there!")
return
/// Annoyingly pre attack is only called in melee
SEND_SIGNAL(target, COMSIG_PRE_FISHING)
@@ -234,7 +236,7 @@
/// Called by hook projectile when hitting things
/obj/item/fishing_rod/proc/hook_hit(atom/atom_hit_by_hook_projectile)
var/mob/user = loc
- if(!istype(user))
+ if(!hook || !istype(user))
return
if(SEND_SIGNAL(atom_hit_by_hook_projectile, COMSIG_FISHING_ROD_CAST, src, user) & FISHING_ROD_CAST_HANDLED)
return
@@ -328,6 +330,8 @@
data["hook_name"] = format_text(hook?.name)
data["hook_icon"] = hook != null ? icon2base64(icon(hook.icon, hook.icon_state)) : null
+ data["busy"] = fishing_line
+
data["description"] = ui_description
return data
@@ -377,23 +381,16 @@
// Trying to remove the item
if(!new_item && current_item)
user.put_in_hands(current_item)
- update_icon()
- return
+ balloon_alert(user, "[slot] removed")
// Trying to insert item into empty slot
- if(new_item && !current_item)
+ else if(new_item && !current_item)
if(!slot_check(new_item, slot))
return
if(user.transferItemToLoc(new_item,src))
- switch(slot)
- if(ROD_SLOT_BAIT)
- bait = new_item
- if(ROD_SLOT_HOOK)
- hook = new_item
- if(ROD_SLOT_LINE)
- line = new_item
- update_icon()
+ set_slot(new_item, slot)
+ balloon_alert(user, "[slot] installed")
/// Trying to swap item
- if(new_item && current_item)
+ else if(new_item && current_item)
if(!slot_check(new_item,slot))
return
if(user.transferItemToLoc(new_item,src))
@@ -405,33 +402,46 @@
if(ROD_SLOT_LINE)
line = new_item
user.put_in_hands(current_item)
- update_icon()
+ balloon_alert(user, "[slot] swapped")
+ update_icon()
+ playsound(src, 'sound/items/click.ogg', 50, TRUE)
+
+///assign an item to the given slot and its standard effects, while Exited() should handle unsetting the slot.
+/obj/item/fishing_rod/proc/set_slot(obj/item/equipment, slot)
+ switch(slot)
+ if(ROD_SLOT_BAIT)
+ bait = equipment
+ if(ROD_SLOT_HOOK)
+ hook = equipment
+ if(ROD_SLOT_LINE)
+ line = equipment
+ cast_range += FISHING_ROD_REEL_CAST_RANGE
/obj/item/fishing_rod/Exited(atom/movable/gone, direction)
. = ..()
if(gone == bait)
bait = null
if(gone == line)
+ cast_range -= FISHING_ROD_REEL_CAST_RANGE
line = null
if(gone == hook)
+ QDEL_NULL(fishing_line)
hook = null
+///Found in the fishing toolbox (the hook and line are separate items)
+/obj/item/fishing_rod/unslotted
+ hook = null
+ line = null
+
/obj/item/fishing_rod/bone
name = "bone fishing rod"
desc = "A humble rod, made with whatever happened to be on hand."
icon_state = "fishing_rod_bone"
reel_overlay = "reel_bone"
default_line_color = "red"
-
-/datum/crafting_recipe/bone_rod
- name = "Bone Fishing Rod"
- result = /obj/item/fishing_rod/bone
- time = 5 SECONDS
- reqs = list(/obj/item/stack/sheet/leather = 1,
- /obj/item/stack/sheet/sinew = 2,
- /obj/item/stack/sheet/bone = 2)
- category = CAT_TOOLS
+ line = null //sinew line (usable to fish in lava) not included
+ hook = /obj/item/fishing_hook/bone
/obj/item/fishing_rod/telescopic
name = "telescopic fishing rod"
@@ -442,8 +452,6 @@
w_class = WEIGHT_CLASS_NORMAL
ui_description = "A collapsible fishing rod that can fit within a backpack."
reel_overlay = "reel_telescopic"
- ///Whether the rod is exteded or not. Tied to the transforming element.
- var/active = FALSE
///The force of the item when extended.
var/active_force = 8
@@ -454,30 +462,31 @@
RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
/obj/item/fishing_rod/telescopic/reason_we_cant_fish(datum/fish_source/target_fish_source)
- if(!active)
+ if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
return "You need to extend your fishing rod before you can cast the line."
return ..()
/obj/item/fishing_rod/telescopic/cast_line(atom/target, mob/user, proximity_flag)
- if(!active)
- to_chat(user, "You need to extend your fishing rod before you can cast the line.")
+ if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
+ if(!proximity_flag)
+ balloon_alert(user, "extend the rod first!")
return
return ..()
/obj/item/fishing_rod/telescopic/get_fishing_overlays()
- if(!active)
+ if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
return list()
return ..()
/obj/item/fishing_rod/telescopic/get_fishing_worn_overlays(mutable_appearance/standing, isinhands, icon_file)
- if(!active)
+ if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
return list()
return ..()
///Stops the fishing rod from being collapsed while fishing.
/obj/item/fishing_rod/telescopic/proc/pre_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
- if(active)
+ if(HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE))
return
//the fishing minigame uses the attack_self signal to let the user end it early without having to drop the rod.
if(HAS_TRAIT(user, TRAIT_GONE_FISHING))
@@ -487,14 +496,12 @@
/obj/item/fishing_rod/telescopic/proc/on_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
- src.active = active
inhand_icon_state = active ? "rod" : null // When inactive, there is no inhand icon_state.
if(user)
balloon_alert(user, active ? "extended" : "collapsed")
playsound(src, 'sound/weapons/batonextend.ogg', 50, TRUE)
update_appearance(UPDATE_OVERLAYS)
- if(fishing_line)
- QDEL_NULL(fishing_line)
+ QDEL_NULL(fishing_line)
return COMPONENT_NO_DEFAULT_MESSAGE
/obj/item/fishing_rod/telescopic/master
@@ -505,6 +512,9 @@
icon_state = "fishing_rod_master"
reel_overlay = "reel_master"
active_force = 13 //It's that sturdy
+ cast_range = 5
+ line = /obj/item/fishing_line/bouncy
+ hook = /obj/item/fishing_hook/weighted
/obj/item/fishing_rod/tech
name = "advanced fishing rod"
@@ -512,6 +522,7 @@
ui_description = "This rod has an infinite supply of synth-bait. Also doubles as an Experi-Scanner for fish."
icon_state = "fishing_rod_science"
reel_overlay = "reel_science"
+ bait = /obj/item/food/bait/doughball/synthetic
/obj/item/fishing_rod/tech/Initialize(mapload)
. = ..()
@@ -529,10 +540,6 @@
experiment_signals = fishing_signals, \
)
- var/obj/item/food/bait/doughball/synthetic/infinite_supply_of_bait = new(src)
- bait = infinite_supply_of_bait
- update_icon()
-
/obj/item/fishing_rod/tech/examine(mob/user)
. = ..()
. += span_notice("Alt-Click to access the Experiment Configuration UI")
@@ -561,6 +568,12 @@
var/obj/item/fishing_rod/owner
var/datum/beam/our_line
+/obj/projectile/fishing_cast/fire(angle, atom/direct_target)
+ if(owner.hook)
+ icon_state = owner.hook.icon_state
+ transform = transform.Scale(1, -1)
+ return ..()
+
/obj/projectile/fishing_cast/Impact(atom/hit_atom)
. = ..()
owner.hook_hit(hit_atom)
@@ -634,3 +647,5 @@
if(NORTH)
override_origin_pixel_x = lefthand ? lefthand_n_px : righthand_n_px
override_origin_pixel_y = lefthand ? lefthand_n_py : righthand_n_py
+
+#undef FISHING_ROD_REEL_CAST_RANGE
diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm
index 120348038f1..887b8f3a2a3 100644
--- a/code/modules/fishing/sources/_fish_source.dm
+++ b/code/modules/fishing/sources/_fish_source.dm
@@ -240,8 +240,8 @@ GLOBAL_LIST(fishing_property_cache)
var/list/final_table = fish_table.Copy()
for(var/result in final_table)
- final_table[result] *= rod.multiplicative_fish_bonus(result, src)
- final_table[result] += rod.additive_fish_bonus(result, src) //Decide on order here so it can be multiplicative
+ final_table[result] *= rod.hook?.get_hook_bonus_multiplicative(result)
+ final_table[result] += rod.hook?.get_hook_bonus_additive(result)//Decide on order here so it can be multiplicative
if(ispath(result, /obj/item/fish))
//Modify fish roll chance
var/obj/item/fish/caught_fish = result
diff --git a/code/modules/movespeed/modifiers/items.dm b/code/modules/movespeed/modifiers/items.dm
index 9fd3d051fac..6bdf2f31760 100644
--- a/code/modules/movespeed/modifiers/items.dm
+++ b/code/modules/movespeed/modifiers/items.dm
@@ -17,8 +17,12 @@
/datum/movespeed_modifier/sphere
multiplicative_slowdown = -0.5
+/datum/movespeed_modifier/hook_jawed
+ multiplicative_slowdown = 4
+
/datum/movespeed_modifier/shooting_assistant
multiplicative_slowdown = 0.5
/datum/movespeed_modifier/binocs_wielded
multiplicative_slowdown = 1.5
+
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 0df2ba2855d..682baac7927 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -792,9 +792,7 @@
set_angle(get_angle(src, target))
original_angle = Angle
if(!nondirectional_sprite)
- var/matrix/matrix = new
- matrix.Turn(Angle)
- transform = matrix
+ transform = transform.Turn(Angle)
trajectory_ignore_forcemove = TRUE
forceMove(starting)
trajectory_ignore_forcemove = FALSE
@@ -811,11 +809,9 @@
pixel_move(pixel_speed_multiplier, FALSE) //move it now!
/obj/projectile/proc/set_angle(new_angle) //wrapper for overrides.
- Angle = new_angle
if(!nondirectional_sprite)
- var/matrix/matrix = new
- matrix.Turn(Angle)
- transform = matrix
+ transform = transform.TurnTo(Angle, new_angle)
+ Angle = new_angle
if(trajectory)
trajectory.set_angle(new_angle)
if(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store))
@@ -827,11 +823,9 @@
/// Same as set_angle, but the reflection continues from the center of the object that reflects it instead of the side
/obj/projectile/proc/set_angle_centered(new_angle)
- Angle = new_angle
if(!nondirectional_sprite)
- var/matrix/matrix = new
- matrix.Turn(Angle)
- transform = matrix
+ transform = transform.TurnTo(Angle, new_angle)
+ Angle = new_angle
if(trajectory)
trajectory.set_angle(new_angle)
@@ -909,10 +903,6 @@
if(!loc || !trajectory)
return
last_projectile_move = world.time
- if(!nondirectional_sprite && !hitscanning)
- var/matrix/matrix = new
- matrix.Turn(Angle)
- transform = matrix
if(homing)
process_homing()
var/forcemoved = FALSE
diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi
index dc95d505ce6..289d02da46a 100644
Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ
diff --git a/icons/obj/fishing.dmi b/icons/obj/fishing.dmi
index f7ab9fc1ad9..8e8be783fb7 100644
Binary files a/icons/obj/fishing.dmi and b/icons/obj/fishing.dmi differ
diff --git a/sound/attributions.txt b/sound/attributions.txt
index 9cbe56eb3ee..9e573778be8 100644
--- a/sound/attributions.txt
+++ b/sound/attributions.txt
@@ -114,6 +114,9 @@ https://freesound.org/people/junggle/sounds/28917/
https://freesound.org/people/inferno/sounds/18397/
https://freesound.org/people/humanoide9000/sounds/330293/
+reel1.ogg, reel2.ogg, reel3.ogg, reel4.ogg and reel5.ogg adapted from pixabay. Free for use under the Pixabay Content License (https://pixabay.com/service/license-summary/):
+https://pixabay.com/sound-effects/reel-78063/
+
throw.ogg, throwhard.ogg and throwsoft.ogg (Royalty-Free and Copyright-Free) are adapted from Jam FX, SmartSound FX and Epic Stock Media in :
https://uppbeat.io/sfx/whoosh-swift-cut/7727/23617
https://uppbeat.io/sfx/whoosh-air-punch/114/1168
diff --git a/sound/items/reel1.ogg b/sound/items/reel1.ogg
new file mode 100644
index 00000000000..0bd2cda89b9
Binary files /dev/null and b/sound/items/reel1.ogg differ
diff --git a/sound/items/reel2.ogg b/sound/items/reel2.ogg
new file mode 100644
index 00000000000..64d2bc1adb4
Binary files /dev/null and b/sound/items/reel2.ogg differ
diff --git a/sound/items/reel3.ogg b/sound/items/reel3.ogg
new file mode 100644
index 00000000000..a1d89779ec1
Binary files /dev/null and b/sound/items/reel3.ogg differ
diff --git a/sound/items/reel4.ogg b/sound/items/reel4.ogg
new file mode 100644
index 00000000000..ae9bdb2f5e3
Binary files /dev/null and b/sound/items/reel4.ogg differ
diff --git a/sound/items/reel5.ogg b/sound/items/reel5.ogg
new file mode 100644
index 00000000000..6c979754a5f
Binary files /dev/null and b/sound/items/reel5.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index acdcf97e8b9..326d17a0dad 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1776,6 +1776,7 @@
#include "code\datums\status_effects\debuffs\fire_stacks.dm"
#include "code\datums\status_effects\debuffs\genetic_damage.dm"
#include "code\datums\status_effects\debuffs\hallucination.dm"
+#include "code\datums\status_effects\debuffs\hooked.dm"
#include "code\datums\status_effects\debuffs\jitteriness.dm"
#include "code\datums\status_effects\debuffs\pacifism.dm"
#include "code\datums\status_effects\debuffs\screen_blur.dm"
diff --git a/tgui/packages/tgui/interfaces/FishingRod.tsx b/tgui/packages/tgui/interfaces/FishingRod.tsx
index a17108cd030..cd1b87051c6 100644
--- a/tgui/packages/tgui/interfaces/FishingRod.tsx
+++ b/tgui/packages/tgui/interfaces/FishingRod.tsx
@@ -64,7 +64,7 @@ export const FishingRod = (props) => {
} = data;
return (
-
+