From 1dff5f6de37bdc33efb3fcd05112c49ed2cc5acf Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Fri, 29 Nov 2024 04:33:18 +0100 Subject: [PATCH] Aquariums are now potential fishing spots. (#88243) ## About The Pull Request You can now fish from aquariums if you wish to. This includes some backend changes to make it possible for the fish table from get_fish_table() to contain instances, and all that it entails up to spawn_reward(), which is a requirement for the gimmick to respect the various traits and other variables of the already instantiated fish rather than read from cached properties. ## Why It's Good For The Game The fish progress score/index had only little nasty flaw that has been nagging me since day one: Not all fish species can be caught. Skipping McGill, which is a peculiar case that for cheevo purposes should be considered a standard goldfish, there is the one, unsignificant yet rare purple sludgefish which can only be gotten as a rare evolution of the generic sludgefish. Talk about petty, but this may be a long-term nit I prefer to handle right now. Also why not? The 'unmarine mastodon' is near impossible to get unless you somehow find a oil well which is locked behind a specific ruin. ## Changelog :cl: fix: Aquariums are now potential fishing spots. /:cl: --- code/__DEFINES/fish.dm | 8 +- code/__HELPERS/cmp.dm | 3 + .../subsystem/processing/fishing.dm | 45 ++++----- code/datums/components/_component.dm | 12 +-- code/datums/components/aquarium.dm | 2 + code/datums/components/fishing_spot.dm | 2 +- code/game/objects/items.dm | 5 +- code/game/turfs/turf.dm | 2 +- code/modules/fishing/bait.dm | 93 +++++++++---------- code/modules/fishing/fish/_fish.dm | 2 + code/modules/fishing/fish/chasm_detritus.dm | 4 +- code/modules/fishing/fish/types/freshwater.dm | 3 + code/modules/fishing/fish/types/station.dm | 1 + code/modules/fishing/fishing_equipment.dm | 8 +- code/modules/fishing/fishing_minigame.dm | 45 +++++++-- code/modules/fishing/sources/_fish_source.dm | 67 ++++++++----- code/modules/fishing/sources/source_types.dm | 40 ++++++++ code/modules/unit_tests/fish_unit_tests.dm | 6 +- strings/fishing_tips.txt | 3 +- 19 files changed, 221 insertions(+), 130 deletions(-) diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm index c64a50cd077..f9f390f95a1 100644 --- a/code/__DEFINES/fish.dm +++ b/code/__DEFINES/fish.dm @@ -187,7 +187,6 @@ ///Flag added when in an aquarium with the right fluid type. #define FISH_FLAG_SAFE_FLUID (1<<7) - #define MIN_AQUARIUM_TEMP T0C #define MAX_AQUARIUM_TEMP (T0C + 100) #define DEFAULT_AQUARIUM_TEMP (T0C + 24) @@ -260,11 +259,8 @@ #define FISH_SOURCE_FLAG_EXPLOSIVE_MALUS (1<<0) /// The fish source is not elegible for random rewards from bluespace fishing rods #define FISH_SOURCE_FLAG_NO_BLUESPACE_ROD (1<<1) -/** - * If present, fish that can be caught from this source won't be included in the 'fish caught' score, unless - * present in other sources without this flag as well. - */ -#define FISH_SOURCE_FLAG_SKIP_CATCHABLES (1<<2) +/// When examined by someone with enough fishing skill, this will also display fish that doesn't have FISH_FLAG_SHOW_IN_CATALOG +#define FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG (1<<2) /** * A macro to ensure the wikimedia filenames of fish icons are unique, especially since there're a couple fish that have diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index 76651964e24..efee782aaeb 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -30,6 +30,9 @@ /proc/cmp_name_dsc(atom/a, atom/b) return sorttext(a.name, b.name) +/proc/cmp_init_name_asc(atom/a, atom/b) + return sorttext(initial(b.name), initial(a.name)) + /proc/cmp_records_asc(datum/record/a, datum/record/b) return sorttext(b.name, a.name) diff --git a/code/controllers/subsystem/processing/fishing.dm b/code/controllers/subsystem/processing/fishing.dm index 477dbf35e51..3f0433b21b9 100644 --- a/code/controllers/subsystem/processing/fishing.dm +++ b/code/controllers/subsystem/processing/fishing.dm @@ -19,10 +19,13 @@ PROCESSING_SUBSYSTEM_DEF(fishing) cached_fish_icons = list() cached_unknown_fish_icons = list() fish_properties = list() + catchable_fish = list() var/icon/questionmark = icon('icons/effects/random_spawners.dmi', "questionmark") var/list/mark_dimension = get_icon_dimensions(questionmark) - for(var/obj/item/fish/fish_type as anything in subtypesof(/obj/item/fish)) + var/list/spawned_fish = list() + var/list/fish_subtypes = sortTim(subtypesof(/obj/item/fish), GLOBAL_PROC_REF(cmp_init_name_asc)) + for(var/obj/item/fish/fish_type as anything in fish_subtypes) var/list/fish_dimensions = get_icon_dimensions(fish_type::icon) var/icon/fish_icon = icon(fish_type::icon, fish_type::icon_state, frame = 1, moving = FALSE) cached_fish_icons[fish_type] = icon2base64(fish_icon) @@ -35,6 +38,7 @@ PROCESSING_SUBSYSTEM_DEF(fishing) cached_unknown_fish_icons[fish_type] = icon2base64(unknown_icon) var/obj/item/fish/fish = new fish_type(null, FALSE) + spawned_fish += fish var/list/properties = list() fish_properties[fish_type] = properties properties[FISH_PROPERTIES_FAV_BAIT] = fish.favorite_bait.Copy() @@ -67,36 +71,35 @@ PROCESSING_SUBSYSTEM_DEF(fishing) properties[FISH_PROPERTIES_BEAUTY_SCORE] = beauty_score - qdel(fish) - - catchable_fish = list() - var/list/all_catchables = list() - for(var/source_type as anything in GLOB.preset_fish_sources) - var/datum/fish_source/source = GLOB.preset_fish_sources[source_type] - if(!(source.fish_source_flags & FISH_SOURCE_FLAG_SKIP_CATCHABLES)) - all_catchables |= source.fish_table - for(var/thing in all_catchables) - if(!ispath(thing, /obj/item/fish)) - continue - var/obj/item/fish/fishie = thing - var/fish_id = initial(fishie.fish_id) + var/fish_id + if(fish.fish_id_redirect_path) + var/obj/item/fish/other_path = fish.fish_id_redirect_path + if(!ispath(other_path, /obj/item/fish)) + stack_trace("[fish.type] has a set 'fish_id_redirect_path' variable but it isn't a fish path but [other_path]") + continue + fish_id = initial(other_path.fish_id) + else + fish_id = fish.fish_id if(!fish_id) - stack_trace("[fishie] doesn't have a set 'fish_id' variable despite being a catchable fish") + stack_trace("[fish.type] doesn't have a set 'fish_id' variable despite being a catchable fish") + continue + if(fish.fish_id_redirect_path) continue if(catchable_fish[fish_id]) - stack_trace("[fishie] has a 'fish_id' value already assigned to [catchable_fish[fish_id]]. fish_id: [fish_id]") + stack_trace("[fish.type] has a 'fish_id' value already assigned to [catchable_fish[fish_id]]. fish_id: [fish_id]") continue - catchable_fish[fish_id] = fishie + catchable_fish[fish_id] = fish.type ///init the list of things lures can catch lure_catchables = list() - var/list/fish_types = subtypesof(/obj/item/fish) for(var/lure_type in typesof(/obj/item/fishing_lure)) var/obj/item/fishing_lure/lure = new lure_type lure_catchables[lure_type] = list() - for(var/obj/item/fish/fish_type as anything in fish_types) - if(lure.is_catchable_fish(fish_type, fish_properties[fish_type])) - lure_catchables[lure_type] += fish_type + for(var/obj/item/fish/fish as anything in spawned_fish) + if(lure.is_catchable_fish(fish, fish_properties[fish.type])) + lure_catchables[lure_type] += fish.type qdel(lure) + QDEL_LIST(spawned_fish) + return SS_INIT_SUCCESS diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 85def333f4c..2887fc55f8d 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -470,13 +470,13 @@ for(var/component_key in dc) var/component_or_list = dc[component_key] if(islist(component_or_list)) - for(var/datum/component/I in component_or_list) - if(I.can_transfer) - target.TakeComponent(I) + for(var/datum/component/component in component_or_list) + if(component.can_transfer) + target.TakeComponent(component) else - var/datum/component/C = component_or_list - if(C.can_transfer) - target.TakeComponent(C) + var/datum/component/component = component_or_list + if(!QDELETED(component) && component.can_transfer) + target.TakeComponent(component) /** * Return the object that is the host of any UI's that this component has diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm index e0ef6d8c04a..00a47695956 100644 --- a/code/datums/components/aquarium.dm +++ b/code/datums/components/aquarium.dm @@ -118,6 +118,7 @@ RegisterSignal(movable, COMSIG_ATOM_UI_INTERACT, PROC_REF(interact)) movable.AddElement(/datum/element/relay_attackers) + movable.AddComponent(/datum/component/fishing_spot, /datum/fish_source/aquarium) for(var/atom/movable/content as anything in movable.contents) if(content.flags_1 & INITIALIZED_1) @@ -150,6 +151,7 @@ beauty_by_content = null tracked_fish_by_type = null movable.remove_traits(list(TRAIT_IS_AQUARIUM, TRAIT_AQUARIUM_PANEL_OPEN, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH), AQUARIUM_TRAIT) + qdel(movable.GetComponent(/datum/component/fishing_spot)) REMOVE_KEEP_TOGETHER(movable, AQUARIUM_TRAIT) /datum/component/aquarium/PreTransfer(atom/movable/new_parent) diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index 3dc99cd8067..3ce68dce339 100644 --- a/code/datums/components/fishing_spot.dm +++ b/code/datums/components/fishing_spot.dm @@ -41,7 +41,7 @@ if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) return - if(!fish_source.has_known_fishes()) + if(!fish_source.has_known_fishes(source)) return examine_text += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index b8aec816391..cd2ec6c6476 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1883,11 +1883,12 @@ return src /// Checks if the bait is liked by the fish type or not. Returns a multiplier that affects the chance of catching it. -/obj/item/proc/check_bait(obj/item/fish/fish_type) +/obj/item/proc/check_bait(obj/item/fish/fish) if(HAS_TRAIT(src, TRAIT_OMNI_BAIT)) return 1 var/catch_multiplier = 1 - var/list/properties = SSfishing.fish_properties[fish_type] + + var/list/properties = SSfishing.fish_properties[isfish(fish) ? fish.type : fish] //Bait matching likes doubles the chance var/list/fav_bait = properties[FISH_PROPERTIES_FAV_BAIT] for(var/bait_identifer in fav_bait) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index aab0f609d0e..bda90858cae 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -836,7 +836,7 @@ GLOBAL_LIST_EMPTY(station_turfs) . = ..() if(!fish_source || !HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) return - if(!GLOB.preset_fish_sources[fish_source].has_known_fishes()) + if(!GLOB.preset_fish_sources[fish_source].has_known_fishes(src)) return . += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") diff --git a/code/modules/fishing/bait.dm b/code/modules/fishing/bait.dm index 275f387cbda..50cb6ef09b1 100644 --- a/code/modules/fishing/bait.dm +++ b/code/modules/fishing/bait.dm @@ -88,13 +88,11 @@ rod.spin_frequency = null ///Called for every fish subtype by the fishing subsystem when initializing, to populate the list of fish that can be catched with this lure. -/obj/item/fishing_lure/proc/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/avg_size = initial(fish_type.average_size) +/obj/item/fishing_lure/proc/is_catchable_fish(obj/item/fish/fish, list/fish_properties) var/intermediate_size = FISH_SIZE_SMALL_MAX + (FISH_SIZE_NORMAL_MAX - FISH_SIZE_SMALL_MAX) - if(!ISINRANGE(avg_size, FISH_SIZE_TINY_MAX * 0.5, intermediate_size)) + if(!ISINRANGE(fish.size, FISH_SIZE_TINY_MAX * 0.5, intermediate_size)) return FALSE - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish_traits)) + if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish.fish_traits)) return FALSE return TRUE @@ -120,11 +118,16 @@ . += span_info("You can catch the following fish with this lure: [english_list(known_fishes)].") ///Check if the fish is in the list of catchable fish for this fishing lure. Return value is a multiplier. -/obj/item/fishing_lure/check_bait(obj/item/fish/fish_type) +/obj/item/fishing_lure/check_bait(obj/item/fish/fish) var/multiplier = 0 - if(is_type_in_list(/obj/item/fishing_lure, SSfishing.fish_properties[fish_type][FISH_PROPERTIES_FAV_BAIT])) + var/is_instance = istype(fish) + var/list/fish_properties = SSfishing.fish_properties[is_instance ? fish.type : fish] + if(is_type_in_list(/obj/item/fishing_lure, fish_properties[FISH_PROPERTIES_FAV_BAIT])) multiplier += 2 - if(fish_type in SSfishing.lure_catchables[type]) + if(is_instance) + if(is_catchable_fish(fish, fish_properties)) + multiplier += 10 + else if(fish in SSfishing.lure_catchables[type]) multiplier += 10 return multiplier @@ -133,12 +136,10 @@ desc = "A bigger fishing lure that may attract larger fish. Tiny or picky prey will remain uninterested." icon_state = "plug" -/obj/item/fishing_lure/plug/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/avg_size = initial(fish_type.average_size) - if(avg_size <= FISH_SIZE_SMALL_MAX) +/obj/item/fishing_lure/plug/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(fish.size <= FISH_SIZE_SMALL_MAX) return FALSE - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish_traits)) + if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish.fish_traits)) return FALSE return TRUE @@ -148,17 +149,17 @@ icon_state = "dropping" spin_frequency = list(1.5 SECONDS, 2.8 SECONDS) -/obj/item/fishing_lure/dropping/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) +/obj/item/fishing_lure/dropping/is_catchable_fish(obj/item/fish/fish, list/fish_properties) var/list/sources = list(/datum/fish_source/toilet, /datum/fish_source/moisture_trap) for(var/datum/fish_source/source as anything in sources) var/datum/fish_source/instance = GLOB.preset_fish_sources[/datum/fish_source/toilet] - if(fish_type in instance.fish_table) + if(fish.type in instance.fish_table) return TRUE var/list/fav_baits = fish_properties[FISH_PROPERTIES_FAV_BAIT] for(var/list/identifier in fav_baits) if(identifier[FISH_BAIT_TYPE] == FISH_BAIT_FOODTYPE && (identifier[FISH_BAIT_VALUE] & (JUNKFOOD|GROSS|TOXIC))) return TRUE - if(initial(fish_type.beauty) <= FISH_BEAUTY_DISGUSTING) + if(fish.beauty <= FISH_BEAUTY_DISGUSTING) return TRUE return FALSE @@ -168,17 +169,15 @@ icon_state = "spoon" spin_frequency = list(1.25 SECONDS, 2.25 SECONDS) -/obj/item/fishing_lure/spoon/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/avg_size = initial(fish_type.average_size) - if(!ISINRANGE(avg_size, FISH_SIZE_TINY_MAX + 1, FISH_SIZE_NORMAL_MAX)) +/obj/item/fishing_lure/spoon/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(!ISINRANGE(fish.size, FISH_SIZE_TINY_MAX + 1, FISH_SIZE_NORMAL_MAX)) return FALSE - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish_traits)) + if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater, /datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish.fish_traits)) return FALSE - var/fluid_type = initial(fish_type.required_fluid_type) + var/fluid_type = fish.required_fluid_type if(fluid_type == AQUARIUM_FLUID_FRESHWATER || fluid_type == AQUARIUM_FLUID_ANADROMOUS || fluid_type == AQUARIUM_FLUID_ANY_WATER) return TRUE - if((/datum/fish_trait/amphibious in fish_traits) && fluid_type == AQUARIUM_FLUID_AIR) + if((/datum/fish_trait/amphibious in fish.fish_traits) && fluid_type == AQUARIUM_FLUID_AIR) return TRUE return FALSE @@ -188,9 +187,8 @@ icon_state = "artificial_fly" spin_frequency = list(1.1 SECONDS, 2 SECONDS) -/obj/item/fishing_lure/artificial_fly/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(/datum/fish_trait/picky_eater in fish_traits) +/obj/item/fishing_lure/artificial_fly/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(/datum/fish_trait/picky_eater in fish.fish_traits) return TRUE return FALSE @@ -216,9 +214,8 @@ . = ..() REMOVE_TRAIT(rod, TRAIT_ROD_IGNORE_ENVIRONMENT, type) -/obj/item/fishing_lure/led/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish_traits)) +/obj/item/fishing_lure/led/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(length(list(/datum/fish_trait/nocturnal, /datum/fish_trait/heavy) & fish.fish_traits)) return TRUE return FALSE @@ -236,9 +233,8 @@ . = ..() REMOVE_TRAIT(rod, TRAIT_ROD_ATTRACT_SHINY_LOVERS, REF(src)) -/obj/item/fishing_lure/lucky_coin/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(/datum/fish_trait/shiny_lover in fish_traits) +/obj/item/fishing_lure/lucky_coin/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(/datum/fish_trait/shiny_lover in fish.fish_traits) return TRUE return FALSE @@ -248,9 +244,8 @@ icon_state = "algae" spin_frequency = list(3 SECONDS, 5 SECONDS) -/obj/item/fishing_lure/algae/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(/datum/fish_trait/vegan in fish_traits) +/obj/item/fishing_lure/algae/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(/datum/fish_trait/vegan in fish.fish_traits) return TRUE return FALSE @@ -260,11 +255,10 @@ icon_state = "grub" spin_frequency = list(1 SECONDS, 2.7 SECONDS) -/obj/item/fishing_lure/grub/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - if(initial(fish_type.average_size) >= FISH_SIZE_SMALL_MAX) +/obj/item/fishing_lure/grub/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(fish.size >= FISH_SIZE_SMALL_MAX) return FALSE - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater) & fish_traits)) + if(length(list(/datum/fish_trait/vegan, /datum/fish_trait/picky_eater) & fish.fish_traits)) return FALSE return TRUE @@ -274,9 +268,8 @@ icon_state = "buzzbait" spin_frequency = list(0.8 SECONDS, 1.7 SECONDS) -/obj/item/fishing_lure/buzzbait/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(/datum/fish_trait/electrogenesis in fish_traits) +/obj/item/fishing_lure/buzzbait/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(HAS_TRAIT(fish, TRAIT_FISH_ELECTROGENESIS)) return TRUE return FALSE @@ -286,14 +279,13 @@ icon_state = "spinnerbait" spin_frequency = list(2 SECONDS, 4 SECONDS) -/obj/item/fishing_lure/spinnerbait/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(!(/datum/fish_trait/predator in fish_traits)) +/obj/item/fishing_lure/spinnerbait/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(!(/datum/fish_trait/predator in fish.fish_traits)) return FALSE - var/init_fluid_type = initial(fish_type.required_fluid_type) + var/init_fluid_type = fish.required_fluid_type if(init_fluid_type == AQUARIUM_FLUID_FRESHWATER || init_fluid_type == AQUARIUM_FLUID_ANADROMOUS || init_fluid_type == AQUARIUM_FLUID_ANY_WATER) return TRUE - if((/datum/fish_trait/amphibious in fish_traits) && init_fluid_type == AQUARIUM_FLUID_AIR) //fluid type is changed to freshwater on init + if((/datum/fish_trait/amphibious in fish.fish_traits) && init_fluid_type == AQUARIUM_FLUID_AIR) //fluid type is changed to freshwater on init return TRUE return FALSE @@ -303,11 +295,10 @@ icon_state = "daisy_chain" spin_frequency = list(2 SECONDS, 4 SECONDS) -/obj/item/fishing_lure/daisy_chain/is_catchable_fish(obj/item/fish/fish_type, list/fish_properties) - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] - if(!(/datum/fish_trait/predator in fish_traits)) +/obj/item/fishing_lure/daisy_chain/is_catchable_fish(obj/item/fish/fish, list/fish_properties) + if(!(/datum/fish_trait/predator in fish.fish_traits)) return FALSE - var/init_fluid_type = initial(fish_type.required_fluid_type) + var/init_fluid_type = fish.required_fluid_type if(init_fluid_type == AQUARIUM_FLUID_SALTWATER || init_fluid_type == AQUARIUM_FLUID_ANADROMOUS || init_fluid_type == AQUARIUM_FLUID_ANY_WATER) return TRUE return FALSE diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index fcad429c2b9..1521eb4cc77 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -170,6 +170,8 @@ * Once set, the value shouldn't be changed, so don't make typos. */ var/fish_id + ///Used to redirect to another fish path so that catching this fish unlocks its entry instead. + var/obj/item/fish/fish_id_redirect_path /obj/item/fish/Initialize(mapload, apply_qualities = TRUE) . = ..() diff --git a/code/modules/fishing/fish/chasm_detritus.dm b/code/modules/fishing/fish/chasm_detritus.dm index 50b35f7d074..05db9571004 100644 --- a/code/modules/fishing/fish/chasm_detritus.dm +++ b/code/modules/fishing/fish/chasm_detritus.dm @@ -61,7 +61,9 @@ GLOBAL_LIST_INIT_TYPED(chasm_detritus_types, /datum/chasm_detritus, init_chasm_d var/default_spawn = pick(default_contents[default_contents_key]) return new default_spawn(fisher_turf) - return determine_detritus(chasm_contents) + var/atom/movable/detritus = determine_detritus(chasm_contents) + detritus.forceMove(fisher_turf) + return detritus /datum/chasm_detritus/proc/get_chasm_contents(turf/fishing_spot) . = list() diff --git a/code/modules/fishing/fish/types/freshwater.dm b/code/modules/fishing/fish/types/freshwater.dm index 16687bc6497..4e7a7d9c9eb 100644 --- a/code/modules/fishing/fish/types/freshwater.dm +++ b/code/modules/fishing/fish/types/freshwater.dm @@ -25,6 +25,7 @@ /obj/item/fish/goldfish/gill name = "McGill" desc = "A great rubber duck tool for Lawyers who can't get a grasp over their case." + fish_id_redirect_path = /obj/item/fish/goldfish stable_population = 1 random_case_rarity = FISH_RARITY_NOPE fish_flags = parent_type::fish_flags & ~FISH_FLAG_SHOW_IN_CATALOG @@ -61,6 +62,7 @@ /obj/item/fish/goldfish/three_eyes/gill name = "McGill" desc = "A great rubber duck tool for Lawyers who can't get a grasp over their case. It looks kinda different today..." + fish_id_redirect_path = /obj/item/fish/goldfish/three_eyes compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/three_eyes) beauty = FISH_BEAUTY_GREAT fish_flags = parent_type::fish_flags & ~FISH_FLAG_SHOW_IN_CATALOG @@ -163,6 +165,7 @@ /obj/item/fish/tadpole name = "tadpole" + fish_id = "tadpole" desc = "The larval spawn of an amphibian. A very minuscle, round creature with a long tail it uses to swim around." icon_state = "tadpole" average_size = 3 diff --git a/code/modules/fishing/fish/types/station.dm b/code/modules/fishing/fish/types/station.dm index 8d958303bf3..353201a1afc 100644 --- a/code/modules/fishing/fish/types/station.dm +++ b/code/modules/fishing/fish/types/station.dm @@ -64,6 +64,7 @@ /obj/item/fish/sludgefish/purple name = "purple sludgefish" + fish_id = "purple_sludgefish" desc = "A misshapen, fragile, loosely fish-like living goop. This one has developed sexual reproduction mechanisms, and a purple tint to boot." icon_state = "sludgefish_purple" random_case_rarity = FISH_RARITY_NOPE diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm index d1ed33e9a21..37aea262426 100644 --- a/code/modules/fishing/fishing_equipment.dm +++ b/code/modules/fishing/fishing_equipment.dm @@ -215,8 +215,8 @@ SIGNAL_HANDLER REMOVE_TRAIT(rod, TRAIT_ROD_REMOVE_FISHING_DUD, REF(src)) -/obj/item/fishing_hook/magnet/get_hook_bonus_multiplicative(fish_type, datum/fish_source/source) - if(fish_type == FISHING_DUD || ispath(fish_type, /obj/item/fish)) +/obj/item/fishing_hook/magnet/get_hook_bonus_multiplicative(fish_type) + if(fish_type == FISHING_DUD || ispath(fish_type, /obj/item/fish) || isfish(fish_type)) return ..() // We multiply the odds by five for everything that's not a fish nor a dud @@ -275,9 +275,9 @@ return "The hook on your fishing rod wasn't meant for traditional fishing, rendering it useless at doing so!" -/obj/item/fishing_hook/rescue/get_hook_bonus_multiplicative(fish_type, datum/fish_source/source) +/obj/item/fishing_hook/rescue/get_hook_bonus_multiplicative(fish_type) // Sorry, you won't catch fish with this. - if(ispath(fish_type, /obj/item/fish)) + if(ispath(fish_type, /obj/item/fish) || isfish(fish_type)) return RESCUE_HOOK_FISH_MULTIPLIER return ..() diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index a8fa4d26ad8..75cd0043d22 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -357,11 +357,12 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) if(win) if(reward_path != FISHING_DUD) playsound(location, 'sound/effects/bigsplash.ogg', 100) - if(ispath(reward_path, /obj/item/fish)) + if(ispath(reward_path, /obj/item/fish) || isfish(reward_path)) var/obj/item/fish/fish_reward = reward_path - var/fish_id = initial(fish_reward.fish_id) + var/obj/item/fish/redirect_path = initial(fish_reward.fish_id_redirect_path) + var/fish_id = ispath(redirect_path, /obj/item/fish) ? initial(redirect_path.fish_id) : initial(fish_reward.fish_id) if(fish_id) - user.client?.give_award(/datum/award/score/progress/fish, user, initial(fish_reward.fish_id)) + user.client?.give_award(/datum/award/score/progress/fish, user, fish_id) SEND_SIGNAL(user, COMSIG_MOB_COMPLETE_FISHING, src, win) if(!QDELETED(src)) qdel(src) @@ -397,7 +398,13 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) playsound(location, 'sound/effects/fish_splash.ogg', 100) if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH)) - fish_icon = GLOB.specific_fish_icons[reward_path] || FISH_ICON_DEF + var/possible_icon + if(isatom(reward_path)) + var/atom/reward = reward_path + possible_icon = GLOB.specific_fish_icons[reward.type] + else + possible_icon = GLOB.specific_fish_icons[reward_path] + fish_icon = possible_icon || FISH_ICON_DEF switch(fish_icon) if(FISH_ICON_DEF) send_alert("fish!!!") @@ -455,11 +462,22 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) SIGNAL_HANDLER interrupt() +/datum/fishing_challenge/proc/on_reward_removed(datum/source) + SIGNAL_HANDLER + send_alert("reward gone!") + interrupt() + +/datum/fishing_challenge/proc/on_fish_death(obj/item/fish/source) + SIGNAL_HANDLER + if(source.status == FISH_DEAD) + win_anyway() + /datum/fishing_challenge/proc/win_anyway() - if(!completed) - //winning by timeout or idling around shouldn't give as much experience. - experience_multiplier *= 0.5 - complete(TRUE) + if(completed) + return + //winning by timeout / fish death shouldn't give as much experience. + experience_multiplier *= 0.5 + complete(TRUE) /datum/fishing_challenge/proc/hurt_fish(datum/source, obj/item/fish/reward) SIGNAL_HANDLER @@ -506,13 +524,15 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) if(difficulty > FISHING_DEFAULT_DIFFICULTY) completion -= MAX_FISH_COMPLETION_MALUS * (difficulty * 0.01) + var/is_fish_instance = isfish(reward_path) + /// Fish minigame properties - if(ispath(reward_path,/obj/item/fish)) + if(ispath(reward_path,/obj/item/fish) || is_fish_instance) var/obj/item/fish/fish = reward_path var/movement_path = initial(fish.fish_movement_type) mover = new movement_path(src) // Apply fish trait modifiers - var/list/fish_traits = SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS] + var/list/fish_traits = is_fish_instance ? fish.fish_traits : SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS] for(var/fish_trait in fish_traits) var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] trait.minigame_mod(used_rod, user, src) @@ -552,6 +572,11 @@ GLOBAL_LIST_EMPTY(fishing_challenges_by_user) var/obj/item/fish/fish = reward_path var/wait_time = (initial(fish.health) / FISH_DAMAGE_PER_SECOND) SECONDS addtimer(CALLBACK(src, PROC_REF(win_anyway)), wait_time, TIMER_DELETE_ME) + else if(ismovable(reward_path)) + var/atom/movable/reward = reward_path + RegisterSignal(reward, COMSIG_MOVABLE_MOVED, PROC_REF(on_reward_removed)) + if(is_fish_instance) + RegisterSignal(reward, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_fish_death)) start_time = world.time ///Throws a stack with prefixed text. diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index a80a2514762..abf3a298462 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -180,16 +180,20 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) // Difficulty modifier added by the rod . += rod.difficulty_modifier - if(!ispath(result,/obj/item/fish)) + var/is_fish_instance = isfish(result) + if(!ispath(result,/obj/item/fish) && !is_fish_instance) // In the future non-fish rewards can have variable difficulty calculated here return var/obj/item/fish/caught_fish = result - var/list/fish_properties = SSfishing.fish_properties[caught_fish] + + //Just to clarify when we should use the path instead of the fish, which can be both a path and an instance. + var/result_path = is_fish_instance ? caught_fish.type : result + // Baseline fish difficulty . += initial(caught_fish.fishing_difficulty_modifier) - + var/list/fish_properties = SSfishing.fish_properties[result_path] if(rod.bait) var/obj/item/bait = rod.bait //Fav bait makes it easier @@ -204,7 +208,11 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) . += DISLIKED_BAIT_DIFFICULTY_MOD // Matching/not matching fish traits and equipment - var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] + var/list/fish_traits + if(is_fish_instance) + fish_traits = caught_fish.fish_traits + else + fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] var/additive_mod = 0 var/multiplicative_mod = 1 @@ -217,6 +225,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) . += additive_mod . *= multiplicative_mod + ///Comsig proc from the fishing minigame for 'roll_reward' /datum/fish_source/proc/roll_reward_minigame(datum/source, obj/item/fishing_rod/rod, mob/fisherman, atom/location, list/rewards) SIGNAL_HANDLER @@ -265,8 +274,6 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) else if(istype(reward, /obj/effect/spawner)) // Do not attempt to forceMove() a spawner. It will break things, and the spawned item should already be at the mob's turf by now. fisherman.balloon_alert(fisherman, "caught something!") return - else // for fishing things like corpses, move them to the turf of the fisherman - INVOKE_ASYNC(reward, TYPE_PROC_REF(/atom/movable, forceMove), get_turf(fisherman)) fisherman.balloon_alert(fisherman, "caught [reward]!") return reward @@ -306,6 +313,10 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) /datum/fish_source/proc/spawn_reward(reward_path, atom/spawn_location, atom/fishing_spot) if(reward_path == FISHING_DUD) return + if(ismovable(reward_path)) + var/atom/movable/reward = reward_path + reward.forceMove(spawn_location) + return reward if(ispath(reward_path, /datum/chasm_detritus)) return GLOB.chasm_detritus_types[reward_path].dispense_detritus(spawn_location, fishing_spot) if(!ispath(reward_path, /atom/movable)) @@ -317,7 +328,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) return reward /// Returns the fish table, with with the unavailable items from fish_counts removed. -/datum/fish_source/proc/get_fish_table(from_explosion = FALSE) +/datum/fish_source/proc/get_fish_table(atom/location, from_explosion = FALSE) var/list/table = fish_table.Copy() //message bottles cannot spawn from explosions. They're meant to be one-time messages (rarely) and photos from past rounds //and it would suck if the pool of bottle messages were constantly being emptied by explosive fishing. @@ -336,8 +347,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) ///Multiplier used to make fishes more common compared to everything else. var/result_multiplier = 1 - - var/list/final_table = get_fish_table() + var/list/final_table = get_fish_table(location) if(bait) for(var/trait in weight_result_multiplier) @@ -357,7 +367,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) 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)) + if(ispath(result, /obj/item/fish) || isfish(result)) if(bait) final_table[result] = round(final_table[result] * result_multiplier, 1) var/mult = bait.check_bait(result) @@ -384,7 +394,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) var/highest_fish_weight var/list/collected_fish_weights = list() for(var/fishable in table) - if(ispath(fishable, /obj/item/fish)) + if(ispath(fishable, /obj/item/fish) || isfish(fishable)) var/fish_weight = table[fishable] collected_fish_weights[fishable] = fish_weight if(fish_weight > highest_fish_weight) @@ -397,30 +407,38 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) table[fish] += round(difference**exponent, 1) /datum/fish_source/proc/get_fish_trait_catch_mods(weight, obj/item/fish/fish, obj/item/fishing_rod/rod, mob/user, atom/location) - if(!ispath(fish, /obj/item/fish)) + var/is_fish_instance = isfish(fish) + if(!ispath(fish, /obj/item/fish) && !is_fish_instance) return weight var/multiplier = 1 - for(var/fish_trait in SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS]) + var/list/fish_traits + if(is_fish_instance) + fish_traits = fish.fish_traits + else + fish_traits = SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS] + for(var/fish_trait in fish_traits) var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] - var/list/mod = trait.catch_weight_mod(rod, user, location, fish) + var/list/mod = trait.catch_weight_mod(rod, user, location, is_fish_instance ? fish.type : fish) weight += mod[ADDITIVE_FISHING_MOD] multiplier *= mod[MULTIPLICATIVE_FISHING_MOD] return round(weight * multiplier, 1) ///returns true if this fishing spot has fish that are shown in the catalog. -/datum/fish_source/proc/has_known_fishes() - for(var/reward in fish_table) - if(!ispath(reward, /obj/item/fish)) +/datum/fish_source/proc/has_known_fishes(atom/location) + var/show_anyway = fish_source_flags & FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG + for(var/reward in get_fish_table(location)) + if(!ispath(reward, /obj/item/fish) && !isfish(reward)) continue var/obj/item/fish/prototype = reward - if(initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG) + if(!show_anyway && initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG) return TRUE return FALSE ///Add a string with the names of catchable fishes to the examine text. /datum/fish_source/proc/get_catchable_fish_names(mob/user, atom/location, list/examine_text) var/list/known_fishes = list() + var/show_anyway = fish_source_flags & FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG var/obj/item/fishing_rod/rod = user.get_active_held_item() var/list/final_table @@ -433,17 +451,18 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) var/list/rodless_weights = list() var/total_rod_weight = 0 var/list/rod_weights = list() - for(var/reward in fish_table) - var/weight = fish_table[reward] + var/list/table = get_fish_table(location) + for(var/reward in table) + var/weight = table[reward] var/final_weight if(rod) total_weight += weight final_weight = final_table[reward] total_rod_weight += final_weight - if(!ispath(reward, /obj/item/fish)) + if(!ispath(reward, /obj/item/fish) && !isfish(reward)) continue var/obj/item/fish/prototype = reward - if(!(initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG)) + if(!show_anyway && !(initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG)) continue if(rod) rodless_weights[reward] = weight @@ -472,7 +491,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) var/info = "You can catch the following fish here" if(rod) - info = span_tooltip("boldened are the fish you're more likely to catch with your current setup. The opposite is true for smaller names", info) + info = span_tooltip("In bold are fish you're more likely to catch with the current setup. The opposite is true for the smaller font", info) examine_text += span_info("[info]: [english_list(known_fishes)].") ///How much the explosive_fishing_score impacts explosive fishing. The higher the value, the stronger the malus for repeated calls @@ -493,7 +512,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) for(var/i in 1 to (severity + 2)) if(!prob((100 + 100 * severity)/i * multiplier)) continue - var/reward_loot = pick_weight(get_fish_table(from_explosion = TRUE)) + var/reward_loot = pick_weight(get_fish_table(location, from_explosion = TRUE)) var/atom/spawn_location = isturf(location) ? location : location.drop_location() var/atom/movable/reward = simple_dispense_reward(reward_loot, spawn_location, location) if(isnull(reward)) diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index c056000c45f..b14ec608558 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -698,6 +698,46 @@ return data +#define RANDOM_AQUARIUM_FISH "random_aquarium_fish" + +/datum/fish_source/aquarium + radial_state = "fish_tank" + fish_table = list( + FISHING_DUD = 10, + ) + fish_source_flags = FISH_SOURCE_FLAG_NO_BLUESPACE_ROD|FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG + fishing_difficulty = FISHING_EASY_DIFFICULTY - 5 + +#undef RANDOM_AQUARIUM_FISH + +/datum/fish_source/aquarium/get_fish_table(atom/location, from_explosion = FALSE) + if(istype(location, /obj/machinery/fishing_portal_generator)) + var/obj/machinery/fishing_portal_generator/portal = location + location = portal.current_linked_atom + var/list/table = list() + for(var/obj/item/fish/fish in location) + if(fish.status == FISH_DEAD) //dead fish cannot be caught + continue + table[fish] = 10 + if(!length(table)) + return fish_table + return table + +/datum/fish_source/aquarium/spawn_reward_from_explosion(atom/location, severity) + return //If the aquarium breaks, all fish are released anyway. + +/datum/fish_source/aquarium/generate_wiki_contents(datum/autowiki/fish_sources/wiki) + var/list/data = list() + + data += LIST_VALUE_WRAP_LISTS(list( + FISH_SOURCE_AUTOWIKI_NAME = "Fish", + FISH_SOURCE_AUTOWIKI_DUD = "", + FISH_SOURCE_AUTOWIKI_WEIGHT = 100, + FISH_SOURCE_AUTOWIKI_NOTES = "Any fish currently inside the aquarium, be they alive or dead.", + )) + + return data + /datum/fish_source/hot_spring catalog_description = "Hot Springs" radial_state = "onsen" diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm index b399dcb94e1..767c1ceeaeb 100644 --- a/code/modules/unit_tests/fish_unit_tests.dm +++ b/code/modules/unit_tests/fish_unit_tests.dm @@ -101,6 +101,7 @@ stable_population = INFINITY breeding_timeout = 0 fish_flags = parent_type::fish_flags & ~(FISH_FLAG_SHOW_IN_CATALOG|FISH_FLAG_EXPERIMENT_SCANNABLE) + fish_id_redirect_path = /obj/item/fish/goldfish //Stops SSfishing from complaining var/expected_num_fillets = 0 //used to know how many fillets should be gotten out of this fish /obj/item/fish/testdummy/add_fillet_type() @@ -368,6 +369,7 @@ /obj/item/fish/chasm_crab/instant_growth fish_traits = list() //We don't want to end up applying traits twice on the resulting lobstrosity + fish_id_redirect_path = /obj/item/fish/chasm_crab /datum/unit_test/fish_sources @@ -419,10 +421,10 @@ /datum/fish_source/unit_test_profound_fisher fish_table = list(/obj/item/fish/testdummy = 1) fish_counts = list(/obj/item/fish/testdummy = 2) - fish_source_flags = parent_type::fish_source_flags | FISH_SOURCE_FLAG_SKIP_CATCHABLES + fish_source_flags = parent_type::fish_source_flags /datum/fish_source/unit_test_all_fish - fish_source_flags = parent_type::fish_source_flags | FISH_SOURCE_FLAG_SKIP_CATCHABLES + fish_source_flags = parent_type::fish_source_flags /datum/fish_source/unit_test_all_fish/New() for(var/fish_type as anything in subtypesof(/obj/item/fish)) diff --git a/strings/fishing_tips.txt b/strings/fishing_tips.txt index 836cab85da5..466540bb187 100644 --- a/strings/fishing_tips.txt +++ b/strings/fishing_tips.txt @@ -54,4 +54,5 @@ Fish can grow in size and weight if you fed them somewhat frequently. Giving the Feeding a fish mutagen can triple the probability of generating evolved offsprings, provided it has an evolution. You can print fishing rods of different materials from an autolathe, which can inrease or decrease fishing difficulty, casting range, experience gained and can have other, special effects. Albeit scarcely, it's possible to catch fish made of the same materials of a custom material fishing rod. Equipping a shiny fishing hook and the quality of the bait can improve your odds. -You can use a fishing rod to snatch random organs during the "manipulate organs" step of the "organ manipulation" surgery. \ No newline at end of file +You can use a fishing rod to snatch random organs during the "manipulate organs" step of the "organ manipulation" surgery. +Aquariums are also potential fishing spots. Only useful for catching fish you couldn't find in the wild, as a personal achievement and nothing more. \ No newline at end of file