You can release fish after catching it (#86126)

## About The Pull Request
This PR nerfs the mood event from fishing from 5 to 4. In exchange,
after catching a fish, you can release it in the appropriate fishing
spot for a minor positive mood event (if it's alive, or if the user has
either the morbid or naive traits). It also counts towards fish
population for fish that are limited in the amount of times they can be
catched. Mobs with the naive trait (clowns) get the positive mood event
even if the fish is dead or being released in a bad place like lava

Some fishing spots like toilets and moisture traps don't have this
option, but that's because they've their own interactions with fish
that'd otherwise be overridden by it.

This PR also fixes mobs with the morbid trait (coroners) not enjoying
aquarium in their own morbid ways and add a few touches pertaining the
naive trait like alternative chat messages when interacting with the
fish.

## Why It's Good For The Game
This gives players a way to get rid of unwanted fish without leaving it
to die on the floor, also it's in the spirit of recreational fishing.

## Changelog

🆑
fix: Fixed morbid mobs (coroners) not enjoying room beauty and aquariums
in their own weird ways.
add: You an now release fish after catching it for a positive moodlet
(or to repopulate certain fishing spot with rare fish).
/🆑

---------

Co-authored-by: necromanceranne <40847847+necromanceranne@users.noreply.github.com>
This commit is contained in:
Ghom
2024-09-15 15:57:41 +02:00
committed by GitHub
parent 00e2f6cd7b
commit adc47b42ee
17 changed files with 112 additions and 23 deletions

View File

@@ -33,6 +33,9 @@
///From /obj/item/fish/update_fish_force: (weight_rank, bonus_malus)
#define COMSIG_FISH_FORCE_UPDATED "fish_force_updated"
///From /obj/item/fish/interact_with_atom_secondary, sent to the target: (fish)
#define COMSIG_FISH_RELEASED_INTO "fish_released_into"
/// Rolling a reward path for a fishing challenge
#define COMSIG_FISHING_CHALLENGE_ROLL_REWARD "fishing_roll_reward"
/// Adjusting the difficulty of a rishing challenge, often based on the reward path

View File

@@ -231,6 +231,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_EXAMINE_FISHING_SPOT "examine_fishing_spot"
///lobstrosities and carps will prioritize/flee from those that have this trait (given by the skill-locked hat)
#define TRAIT_SCARY_FISHERMAN "scary_fisherman"
/// Atoms with this trait can be right-clicked with a fish to release them, presumably back in the fishing spot they were caught from.
#define TRAIT_CATCH_AND_RELEASE "catch_and_release"
///This trait lets you get the size and weight of the fish by examining them
#define TRAIT_EXAMINE_FISH "examine_fish"
///This trait lets you roughly know if the fish is dead, starving, drowning or sick by examining them

View File

@@ -10,6 +10,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_AI_PAUSED" = TRAIT_AI_PAUSED,
"TRAIT_BANNED_FROM_CARGO_SHUTTLE" = TRAIT_BANNED_FROM_CARGO_SHUTTLE,
"TRAIT_BEING_SHOCKED" = TRAIT_BEING_SHOCKED,
"TRAIT_CATCH_AND_RELEASE" = TRAIT_CATCH_AND_RELEASE,
"TRAIT_COMMISSIONED" = TRAIT_COMMISSIONED,
"TRAIT_CLIMBABLE" = TRAIT_CLIMBABLE,
"TRAIT_CURRENTLY_CLEANING" = TRAIT_CURRENTLY_CLEANING,

View File

@@ -4,8 +4,9 @@
GLOBAL_LIST_INIT(admin_visible_traits, list(
/atom = list(
"TRAIT_UNHITTABLE_BY_PROJECTILES" = TRAIT_UNHITTABLE_BY_PROJECTILES,
"TRAIT_CATCH_AND_RELEASE" = TRAIT_CATCH_AND_RELEASE,
"TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER,
"TRAIT_UNHITTABLE_BY_PROJECTILES" = TRAIT_UNHITTABLE_BY_PROJECTILES,
),
/atom/movable = list(
"TRAIT_ASHSTORM_IMMUNE" = TRAIT_ASHSTORM_IMMUNE,

View File

@@ -19,6 +19,7 @@
RegisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examined_more))
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))
ADD_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src))
/datum/component/fishing_spot/Destroy()
@@ -82,3 +83,7 @@
/datum/component/fishing_spot/proc/explosive_fishing(atom/location, severity)
SIGNAL_HANDLER
fish_source.spawn_reward_from_explosion(location, severity)
/datum/component/fishing_spot/proc/fish_released(datum/source, obj/item/fish/fish, mob/living/releaser)
SIGNAL_HANDLER
fish_source.readd_fish(fish, releaser)

View File

@@ -67,7 +67,7 @@
for(var/mob/living/living in location)
living.update_turf_movespeed()
/// Signals and components are carried over when the turf is changed, so they've to be readded post-change.
/// Signals are carried over when the turf is changed, but traits aren't, so they've to be readded post-change.
/datum/element/give_turf_traits/proc/pre_change_turf(turf/changed, path, list/new_baseturfs, flags, list/post_change_callbacks)
SIGNAL_HANDLER
post_change_callbacks += CALLBACK(src, PROC_REF(reoccupy_turf))

