diff --git a/code/__DEFINES/dcs/signals/signals_fish.dm b/code/__DEFINES/dcs/signals/signals_fish.dm index a40e731fc7b..c70cdfde1a3 100644 --- a/code/__DEFINES/dcs/signals/signals_fish.dm +++ b/code/__DEFINES/dcs/signals/signals_fish.dm @@ -28,6 +28,8 @@ #define COMSIG_FISHING_ROD_CAUGHT_FISH "fishing_rod_caught_fish" /// From /obj/item/fishing_rod/proc/hook_item(): (reward, user) #define COMSIG_FISHING_ROD_HOOKED_ITEM "fishing_rod_hooked_item" +/// From /datum/fish_source/proc/use_slot(), sent to the slotted item: (obj/item/fishing_rod/rod) +#define COMSIG_FISHING_EQUIPMENT_SLOTTED "fishing_equipment_slotted" /// Sent when the challenge is to be interrupted: (reason) #define COMSIG_FISHING_SOURCE_INTERRUPT_CHALLENGE "fishing_spot_interrupt_challenge" diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm index 62954a649e2..24c3c963598 100644 --- a/code/__DEFINES/fish.dm +++ b/code/__DEFINES/fish.dm @@ -3,6 +3,7 @@ // Baseline fishing difficulty levels #define FISHING_DEFAULT_DIFFICULTY 15 +#define FISHING_EASY_DIFFICULTY 10 /// Difficulty modifier when bait is fish's favorite #define FAV_BAIT_DIFFICULTY_MOD -5 @@ -52,6 +53,8 @@ #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) +///Skip the biting phase and go straight to the fishing phase. +#define FISHING_LINE_AUTOREEL (1 << 4) ///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 << 0) @@ -65,6 +68,8 @@ #define FISHING_MINIGAME_RULE_ANTIGRAV (1 << 4) ///Will filp the minigame hud for the duration of the effect #define FISHING_MINIGAME_RULE_FLIP (1 << 5) +///Skip the biting phase and go straight to the minigame, avoiding the penalty for having slow reflexes. +#define FISHING_MINIGAME_AUTOREEL (1 << 6) ///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/game/objects/items.dm b/code/game/objects/items.dm index c82164dd7f4..ba9e4912aff 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -545,20 +545,20 @@ return return attempt_pickup(user) -/obj/item/proc/attempt_pickup(mob/user) +/obj/item/proc/attempt_pickup(mob/user, skip_grav = FALSE) . = TRUE if(!(interaction_flags_item & INTERACT_ITEM_ATTACK_HAND_PICKUP)) //See if we're supposed to auto pickup. return - //Heavy gravity makes picking up things very slow. - var/grav = user.has_gravity() - if(grav > STANDARD_GRAVITY) - var/grav_power = min(3,grav - STANDARD_GRAVITY) - to_chat(user,span_notice("You start picking up [src]...")) - if(!do_after(user, 30 * grav_power, src)) - return - + if(!skip_grav) + //Heavy gravity makes picking up things very slow. + var/grav = user.has_gravity() + if(grav > STANDARD_GRAVITY) + var/grav_power = min(3,grav - STANDARD_GRAVITY) + to_chat(user,span_notice("You start picking up [src]...")) + if(!do_after(user, 30 * grav_power, src)) + return //If the item is in a storage item, take it out var/outside_storage = !loc.atom_storage diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm index b1e5ddc4c18..4b30951d89a 100644 --- a/code/modules/fishing/fishing_equipment.dm +++ b/code/modules/fishing/fishing_equipment.dm @@ -41,12 +41,68 @@ /obj/item/fishing_line/sinew name = "fishing 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" +/** + * A special line reel that let you skip the biting phase of the minigame, netting you a completion bonus, + * and thrown hooked items at you, so you can rapidly catch them from afar. + * It may also work on mobs if the right hook is attached. + */ +/obj/item/fishing_line/auto_reel + name = "fishing line auto-reel" + desc = "A fishing line that automatically starts reeling in fish the moment they bite. Also good for hurling things at yourself." + icon_state = "reel_auto" + fishing_line_traits = FISHING_LINE_AUTOREEL + line_color = "#F88414" + +/obj/item/fishing_line/auto_reel/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_FISHING_EQUIPMENT_SLOTTED, PROC_REF(line_equipped)) + +/obj/item/fishing_line/auto_reel/proc/line_equipped(datum/source, obj/item/fishing_rod/rod) + SIGNAL_HANDLER + RegisterSignal(rod, COMSIG_FISHING_ROD_HOOKED_ITEM, PROC_REF(on_hooked_item)) + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_removed)) + +/obj/item/fishing_line/auto_reel/proc/on_removed(atom/movable/source, atom/old_loc, dir, forced) + SIGNAL_HANDLER + UnregisterSignal(src, COMSIG_MOVABLE_MOVED) + UnregisterSignal(old_loc, COMSIG_FISHING_ROD_HOOKED_ITEM) + +/obj/item/fishing_line/auto_reel/proc/on_hooked_item(obj/item/fishing_rod/source, atom/target, mob/living/user) + SIGNAL_HANDLER + if(!ismovable(target)) + return + var/atom/movable/movable_target = target + var/please_be_gentle = FALSE + var/atom/destination + var/datum/callback/throw_callback + if(isliving(movable_target) || !isitem(movable_target)) + destination = get_step_towards(user, target) + please_be_gentle = TRUE + else + destination = user + throw_callback = CALLBACK(src, PROC_REF(clear_hitby_signal), movable_target) + RegisterSignal(movable_target, COMSIG_ATOM_PREHITBY, PROC_REF(catch_it_chucklenut)) + + if(!movable_target.safe_throw_at(destination, source.cast_range, 2, callback = throw_callback, gentle = please_be_gentle)) + UnregisterSignal(movable_target, COMSIG_ATOM_PREHITBY) + else + playsound(src, 'sound/weapons/batonextend.ogg', 50, TRUE) + +/obj/item/fishing_line/auto_reel/proc/catch_it_chucklenut(obj/item/source, atom/hit_atom, datum/thrownthing/throwingdatum) + SIGNAL_HANDLER + var/mob/living/user = throwingdatum.initial_target.resolve() + if(QDELETED(user) || hit_atom != user) + return + if(user.try_catch_item(source, skip_throw_mode_check = TRUE, try_offhand = TRUE)) + return COMSIG_HIT_PREVENTED + +/obj/item/fishing_line/auto_reel/proc/clear_hitby_signal(obj/item/item) + UnregisterSignal(item, COMSIG_ATOM_PREHITBY) + // Hooks /obj/item/fishing_hook diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index a5e1dd142b1..e2108848b0b 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -18,9 +18,15 @@ // Acceleration mod when bait is over fish #define FISH_ON_BAIT_ACCELERATION_MULT 0.6 /// The minimum velocity required for the bait to bounce -#define BAIT_MIN_VELOCITY_BOUNCE 200 +#define BAIT_MIN_VELOCITY_BOUNCE 150 /// The extra deceleration of velocity that happens when the bait switches direction -#define BAIT_DECELERATION_MULT 2 +#define BAIT_DECELERATION_MULT 1.5 + +/// Reduce initial completion rate depending on difficulty +#define MAX_FISH_COMPLETION_MALUS 15 +/// The window of time between biting phase and back to baiting phase +#define BITING_TIME_WINDOW 4 SECONDS + ///Defines to know how the bait is moving on the minigame slider. #define REELING_STATE_IDLE 0 @@ -162,6 +168,8 @@ if(rod.line.fishing_line_traits & FISHING_LINE_STIFF) completion_loss += 1 completion_gain -= 1 + if(rod.line.fishing_line_traits & FISHING_LINE_AUTOREEL) + special_effects |= FISHING_MINIGAME_AUTOREEL if(rod.hook) if(rod.hook.fishing_hook_traits & FISHING_HOOK_WEIGHTED) bait_bounce_mult = 0.1 @@ -180,6 +188,9 @@ difficulty += comp.fish_source.calculate_difficulty(reward_path, rod, user, src) difficulty = clamp(round(difficulty), 1, 100) + if(difficulty > FISHING_EASY_DIFFICULTY) + completion -= round(MAX_FISH_COMPLETION_MALUS * (difficulty/100), 1) + if(HAS_TRAIT(user, TRAIT_REVEAL_FISH) || (user.mind && HAS_TRAIT(user.mind, TRAIT_REVEAL_FISH))) fish_icon = GLOB.specific_fish_icons[reward_path] || "fish" @@ -259,7 +270,7 @@ return if(phase == WAIT_PHASE) //Reset wait send_alert("miss!") - start_baiting_phase() + start_baiting_phase(TRUE) else if(phase == BITING_PHASE) start_minigame_phase() return COMSIG_MOB_CANCEL_CLICKON @@ -302,14 +313,19 @@ if(!QDELETED(src)) qdel(src) -/datum/fishing_challenge/proc/start_baiting_phase() +/datum/fishing_challenge/proc/start_baiting_phase(penalty = FALSE) + var/wait_time + if(penalty) + wait_time = min(timeleft(next_phase_timer) + rand(3 SECONDS, 5 SECONDS), 30 SECONDS) + else + wait_time = rand(1 SECONDS, 30 SECONDS) + if(special_effects & FISHING_MINIGAME_AUTOREEL && wait_time >= 15 SECONDS) + wait_time = max(wait_time - 7.5 SECONDS, 15 SECONDS) deltimer(next_phase_timer) phase = WAIT_PHASE //Bobbing animation animate(lure, pixel_y = 1, time = 1 SECONDS, loop = -1, flags = ANIMATION_RELATIVE) animate(pixel_y = -1, time = 1 SECONDS, flags = ANIMATION_RELATIVE) - //Setup next phase - var/wait_time = rand(1 SECONDS, 30 SECONDS) next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_biting_phase)), wait_time, TIMER_STOPPABLE) /datum/fishing_challenge/proc/start_biting_phase() @@ -342,9 +358,11 @@ send_alert("!!!") animate(lure, pixel_y = 3, time = 5, loop = -1, flags = ANIMATION_RELATIVE) animate(pixel_y = -3, time = 5, flags = ANIMATION_RELATIVE) + if(special_effects & FISHING_MINIGAME_AUTOREEL) + start_minigame_phase(auto_reel = TRUE) + return // Setup next phase - var/wait_time = rand(3 SECONDS, 6 SECONDS) - next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_baiting_phase)), wait_time, TIMER_STOPPABLE) + next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_baiting_phase)), BITING_TIME_WINDOW, TIMER_STOPPABLE) ///The damage dealt per second to the fish when FISHING_MINIGAME_RULE_KILL is active. #define FISH_DAMAGE_PER_SECOND 2 @@ -366,7 +384,21 @@ var/damage = CEILING((world.time - start_time)/10 * FISH_DAMAGE_PER_SECOND, 1) reward.adjust_health(reward.health - damage) -/datum/fishing_challenge/proc/start_minigame_phase() +/datum/fishing_challenge/proc/start_minigame_phase(auto_reel = FALSE) + if(auto_reel) + completion *= 1.3 + else + var/time_left = timeleft(next_phase_timer) + switch(time_left) + if(0 to BITING_TIME_WINDOW - 3 SECONDS) + completion *= 0.65 + if(BITING_TIME_WINDOW - 3 SECONDS to BITING_TIME_WINDOW - 2 SECONDS) + completion *= 0.82 + if(BITING_TIME_WINDOW - 1 SECONDS to BITING_TIME_WINDOW - 0.5 SECONDS) + completion *= 1.2 + if(BITING_TIME_WINDOW - 0.5 SECONDS to BITING_TIME_WINDOW) + completion *= 1.4 + completion = round(completion, 1) if(!prepare_minigame_hud()) return phase = MINIGAME_PHASE @@ -691,3 +723,5 @@ #undef REELING_STATE_UP #undef REELING_STATE_DOWN +#undef MAX_FISH_COMPLETION_MALUS +#undef BITING_TIME_WINDOW diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index 8a3035a9cc9..0578ffb0789 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -39,6 +39,9 @@ ///The name of the icon state of the reel overlay var/reel_overlay = "reel_overlay" + ///Prevents spamming the line casting, without affecting the player's click cooldown. + COOLDOWN_DECLARE(casting_cd) + /obj/item/fishing_rod/Initialize(mapload) . = ..() register_context() @@ -220,6 +223,8 @@ if(!CheckToolReach(user, target, cast_range)) balloon_alert(user, "cannot reach there!") return + if(!COOLDOWN_FINISHED(src, casting_cd)) + return /// Annoyingly pre attack is only called in melee SEND_SIGNAL(target, COMSIG_PRE_FISHING) casting = TRUE @@ -232,6 +237,7 @@ cast_projectile.impacted = list(user = TRUE) cast_projectile.preparePixelProjectile(target, user) cast_projectile.fire() + COOLDOWN_START(src, casting_cd, 1 SECONDS) /// Called by hook projectile when hitting things /obj/item/fishing_rod/proc/hook_hit(atom/atom_hit_by_hook_projectile) @@ -361,9 +367,7 @@ if("slot_action") // Simple click with empty hand to remove, click with item to insert/switch var/obj/item/held_item = user.get_active_held_item() - if(held_item == src) - return - use_slot(params["slot"], user, held_item) + use_slot(params["slot"], user, held_item == src ? null : held_item) return TRUE /// Ideally this will be replaced with generic slotted storage datum + display @@ -404,6 +408,9 @@ user.put_in_hands(current_item) balloon_alert(user, "[slot] swapped") + if(new_item) + SEND_SIGNAL(new_item, COMSIG_FISHING_EQUIPMENT_SLOTTED, src) + update_icon() playsound(src, 'sound/items/click.ogg', 50, TRUE) diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index edab6dc0db5..5cdc2d2b841 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -262,7 +262,7 @@ fish_counts = list( /obj/item/storage/wallet/money = 2, ) - fishing_difficulty = FISHING_DEFAULT_DIFFICULTY - 5 //For beginners + fishing_difficulty = FISHING_EASY_DIFFICULTY //For beginners /datum/fish_source/holographic catalog_description = "Holographic water" @@ -275,7 +275,7 @@ /obj/item/fish/holo/checkered = 5, /obj/item/fish/holo/halffish = 5, ) - fishing_difficulty = FISHING_DEFAULT_DIFFICULTY - 5 + fishing_difficulty = FISHING_EASY_DIFFICULTY /datum/fish_source/holographic/reason_we_cant_fish(obj/item/fishing_rod/rod, mob/fisherman, atom/parent) . = ..() @@ -332,7 +332,7 @@ /mob/living/basic/frog = 1, /mob/living/basic/axolotl = 1, ) - fishing_difficulty = FISHING_DEFAULT_DIFFICULTY - 10 + fishing_difficulty = FISHING_EASY_DIFFICULTY - 5 /datum/fish_source/hydro_tray/reason_we_cant_fish(obj/item/fishing_rod/rod, mob/fisherman, atom/parent) if(!istype(parent, /obj/machinery/hydroponics/constructable)) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index d0717972d59..909cf50618b 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -69,25 +69,19 @@ if(P.catastropic_dismemberment) apply_damage(P.damage, P.damtype, BODY_ZONE_CHEST, wound_bonus = P.wound_bonus) //stops a projectile blowing off a limb effectively doing no damage. Mostly relevant for sniper rifles. -/mob/living/carbon/proc/can_catch_item(skip_throw_mode_check) - . = FALSE +/mob/living/carbon/try_catch_item(obj/item/item, skip_throw_mode_check = FALSE, try_offhand = FALSE) + . = ..() + if(.) + throw_mode_off(THROW_MODE_TOGGLE) + +/mob/living/carbon/can_catch_item(skip_throw_mode_check = FALSE, try_offhand = FALSE) if(!skip_throw_mode_check && !throw_mode) - return - if(get_active_held_item()) - return - if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) - return - return TRUE + return FALSE + return ..() -/mob/living/carbon/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) - if(!skipcatch && can_catch_item() && isitem(AM) && !HAS_TRAIT(AM, TRAIT_UNCATCHABLE) && isturf(AM.loc)) - var/obj/item/I = AM - I.attack_hand(src) - if(get_active_held_item() == I) //if our attack_hand() picks up the item... - visible_message(span_warning("[src] catches [I]!"), \ - span_userdanger("You catch [I] in mid-air!")) - throw_mode_off(THROW_MODE_TOGGLE) - return TRUE +/mob/living/carbon/hitby(atom/movable/movable, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + if(!skipcatch && try_catch_item(movable)) + return TRUE return ..() /mob/living/carbon/send_item_attack_message(obj/item/I, mob/living/user, hit_area, def_zone) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 55cb51c97dd..3a4b0091b51 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -222,6 +222,26 @@ hitpush = FALSE return ..() +///The core of catching thrown items, which non-carbons cannot without the help of items or abilities yet, as they've no throw mode. +/mob/living/proc/try_catch_item(obj/item/item, skip_throw_mode_check = FALSE, try_offhand = FALSE) + if(!can_catch_item(skip_throw_mode_check, try_offhand) || !isitem(item) || HAS_TRAIT(item, TRAIT_UNCATCHABLE) || !isturf(item.loc)) + return FALSE + if(!can_hold_items(item)) + return FALSE + INVOKE_ASYNC(item, TYPE_PROC_REF(/obj/item, attempt_pickup), src, TRUE) + if(get_active_held_item() == item) //if our attack_hand() picks up the item... + visible_message(span_warning("[src] catches [item]!"), \ + span_userdanger("You catch [item] in mid-air!")) + return TRUE + +///Checks the requites for catching a throw item. +/mob/living/proc/can_catch_item(skip_throw_mode_check = FALSE, try_offhand = FALSE) + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + return FALSE + if(get_active_held_item() && (!try_offhand || get_inactive_held_item() || !swap_hand())) + return FALSE + return TRUE + /mob/living/fire_act() . = ..() adjust_fire_stacks(3) diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index ae8f7e43a40..e17b53da692 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -972,7 +972,7 @@ category = list( RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE ) - departmental_flags = DEPARTMENT_BITFLAG_SERVICE + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE /datum/design/stabilized_hook name = "Gyro-Stabilized Hook" @@ -984,7 +984,19 @@ category = list( RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE ) - departmental_flags = DEPARTMENT_BITFLAG_SERVICE + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE + +/datum/design/auto_reel + name = "Fishing Line Auto-Reel" + desc = "An advanced line reel which can be used speed up both fishing and casually snagging other items in your direction." + id = "auto_reel" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 4, /datum/material/gold = SMALL_MATERIAL_AMOUNT * 3, /datum/material/silver = SMALL_MATERIAL_AMOUNT * 3) + build_path = /obj/item/fishing_line/auto_reel + category = list( + RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE /datum/design/fish_analyzer name = "Fish Analyzer" @@ -996,7 +1008,7 @@ category = list( RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE ) - departmental_flags = DEPARTMENT_BITFLAG_SERVICE + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE // Coffeemaker Stuff diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 32d834a19ba..4a02e368a8e 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -1549,6 +1549,7 @@ design_ids = list( "fishing_rod_tech", "stabilized_hook", + "auto_reel", "fish_analyzer", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000) diff --git a/icons/obj/fishing.dmi b/icons/obj/fishing.dmi index 8e8be783fb7..92d7da8238a 100644 Binary files a/icons/obj/fishing.dmi and b/icons/obj/fishing.dmi differ