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 ( - +