View File

@@ -20,9 +20,11 @@
RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined))
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))
/datum/element/lazy_fishing_spot/Detach(datum/target)
UnregisterSignal(target, list(
COMSIG_FISH_RELEASED_INTO,
COMSIG_PRE_FISHING,
COMSIG_NPC_FISHING,
COMSIG_ATOM_EXAMINE,
@@ -66,3 +68,8 @@
/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/fish_released(datum/source, obj/item/fish/fish, mob/living/releaser)
SIGNAL_HANDLER
var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration]
fish_source.readd_fish(fish, releaser)

View File

@@ -406,7 +406,7 @@
clear_mood_event(MOOD_CATEGORY_AREA_BEAUTY)
return
if(HAS_TRAIT(mob_parent, TRAIT_MORBID))
if(HAS_MIND_TRAIT(mob_parent, TRAIT_MORBID))
if(HAS_TRAIT(mob_parent, TRAIT_SNOB))
switch(area_to_beautify.beauty)
if(BEAUTY_LEVEL_DECENT to BEAUTY_LEVEL_GOOD)

View File

@@ -333,9 +333,23 @@
/datum/mood_event/fishing
description = "Fishing is relaxing."
mood_change = 5
mood_change = 4
timeout = 3 MINUTES
/datum/mood_event/fish_released
description = "Go, fish, swim and be free!"
mood_change = 1
timeout = 2 MINUTES
/datum/mood_event/fish_released/add_effects(morbid, obj/item/fish/fish)
if(!morbid)
description = "Go, [fish.name], swim and be free!"
return
if(fish.status == FISH_DEAD)
description = "Some scavenger will surely find a use for the remains of [fish.name]. How pragmatic."
else
description = "Returned to the burden of the deep. But is this truly a mercy, [fish.name]? There will always be bigger fish..."
/datum/mood_event/kobun
description = "You are all loved by the Universe. Im not alone, and you arent either."
mood_change = 14

View File

@@ -53,6 +53,7 @@
return NONE
balloon_alert(user, "dug hole")
AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[/datum/fish_source/ice_fishing])
ADD_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT)
add_overlay(mutable_appearance('icons/turf/overlays.dmi', "ice_hole"))
can_make_hole = FALSE
RemoveElement(/datum/element/contextual_screentip_tools, tool_screentips)

View File

@@ -48,6 +48,8 @@
. = ..()
if(fish_source_type)
AddElement(/datum/element/lazy_fishing_spot, fish_source_type)
// You can release chrabs and lavaloops and likes in lava, or be an absolute scumbag and drop other fish there too.
ADD_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT)
refresh_light()
if(!smoothing_flags)
update_appearance()

View File

@@ -30,6 +30,7 @@
AddElement(/datum/element/watery_tile)
if(!isnull(fishing_datum))
AddElement(/datum/element/lazy_fishing_spot, fishing_datum)
ADD_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT)
/turf/open/water/jungle

View File

@@ -295,7 +295,7 @@
else
dead_fish++
var/morb = HAS_TRAIT(user, TRAIT_MORBID)
var/morb = HAS_MIND_TRAIT(user, TRAIT_MORBID)
//Check if there are live fish - good mood
//All fish dead - bad mood.
//No fish - nothing.

View File

@@ -181,6 +181,34 @@
update_size_and_weight()
register_evolutions()
register_item_context()
/obj/item/fish/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(HAS_TRAIT(source, TRAIT_CATCH_AND_RELEASE))
context[SCREENTIP_CONTEXT_RMB] = "Release"
return CONTEXTUAL_SCREENTIP_SET
return NONE
/obj/item/fish/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
if(!HAS_TRAIT(interacting_with, TRAIT_CATCH_AND_RELEASE))
return NONE
if(HAS_TRAIT(src, TRAIT_NODROP))
balloon_alert(user, "it's stuck to your hand!")
return ITEM_INTERACT_BLOCKING
balloon_alert(user, "releasing fish...")
if(!do_after(src, 3 SECONDS, interacting_with))
return ITEM_INTERACT_BLOCKING
balloon_alert(user, "fish released")
var/goodbye_text = "Bye bye [name]."
if(status == FISH_DEAD && !HAS_MIND_TRAIT(user, TRAIT_NAIVE))
goodbye_text = "May it rest in peace..."
user.visible_message(span_notice("[user] releases [src] into [interacting_with]"), \
span_notice("You release [src] into [interacting_with]. [goodbye_text]"), \
span_notice("You hear a splash."))
playsound(interacting_with, 'sound/effects/splash.ogg', 50)
SEND_SIGNAL(interacting_with, COMSIG_FISH_RELEASED_INTO, src)
qdel(src)
return ITEM_INTERACT_SUCCESS
///Main proc that makes the fish edible.
/obj/item/fish/proc/make_edible()
@@ -361,29 +389,30 @@
if(!istype(item, /obj/item/fish_feed))
return ..()
if(!item.reagents.total_volume)
balloon_alert(user, "[item] is empty!")
balloon_alert(user, "[item.name] is empty!")
return TRUE
if(status == FISH_DEAD)
balloon_alert(user, "[src] is dead!")
balloon_alert(user, "[name] [HAS_MIND_TRAIT(user, TRAIT_NAIVE) ? "isn't hungry" : "is dead!"]")
return TRUE
feed(item.reagents)
balloon_alert(user, "fed [src]")
balloon_alert(user, "fed [name]")
return TRUE
/obj/item/fish/examine(mob/user)
. = ..()
if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_DEEPER_FISH))
if(status == FISH_DEAD)
. += span_deadsay("it's dead.")
var/list/warnings = list()
if(is_hungry())
warnings += "starving"
if(!HAS_TRAIT(src, TRAIT_FISH_STASIS) && !proper_environment())
warnings += "drowning"
if(health < initial(health) * 0.6)
warnings += "sick"
if(length(warnings))
. += span_warning("it's [english_list(warnings)]")
. += span_deadsay("It's [HAS_MIND_TRAIT(user, TRAIT_NAIVE) ? "taking the big snooze" : "dead"].")
else
var/list/warnings = list()
if(is_hungry())
warnings += "starving"
if(!HAS_TRAIT(src, TRAIT_FISH_STASIS) && !proper_environment())
warnings += "drowning"
if(health < initial(health) * 0.6)
warnings += "sick"
if(length(warnings))
. += span_warning("It's [english_list(warnings)].")
if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISH))
. += span_notice("It's [size] cm long.")
. += span_notice("It weighs [weight] g.")

