From 1aad3e118c5e3176cdfd254daa054e2ba750b4f3 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:14:04 +0200 Subject: [PATCH] You can now link fishing portal generators to other fishing spots. (#86291) ## About The Pull Request You can now interact with the fishing portal generator with a multitool to load it in the buffer and subsequently link fishing spots to it by tapping them with the same multitool. The maximum number of fishing spots that can be linked at once depends on the tier of the matter bins of the machinery. Normally, while you can link fishing spots from other z-levels, they can only be activated if they're on the same z-level as the machinery (or if both are on station for multi-z stations). This limitation can be bypassed by upgrading the machinery with a tier 3 or higher capacitator. While it's possible, I'm not spriting new fishing portal overlays and icons for the radial menu for every fish source out there (yet). The code is enough work for now. This also comes with a unit test, because there is no such thing as too many unit tests for fishing. ## Why It's Good For The Game Fish portal generators are designed to let players fish a different bunch of things while being able to be moved wherever you like, unlike a lake or an ocean, with all the comfort of being able to able to catch fish from distant locations. Allowing players to link other fishing spots to it fits its design. It also means that you can go out and explore, find more fishing spots and then return to the station without having to detach yourself from the ongoing round for several more minutes. ## Changelog :cl: add: You can now link fishing portal generators to other fishing spots with a multitool. The number of fishing spots that can be linked at once and whether the link can be activated from different z levels depends on the tier of the stock parts it's built with. /:cl: --------- Co-authored-by: Emmett Gaines --- code/__DEFINES/dcs/signals/signals_object.dm | 2 + code/__DEFINES/traits/declarations.dm | 2 + code/_globalvars/traits/_traits.dm | 3 +- code/datums/components/fishing_spot.dm | 8 + code/datums/elements/lazy_fishing_spot.dm | 10 + code/game/objects/items/devices/multitool.dm | 11 +- .../modules/fishing/fishing_portal_machine.dm | 206 +++++++++++++++++- code/modules/fishing/sources/_fish_source.dm | 4 + code/modules/fishing/sources/source_types.dm | 11 +- code/modules/unit_tests/fish_unit_tests.dm | 48 ++++ icons/hud/radial_fishing.dmi | Bin 1242 -> 1439 bytes strings/fishing_tips.txt | 2 + 12 files changed, 291 insertions(+), 16 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 651f630db09..1b167de3354 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -194,6 +194,8 @@ #define COMSIG_TOOL_IN_USE "tool_in_use" ///from base of [/obj/item/proc/tool_start_check]: (mob/living/user) #define COMSIG_TOOL_START_USE "tool_start_use" +/// From /obj/item/multitool/remove_buffer(): (buffer) +#define COMSIG_MULTITOOL_REMOVE_BUFFER "multitool_remove_buffer" ///from [/obj/item/proc/disableEmbedding]: #define COMSIG_ITEM_DISABLE_EMBED "item_disable_embed" ///from [/obj/effect/mine/proc/triggermine]: diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 1fb47c9704f..c911181c431 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -239,6 +239,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_EXAMINE_DEEPER_FISH "examine_deeper_fish" ///Trait given to turfs or objects that can be fished from #define TRAIT_FISHING_SPOT "fishing_spot" +///This trait prevents the fishing spot from being linked to the fish-porter when a multitool is being used. +#define TRAIT_UNLINKABLE_FISHING_SPOT "unlinkable_fishing_spot" ///Trait given to mobs that can fish without a rod #define TRAIT_PROFOUND_FISHER "profound_fisher" /// If an atom has this trait, then you can toss a bottle with a message in it. diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index cacce9b1c68..8c4f44979a2 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -18,6 +18,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_DO_NOT_SPLASH" = TRAIT_DO_NOT_SPLASH, "TRAIT_DRIED" = TRAIT_DRIED, "TRAIT_DRYABLE" = TRAIT_DRYABLE, + "TRAIT_FISHING_SPOT" = TRAIT_FISHING_SPOT, "TRAIT_FOOD_CHEF_MADE" = TRAIT_FOOD_CHEF_MADE, "TRAIT_FOOD_FRIED" = TRAIT_FOOD_FRIED, "TRAIT_QUALITY_FOOD_INGREDIENT" = TRAIT_QUALITY_FOOD_INGREDIENT, @@ -30,6 +31,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SPINNING" = TRAIT_SPINNING, "TRAIT_STICKERED" = TRAIT_STICKERED, "TRAIT_UNHITTABLE_BY_PROJECTILES" = TRAIT_UNHITTABLE_BY_PROJECTILES, + "TRAIT_UNLINKABLE_FISHING_SPOT" = TRAIT_UNLINKABLE_FISHING_SPOT, ), /atom/movable = list( "TRAIT_ACTIVE_STORAGE" = TRAIT_ACTIVE_STORAGE, @@ -676,7 +678,6 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CONTAINMENT_FIELD" = TRAIT_CONTAINMENT_FIELD, "TRAIT_ELEVATED_TURF" = TRAIT_ELEVATED_TURF, "TRAIT_FIREDOOR_STOP" = TRAIT_FIREDOOR_STOP, - "TRAIT_FISHING_SPOT" = TRAIT_FISHING_SPOT, "TRAIT_HYPERSPACE_STOPPED" = TRAIT_HYPERSPACE_STOPPED, "TRAIT_IMMERSE_STOPPED" = TRAIT_IMMERSE_STOPPED, "TRAIT_LAVA_STOPPED" = TRAIT_LAVA_STOPPED, diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index 5f0fd143444..982b0da2df7 100644 --- a/code/datums/components/fishing_spot.dm +++ b/code/datums/components/fishing_spot.dm @@ -20,9 +20,11 @@ RegisterSignal(parent, COMSIG_NPC_FISHING, PROC_REF(return_fishing_spot)) RegisterSignal(parent, COMSIG_ATOM_EX_ACT, PROC_REF(explosive_fishing)) RegisterSignal(parent, COMSIG_FISH_RELEASED_INTO, PROC_REF(fish_released)) + RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(link_to_fish_porter)) ADD_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src)) /datum/component/fishing_spot/Destroy() + REMOVE_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src)) fish_source.on_fishing_spot_del(src) fish_source = null REMOVE_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src)) @@ -84,6 +86,12 @@ SIGNAL_HANDLER fish_source.spawn_reward_from_explosion(location, severity) +/datum/component/fishing_spot/proc/link_to_fish_porter(atom/source, mob/user, obj/item/multitool/tool) + SIGNAL_HANDLER + if(istype(tool.buffer, /obj/machinery/fishing_portal_generator)) + var/obj/machinery/fishing_portal_generator/portal = tool.buffer + return portal.link_fishing_spot(fish_source, source, user) + /datum/component/fishing_spot/proc/fish_released(datum/source, obj/item/fish/fish, mob/living/releaser) SIGNAL_HANDLER fish_source.readd_fish(fish, releaser) diff --git a/code/datums/elements/lazy_fishing_spot.dm b/code/datums/elements/lazy_fishing_spot.dm index 1066f0179ea..67edcea2e88 100644 --- a/code/datums/elements/lazy_fishing_spot.dm +++ b/code/datums/elements/lazy_fishing_spot.dm @@ -21,6 +21,7 @@ RegisterSignal(target, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examined_more)) RegisterSignal(target, COMSIG_ATOM_EX_ACT, PROC_REF(explosive_fishing)) RegisterSignal(target, COMSIG_FISH_RELEASED_INTO, PROC_REF(fish_released)) + RegisterSignal(target, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(link_to_fish_porter)) /datum/element/lazy_fishing_spot/Detach(datum/target) UnregisterSignal(target, list( @@ -30,6 +31,7 @@ COMSIG_ATOM_EXAMINE, COMSIG_ATOM_EXAMINE_MORE, COMSIG_ATOM_EX_ACT, + COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), )) REMOVE_TRAIT(target, TRAIT_FISHING_SPOT, REF(src)) return ..() @@ -69,6 +71,14 @@ /datum/element/lazy_fishing_spot/proc/return_glob_fishing_spot(datum/source, list/fish_spot_container) fish_spot_container[NPC_FISHING_SPOT] = GLOB.preset_fish_sources[configuration] +/datum/element/lazy_fishing_spot/proc/link_to_fish_porter(atom/source, mob/user, obj/item/multitool/tool) + SIGNAL_HANDLER + if(!istype(tool.buffer, /obj/machinery/fishing_portal_generator)) + return + var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] + var/obj/machinery/fishing_portal_generator/portal = tool.buffer + return portal.link_fishing_spot(fish_source, source, user) + /datum/element/lazy_fishing_spot/proc/fish_released(datum/source, obj/item/fish/fish, mob/living/releaser) SIGNAL_HANDLER var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 03ed7f927b0..8e29be708db 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -35,6 +35,11 @@ var/apc_scanner = TRUE COOLDOWN_DECLARE(next_apc_scan) +/obj/item/multitool/Destroy() + if(buffer) + remove_buffer(buffer) + return ..() + /obj/item/multitool/examine(mob/user) . = ..() . += span_notice("Its buffer [buffer ? "contains [buffer]." : "is empty."]") @@ -70,9 +75,10 @@ /obj/item/multitool/proc/set_buffer(datum/buffer) if(src.buffer) UnregisterSignal(src.buffer, COMSIG_QDELETING) + remove_buffer(src.buffer) src.buffer = buffer if(!QDELETED(buffer)) - RegisterSignal(buffer, COMSIG_QDELETING, PROC_REF(on_buffer_del)) + RegisterSignal(buffer, COMSIG_QDELETING, PROC_REF(remove_buffer)) /** * Called when the buffer's stored object is deleted @@ -80,8 +86,9 @@ * This proc does not clear the buffer of the multitool, it is here to * handle the deletion of the object the buffer references */ -/obj/item/multitool/proc/on_buffer_del(datum/source) +/obj/item/multitool/proc/remove_buffer(datum/source) SIGNAL_HANDLER + SEND_SIGNAL(src, COMSIG_MULTITOOL_REMOVE_BUFFER, source) buffer = null // Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. diff --git a/code/modules/fishing/fishing_portal_machine.dm b/code/modules/fishing/fishing_portal_machine.dm index 9444d1368e3..f322bf92c78 100644 --- a/code/modules/fishing/fishing_portal_machine.dm +++ b/code/modules/fishing/fishing_portal_machine.dm @@ -11,6 +11,46 @@ ///The current fishing spot loaded in var/datum/component/fishing_spot/active + ///A list of fishing spot it's linked to with a multitool. + var/list/linked_fishing_spots + ///The maximum number of fishing spots it can be linked to + var/max_fishing_spots = 1 + ///If true, the fishing portal can stay connected to a linked fishing spot even on different z-levels + var/long_range_link = FALSE + +/obj/machinery/fishing_portal_generator/Initialize(mapload) + . = ..() + var/static/list/tool_screentips = list( + TOOL_MULTITOOL = list( + SCREENTIP_CONTEXT_LMB = "Link", + SCREENTIP_CONTEXT_RMB = "Unlink fishing spots" + ), + ) + AddElement(/datum/element/contextual_screentip_tools, tool_screentips) + ADD_TRAIT(src, TRAIT_UNLINKABLE_FISHING_SPOT, INNATE_TRAIT) + +/obj/machinery/fishing_portal_generator/Destroy() + deactivate() + linked_fishing_spots = null + return ..() + +///Higher tier parts let you link to more fishing spots at once and eventually let you connect through different zlevels. +/obj/machinery/fishing_portal_generator/RefreshParts() + . = ..() + max_fishing_spots = 0 + long_range_link = FALSE + for(var/datum/stock_part/matter_bin/matter_bin in component_parts) + max_fishing_spots += matter_bin.tier * 0.5 + max_fishing_spots = ROUND_UP(max_fishing_spots) + for(var/datum/stock_part/capacitor/capacitor in component_parts) + if(capacitor.tier >= 3) + long_range_link = TRUE + if(!long_range_link) + check_fishing_spot_z() + if(length(linked_fishing_spots) > max_fishing_spots) + if(active) + deactivate() + linked_fishing_spots.len = max_fishing_spots /obj/machinery/fishing_portal_generator/on_set_panel_open() update_appearance() @@ -21,9 +61,94 @@ default_unfasten_wrench(user, tool) return ITEM_INTERACT_SUCCESS +/obj/machinery/fishing_portal_generator/multitool_act(mob/living/user, obj/item/multitool/tool) + if(machine_stat & NOPOWER) + balloon_alert(user, "no power!") + return ITEM_INTERACT_BLOCKING + var/unlink = tool.buffer == src + tool.set_buffer(unlink ? null : src) + balloon_alert(user, "fish-porter [unlink ? "un" : ""]linked") + if(!unlink) + tool.item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS + RegisterSignal(tool, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, PROC_REF(multitool_context)) + RegisterSignal(tool, COMSIG_MULTITOOL_REMOVE_BUFFER, PROC_REF(multitool_unbuffered)) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/fishing_portal_generator/multitool_act_secondary(mob/living/user, obj/item/tool) + if(machine_stat & NOPOWER) + balloon_alert(user, "no power!") + return ITEM_INTERACT_BLOCKING + if(!length(linked_fishing_spots)) + balloon_alert(user, "nothing to unlink!") + return ITEM_INTERACT_BLOCKING + var/list/fishing_list = list() + var/id = 1 + for(var/atom/spot as anything in linked_fishing_spots) + var/choice_name = "[spot.name] ([id])" + fishing_list[choice_name] = spot + id++ + var/list/choices = list() + for(var/radial_name in fishing_list) + var/datum/fish_source/source = fishing_list[radial_name] + var/mutable_appearance/appearance = mutable_appearance('icons/hud/radial_fishing.dmi', source.radial_state) + appearance.add_overlay('icons/hud/radial_fishing.dmi', "minus_sign") + choices[radial_name] = appearance + + var/choice = show_radial_menu(user, src, choices, radius = 38, custom_check = CALLBACK(src, TYPE_PROC_REF(/atom, can_interact), user), tooltips = TRUE) + if(!choice) + return + var/atom/spot = fishing_list[choice] + if(QDELETED(spot) || !(spot in linked_fishing_spots) || !can_interact(user)) + return + unlink_fishing_spot(spot) + balloon_alert(user, "fishing spot unlinked") + +/obj/machinery/fishing_portal_generator/proc/multitool_context(obj/item/source, list/context, atom/target, mob/living/user) + SIGNAL_HANDLER + if(HAS_TRAIT(target, TRAIT_FISHING_SPOT) && !HAS_TRAIT(target, TRAIT_UNLINKABLE_FISHING_SPOT)) + context[SCREENTIP_CONTEXT_LMB] = "Link to fish-porter" + return CONTEXTUAL_SCREENTIP_SET + return NONE + +/obj/machinery/fishing_portal_generator/proc/multitool_unbuffered(datum/source, datum/buffer) + SIGNAL_HANDLER + UnregisterSignal(source, list(COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, COMSIG_MULTITOOL_REMOVE_BUFFER)) + +///Called when using a multitool on any other fishing source. +/obj/machinery/fishing_portal_generator/proc/link_fishing_spot(datum/fish_source/source, atom/spot, mob/living/user) + if(istype(spot, /obj/machinery/fishing_portal_generator)) //Don't link it to itself or other fishing portals. + return + if(length(linked_fishing_spots) >= max_fishing_spots) + spot.balloon_alert(user, "cannot link more!") + return ITEM_INTERACT_BLOCKING + for(var/other_spot in linked_fishing_spots) + var/datum/fish_source/stored = linked_fishing_spots[other_spot] + if(stored == source) + spot.balloon_alert(user, "already linked!") + playsound(src, 'sound/machines/buzz-sigh.ogg', 15, FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + return ITEM_INTERACT_BLOCKING + if(HAS_TRAIT(spot, TRAIT_UNLINKABLE_FISHING_SPOT)) + spot.balloon_alert(user, "unlinkable fishing spot!") + playsound(src, 'sound/machines/buzz-sigh.ogg', 15, FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + return ITEM_INTERACT_BLOCKING + LAZYSET(linked_fishing_spots, spot, source) + RegisterSignal(spot, SIGNAL_REMOVETRAIT(TRAIT_FISHING_SPOT), PROC_REF(unlink_fishing_spot)) + spot.balloon_alert(user, "fishing spot linked") + playsound(spot, 'sound/machines/ping.ogg', 15, TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/fishing_portal_generator/proc/unlink_fishing_spot(atom/spot) + SIGNAL_HANDLER + var/datum/fish_source/source = linked_fishing_spots[spot] + if(active?.fish_source == source) + deactivate() + LAZYREMOVE(linked_fishing_spots, spot) + UnregisterSignal(spot, SIGNAL_REMOVETRAIT(TRAIT_FISHING_SPOT)) + /obj/machinery/fishing_portal_generator/examine(mob/user) . = ..() - . += span_notice("You can unlock further portal settings by completing fish scanning experiments.") + . += span_notice("You can unlock further portal settings by completing fish scanning experiments, \ + or by connecting it to other fishing spots with a multitool.") /obj/machinery/fishing_portal_generator/emag_act(mob/user, obj/item/card/emag/emag_card) if(obj_flags & EMAGGED) @@ -47,21 +172,78 @@ if(!active) return . += "portal_on" - var/datum/fish_source/portal/portal = active.fish_source + var/datum/fish_source/portal = active.fish_source . += portal.overlay_state . += emissive_appearance(icon, "portal_emissive", src) -/obj/machinery/fishing_portal_generator/proc/activate(datum/fish_source/selected_source) +/obj/machinery/fishing_portal_generator/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents) + . = ..() + check_fishing_spot_z() + +/obj/machinery/fishing_portal_generator/proc/check_fishing_spot_z() + if(!active || long_range_link || istype(active.fish_source, /datum/fish_source/portal)) + return + var/turf/new_turf = get_turf(src) + if(!new_turf) + deactivate() + return + for(var/atom/spot as anything in linked_fishing_spots) + if(linked_fishing_spots[spot] != active.fish_source) + continue + var/turf/turf = get_turf(spot) + if(turf.z != new_turf.z && !(is_station_level(turf.z) && is_station_level(new_turf.z))) + deactivate() + +/obj/machinery/fishing_portal_generator/proc/activate(datum/fish_source/selected_source, mob/user) + if(QDELETED(selected_source)) + return + if(machine_stat & NOPOWER) + balloon_alert(user, "no power!") + return ITEM_INTERACT_BLOCKING + if(!istype(selected_source, /datum/fish_source/portal)) //likely from a linked fishing spot + var/abort = TRUE + for(var/atom/spot as anything in linked_fishing_spots) + if(linked_fishing_spots[spot] != selected_source) + continue + if(long_range_link) + abort = FALSE + var/turf/spot_turf = get_turf(spot) + var/turf/turf = get_turf(src) + if(turf.z == spot_turf.z || (is_station_level(turf.z) && is_station_level(spot_turf.z))) + abort = FALSE + if(!abort) + RegisterSignal(spot, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_fishing_spot_z_level_changed)) + break + if(abort) + balloon_alert(user, "cannot reach linked!") + return + active = AddComponent(/datum/component/fishing_spot, selected_source) ADD_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT) - use_power = ACTIVE_POWER_USE + if(use_power != NO_POWER_USE) + use_power = ACTIVE_POWER_USE update_icon() /obj/machinery/fishing_portal_generator/proc/deactivate() + if(!active) + return + if(!istype(active.fish_source, /datum/fish_source/portal)) + for(var/atom/spot as anything in linked_fishing_spots) + if(linked_fishing_spots[spot] == active.fish_source) + UnregisterSignal(spot, COMSIG_MOVABLE_Z_CHANGED) QDEL_NULL(active) - use_power = IDLE_POWER_USE + REMOVE_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT) - update_icon() + if(!QDELETED(src)) + if(use_power != NO_POWER_USE) + use_power = IDLE_POWER_USE + update_icon() + +/obj/machinery/fishing_portal_generator/proc/on_fishing_spot_z_level_changed(atom/spot, turf/old_turf, turf/new_turf, same_z_layer) + SIGNAL_HANDLER + var/turf/turf = get_turf(src) + if(turf.z != new_turf.z && !(is_station_level(turf.z) && is_station_level(new_turf.z))) + deactivate() /obj/machinery/fishing_portal_generator/on_set_is_operational(old_value) if(old_value) @@ -90,18 +272,24 @@ var/datum/fish_source/portal/reward = GLOB.preset_fish_sources[experiment.fish_source_reward] available_fish_sources[reward.radial_name] = reward + var/id = 1 + for(var/atom/spot as anything in linked_fishing_spots) + var/choice_name = "[spot.name] ([id])" + available_fish_sources[choice_name] = linked_fishing_spots[spot] + id++ + if(length(available_fish_sources) == 1) - activate(default) + activate(default, user) return var/list/choices = list() for(var/radial_name in available_fish_sources) - var/datum/fish_source/portal/source = available_fish_sources[radial_name] + var/datum/fish_source/source = available_fish_sources[radial_name] choices[radial_name] = image(icon = 'icons/hud/radial_fishing.dmi', icon_state = source.radial_state) var/choice = show_radial_menu(user, src, choices, radius = 38, custom_check = CALLBACK(src, TYPE_PROC_REF(/atom, can_interact), user), tooltips = TRUE) if(!choice || !can_interact(user)) return - activate(available_fish_sources[choice]) + activate(available_fish_sources[choice], user) /obj/machinery/fishing_portal_generator/emagged obj_flags = parent_type::obj_flags | EMAGGED diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index 815f3c34d23..38455068ce2 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -82,6 +82,10 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) var/explosive_malus = FALSE /// If explosive_malus is true, this will be used to keep track of the turfs where an explosion happened for when we'll spawn the loot. var/list/exploded_turfs + ///When linked to a fishing portal, this will be the icon_state of this option in the radial menu + var/radial_state = "default" + ///When selected by the fishing portal, this will be the icon_state of the overlay shown on the machine. + var/overlay_state = "portal_aquarium" /// Mindless mobs that can fish will never pull up items on this list var/static/list/profound_fisher_blacklist = typecacheof(list( /mob/living/basic/mining/lobstrosity, diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index 95eb10a04aa..ef2cdf87989 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -106,12 +106,9 @@ /obj/item/fish/goldfish/three_eyes = 3, ) catalog_description = "Aquarium dimension (Fishing portal generator)" + radial_state = "fish_tank" ///The name of this option shown in the radial menu on the fishing portal generator var/radial_name = "Aquarium" - ///The icon state shown for this option in the radial menu - var/radial_state = "fish_tank" - ///The icon state of the overlay shown on the machine when active. - var/overlay_state = "portal_aquarium" /datum/fish_source/portal/beach fish_table = list( @@ -404,6 +401,12 @@ ) fishing_difficulty = FISHING_EASY_DIFFICULTY +/datum/fish_source/holographic/on_fishing_spot_init(datum/component/fishing_spot/spot) + ADD_TRAIT(spot.parent, TRAIT_UNLINKABLE_FISHING_SPOT, REF(src)) //You would have to be inside the holodeck anyway... + +/datum/fish_source/holographic/on_fishing_spot_del(datum/component/fishing_spot/spot) + REMOVE_TRAIT(spot.parent, TRAIT_UNLINKABLE_FISHING_SPOT, REF(src)) + /datum/fish_source/holographic/generate_wiki_contents(datum/autowiki/fish_sources/wiki) var/obj/item/fish/prototype = /obj/item/fish/holo/checkered return LIST_VALUE_WRAP_LISTS(list( diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm index 91d0c006522..809bf60d230 100644 --- a/code/modules/unit_tests/fish_unit_tests.dm +++ b/code/modules/unit_tests/fish_unit_tests.dm @@ -190,6 +190,54 @@ . = ..() probability = 0 //works around the global list initialization skipping abstract/impossible evolutions. +///A test that checks that fishing portals can be linked and function as expected +/datum/unit_test/fish_portal_gen_linking + +/datum/unit_test/fish_portal_gen_linking/Run() + var/mob/living/carbon/human/consistent/user = allocate(/mob/living/carbon/human/consistent) + var/obj/machinery/fishing_portal_generator/portal = allocate(/obj/machinery/fishing_portal_generator/no_power) + var/obj/structure/toilet/unit_test/fishing_spot = new(get_turf(user)) //This is deleted during the test + var/obj/structure/moisture_trap/extra_spot = allocate(/obj/structure/moisture_trap) + var/obj/machinery/hydroponics/constructable/inaccessible = allocate(/obj/machinery/hydroponics/constructable) + ADD_TRAIT(inaccessible, TRAIT_UNLINKABLE_FISHING_SPOT, INNATE_TRAIT) + var/obj/item/multitool/tool = allocate(/obj/item/multitool) + var/datum/fish_source/toilet/fish_source = GLOB.preset_fish_sources[/datum/fish_source/toilet] + + portal.max_fishing_spots = 1 //We've no scrying orb to know if it'll be buffed or nerfed this in the future. We only have space for one here. + portal.activate(fish_source, user) + TEST_ASSERT(!portal.active, "[portal] was activated with a fish source from an unlinked fishing spot") + portal.multitool_act(user, tool) + TEST_ASSERT_EQUAL(tool.buffer, portal, "[portal] wasn't set as buffer for [tool]") + tool.melee_attack_chain(user, fishing_spot) + TEST_ASSERT_EQUAL(LAZYACCESS(portal.linked_fishing_spots, fishing_spot), fish_source, "We tried linking [portal] to the fishing spot but didn't succeed.") + portal.activate(fish_source, user) + TEST_ASSERT(portal.active?.fish_source == fish_source, "[portal] can't acces a fish source from a linked fishing spot") + //Let's move the fishing spot away. This is fine as long as the portal moves to another z level, away from the toilet + var/turf/other_z_turf = pick(GLOB.newplayer_start) + portal.forceMove(other_z_turf) + TEST_ASSERT(!portal.active, "[portal] (not upgraded) is still active though the fishing spot is on another z-level.[portal.z == fishing_spot.z ? " Actually they're still on the same level!" : ""]") + portal.long_range_link = TRUE + portal.activate(fish_source, user) + TEST_ASSERT(portal.active?.fish_source == fish_source, "[portal] can't acces a fish source from a linked fishing spot on a different z-level despite being upgraded") + fishing_spot.forceMove(other_z_turf) + portal.forceMove(get_turf(user)) + TEST_ASSERT(portal.active?.fish_source == fish_source, "[portal] (upgraded) deactivated while changing z-level") + tool.melee_attack_chain(user, extra_spot) + TEST_ASSERT_EQUAL(length(portal.linked_fishing_spots), 1, "We managed to link to another fishing spot when there's only space for one") + TEST_ASSERT_EQUAL(LAZYACCESS(portal.linked_fishing_spots, fishing_spot), fish_source, "linking to another fishing spot fouled up the other linked spots") + QDEL_NULL(fishing_spot) + TEST_ASSERT(!portal.active, "[portal] is still linked to the fish source of the deleted fishing spot it's associated to") + tool.melee_attack_chain(user, inaccessible) + TEST_ASSERT(!length(portal.linked_fishing_spots), "We managed to link to an unlinkable fishing spot") + +/obj/machinery/fishing_portal_generator/no_power + use_power = NO_POWER_USE + +/obj/structure/toilet/unit_test/Initialize(mapload) + . = ..() + if(!HAS_TRAIT(src, TRAIT_FISHING_SPOT)) //Ensure this toilet has a fishing spot because only maploaded ones have it. + AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/toilet) + // we want no default spawns in this unit test /datum/chasm_detritus/restricted/bodies/no_defaults default_contents_chance = 0 diff --git a/icons/hud/radial_fishing.dmi b/icons/hud/radial_fishing.dmi index 65fd55176b7c82eebfb1e59fd2bc666339329be3..6870698fbe689eb1c607bd7578aad0e3f9eefde4 100644 GIT binary patch delta 1321 zcmV+^1=jl737-p)BmtI@C1-zdSAF9x0HdeEczm1R;ONWD*?Kr@vjzb5^Ygk{Zf$Xr z^S3maQ+NN&GtrGU;i56paW7w!8#@3100DGTPE!Ct=GbNc006^!R9JLGWpiV4X>fFD zZ*Bkpc$|%q%?iRW5QNXoQv|(>e^)P}NDul7VcTq*rA<<^iQ?NEEO>vDc$-;1*kNWV zkLOE$lc#Gf9vGOslLxt9twr@D9Zfd7t*8uAZ{ln?Lc}Z=9kEdn+^`Uproj2O5mgt! z;;g^q1fx|ALTid>AAE9J89!o?WrW5T!NMkvSC6|9#aena&UCQ(A^qKDsoRo&mNg_qX000B%Nklz0}@ z6om;6-l(l84m#BtTSlg!Ain?0^_+xYASbA>T{&Ft4U|GHXA2If1f7`yB zz}gJv9sonZ=U^~sLlBQQ-SL7#`&I-7F+dc|2onA;FM|P4TKIoEjxFUl4q+exLJt!F zpo#);bs}6NFh9+P!-2p&NI?J5XmpDJ{zJIN29E*?|Iv*Y3V)F=?ILSx5gEYP9*@T+ z0QwINBWG+fnF>t^1cL(b@w?#fxV9N!@<-!osrp^l3cFl%2n_+SsRT@>Jd)aN3IGvg z5g@ll0Gpuuw?cpXNA*5+n$cBeY&M%AU`ExDq5Jnz$j|`J4@KHkL;F%h15Fisj+p2< zssbAP5up({V8Z`%08wS9DFB=!qeenC0;*3zHoDu?S|kWp3Sqz)CZD^Trdrcl2M?(P zx|hroSzMkA_qpK~Rzhh1+qy9V<(78-y?XWPCH3URgfV|lsLgVF$6^Tf3zWAc2;%%M z&hWIoU&y@TFQkYVn0lz74i0{)m2VhTv!4I%8_7453?U_0`W5X|TE z{d!GJWmc?lWj7IkLTJCM1N;$Y39|p;uGf2gHDbb^1E}4A)g!1s4`sH+91DODtcU=- zB0~YW-!M6HA-$8g*TvM`@Ad1LA=yUWf+|D9_ys{CR<7=^uBNyzkgqr;!nC7pk+E7) zMYMlplVX}^f2;eREU)!1D7|_;U%wPR2k^U89T0}$=Mw>8csN8+cf!}7QePGSIEtb} zd}t&j_W%@C{14Ib7{+l^mXcF?2Z2-wV?SEfXB;;rsS6;EkCuqT3`nxmxh)~&0o(-S ztpU(O+`lP9Nd|!S1WqwE(Io+01OUI@(@lR50g_XeWv3hgIecwh>(E$%6n(QiKjUYI zG=))UxRS!q^X!C-E@cQK<4jI5JMgb-nIq#&PR^$s)Xl$PK~3^3JD;Hjc{jqRF#z7A z=e&Ijp~BEqWbS`SFWm?YGnp?Fsg*)EWt{+q5+tWQ6}~A)ih1pt`r?W fFxI$NuYab$fyslZD}|$g00000NkvXXu0mjf;PzC6 delta 1122 zcmV-o1fBbz3)%^gBmsDlC1-!Yz`&!Y!gzd}-{9!W%-Qwx^KEgGybpbf00001bW%=J z06^y0W&i*HwR%)obVOxyV{&P5bZKvH004NLjgY+#f-n??=k_TY*o}Xyi@_KMd4=Xe zFSLQS*jqHdy^SPprf%n)e8b6Ciu2`KH=?@L{DFatcj6?Dn=NNgqoRLK<*?^WqZxEu z3q^>SV$mDYGJ+cm&Xft1wjF1E0E=t>k~2(Jwg{chqC@bDQ&Rg0vn(OB9%7!Gp+gyb zH{u`g%PAwt!I(c_4Z&mvhf3ZTq?qhU3*o1bQe2wqHtNtAU zIQIAbPZMCtU_Ju?5`G3ju!Nux$QWpV z(w7McFvS6+zD0OMF#R+{QJ^qiB#?g`$Il4pAK^6!z6$94<0pSHbp9$|)F}Mh=i9Tcw0S02V-wKSlpA^``+-{$#xEHNNXn;Z%wbVI~0BYCy7;k&JFL z0EA$RfVrInKtk?6I>{fy`^;&^R~cZp+Yw;L)ey-2XD?z{fbf?gYpRibFQ$REhP^~2 zc8-RC8Gk}p1POmgj5S~1#k)xk*OM31Olixhg^SS0RAi>JmCSK2qpe_;{ZK@0lX^t zPXRK9;_ZL;n+;Go^59^102#gxGz6R#LB%Kp6Tzd(NErKfDS8Uf2Qx{q8r2}!fD7u~ z^vB-=fZr2@$Uc}N%iCazAOR-f9W{)=k2``8*OUcxE8$ECmv=*#;XF9U1rbD+W!K}8 zo669waeX&2fKFt883T%#vV__HNRP*>y&8eCmjHimH-K>jlp)RzW=G4Bfo5mfXYMZ91+u9YOYU}2HuiDm8mMR?k-Scj3 z>nK)9IP|-x;&271-2$+Oq`#{SX^Oih`da{J73c{~06837XyAqRPXGV_07*qoM6N<$f`Z}@X#fBK diff --git a/strings/fishing_tips.txt b/strings/fishing_tips.txt index ce8f601d375..6b25cc5e539 100644 --- a/strings/fishing_tips.txt +++ b/strings/fishing_tips.txt @@ -15,6 +15,8 @@ You may find worms by digging through sand, ash and snow. You can revive fish by using a Lazarus Injector on them. However, using Strange Reagent would be a smarter option here. You can feed fish outside of an aquarium by tapping them with a can of fish feed. More fishing rods and fish cases can be printed at the autolathe. +You can link the fish portal generator to other fishing spots with a multitool. The maximum amount of fishing spots that can be linked and whether or not the link works on different z-levels depends on the quality of the machinery components. +The actual name of the fishing portal generator is 'fish-porter 3000'. They're totally the same thing however. Seeking alternative ways to catch fish without bothering to do it yourself? Explosives can be thrown at fishing spots to get several (dead) fishes in a pinch. You can raise lobstrosities from chasm chrabs. However, lobstrosities can only be tamed with spare limbs or lavaloop fish while still young. Lavaloop fish make for dangerous yet somewhat effective throwing weapons against big fauna.