View File

@@ -53,12 +53,14 @@
/obj/machinery/fishing_portal_generator/proc/activate(datum/fish_source/selected_source)
active = AddComponent(/datum/component/fishing_spot, selected_source)
ADD_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT)
use_power = ACTIVE_POWER_USE
update_icon()
/obj/machinery/fishing_portal_generator/proc/deactivate()
QDEL_NULL(active)
use_power = IDLE_POWER_USE
REMOVE_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT)
update_icon()
/obj/machinery/fishing_portal_generator/on_set_is_operational(old_value)

View File

@@ -220,7 +220,7 @@
return BEAM_CANCEL_DRAW
/obj/item/fishing_rod/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
//this prevent trying to use telekinesis to fish (which would be broken anyway)
//this prevent trying to use telekinesis to fish (which would be broken anyway), also whacking people with a rod.
if(!user.contains(src) || (user.combat_mode && !isturf(interacting_with)) ||HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
return ..()
return ranged_interact_with_atom(interacting_with, user, modifiers)

View File

@@ -275,13 +275,16 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons())
SEND_SIGNAL(src, COMSIG_FISH_SOURCE_REWARD_DISPENSED, reward)
return reward
/datum/fish_source/proc/regen_count(reward_path, regen_time)
/datum/fish_source/proc/regen_count(reward_path)
if(!LAZYACCESS(currently_on_regen, reward_path))
return
fish_counts[reward_path] += 1
currently_on_regen[reward_path] -= 1
if(!currently_on_regen[reward_path])
if(currently_on_regen[reward_path] <= 0)
LAZYREMOVE(currently_on_regen, reward_path)
else
addtimer(CALLBACK(src, PROC_REF(regen_count), reward_path), regen_time)
return
var/regen_time = fish_count_regen[reward_path]
addtimer(CALLBACK(src, PROC_REF(regen_count), reward_path), regen_time)
/// Spawns a reward from a atom path right where the fisherman is. Part of the dispense_reward() logic.
/datum/fish_source/proc/spawn_reward(reward_path, atom/spawn_location, turf/fishing_spot)
@@ -464,6 +467,24 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons())
if(severity >= EXPLODE_DEVASTATE)
reward.ex_act(EXPLODE_LIGHT)
///Called when releasing a fish in a fishing spot with the TRAIT_CATCH_AND_RELEASE trait.
/datum/fish_source/proc/readd_fish(obj/item/fish/fish, mob/living/releaser)
var/is_morbid = HAS_MIND_TRAIT(releaser, TRAIT_MORBID)
var/is_naive = HAS_MIND_TRAIT(releaser, TRAIT_NAIVE)
if(fish.status == FISH_DEAD) //ded fish won't repopulate the sea.
if(is_naive || is_morbid)
releaser.add_mood_event("fish_released", /datum/mood_event/fish_released, is_morbid && !is_naive, fish)
return
if(((fish.type in fish_table) != is_morbid) || is_naive)
releaser.add_mood_event("fish_released", /datum/mood_event/fish_released, is_morbid && !is_naive, fish)
if(isnull(fish_counts[fish.type])) //This fish can be caught indefinitely so it won't matter.
return
//If this fish population isn't recovering from recent losses, we just increase it.
if(!LAZYACCESS(currently_on_regen, fish.type))
fish_counts[fish.type] += 1
else
regen_count(fish.type)
/**
* Called by /datum/autowiki/fish_sources unless the catalog entry for this fish source is null.
* It should Return a list of entries with keys named "name", "icon", "weight" and "notes"