diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index 24d7cd0b170..925c7bc4e71 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -119,6 +119,9 @@ /// From base of area/Exited(): (area/left, direction) #define COMSIG_MOVABLE_EXITED_AREA "movable_exited_area" +///from base of /datum/component/splat/splat: (hit_atom) +#define COMSIG_MOVABLE_SPLAT "movable_splat" + ///from base of /atom/movable/point_at: (atom/A, obj/effect/temp_visual/point/point) #define COMSIG_MOVABLE_POINTED "movable_pointed" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 85e6a62d46c..1b2d82ca9c5 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -170,8 +170,8 @@ ///Called on user, from base of /datum/strippable_item/try_(un)equip() (atom/target, obj/item/equipping?) #define COMSIG_TRY_STRIP "try_strip" #define COMPONENT_CANT_STRIP (1<<0) -///From /datum/component/creamed/Initialize() -#define COMSIG_MOB_CREAMED "mob_creamed" +///From /datum/component/face_decal/splat/Initialize() +#define COMSIG_MOB_HIT_BY_SPLAT "hit_by_splat" ///From /obj/item/gun/proc/check_botched() #define COMSIG_MOB_CLUMSY_SHOOT_FOOT "mob_clumsy_shoot_foot" ///from /obj/item/hand_item/slapper/attack_atom(): (source=obj/structure/table/slammed_table, mob/living/slammer) diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm index d6bcc2ec796..04557b4a8e6 100644 --- a/code/__DEFINES/fish.dm +++ b/code/__DEFINES/fish.dm @@ -67,9 +67,11 @@ #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) +///The fish will fade in and out at intervals +#define FISHING_MINIGAME_RULE_CAMO (1 << 7) ///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) +#define FISHING_MINIGAME_ACTIVE_EFFECTS (FISHING_MINIGAME_RULE_ANTIGRAV|FISHING_MINIGAME_RULE_FLIP|FISHING_MINIGAME_RULE_CAMO) /// The default additive value for fishing hook catch weight modifiers. #define FISHING_DEFAULT_HOOK_BONUS_ADDITIVE 0 @@ -182,3 +184,15 @@ //Minigame defines /// The height of the minigame slider. Not in pixels, but minigame units. #define FISHING_MINIGAME_AREA 1000 + +///Defines for fish properties from the collect_fish_properties proc +#define FISH_PROPERTIES_FAV_BAIT "fav_bait" +#define FISH_PROPERTIES_BAD_BAIT "bad_bait" +#define FISH_PROPERTIES_TRAITS "fish_traits" + +///Define for favorite and disliked baits that aren't just item typepaths. +#define FISH_BAIT_TYPE "Type" +#define FISH_BAIT_FOODTYPE "Foodtype" +#define FISH_BAIT_REAGENT "Reagent" +#define FISH_BAIT_VALUE "Value" +#define FISH_BAIT_AMOUNT "Amount" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 11b1e0d2b31..3b1193247de 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -748,6 +748,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NEEDS_TWO_HANDS "needstwohands" /// Can't be catched when thrown #define TRAIT_UNCATCHABLE "uncatchable" +/// You won't catch duds while fishing with this rod. +#define TRAIT_ROD_REMOVE_FISHING_DUD "rod_remove_fishing_dud" /// Stuff that can go inside fish cases #define TRAIT_FISH_CASE_COMPATIBILE "fish_case_compatibile" /// If the item can be used as a bit. @@ -761,7 +763,14 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Baits with this trait will ignore bait preferences and related fish traits. #define TRAIT_OMNI_BAIT "omni_bait" /// The bait won't be consumed when used -#define TRAIT_BAIT_UNCONSUMABLE "bait_unconsumabe" +#define TRAIT_BAIT_UNCONSUMABLE "bait_unconsumable" +/// This bait ignores environmental conditions for fishing (like low light for nocturnal fish) +#define TRAIT_BAIT_IGNORE_ENVIRONMENT "bait_ignore_environment" +/** + * This bait won't apply TRAIT_ROD_REMOVE_FISHING_DUD to the rod it's attached on, + * instead, it'll allow the fishing dud to be there unless there's at least one fish that likes the bait + */ +#define TRAIT_BAIT_ALLOW_FISHING_DUD "bait_dont_affect_fishing_dud" /// Plants that were mutated as a result of passive instability, not a mutation threshold. #define TRAIT_PLANT_WILDMUTATE "wildmutation" /// If you hit an APC with exposed internals with this item it will try to shock you @@ -959,6 +968,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_FISH_RECESSIVE "fish_recessive" ///This fish comes equipped with a stinger (increased damage and potentially venomous if also toxic) #define TRAIT_FISH_STINGER "fish_stinger" +///This fish is currently on cooldown and cannot splash ink unto people's faces +#define TRAIT_FISH_INK_ON_COOLDOWN "fish_ink_on_cooldown" +///This fish requires two hands to carry even if smaller than FISH_SIZE_TWO_HANDS_REQUIRED, as long as it's bulky-sized. +#define TRAIT_FISH_SHOULD_TWOHANDED "should_twohanded" /// Trait given to angelic constructs to let them purge cult runes #define TRAIT_ANGELIC "angelic" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 8a3697eaa4b..c1bc27c25cf 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -544,6 +544,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /obj/item = list( "TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING, + "TRAIT_BAIT_ALLOW_FISHING_DUD" = TRAIT_BAIT_ALLOW_FISHING_DUD, + "TRAIT_BAIT_IGNORE_ENVIRONMENT" = TRAIT_BAIT_IGNORE_ENVIRONMENT, "TRAIT_BAIT_UNCONSUMABLE" = TRAIT_BAIT_UNCONSUMABLE, "TRAIT_BAKEABLE" = TRAIT_BAKEABLE, "TRAIT_BASIC_QUALITY_BAIT" = TRAIT_BASIC_QUALITY_BAIT, @@ -604,16 +606,21 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_FISH_FED_LUBE" = TRAIT_FISH_FED_LUBE, "TRAIT_FISH_FLOPPING" = TRAIT_FISH_FLOPPING, "TRAIT_FISH_FROM_CASE" = TRAIT_FISH_FROM_CASE, + "TRAIT_FISH_INK_ON_COOLDOWN" = TRAIT_FISH_INK_ON_COOLDOWN, "TRAIT_FISH_NO_HUNGER" = TRAIT_FISH_NO_HUNGER, "TRAIT_FISH_NO_MATING" = TRAIT_FISH_NO_MATING, "TRAIT_FISH_RECESSIVE" = TRAIT_FISH_RECESSIVE, "TRAIT_FISH_SELF_REPRODUCE" = TRAIT_FISH_SELF_REPRODUCE, + "TRAIT_FISH_SHOULD_TWOHANDED" = TRAIT_FISH_SHOULD_TWOHANDED, "TRAIT_FISH_STASIS" = TRAIT_FISH_STASIS, "TRAIT_FISH_STINGER" = TRAIT_FISH_STINGER, "TRAIT_FISH_TOXIN_IMMUNE" = TRAIT_FISH_TOXIN_IMMUNE, "TRAIT_RESIST_EMULSIFY" = TRAIT_RESIST_EMULSIFY, "TRAIT_YUCKY_FISH" = TRAIT_YUCKY_FISH, ), + /obj/item/fishing_rod = list( + "TRAIT_ROD_REMOVE_FISHING_DUD" = TRAIT_ROD_REMOVE_FISHING_DUD, + ), /obj/item/integrated_circuit = list( "TRAIT_CIRCUIT_UI_OPEN" = TRAIT_CIRCUIT_UI_OPEN, "TRAIT_CIRCUIT_UNDUPABLE" = TRAIT_CIRCUIT_UNDUPABLE, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index afb87091098..926d2002c0d 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -339,16 +339,21 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_FISH_ELECTROGENESIS" = TRAIT_FISH_ELECTROGENESIS, "TRAIT_FISH_FED_LUBE" = TRAIT_FISH_FED_LUBE, "TRAIT_FISH_FROM_CASE" = TRAIT_FISH_FROM_CASE, + "TRAIT_FISH_INK_ON_COOLDOWN" = TRAIT_FISH_INK_ON_COOLDOWN, "TRAIT_FISH_NO_HUNGER" = TRAIT_FISH_NO_HUNGER, "TRAIT_FISH_NO_MATING" = TRAIT_FISH_NO_MATING, "TRAIT_FISH_RECESSIVE" = TRAIT_FISH_RECESSIVE, "TRAIT_FISH_SELF_REPRODUCE" = TRAIT_FISH_SELF_REPRODUCE, + "TRAIT_FISH_SHOULD_TWOHANDED" = TRAIT_FISH_SHOULD_TWOHANDED, "TRAIT_FISH_STASIS" = TRAIT_FISH_STASIS, "TRAIT_FISH_STINGER" = TRAIT_FISH_STINGER, "TRAIT_FISH_TOXIN_IMMUNE" = TRAIT_FISH_TOXIN_IMMUNE, "TRAIT_RESIST_EMULSIFY" = TRAIT_RESIST_EMULSIFY, "TRAIT_YUCKY_FISH" = TRAIT_YUCKY_FISH, ), + /obj/item/fishing_rod = list( + "TRAIT_ROD_REMOVE_FISHING_DUD" = TRAIT_ROD_REMOVE_FISHING_DUD, + ), /obj/item/organ/internal/liver = list( "TRAIT_BALLMER_SCIENTIST" = TRAIT_BALLMER_SCIENTIST, "TRAIT_COMEDY_METABOLISM" = TRAIT_COMEDY_METABOLISM, diff --git a/code/controllers/subsystem/processing/fishing.dm b/code/controllers/subsystem/processing/fishing.dm index ca54141de82..a81ff0dec67 100644 --- a/code/controllers/subsystem/processing/fishing.dm +++ b/code/controllers/subsystem/processing/fishing.dm @@ -1,4 +1,33 @@ /// subsystem for the fishing minigame processing. PROCESSING_SUBSYSTEM_DEF(fishing) name = "Fishing" + flags = SS_BACKGROUND|SS_POST_FIRE_TIMING wait = 0.05 SECONDS // If you raise it to 0.1 SECONDS, you better also modify [datum/fish_movement/move_fish()] + ///Cached fish properties so we don't have to initalize fish every time + var/list/fish_properties + ///A cache of fish that can be caught by each type of fishing lure + var/list/lure_catchables + +/datum/controller/subsystem/processing/fishing/Initialize() + ///init the properties + fish_properties = list() + for(var/fish_type in subtypesof(/obj/item/fish)) + var/obj/item/fish/fish = new fish_type(null, FALSE) + fish_properties[fish_type] = list() + fish_properties[fish_type][FISH_PROPERTIES_FAV_BAIT] = fish.favorite_bait.Copy() + fish_properties[fish_type][FISH_PROPERTIES_BAD_BAIT] = fish.disliked_bait.Copy() + fish_properties[fish_type][FISH_PROPERTIES_TRAITS] = fish.fish_traits.Copy() + qdel(fish) + + ///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 + qdel(lure) + + return SS_INIT_SUCCESS diff --git a/code/datums/ai/cursed/cursed_controller.dm b/code/datums/ai/cursed/cursed_controller.dm index 4d0f6c6f5fd..aa32496f357 100644 --- a/code/datums/ai/cursed/cursed_controller.dm +++ b/code/datums/ai/cursed/cursed_controller.dm @@ -27,9 +27,9 @@ return ..() //Run parent at end ///signal called by the pawn hitting something after a throw -/datum/ai_controller/cursed/proc/on_throw_hit(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum) +/datum/ai_controller/cursed/proc/on_throw_hit(datum/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) SIGNAL_HANDLER - if(!iscarbon(hit_atom)) + if(caught || !iscarbon(hit_atom)) return //equipcode has sleeps all over it. INVOKE_ASYNC(src, PROC_REF(try_equipping_to_target_slot), hit_atom) diff --git a/code/datums/components/boomerang.dm b/code/datums/components/boomerang.dm index 23dd63d1467..954e752da1e 100644 --- a/code/datums/components/boomerang.dm +++ b/code/datums/components/boomerang.dm @@ -60,12 +60,11 @@ * * hit_atom: The atom that has been hit by the boomerang component. * * init_throwing_datum: The thrownthing datum that originally impacted the object, that we use to build the new throwing datum for the rebound. */ -/datum/component/boomerang/proc/return_hit_throw(datum/source, atom/hit_atom, datum/thrownthing/init_throwing_datum) +/datum/component/boomerang/proc/return_hit_throw(datum/source, atom/hit_atom, datum/thrownthing/init_throwing_datum, caught) SIGNAL_HANDLER - if (!COOLDOWN_FINISHED(src, last_boomerang_throw)) + if (!COOLDOWN_FINISHED(src, last_boomerang_throw) || caught) return - var/obj/item/true_parent = parent - aerodynamic_swing(init_throwing_datum, true_parent) + aerodynamic_swing(init_throwing_datum, parent) /** * Proc that triggers when the thrown boomerang does not hit a target. diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm index 6cae053afd1..0d65d2840f3 100644 --- a/code/datums/components/chasm.dm +++ b/code/datums/components/chasm.dm @@ -14,7 +14,7 @@ /obj/effect/constructing_effect, /obj/effect/dummy/phased_mob, /obj/effect/ebeam, - /obj/effect/fishing_lure, + /obj/effect/fishing_float, /obj/effect/hotspot, /obj/effect/landmark, /obj/effect/light_emitter/tendril, diff --git a/code/datums/components/explodable.dm b/code/datums/components/explodable.dm index 439b1563521..9dc8db3bbc4 100644 --- a/code/datums/components/explodable.dm +++ b/code/datums/components/explodable.dm @@ -60,10 +60,11 @@ return check_if_detonate(I) -/datum/component/explodable/proc/explodable_impact(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum) +/datum/component/explodable/proc/explodable_impact(datum/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) SIGNAL_HANDLER - check_if_detonate(hit_atom) + if(!caught) + check_if_detonate(hit_atom) /datum/component/explodable/proc/explodable_bump(datum/source, atom/A) SIGNAL_HANDLER diff --git a/code/datums/components/face_decal.dm b/code/datums/components/face_decal.dm index f045a4f18df..df70f8a3f49 100644 --- a/code/datums/components/face_decal.dm +++ b/code/datums/components/face_decal.dm @@ -53,6 +53,8 @@ carbon_parent.update_body_parts() else normal_overlay = get_normal_overlay() + normal_overlay.color = color + RegisterSignals(parent, list( COMSIG_COMPONENT_CLEAN_ACT, @@ -113,37 +115,39 @@ SIGNAL_HANDLER qdel(src) -/// Creampie subtype, handling signals and mood logic +/// splat subtype, handling signals and mood logic -GLOBAL_LIST_INIT(creamable, typecacheof(list( - /mob/living/carbon/human, - /mob/living/basic/pet/dog/corgi, - /mob/living/silicon/ai, +GLOBAL_LIST_INIT(splattable, zebra_typecacheof(list( + /mob/living/carbon/human = "human", + /mob/living/basic/pet/dog/corgi = "corgi", + /mob/living/silicon/ai = "ai", ))) -/datum/component/face_decal/creampie/Initialize() - . = ..() - if(!is_type_in_typecache(parent, GLOB.creamable)) +/datum/component/face_decal/splat + ///The mood_event that we add + var/mood_event_type + +/datum/component/face_decal/splat/Initialize(icon_state, layers, color, memory_type = /datum/memory/witnessed_creampie, mood_event_type = /datum/mood_event/creampie) + if(!is_type_in_typecache(parent, GLOB.splattable)) return COMPONENT_INCOMPATIBLE - SEND_SIGNAL(parent, COMSIG_MOB_CREAMED, src) - add_memory_in_range(parent, 7, /datum/memory/witnessed_creampie, protagonist = parent) + . = ..() -/datum/component/face_decal/creampie/get_normal_overlay() - if(iscorgi(parent)) - return mutable_appearance('icons/mob/effects/creampie.dmi', "[icon_state]_corgi") + SEND_SIGNAL(parent, COMSIG_MOB_HIT_BY_SPLAT, src) + add_memory_in_range(parent, 7, memory_type, protagonist = parent) + src.mood_event_type = mood_event_type - if(isAI(parent)) - return mutable_appearance('icons/mob/effects/creampie.dmi', "[icon_state]_ai") +/datum/component/face_decal/splat/get_normal_overlay() + return mutable_appearance('icons/mob/effects/face_decal.dmi', "[icon_state]_[GLOB.splattable[type]]") -/datum/component/face_decal/creampie/RegisterWithParent() +/datum/component/face_decal/splat/RegisterWithParent() . = ..() if(iscarbon(parent)) var/mob/living/carbon/human/carbon_parent = parent - carbon_parent.add_mood_event("creampie", /datum/mood_event/creampie) + carbon_parent.add_mood_event("splat", mood_event_type) -/datum/component/face_decal/creampie/UnregisterFromParent() +/datum/component/face_decal/splat/UnregisterFromParent() . = ..() if(iscarbon(parent)) var/mob/living/carbon/carbon_parent = parent - carbon_parent.clear_mood_event("creampie") + carbon_parent.clear_mood_event("splat") diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index cc21f22eb4b..b825354d7be 100644 --- a/code/datums/components/fishing_spot.dm +++ b/code/datums/components/fishing_spot.dm @@ -44,15 +44,7 @@ if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) return - var/has_known_fishes = FALSE - for(var/reward in fish_source.fish_table) - if(!ispath(reward, /obj/item/fish)) - continue - var/obj/item/fish/prototype = reward - if(initial(prototype.show_in_catalog)) - has_known_fishes = TRUE - break - if(!has_known_fishes) + if(!fish_source.has_known_fishes()) return examine_text += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") @@ -62,18 +54,7 @@ if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) return - var/list/known_fishes = list() - for(var/reward in fish_source.fish_table) - if(!ispath(reward, /obj/item/fish)) - continue - var/obj/item/fish/prototype = reward - if(initial(prototype.show_in_catalog)) - known_fishes += initial(prototype.name) - - if(!length(known_fishes)) - return - - examine_text += span_info("You can catch the following fish here: [english_list(known_fishes)].") + fish_source.get_catchable_fish_names(user, parent, examine_text) /datum/component/fishing_spot/proc/try_start_fishing(obj/item/possibly_rod, mob/user) SIGNAL_HANDLER @@ -94,7 +75,7 @@ /datum/component/fishing_spot/proc/start_fishing_challenge(obj/item/fishing_rod/rod, mob/user) /// Roll what we caught based on modified table - var/result = fish_source.roll_reward(rod, user) + var/result = fish_source.roll_reward(rod, user, parent) var/datum/fishing_challenge/challenge = new(src, result, rod, user) fish_source.pre_challenge_started(rod, user, challenge) challenge.start(user) diff --git a/code/datums/components/profound_fisher.dm b/code/datums/components/profound_fisher.dm index 947e9d26f51..5bd5af12943 100644 --- a/code/datums/components/profound_fisher.dm +++ b/code/datums/components/profound_fisher.dm @@ -111,17 +111,17 @@ var/datum/fish_source/fish_spot = fish_spot_container[NPC_FISHING_SPOT] if(isnull(fish_spot)) return null - var/obj/effect/fishing_lure/lure = new(get_turf(target), target) - playsound(lure, 'sound/effects/splash.ogg', 100) + var/obj/effect/fishing_float/float = new(get_turf(target), target) + playsound(float, 'sound/effects/splash.ogg', 100) var/happiness_percentage = living_parent.ai_controller?.blackboard[BB_BASIC_HAPPINESS] / 100 var/fishing_speed = 10 SECONDS - round(4 SECONDS * happiness_percentage) if(!do_after(living_parent, fishing_speed, target = target) && !QDELETED(fish_spot)) - qdel(lure) + qdel(float) return var/reward_loot = fish_spot.roll_reward(our_rod, parent) fish_spot.dispense_reward(reward_loot, parent, target) - playsound(lure, 'sound/effects/bigsplash.ogg', 100) - qdel(lure) + playsound(float, 'sound/effects/bigsplash.ogg', 100) + qdel(float) /obj/item/fishing_rod/mob_fisher line = /obj/item/fishing_line/reinforced diff --git a/code/datums/components/sitcomlaughter.dm b/code/datums/components/sitcomlaughter.dm index 62e9276b1d7..02ed818ea30 100644 --- a/code/datums/components/sitcomlaughter.dm +++ b/code/datums/components/sitcomlaughter.dm @@ -1,6 +1,6 @@ /datum/component/wearertargeting/sitcomlaughter valid_slots = list(ITEM_SLOT_HANDS, ITEM_SLOT_BELT, ITEM_SLOT_ID, ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET, ITEM_SLOT_SUITSTORE, ITEM_SLOT_DEX_STORAGE) - signals = list(COMSIG_MOB_CREAMED, COMSIG_ON_CARBON_SLIP, COMSIG_POST_TILT_AND_CRUSH, COMSIG_MOB_CLUMSY_SHOOT_FOOT) + signals = list(COMSIG_MOB_HIT_BY_SPLAT, COMSIG_ON_CARBON_SLIP, COMSIG_POST_TILT_AND_CRUSH, COMSIG_MOB_CLUMSY_SHOOT_FOOT) proctype = PROC_REF(EngageInComedy) mobtype = /mob/living ///Sounds used for when user has a sitcom action occur diff --git a/code/datums/components/splat.dm b/code/datums/components/splat.dm new file mode 100644 index 00000000000..5d47d17b98c --- /dev/null +++ b/code/datums/components/splat.dm @@ -0,0 +1,74 @@ +/datum/component/splat + ///The icon state to use for the decal + var/icon_state + ///The bodypart layer to use for the decal + var/layer + ///The type of memory to celebrate the event of getting hit by this + var/memory_type + ///The type of smudge we create on the floor + var/smudge_type + ///The moodlet passed down to the creamed component + var/moodlet_type + ///The color we give to the creamed component/overlay + var/splat_color + ///The callback called when a mob is hit by this + var/datum/callback/hit_callback + +/datum/component/splat/Initialize( + icon_state = "creampie", + layer = EXTERNAL_FRONT, + memory_type = /datum/memory/witnessed_creampie, + smudge_type = /obj/effect/decal/cleanable/food/pie_smudge, + moodlet_type = /datum/mood_event/creampie, + splat_color, + datum/callback/hit_callback, +) + . = ..() + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + src.icon_state = icon_state + src.layer = layer + src.memory_type = memory_type + src.smudge_type = smudge_type + src.moodlet_type = moodlet_type + src.hit_callback = hit_callback + src.splat_color = splat_color + +/datum/component/splat/Destroy() + hit_callback = null + return ..() + +/datum/component/splat/RegisterWithParent() + if(isprojectile(parent)) + RegisterSignal(parent, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(projectile_splat)) + else + RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, PROC_REF(throw_splat)) + +/datum/component/splat/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_MOVABLE_IMPACT, COMSIG_PROJECTILE_SELF_ON_HIT)) + +/datum/component/splat/proc/projectile_splat(obj/projectile/source, atom/firer, atom/target, angle, hit_limb_zone, blocked) + SIGNAL_HANDLER + if(blocked != 100) + splat(source, target) + +/datum/component/splat/proc/throw_splat(atom/movable/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) + SIGNAL_HANDLER + if(caught) //someone caught us! + return + splat(source, hit_atom) + +/datum/component/splat/proc/splat(atom/movable/source, atom/hit_atom) + var/turf/hit_turf = get_turf(hit_atom) + new smudge_type(hit_turf) + var/can_splat_on = TRUE + if(isliving(hit_atom)) + var/mob/living/living_target_getting_hit = hit_atom + if(iscarbon(living_target_getting_hit)) + can_splat_on = !!(living_target_getting_hit.get_bodypart(BODY_ZONE_HEAD)) + hit_callback?.Invoke(living_target_getting_hit, can_splat_on) + if(can_splat_on && is_type_in_typecache(hit_atom, GLOB.splattable)) + hit_atom.AddComponent(/datum/component/face_decal/splat, icon_state, layer, splat_color || source.color, memory_type, moodlet_type) + SEND_SIGNAL(source, COMSIG_MOVABLE_SPLAT, hit_atom) + qdel(source) diff --git a/code/datums/elements/can_shatter.dm b/code/datums/elements/can_shatter.dm index be7e02e25b4..df19e4ef123 100644 --- a/code/datums/elements/can_shatter.dm +++ b/code/datums/elements/can_shatter.dm @@ -45,9 +45,10 @@ shatter(source, impacted_turf) /// Tells the parent to shatter if we are thrown and impact something -/datum/element/can_shatter/proc/on_throw_impact(datum/source, atom/hit_atom) +/datum/element/can_shatter/proc/on_throw_impact(datum/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) SIGNAL_HANDLER - + if(caught) + return shatter(source, hit_atom) /// Handles the actual shattering part, throwing shards of whatever is defined on the component everywhere diff --git a/code/datums/elements/firestacker.dm b/code/datums/elements/firestacker.dm index b7bad65cc6c..a512e5e89c7 100644 --- a/code/datums/elements/firestacker.dm +++ b/code/datums/elements/firestacker.dm @@ -27,10 +27,10 @@ /datum/element/firestacker/proc/stack_on(datum/owner, mob/living/target) target.adjust_fire_stacks(amount) -/datum/element/firestacker/proc/impact(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum) +/datum/element/firestacker/proc/impact(datum/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) SIGNAL_HANDLER - if(isliving(hit_atom)) + if(!caught && isliving(hit_atom)) stack_on(source, hit_atom) /datum/element/firestacker/proc/item_attack(datum/source, atom/movable/target, mob/living/user) diff --git a/code/datums/elements/food/food_trash.dm b/code/datums/elements/food/food_trash.dm index 6df36c82c4c..244de8d7e84 100644 --- a/code/datums/elements/food/food_trash.dm +++ b/code/datums/elements/food/food_trash.dm @@ -22,11 +22,14 @@ RegisterSignal(target, COMSIG_ITEM_ATTACK_SELF, PROC_REF(open_trash)) if(flags & FOOD_TRASH_POPABLE) RegisterSignal(target, COMSIG_FOOD_CROSSED, PROC_REF(food_crossed)) - RegisterSignal(target, COMSIG_ITEM_ON_GRIND, PROC_REF(generate_trash)) - RegisterSignal(target, COMSIG_ITEM_ON_JUICE, PROC_REF(generate_trash)) - RegisterSignal(target, COMSIG_ITEM_USED_AS_INGREDIENT, PROC_REF(generate_trash)) - RegisterSignal(target, COMSIG_ITEM_ON_COMPOSTED, PROC_REF(generate_trash)) - RegisterSignal(target, COMSIG_ITEM_SOLD_TO_CUSTOMER, PROC_REF(generate_trash)) + RegisterSignals(target, list( + COMSIG_ITEM_ON_GRIND, + COMSIG_ITEM_ON_JUICE, + COMSIG_ITEM_USED_AS_INGREDIENT, + COMSIG_ITEM_ON_COMPOSTED, + COMSIG_ITEM_SOLD_TO_CUSTOMER, + COMSIG_MOVABLE_SPLAT, + ), PROC_REF(generate_trash)) /datum/element/food_trash/Detach(datum/target) . = ..() @@ -38,7 +41,9 @@ COMSIG_ITEM_ON_JUICE, COMSIG_ITEM_USED_AS_INGREDIENT, COMSIG_ITEM_ON_COMPOSTED, - COMSIG_ITEM_SOLD_TO_CUSTOMER,)) + COMSIG_ITEM_SOLD_TO_CUSTOMER, + COMSIG_MOVABLE_SPLAT, + )) /datum/element/food_trash/proc/generate_trash(datum/source, mob/living/eater, mob/living/feeder) SIGNAL_HANDLER diff --git a/code/datums/elements/frozen.dm b/code/datums/elements/frozen.dm index d112ef31b5f..df857cdd6ef 100644 --- a/code/datums/elements/frozen.dm +++ b/code/datums/elements/frozen.dm @@ -28,7 +28,7 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 organ.organ_flags |= ORGAN_FROZEN RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) - RegisterSignal(target, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(shatter_on_throw)) + RegisterSignal(target, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(shatter_on_landed)) RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(shatter_on_throw)) RegisterSignal(target, COMSIG_OBJ_UNFREEZE, PROC_REF(on_unfreeze)) @@ -54,8 +54,13 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 SIGNAL_HANDLER Detach(source) -///signal handler for COMSIG_MOVABLE_POST_THROW that shatters our target after impacting after a throw -/datum/element/frozen/proc/shatter_on_throw(datum/target, datum/thrownthing/throwingdatum) +/datum/element/frozen/proc/shatter_on_throw(datum/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) + SIGNAL_HANDLER + if(!caught) + shatter_on_landed(source, throwing_datum) + +///signal handler that shatters our target after impacting after a throw. +/datum/element/frozen/proc/shatter_on_landed(datum/target, datum/thrownthing/throwingdatum) SIGNAL_HANDLER var/obj/obj_target = target if(ismob(throwingdatum.thrower)) diff --git a/code/datums/elements/lazy_fishing_spot.dm b/code/datums/elements/lazy_fishing_spot.dm index 1ba296bfe73..b84826def1e 100644 --- a/code/datums/elements/lazy_fishing_spot.dm +++ b/code/datums/elements/lazy_fishing_spot.dm @@ -41,15 +41,7 @@ var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] - var/has_known_fishes = FALSE - for(var/reward in fish_source.fish_table) - if(!ispath(reward, /obj/item/fish)) - continue - var/obj/item/fish/prototype = reward - if(initial(prototype.show_in_catalog)) - has_known_fishes = TRUE - break - if(!has_known_fishes) + if(!fish_source.has_known_fishes()) return examine_text += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") @@ -60,19 +52,7 @@ return var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] - - var/list/known_fishes = list() - for(var/reward in fish_source.fish_table) - if(!ispath(reward, /obj/item/fish)) - continue - var/obj/item/fish/prototype = reward - if(initial(prototype.show_in_catalog)) - known_fishes += initial(prototype.name) - - if(!length(known_fishes)) - return - - examine_text += span_info("You can catch the following fish here: [english_list(known_fishes)].") + fish_source.get_catchable_fish_names(user, source, examine_text) /datum/element/lazy_fishing_spot/proc/explosive_fishing(atom/location, severity) SIGNAL_HANDLER diff --git a/code/datums/memory/general_memories.dm b/code/datums/memory/general_memories.dm index b8c44c6fca8..eca745d3283 100644 --- a/code/datums/memory/general_memories.dm +++ b/code/datums/memory/general_memories.dm @@ -162,6 +162,28 @@ "[protagonist_name] [mood_verb] as they lick off some of the pie", ) +/// Witnessed someone get splashed with squid ink. +/datum/memory/witnessed_inking + story_value = STORY_VALUE_OKAY + memory_flags = MEMORY_CHECK_BLINDNESS + // Protagonist - The mob that got pied + +/datum/memory/witnessed_inking/get_names() + return list("The inking of [protagonist_name].") + +/datum/memory/witnessed_inking/get_starts() + return list( + "[protagonist_name]'s face being covered in squid ink", + "[protagonist_name] getting squid-inked", + ) + +/datum/memory/witnessed_inking/get_moods() + return list( + "[protagonist_name] [mood_verb] as ink drips off their face", + "[protagonist_name] [mood_verb] because of their now expanded laundry task.", + "[protagonist_name] [mood_verb] as they wipe the ink off their face.", + ) + /// Got slipped by something. /datum/memory/was_slipped story_value = STORY_VALUE_MEH diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 6ad0580e555..82cd275aef2 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -33,6 +33,11 @@ mood_change = -2 timeout = 3 MINUTES +/datum/mood_event/inked + description = "I've been splashed with squid ink. Tastes like salt." + mood_change = -3 + timeout = 3 MINUTES + /datum/mood_event/slipped description = "I slipped. I should be more careful next time..." mood_change = -2 diff --git a/code/game/atom/atom_act.dm b/code/game/atom/atom_act.dm index c9951b92094..7b69f02340c 100644 --- a/code/game/atom/atom_act.dm +++ b/code/game/atom/atom_act.dm @@ -115,11 +115,15 @@ * Im not sure why this the case, maybe to prevent lots of hitby's if the thrown object is * deleted shortly after hitting something (during explosions or other massive events that * throw lots of items around - singularity being a notable example) + * + * Worth of note: If hitby returns TRUE, it means the object has been blocked or catched by src. + * So far, this is only possible for living mobs and carbons, who can hold shields and catch thrown items. */ /atom/proc/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) SEND_SIGNAL(src, COMSIG_ATOM_HITBY, hitting_atom, skipcatch, hitpush, blocked, throwingdatum) if(density && !has_gravity(hitting_atom)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...). addtimer(CALLBACK(src, PROC_REF(hitby_react), hitting_atom), 0.2 SECONDS) + return FALSE /** * We have have actually hit the passed in atom diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index e4aadc639e4..8f8d350c927 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1283,16 +1283,22 @@ /atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) set waitfor = FALSE var/hitpush = TRUE - var/impact_signal = SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_IMPACT, hit_atom, throwingdatum) - if(impact_signal & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH) - hitpush = FALSE // hacky, tie this to something else or a proper workaround later - - if(impact_signal && (impact_signal & COMPONENT_MOVABLE_IMPACT_NEVERMIND)) + var/impact_flags = pre_impact(hit_atom, throwingdatum) + if(impact_flags & COMPONENT_MOVABLE_IMPACT_NEVERMIND) return // in case a signal interceptor broke or deleted the thing before we could process our hit - if(SEND_SIGNAL(hit_atom, COMSIG_ATOM_PREHITBY, src, throwingdatum) & COMSIG_HIT_PREVENTED) - return - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush) + if(impact_flags & COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH) + hitpush = FALSE + var/caught = hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush) + SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum, caught) + return caught + +///Called before we attempt to call hitby and send the COMSIG_MOVABLE_IMPACT signal +/atom/movable/proc/pre_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + var/impact_flags = SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_IMPACT, hit_atom, throwingdatum) + var/target_flags = SEND_SIGNAL(hit_atom, COMSIG_ATOM_PREHITBY, src, throwingdatum) + if(target_flags & COMSIG_HIT_PREVENTED) + impact_flags |= COMPONENT_MOVABLE_IMPACT_NEVERMIND + return impact_flags /atom/movable/hitby(atom/movable/hitting_atom, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) if(HAS_TRAIT(src, TRAIT_NO_THROW_HITPUSH)) diff --git a/code/game/objects/effects/decals/cleanable/food.dm b/code/game/objects/effects/decals/cleanable/food.dm index 0fc4352c78d..23c266ecb59 100644 --- a/code/game/objects/effects/decals/cleanable/food.dm +++ b/code/game/objects/effects/decals/cleanable/food.dm @@ -58,3 +58,14 @@ name = "flour" desc = "It's still good. Four second rule!" icon_state = "flour" + +/obj/effect/decal/cleanable/food/squid_ink + name = "ink smear" + desc = "a smear from some inky substance..." + icon = 'icons/mob/silicon/robots.dmi' + icon_state = "floor1" + color = COLOR_DARK + +/obj/effect/decal/cleanable/food/squid_ink/Initialize(mapload, list/datum/disease/diseases) + icon_state = "floor[rand(1, 7)]" + return ..() diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 816ccaf8e6a..97c7937f674 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -831,36 +831,30 @@ . = ..() do_drop_animation(master_storage.parent) -/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(QDELETED(hit_atom)) - return - if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_IMPACT, hit_atom, throwingdatum) & COMPONENT_MOVABLE_IMPACT_NEVERMIND) - return - if(SEND_SIGNAL(hit_atom, COMSIG_ATOM_PREHITBY, src, throwingdatum) & COMSIG_HIT_PREVENTED) - return - - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - if(get_temperature() && isliving(hit_atom)) - var/mob/living/L = hit_atom - L.ignite_mob() - var/itempush = 1 +/obj/item/pre_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + var/impact_flags = ..() if(w_class < WEIGHT_CLASS_BULKY) - itempush = 0 //too light to push anything - if(isliving(hit_atom)) //Living mobs handle hit sounds differently. - var/volume = get_volume_by_throwforce_and_or_w_class() - if (throwforce > 0 || HAS_TRAIT(src, TRAIT_CUSTOM_TAP_SOUND)) - if (mob_throw_hit_sound) - playsound(hit_atom, mob_throw_hit_sound, volume, TRUE, -1) - else if(hitsound) - playsound(hit_atom, hitsound, volume, TRUE, -1) - else - playsound(hit_atom, 'sound/weapons/genhit.ogg',volume, TRUE, -1) - else - playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1) + impact_flags |= COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH + if(!(impact_flags & COMPONENT_MOVABLE_IMPACT_NEVERMIND) && get_temperature() && isliving(hit_atom)) + var/mob/living/victim = hit_atom + victim.ignite_mob() + return impact_flags - else +/obj/item/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(!isliving(hit_atom)) //Living mobs handle hit sounds differently. playsound(src, drop_sound, YEET_SOUND_VOLUME, ignore_walls = FALSE) - return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum) + return + var/volume = get_volume_by_throwforce_and_or_w_class() + if (throwforce > 0 || HAS_TRAIT(src, TRAIT_CUSTOM_TAP_SOUND)) + if (mob_throw_hit_sound) + playsound(hit_atom, mob_throw_hit_sound, volume, TRUE, -1) + else if(hitsound) + playsound(hit_atom, hitsound, volume, TRUE, -1) + else + playsound(hit_atom, 'sound/weapons/genhit.ogg',volume, TRUE, -1) + else + playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1) /obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) if(HAS_TRAIT(src, TRAIT_NODROP)) @@ -1765,3 +1759,37 @@ RETURN_TYPE(/obj/item) 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) + if(HAS_TRAIT(src, TRAIT_OMNI_BAIT)) + return 1 + var/catch_multiplier = 1 + var/list/properties = SSfishing.fish_properties[fish_type] + //Bait matching likes doubles the chance + var/list/fav_bait = properties[FISH_PROPERTIES_FAV_BAIT] + for(var/bait_identifer in fav_bait) + if(is_matching_bait(src, bait_identifer)) + catch_multiplier *= 2 + //Bait matching dislikes + var/list/disliked_bait = properties[FISH_PROPERTIES_BAD_BAIT] + for(var/bait_identifer in disliked_bait) + if(is_matching_bait(src, bait_identifer)) + catch_multiplier *= 0.5 + return catch_multiplier + +/// Helper proc that checks if a bait matches identifier from fav/disliked bait list +/proc/is_matching_bait(obj/item/bait, identifier) + if(ispath(identifier)) //Just a path + return istype(bait, identifier) + if(!islist(identifier)) + return HAS_TRAIT(bait, identifier) + var/list/special_identifier = identifier + switch(special_identifier[FISH_BAIT_TYPE]) + if(FISH_BAIT_FOODTYPE) + var/obj/item/food/food_bait = bait + return istype(food_bait) && food_bait.foodtypes & special_identifier[FISH_BAIT_VALUE] + if(FISH_BAIT_REAGENT) + return bait.reagents?.has_reagent(special_identifier[FISH_BAIT_VALUE], special_identifier[FISH_BAIT_AMOUNT], check_subtypes = TRUE) + else + CRASH("Unknown bait identifier in fish favourite/disliked list") diff --git a/code/game/objects/items/food/misc.dm b/code/game/objects/items/food/misc.dm index 53781e8a1ea..b2cf4d132c5 100644 --- a/code/game/objects/items/food/misc.dm +++ b/code/game/objects/items/food/misc.dm @@ -805,3 +805,30 @@ foodtypes = VEGETABLES | GRAIN w_class = WEIGHT_CLASS_TINY crafting_complexity = FOOD_COMPLEXITY_4 + +///Extracted from squids, or any fish with the ink fish trait. +/obj/item/food/ink_sac + name = "ink sac" + desc = "the ink sac from some sort of fish or mollusk. It could be canned with a processor." + icon_state = "ink_sac" + food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/salt = 5) + tastes = list("seafood" = 3) + foodtypes = SEAFOOD|RAW + +/obj/item/food/ink_sac/Initialize(mapload) + . = ..() + AddComponent(/datum/component/splat, \ + memory_type = /datum/memory/witnessed_inking, \ + smudge_type = /obj/effect/decal/cleanable/food/squid_ink, \ + moodlet_type = /datum/mood_event/inked, \ + splat_color = COLOR_NEARLY_ALL_BLACK, \ + hit_callback = CALLBACK(src, PROC_REF(blind_em)), \ + ) + +/obj/item/food/ink_sac/proc/blind_em(mob/living/victim, can_splat_on) + if(can_splat_on) + victim.adjust_temp_blindness_up_to(7 SECONDS, 10 SECONDS) + victim.adjust_confusion_up_to(3.5 SECONDS, 6 SECONDS) + victim.Paralyze(2 SECONDS) //splat! + victim.visible_message(span_warning("[victim] is inked by [src]!"), span_userdanger("You've been inked by [src]!")) + playsound(victim, SFX_DESECRATION, 50, TRUE) diff --git a/code/game/objects/items/food/packaged.dm b/code/game/objects/items/food/packaged.dm index 0f08fd8f57d..b3578204a2d 100644 --- a/code/game/objects/items/food/packaged.dm +++ b/code/game/objects/items/food/packaged.dm @@ -156,6 +156,25 @@ tastes = list("seafood" = 7, "tin" = 1) foodtypes = SEAFOOD +/obj/item/food/canned/squid_ink/open_can(mob/user) + . = ..() + AddComponent(/datum/component/splat, \ + memory_type = /datum/memory/witnessed_inking, \ + smudge_type = /obj/effect/decal/cleanable/food/squid_ink, \ + moodlet_type = /datum/mood_event/inked, \ + splat_color = COLOR_NEARLY_ALL_BLACK, \ + hit_callback = CALLBACK(src, PROC_REF(blind_em)), \ + ) + ADD_TRAIT(src, TRAIT_UNCATCHABLE, INNATE_TRAIT) //good luck catching the liquid + +/obj/item/food/canned/squid_ink/proc/blind_em(mob/living/victim, can_splat_on) + if(can_splat_on) + victim.adjust_temp_blindness_up_to(7 SECONDS, 10 SECONDS) + victim.adjust_confusion_up_to(3.5 SECONDS, 6 SECONDS) + victim.Paralyze(2 SECONDS) //splat! + victim.visible_message(span_warning("[victim] is inked by [src]!"), span_userdanger("You've been inked by [src]!")) + playsound(victim, SFX_DESECRATION, 50, TRUE) + /obj/item/food/canned/chap name = "can of CHAP" desc = "CHAP: Chopped Ham And Pork. The classic American canned meat product that won a world war, then sent millions of servicemen home with heart congestion." diff --git a/code/game/objects/items/food/pie.dm b/code/game/objects/items/food/pie.dm index 0cb5af860e8..6d73bc2f5df 100644 --- a/code/game/objects/items/food/pie.dm +++ b/code/game/objects/items/food/pie.dm @@ -74,32 +74,17 @@ var/stunning = TRUE crafting_complexity = FOOD_COMPLEXITY_3 -/obj/item/food/pie/cream/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) +/obj/item/food/pie/cream/Initialize(mapload) . = ..() - if(!.) //if we're not being caught - splat(hit_atom) + AddComponent(/datum/component/splat, hit_callback = CALLBACK(src, PROC_REF(stun_and_blur))) -/obj/item/food/pie/cream/proc/splat(atom/movable/hit_atom) - if(isliving(loc)) //someone caught us! - return - var/turf/hit_turf = get_turf(hit_atom) - new/obj/effect/decal/cleanable/food/pie_smudge(hit_turf) - if(reagents?.total_volume) - reagents.expose(hit_atom, TOUCH) - var/is_creamable = TRUE - if(isliving(hit_atom)) - var/mob/living/living_target_getting_hit = hit_atom - if(stunning) - living_target_getting_hit.Paralyze(2 SECONDS) //splat! - if(iscarbon(living_target_getting_hit)) - is_creamable = !!(living_target_getting_hit.get_bodypart(BODY_ZONE_HEAD)) - if(is_creamable) - living_target_getting_hit.adjust_eye_blur(2 SECONDS) - living_target_getting_hit.visible_message(span_warning("[living_target_getting_hit] is creamed by [src]!"), span_userdanger("You've been creamed by [src]!")) - playsound(living_target_getting_hit, SFX_DESECRATION, 50, TRUE) - if(is_creamable && is_type_in_typecache(hit_atom, GLOB.creamable)) - hit_atom.AddComponent(/datum/component/face_decal/creampie, "creampie", EXTERNAL_FRONT) - qdel(src) +/obj/item/food/pie/cream/proc/stun_and_blur(mob/living/victim, can_splat_on) + if(stunning) + victim.Paralyze(2 SECONDS) //splat! + if(can_splat_on) + victim.adjust_eye_blur(2 SECONDS) + victim.visible_message(span_warning("[victim] is creamed by [src]!"), span_userdanger("You've been creamed by [src]!")) + playsound(victim, SFX_DESECRATION, 50, TRUE) /obj/item/food/pie/cream/nostun stunning = FALSE diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm index aa42b5cacde..f7ce106d580 100644 --- a/code/modules/cargo/goodies.dm +++ b/code/modules/cargo/goodies.dm @@ -237,6 +237,12 @@ cost = PAYCHECK_CREW contains = list(/obj/item/storage/box/fishing_lines) +/datum/supply_pack/goody/fishing_lure_set + name = "Fishing Lures Set" + desc = "A set of bite-resistant fishing lures to fish all (most) sort of fish. Beat randomness to a curb today!" + cost = PAYCHECK_CREW * 8 + contains = list(/obj/item/storage/box/fishing_lures) + /datum/supply_pack/goody/fishing_hook_rescue name = "Rescue Fishing Hook Single-Pack" desc = "For when your fellow miner has inevitably fallen into a chasm, and it's up to you to save them." @@ -252,7 +258,7 @@ /datum/supply_pack/goody/fish_feed name = "Can of Fish Food Single-Pack" desc = "For keeping your little friends fed and alive." - cost = PAYCHECK_CREW * 1 + cost = PAYCHECK_CREW contains = list(/obj/item/fish_feed) /datum/supply_pack/goody/naturalbait diff --git a/code/modules/fishing/admin.dm b/code/modules/fishing/admin.dm index ba5c29a7fd1..f4643201a77 100644 --- a/code/modules/fishing/admin.dm +++ b/code/modules/fishing/admin.dm @@ -51,7 +51,7 @@ ADMIN_VERB(fishing_calculator, R_DEBUG, "Fishing Calculator", "A calculator... f temporary_rod.set_slot(new line_type(temporary_rod), ROD_SLOT_LINE) var/result_table = list() - var/modified_table = spot.get_modified_fish_table(temporary_rod,user) + var/modified_table = spot.get_modified_fish_table(temporary_rod, user, null) for(var/result_type in spot.fish_table) // through this not modified to display 0 chance ones too var/list/info = list() info["result"] = result_type diff --git a/code/modules/fishing/bait.dm b/code/modules/fishing/bait.dm index 8eb8911a864..8fb66ad4c3d 100644 --- a/code/modules/fishing/bait.dm +++ b/code/modules/fishing/bait.dm @@ -61,21 +61,239 @@ bait_type = /obj/item/food/bait/doughball/synthetic/super uses_left = 12 +/obj/item/fishing_lure + name = "artificial minnow" + desc = "A fishing lure meant to attract smaller omnivore fish." + icon = 'icons/obj/fishing.dmi' + icon_state = "minnow" + w_class = WEIGHT_CLASS_SMALL + /** + * A list with two keys delimiting the spinning interval in which a mouse click has to be pressed while fishing. + * This is passed down to the fishing rod, and then to the lure during the minigame. + */ + var/spin_frequency = list(2 SECONDS, 3 SECONDS) -/// Helper proc that checks if a bait matches identifier from fav/disliked bait list -/proc/is_matching_bait(obj/item/bait, identifier) - if(ispath(identifier)) //Just a path - return istype(bait, identifier) - if(islist(identifier)) - var/list/special_identifier = identifier - switch(special_identifier["Type"]) - if("Foodtype") - var/obj/item/food/food_bait = bait - return istype(food_bait) && food_bait.foodtypes & special_identifier["Value"] - if("Reagent") - return bait.reagents?.has_reagent(special_identifier["Value"], special_identifier["Amount"], check_subtypes = TRUE) - else - CRASH("Unknown bait identifier in fish favourite/disliked list") - else - return HAS_TRAIT(bait, identifier) +/obj/item/fishing_lure/Initialize(mapload) + . = ..() + add_traits(list(TRAIT_FISHING_BAIT, TRAIT_BAIT_ALLOW_FISHING_DUD, TRAIT_OMNI_BAIT, TRAIT_BAIT_UNCONSUMABLE), INNATE_TRAIT) + RegisterSignal(src, COMSIG_FISHING_EQUIPMENT_SLOTTED, PROC_REF(lure_equipped)) +/obj/item/fishing_lure/proc/lure_equipped(datum/source, obj/item/fishing_rod/rod) + SIGNAL_HANDLER + rod.spin_frequency = spin_frequency + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_removed)) + +/obj/item/fishing_lure/proc/on_removed(atom/movable/source, obj/item/fishing_rod/rod, dir, forced) + SIGNAL_HANDLER + UnregisterSignal(src, COMSIG_MOVABLE_MOVED) + 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) + 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)) + 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)) + return FALSE + return TRUE + +/obj/item/fishing_lure/examine(mob/user) + . = ..() + . += span_info("It has to be spun with a frequency of [spin_frequency[1] * 0.1] to [spin_frequency[2] * 0.1] seconds while fishing.") + if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) + . += span_tinynotice("Thanks to your experience, you can examine it again to get a list of fish you can catch with it.") + +/obj/item/fishing_lure/examine_more(mob/user) + . = ..() + if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) + return + + var/list/known_fishes = list() + for(var/obj/item/fish/fish_type as anything in SSfishing.lure_catchables[type]) + if(initial(fish_type.show_in_catalog)) + known_fishes += initial(fish_type.name) + + if(!length(known_fishes)) + return + + . += 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) + var/multiplier = 0 + if(is_type_in_list(/obj/item/fishing_lure, SSfishing.fish_properties[fish_type][FISH_PROPERTIES_FAV_BAIT])) + multiplier += 2 + if(fish_type in SSfishing.lure_catchables[type]) + multiplier += 10 + return multiplier + +/obj/item/fishing_lure/plug + name = "big plug lure" + desc = "A fishing lure used to catch larger omnivore fish." + 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) + 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)) + return FALSE + return TRUE + +/obj/item/fishing_lure/dropping + name = "plastic dropping" + desc = "A fishing lure to catch all sort of slimy, ratty, disgusting and/or junk-loving fish." + icon_state = "dropping" + spin_frequency = list(1.5 SECONDS, 2.5 SECONDS) + +/obj/item/fishing_lure/dropping/is_catchable_fish(obj/item/fish/fish_type, 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) + 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) + return TRUE + return FALSE + +/obj/item/fishing_lure/spoon + name = "\improper Indy spoon lure" + desc = "A lustrous piece of metal mimicking the scales of a fish. Good for catching small to medium freshwater omnivore fish." + icon_state = "spoon" + spin_frequency = list(1.25 SECONDS, 2.1 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)) + 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)) + return FALSE + var/fluid_type = initial(fish_type.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) + return TRUE + return FALSE + +/obj/item/fishing_lure/artificial_fly + name = "\improper Silkbuzz artificial fly" + desc = "A fishing lure resembling a large wooly fly. Good for catching all sort of picky fish." + icon_state = "artificial_fly" + spin_frequency = list(1.1 SECONDS, 1.9 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) + return TRUE + return FALSE + +/obj/item/fishing_lure/led + name = "\improper LED fishing lure" + desc = "A heavy, waterproof and fish-looking LED stick, used to catch abyssal and demersal fish alike." + icon_state = "led" + spin_frequency = list(3 SECONDS, 3.75 SECONDS) + +/obj/item/fishing_lure/led/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_BAIT_IGNORE_ENVIRONMENT, INNATE_TRAIT) + update_appearance(UPDATE_OVERLAYS) + +/obj/item/fishing_lure/led/update_overlays() + . = ..() + . += emissive_appearance(icon, "led_emissive", src) + +/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)) + return TRUE + return FALSE + +/obj/item/fishing_lure/lucky_coin + name = "\improper Maneki-Coin lure" + desc = "A faux-gold lure used to attract shiny-loving fish." + icon_state = "lucky_coin" + spin_frequency = list(1.5 SECONDS, 2.5 SECONDS) + +/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) + return TRUE + return FALSE + +/obj/item/fishing_lure/algae + name = "plastic algae lure" + desc = "A soft clump of fake algae used to attract herbivore water critters." + 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) + return TRUE + return FALSE + +/obj/item/fishing_lure/grub + name = "\improper Twister Worm lure" + desc = "A soft plastic lure with the body of a grub and a twisting tail. Good for panfish and other small omnivore fish." + icon_state = "grub" + spin_frequency = list(1 SECONDS, 2.5 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) + 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)) + return FALSE + return TRUE + +/obj/item/fishing_lure/buzzbait + name = "\improper Electric-Buzz lure" + desc = "A metallic, colored clanked attached to a series of cables that somehow attract shock-worthy fish." + icon_state = "buzzbait" + spin_frequency = list(0.8 SECONDS, 1.6 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) + return TRUE + return FALSE + +/obj/item/fishing_lure/spinnerbait + name = "spinnerbait lure" + desc = "A versatile lure, good for catching all sort of predatory freshwater fish." + 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)) + return FALSE + var/init_fluid_type = initial(fish_type.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 + return TRUE + return FALSE + +/obj/item/fishing_lure/daisy_chain + name = "daisy chain lure" + desc = "A lure resembling a small school of fish, good for catching several saltwater predators." + 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)) + return FALSE + var/init_fluid_type = initial(fish_type.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 35eb170e6a8..08d09188e6b 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -208,6 +208,8 @@ if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISH)) . += span_notice("It's [size] cm long.") . += span_notice("It weighs [weight] g.") + if(HAS_TRAIT(src, TRAIT_FISHING_BAIT)) + . += span_smallnoticeital("It can be used as a fishing bait.") ///Randomizes weight and size. /obj/item/fish/proc/randomize_size_and_weight(base_size = average_size, base_weight = average_weight, deviation = weight_size_deviation) @@ -255,7 +257,7 @@ inhand_icon_state = "fish_huge" update_weight_class(WEIGHT_CLASS_GIGANTIC) - if(size > FISH_SIZE_TWO_HANDS_REQUIRED) + if(size > FISH_SIZE_TWO_HANDS_REQUIRED || (HAS_TRAIT(src, TRAIT_FISH_SHOULD_TWOHANDED) && w_class >= WEIGHT_CLASS_BULKY)) inhand_icon_state = "[inhand_icon_state]_wielded" AddComponent(/datum/component/two_handed, require_twohands = TRUE) diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm index 3667a038bff..e51b810d16b 100644 --- a/code/modules/fishing/fish/fish_traits.dm +++ b/code/modules/fishing/fish/fish_traits.dm @@ -46,7 +46,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) return list(ADDITIVE_FISHING_MOD = 0, MULTIPLICATIVE_FISHING_MOD = 1) /// Catch weight table modifier from this mod, needs to return a list with two values -/datum/fish_trait/proc/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman) +/datum/fish_trait/proc/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) SHOULD_CALL_PARENT(TRUE) return list(ADDITIVE_FISHING_MOD = 0, MULTIPLICATIVE_FISHING_MOD = 1) @@ -113,8 +113,8 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) var/live_amount = max(round((source.weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * live_mult, 0.1), live_mult) var/dead_amount = max(round((source.weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * dead_mult, 0.1), dead_mult) var/is_dead = source.status == FISH_DEAD - source.RemoveElement(/datum/element/venomous, venom_path, is_dead ? live_amount : dead_amount) - source.AddElement(/datum/element/venomous, venom_path, is_dead ? dead_amount : live_amount) + source.RemoveElement(/datum/element/venomous, venom_path, is_dead ? live_amount : dead_amount, thrown_effect = TRUE) + source.AddElement(/datum/element/venomous, venom_path, is_dead ? dead_amount : live_amount, thrown_effect = TRUE) /datum/fish_trait/wary name = "Wary" @@ -138,27 +138,30 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/picky_eater name = "Picky Eater" - catalog_description = "This fish is very picky and will ignore low quality bait." + catalog_description = "This fish is very picky and will ignore low quality bait (unless it's amongst its favorites)." -/datum/fish_trait/picky_eater/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman) +/datum/fish_trait/picky_eater/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) . = ..() if(!rod.bait) .[MULTIPLICATIVE_FISHING_MOD] = 0 return if(HAS_TRAIT(rod.bait, TRAIT_OMNI_BAIT)) return + if(is_matching_bait(rod.bait, SSfishing.fish_properties[fish_type][FISH_PROPERTIES_FAV_BAIT])) //we like this bait anyway + return if(!HAS_TRAIT(rod.bait, TRAIT_GOOD_QUALITY_BAIT) && !HAS_TRAIT(rod.bait, TRAIT_GREAT_QUALITY_BAIT)) .[MULTIPLICATIVE_FISHING_MOD] = 0 - /datum/fish_trait/nocturnal name = "Nocturnal" catalog_description = "This fish avoids bright lights, fishing and storing in darkness recommended." -/datum/fish_trait/nocturnal/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman) +/datum/fish_trait/nocturnal/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) . = ..() - var/turf/turf = get_turf(fisherman) - var/light_amount = turf.get_lumcount() + if(rod.bait && HAS_TRAIT(rod.bait, TRAIT_BAIT_IGNORE_ENVIRONMENT)) + return + var/turf/turf = get_turf(location) + var/light_amount = turf?.get_lumcount() if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) .[MULTIPLICATIVE_FISHING_MOD] = 0 @@ -195,7 +198,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) mob.apply_status_effect(/datum/status_effect/shadow_regeneration) /datum/fish_trait/heavy - name = "Heavy" + name = "Demersal" catalog_description = "This fish tends to stay near the waterbed." /datum/fish_trait/heavy/apply_to_mob(mob/living/basic/mob) @@ -215,7 +218,28 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish can only be baited with meat." incompatible_traits = list(/datum/fish_trait/vegan) -/datum/fish_trait/carnivore/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman) +/datum/fish_trait/carnivore/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) + . = ..() + if(!rod.bait) + .[MULTIPLICATIVE_FISHING_MOD] = 0 + return + if(HAS_TRAIT(rod.bait, TRAIT_OMNI_BAIT)) + return + if(isfish(rod.bait)) + return + if(!istype(rod.bait, /obj/item/food)) + .[MULTIPLICATIVE_FISHING_MOD] = 0 + return + var/obj/item/food/food_bait = rod.bait + if(!(food_bait.foodtypes & (MEAT|SEAFOOD|BUGS))) + .[MULTIPLICATIVE_FISHING_MOD] = 0 + +/datum/fish_trait/vegan + name = "Herbivore" + catalog_description = "This fish can only be baited with fresh produce." + incompatible_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/necrophage) + +/datum/fish_trait/vegan/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) . = ..() if(!rod.bait) .[MULTIPLICATIVE_FISHING_MOD] = 0 @@ -225,23 +249,10 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) if(!istype(rod.bait, /obj/item/food)) .[MULTIPLICATIVE_FISHING_MOD] = 0 return + if(istype(rod.bait, /obj/item/food/grown)) + return var/obj/item/food/food_bait = rod.bait - if(!(food_bait.foodtypes & MEAT)) - .[MULTIPLICATIVE_FISHING_MOD] = 0 - -/datum/fish_trait/vegan - name = "Herbivore" - catalog_description = "This fish can only be baited with fresh produce." - incompatible_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/necrophage) - -/datum/fish_trait/vegan/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman) - . = ..() - if(!rod.bait) - .[MULTIPLICATIVE_FISHING_MOD] = 0 - return - if(HAS_TRAIT(rod.bait, TRAIT_OMNI_BAIT)) - return - if(!istype(rod.bait, /obj/item/food/grown)) + if(food_bait.foodtypes & (MEAT|SEAFOOD|GORE|BUGS|DAIRY) || !(food_bait.foodtypes & (VEGETABLES|FRUIT))) .[MULTIPLICATIVE_FISHING_MOD] = 0 /datum/fish_trait/emulsijack @@ -371,6 +382,11 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "It's a predatory fish. It'll hunt down and eat live fishes of smaller size when hungry." incompatible_traits = list(/datum/fish_trait/vegan) +/datum/fish_trait/predator/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type) + . = ..() + if(isfish(rod.bait)) + .[MULTIPLICATIVE_FISHING_MOD] *= 2 + /datum/fish_trait/predator/apply_to_fish(obj/item/fish/fish) . = ..() RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(eat_fishes)) @@ -647,3 +663,60 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) if(!HAS_TRAIT(source, TRAIT_FISH_STINGER)) return change_venom_on_death(source, /datum/reagent/toxin/venom, 0.7, 0.3) + +/datum/fish_trait/ink + name = "Ink Production" + catalog_description = "This fish possess a sac that produces ink." + diff_traits_inheritability = 70 + spontaneous_manifest_types = list(/obj/item/fish/squid = 35) + +/datum/fish_trait/ink/apply_to_fish(obj/item/fish/fish) + . = ..() + RegisterSignal(fish, COMSIG_ATOM_PROCESSED, PROC_REF(on_process)) + RegisterSignal(fish, COMSIG_ITEM_ATTACK_ZONE, PROC_REF(attacked_someone)) + +/datum/fish_trait/ink/proc/attacked_someone(obj/item/fish/source, mob/living/target, mob/living/user, zone) + SIGNAL_HANDLER + if(HAS_TRAIT(source, TRAIT_FISH_INK_ON_COOLDOWN) || source.status == FISH_DEAD) + return + if(!iscarbon(target) || target.get_bodypart(BODY_ZONE_HEAD)) + target.adjust_temp_blindness_up_to(4 SECONDS, 8 SECONDS) + target.adjust_confusion_up_to(1.5 SECONDS, 4 SECONDS) + target.AddComponent(/datum/component/face_decal/splat, \ + color = COLOR_NEARLY_ALL_BLACK, \ + memory_type = /datum/memory/witnessed_inking, \ + mood_event_type = /datum/mood_event/inked, \ + ) + target.visible_message(span_warning("[target] is inked by [source]!"), span_userdanger("You've been inked by [source]!")) + playsound(target, SFX_DESECRATION, 50, TRUE) + ADD_TRAIT(source, TRAIT_FISH_INK_ON_COOLDOWN, FISH_TRAIT_DATUM) + addtimer(TRAIT_CALLBACK_REMOVE(source, TRAIT_FISH_INK_ON_COOLDOWN, FISH_TRAIT_DATUM), 9 SECONDS) + +/datum/fish_trait/ink/proc/on_process(obj/item/fish/source, mob/living/user, obj/item/process_item, list/results) + SIGNAL_HANDLER + new /obj/item/food/ink_sac(source.drop_location()) + +/datum/fish_trait/camouflage + name = "Camouflage" + catalog_description = "This fish possess the ability to blend with its surroundings." + spontaneous_manifest_types = list(/obj/item/fish/squid = 35) + +/datum/fish_trait/camouflage/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame) + minigame.special_effects |= FISHING_MINIGAME_RULE_CAMO + +/datum/fish_trait/camouflage/apply_to_fish(obj/item/fish/fish) + . = ..() + RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(fade_out)) + RegisterSignals(fish, list(COMSIG_MOVABLE_MOVED, COMSIG_FISH_STATUS_CHANGED), PROC_REF(reset_alpha)) + +/datum/fish_trait/camouflage/proc/fade_out(obj/item/fish/source, seconds_per_tick) + SIGNAL_HANDLER + if(source.status == FISH_DEAD || source.last_move + 5 SECONDS >= world.time) + return + source.alpha = max(source.alpha - 10 * seconds_per_tick, 10) + +/datum/fish_trait/camouflage/proc/reset_alpha(obj/item/fish/source) + SIGNAL_HANDLER + var/init_alpha = initial(source.alpha) + if(init_alpha != source.alpha) + animate(source.alpha, alpha = init_alpha, time = 1.2 SECONDS, easing = CIRCULAR_EASING|EASE_OUT) diff --git a/code/modules/fishing/fish/fish_types.dm b/code/modules/fishing/fish/fish_types.dm deleted file mode 100644 index 81c716cecc2..00000000000 --- a/code/modules/fishing/fish/fish_types.dm +++ /dev/null @@ -1,1205 +0,0 @@ -// Freshwater fish - -/obj/item/fish/goldfish - name = "goldfish" - desc = "Despite common belief, goldfish do not have three-second memories. \ - They can actually remember things that happened up to three months ago." - icon_state = "goldfish" - dedicated_in_aquarium_icon_state = "fish_greyscale" - aquarium_vc_color = "#D8540D" - sprite_width = 5 - sprite_height = 3 - stable_population = 3 - average_size = 30 - average_weight = 500 - weight_size_deviation = 0.35 - favorite_bait = list(/obj/item/food/bait/worm) - required_temperature_min = MIN_AQUARIUM_TEMP+18 - required_temperature_max = MIN_AQUARIUM_TEMP+26 - evolution_types = list(/datum/fish_evolution/three_eyes, /datum/fish_evolution/chainsawfish) - compatible_types = list(/obj/item/fish/goldfish/gill, /obj/item/fish/goldfish/three_eyes, /obj/item/fish/goldfish/three_eyes/gill) - -/obj/item/fish/goldfish/gill - name = "McGill" - desc = "A great rubber duck tool for Lawyers who can't get a grasp over their case." - stable_population = 1 - random_case_rarity = FISH_RARITY_NOPE - show_in_catalog = FALSE - beauty = FISH_BEAUTY_GOOD - compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/three_eyes) - fish_traits = list(/datum/fish_trait/recessive) - -/obj/item/fish/goldfish/three_eyes - name = "three-eyed goldfish" - desc = "A goldfish with an extra half a pair of eyes. You wonder what it's been feeding on lately..." - icon_state = "three_eyes" - stable_population = 4 - fish_traits = list(/datum/fish_trait/recessive, /datum/fish_trait/shiny_lover) - compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/gill, /obj/item/fish/goldfish/three_eyes/gill) - beauty = FISH_BEAUTY_GOOD - fishing_difficulty_modifier = 10 - random_case_rarity = FISH_RARITY_VERY_RARE - food = /datum/reagent/toxin/mutagen - favorite_bait = list( - list( - "Type" = "Reagent", - "Value" = /datum/reagent/toxin/mutagen, - "Amount" = 3, - ), - ) - -/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..." - compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/three_eyes) - beauty = FISH_BEAUTY_GREAT - show_in_catalog = FALSE - stable_population = 1 - random_case_rarity = FISH_RARITY_NOPE - -/obj/item/fish/angelfish - name = "angelfish" - desc = "Young Angelfish often live in groups, while adults prefer solitary life. They become territorial and aggressive toward other fish when they reach adulthood." - icon_state = "angelfish" - sprite_width = 4 - sprite_height = 7 - average_size = 30 - average_weight = 500 - stable_population = 3 - fish_traits = list(/datum/fish_trait/aggressive) - required_temperature_min = MIN_AQUARIUM_TEMP+22 - required_temperature_max = MIN_AQUARIUM_TEMP+30 - -/obj/item/fish/guppy - name = "guppy" - desc = "Guppy is also known as rainbow fish because of the brightly colored body and fins." - icon_state = "guppy" - sprite_width = 5 - sprite_height = 2 - average_size = 30 - average_weight = 500 - stable_population = 6 - required_temperature_min = MIN_AQUARIUM_TEMP+20 - required_temperature_max = MIN_AQUARIUM_TEMP+28 - -/obj/item/fish/plasmatetra - name = "plasma tetra" - desc = "Due to their small size, tetras are prey to many predators in their watery world, including eels, crustaceans, and invertebrates." - icon_state = "plastetra" - sprite_width = 4 - sprite_height = 2 - average_size = 30 - average_weight = 500 - stable_population = 3 - required_temperature_min = MIN_AQUARIUM_TEMP+20 - required_temperature_max = MIN_AQUARIUM_TEMP+28 - -/obj/item/fish/catfish - name = "catfish" - desc = "A catfish has about 100,000 taste buds, and their bodies are covered with them to help detect chemicals present in the water and also to respond to touch." - icon_state = "catfish" - sprite_width = 8 - sprite_height = 4 - average_size = 80 - average_weight = 1600 - weight_size_deviation = 0.35 - stable_population = 3 - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = JUNKFOOD - ) - ) - required_temperature_min = MIN_AQUARIUM_TEMP+12 - required_temperature_max = MIN_AQUARIUM_TEMP+30 - beauty = FISH_BEAUTY_GOOD - -/obj/item/fish/tadpole - name = "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 - average_weight = 10 - sprite_width = 3 - sprite_height = 1 - health = 50 - feeding_frequency = 1.5 MINUTES - required_temperature_min = MIN_AQUARIUM_TEMP+15 - required_temperature_max = MIN_AQUARIUM_TEMP+20 - fillet_type = null - fish_traits = list(/datum/fish_trait/no_mating) //They grow into frogs and that's it. - beauty = FISH_BEAUTY_NULL - random_case_rarity = FISH_RARITY_NOPE //Why would you want generic frog tadpoles you get from ponds inside fish cases? - /// Once dead, tadpoles disappear after a dozen seconds, since you can get infinite tadpoles. - var/del_timerid - -/obj/item/fish/tadpole/Initialize(mapload, apply_qualities = TRUE) - . = ..() - AddComponent(/datum/component/fish_growth, /mob/living/basic/frog, 100 / rand(2.5, 3 MINUTES) * 10) - RegisterSignal(src, COMSIG_FISH_BEFORE_GROWING, PROC_REF(growth_checks)) - RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth)) - -/obj/item/fish/tadpole/set_status(new_status, silent = FALSE) - . = ..() - if(status == FISH_DEAD) - del_timerid = QDEL_IN_STOPPABLE(src, 12 SECONDS) - else - deltimer(del_timerid) - -/obj/item/fish/tadpole/proc/growth_checks(datum/source, seconds_per_tick) - SIGNAL_HANDLER - var/hunger = CLAMP01((world.time - last_feeding) / feeding_frequency) - if(hunger >= 0.7) //too hungry to grow - return COMPONENT_DONT_GROW - var/obj/structure/aquarium/aquarium = loc - if(!aquarium.allow_breeding) //the aquarium has breeding disabled - return COMPONENT_DONT_GROW - -/obj/item/fish/tadpole/proc/on_growth(datum/source, mob/living/basic/frog/result) - SIGNAL_HANDLER - playsound(result, result.attack_sound, 50, TRUE) // reeeeeeeeeeeeeee... - -/obj/item/fish/tadpole/get_export_price(price, percent) - return 2 //two credits. Tadpoles aren't really that valueable. - -// Saltwater fish below - -/obj/item/fish/clownfish - name = "clownfish" - desc = "Clownfish catch prey by swimming onto the reef, attracting larger fish, and luring them back to the anemone. The anemone will sting and eat the larger fish, leaving the remains for the clownfish." - icon_state = "clownfish" - required_fluid_type = AQUARIUM_FLUID_SALTWATER - sprite_width = 7 - sprite_height = 4 - average_size = 30 - average_weight = 500 - stable_population = 4 - fish_traits = list(/datum/fish_trait/picky_eater) - evolution_types = list(/datum/fish_evolution/lubefish) - compatible_types = list(/obj/item/fish/clownfish/lube) - required_temperature_min = MIN_AQUARIUM_TEMP+22 - required_temperature_max = MIN_AQUARIUM_TEMP+30 - -/obj/item/fish/clownfish/lube - name = "lubefish" - desc = "A clownfish exposed to cherry-flavored lube for far too long. First discovered the days following a cargo incident around the seas of Europa, when thousands of thousands of thousands..." - icon_state = "lubefish" - random_case_rarity = FISH_RARITY_VERY_RARE - fish_traits = list(/datum/fish_trait/picky_eater, /datum/fish_trait/lubed) - evolution_types = null - compatible_types = list(/obj/item/fish/clownfish) - food = /datum/reagent/lube - fishing_difficulty_modifier = 5 - beauty = FISH_BEAUTY_GREAT - -/obj/item/fish/cardinal - name = "cardinalfish" - desc = "Cardinalfish are often found near sea urchins, where the fish hide when threatened." - icon_state = "cardinalfish" - required_fluid_type = AQUARIUM_FLUID_SALTWATER - sprite_width = 6 - sprite_height = 3 - average_size = 30 - average_weight = 500 - stable_population = 4 - fish_traits = list(/datum/fish_trait/vegan) - required_temperature_min = MIN_AQUARIUM_TEMP+22 - required_temperature_max = MIN_AQUARIUM_TEMP+30 - -/obj/item/fish/greenchromis - name = "green chromis" - desc = "The Chromis can vary in color from blue to green depending on the lighting and distance from the lights." - icon_state = "greenchromis" - required_fluid_type = AQUARIUM_FLUID_SALTWATER - sprite_width = 5 - sprite_height = 3 - average_size = 30 - average_weight = 500 - stable_population = 5 - required_temperature_min = MIN_AQUARIUM_TEMP+23 - required_temperature_max = MIN_AQUARIUM_TEMP+28 - - fishing_difficulty_modifier = 5 // Bit harder - -/obj/item/fish/firefish - name = "firefish goby" - desc = "To communicate in the wild, the firefish uses its dorsal fin to alert others of potential danger." - icon_state = "firefish" - sprite_width = 5 - sprite_height = 3 - required_fluid_type = AQUARIUM_FLUID_SALTWATER - average_size = 30 - average_weight = 500 - stable_population = 3 - disliked_bait = list(/obj/item/food/bait/worm, /obj/item/food/bait/doughball) - fish_movement_type = /datum/fish_movement/zippy - required_temperature_min = MIN_AQUARIUM_TEMP+23 - required_temperature_max = MIN_AQUARIUM_TEMP+28 - -/obj/item/fish/pufferfish - name = "pufferfish" - desc = "They say that one pufferfish contains enough toxins to kill 30 people, although in the last few decades they've been genetically engineered en masse to be less poisonous." - icon_state = "pufferfish" - required_fluid_type = AQUARIUM_FLUID_SALTWATER - sprite_width = 8 - sprite_height = 6 - average_size = 60 - average_weight = 1000 - stable_population = 3 - required_temperature_min = MIN_AQUARIUM_TEMP+23 - required_temperature_max = MIN_AQUARIUM_TEMP+28 - fillet_type = /obj/item/food/fishmeat/quality //Too bad they're poisonous - fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/toxic) - beauty = FISH_BEAUTY_GOOD - -/obj/item/fish/lanternfish - name = "lanternfish" - desc = "Typically found in areas below 6600 feet below the surface of the ocean, they live in complete darkness." - icon_state = "lanternfish" - required_fluid_type = AQUARIUM_FLUID_SALTWATER - random_case_rarity = FISH_RARITY_VERY_RARE - sprite_width = 6 - sprite_height = 5 - average_size = 50 - average_weight = 1000 - stable_population = 3 - fish_traits = list(/datum/fish_trait/nocturnal) - required_temperature_min = MIN_AQUARIUM_TEMP+2 //My source is that the water at a depth 6600 feet is pretty darn cold. - required_temperature_max = MIN_AQUARIUM_TEMP+18 - beauty = FISH_BEAUTY_NULL - -//Tiziran Fish -/obj/item/fish/dwarf_moonfish - name = "dwarf moonfish" - desc = "Ordinarily in the wild, the Zagoskian moonfish is around the size of a tuna, however through selective breeding a smaller breed suitable for being kept as an aquarium pet has been created." - icon_state = "dwarf_moonfish" - sprite_height = 6 - sprite_width = 6 - required_fluid_type = AQUARIUM_FLUID_SALTWATER - stable_population = 2 - fillet_type = /obj/item/food/fishmeat/moonfish - average_size = 60 - average_weight = 1000 - required_temperature_min = MIN_AQUARIUM_TEMP+20 - required_temperature_max = MIN_AQUARIUM_TEMP+30 - beauty = FISH_BEAUTY_GOOD - -/obj/item/fish/gunner_jellyfish - name = "gunner jellyfish" - desc = "So called due to their resemblance to an artillery shell, the gunner jellyfish is native to Tizira, where it is enjoyed as a delicacy. Produces a mild hallucinogen that is destroyed by cooking." - icon_state = "gunner_jellyfish" - sprite_height = 4 - sprite_width = 5 - required_fluid_type = AQUARIUM_FLUID_SALTWATER - stable_population = 4 - fillet_type = /obj/item/food/fishmeat/gunner_jellyfish - required_temperature_min = MIN_AQUARIUM_TEMP+24 - required_temperature_max = MIN_AQUARIUM_TEMP+32 - beauty = FISH_BEAUTY_GOOD - -/obj/item/fish/needlefish - name = "needlefish" - desc = "A tiny, transparent fish which resides in large schools in the oceans of Tizira. A common food for other, larger fish." - icon_state = "needlefish" - sprite_height = 3 - sprite_width = 7 - required_fluid_type = AQUARIUM_FLUID_SALTWATER - stable_population = 12 - breeding_timeout = 1 MINUTES - fillet_type = null - average_size = 20 - average_weight = 300 - fish_traits = list(/datum/fish_trait/carnivore) - required_temperature_min = MIN_AQUARIUM_TEMP+10 - required_temperature_max = MIN_AQUARIUM_TEMP+32 - -/obj/item/fish/armorfish - name = "armorfish" - desc = "A small shellfish native to Tizira's oceans, known for its exceptionally hard shell. Consumed similarly to prawns." - icon_state = "armorfish" - sprite_height = 5 - sprite_width = 6 - average_size = 25 - average_weight = 350 - required_fluid_type = AQUARIUM_FLUID_SALTWATER - stable_population = 10 - breeding_timeout = 1.25 MINUTES - fillet_type = /obj/item/food/fishmeat/armorfish - fish_movement_type = /datum/fish_movement/slow - required_temperature_min = MIN_AQUARIUM_TEMP+10 - required_temperature_max = MIN_AQUARIUM_TEMP+32 - -/// Commonly found on the mining fishing spots. Can be grown into lobstrosities -/obj/item/fish/chasm_crab - name = "chasm chrab" - desc = "The young of the lobstrosity mature in pools below the earth, eating what falls in until large enough to clamber out. Those found near the station are well-fed." - icon_state = "chrab" - sprite_height = 9 - sprite_width = 8 - stable_population = 4 - feeding_frequency = 10 MINUTES - random_case_rarity = FISH_RARITY_RARE - fillet_type = /obj/item/food/meat/slab/rawcrab - required_temperature_min = MIN_AQUARIUM_TEMP+9 - required_temperature_max = LAVALAND_MAX_TEMPERATURE+50 - min_pressure = HAZARD_LOW_PRESSURE - safe_air_limits = list( - /datum/gas/oxygen = list(2, 100), - /datum/gas/nitrogen, - /datum/gas/carbon_dioxide = list(0, 20), - /datum/gas/water_vapor, - /datum/gas/plasma = list(0, 5), - /datum/gas/bz = list(0, 5), - /datum/gas/miasma = list(0, 5), - ) - evolution_types = list(/datum/fish_evolution/ice_chrab) - compatible_types = list(/obj/item/fish/chasm_crab/ice) - beauty = FISH_BEAUTY_GOOD - ///This value represents how much the crab needs aren't being met. Higher values translate to a more likely hostile lobstrosity. - var/anger = 0 - ///The lobstrosity type this matures into - var/lob_type = /mob/living/basic/mining/lobstrosity/juvenile/lava - ///at which rate the crab gains maturation - var/growth_rate = 100 / (10 MINUTES) * 10 - -/obj/item/fish/chasm_crab/Initialize(mapload, apply_qualities = TRUE) - . = ..() - RegisterSignal(src, COMSIG_FISH_BEFORE_GROWING, PROC_REF(growth_checks)) - RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth)) - -///A chasm crab growth speed is determined by its initial weight and size, ergo bigger crabs for faster lobstrosities -/obj/item/fish/chasm_crab/update_size_and_weight(new_size = average_size, new_weight = average_weight) - . = ..() - var/multiplier = 1 - switch(size) - if(0 to FISH_SIZE_TINY_MAX) - multiplier -= 0.2 - if(FISH_SIZE_SMALL_MAX to FISH_SIZE_NORMAL_MAX) - multiplier += 0.2 - if(FISH_SIZE_NORMAL_MAX to FISH_SIZE_BULKY_MAX) - multiplier += 0.5 - if(FISH_SIZE_BULKY_MAX to INFINITY) - multiplier += 0.8 - - if(weight <= 800) - multiplier -= 0.1 * round((1000 - weight) / 200) - else if(weight >= 1500) - multiplier += min(0.1 * round((weight - 1000) / 500), 2) - - AddComponent(/datum/component/fish_growth, lob_type, initial(growth_rate) * multiplier) - -/obj/item/fish/chasm_crab/proc/growth_checks(datum/source, seconds_per_tick) - SIGNAL_HANDLER - var/hunger = CLAMP01((world.time - last_feeding) / feeding_frequency) - if(health <= initial(health) * 0.6 || hunger >= 0.6) //if too hurt or hungry, don't grow. - anger += growth_rate * 2 * seconds_per_tick - return COMPONENT_DONT_GROW - - if(hunger >= 0.4) //I'm hungry and angry - anger += growth_rate * 0.6 * seconds_per_tick - - if(!isaquarium(loc)) - return - - var/obj/structure/aquarium/aquarium = loc - if(!aquarium.allow_breeding) //the aquarium has breeding disabled - return COMPONENT_DONT_GROW - if(!locate(/obj/item/aquarium_prop) in aquarium) //the aquarium deco is quite barren - anger += growth_rate * 0.25 * seconds_per_tick - var/fish_count = length(aquarium.get_fishes()) - if(!ISINRANGE(fish_count, 3, AQUARIUM_MAX_BREEDING_POPULATION * 0.5)) //too lonely or overcrowded - anger += growth_rate * 0.3 * seconds_per_tick - if(fish_count > AQUARIUM_MAX_BREEDING_POPULATION * 0.5) //check if there's enough room to maturate. - return COMPONENT_DONT_GROW - -/obj/item/fish/chasm_crab/proc/on_growth(datum/source, mob/living/basic/mining/lobstrosity/juvenile/result) - SIGNAL_HANDLER - if(!prob(anger)) - result.AddElement(/datum/element/ai_retaliate) - qdel(result.ai_controller) - result.ai_controller = new /datum/ai_controller/basic_controller/lobstrosity/juvenile/calm(result) - else if(anger < 30) //not really that mad, just a bit unstable. - qdel(result.ai_controller) - result.ai_controller = new /datum/ai_controller/basic_controller/lobstrosity/juvenile/capricious(result) - -/obj/item/fish/chasm_crab/ice - name = "arctic chrab" - desc = "A subspecies of chasm chrabs that has adapted to the cold climate and lack of abysmal holes of the icemoon." - icon_state = "arctic_chrab" - required_temperature_min = ICEBOX_MIN_TEMPERATURE-20 - required_temperature_max = MIN_AQUARIUM_TEMP+15 - evolution_types = list(/datum/fish_evolution/chasm_chrab) - compatible_types = list(/obj/item/fish/chasm_crab) - beauty = FISH_BEAUTY_GREAT - lob_type = /mob/living/basic/mining/lobstrosity/juvenile - -/obj/item/fish/donkfish - name = "donk co. company patent donkfish" - desc = "A lab-grown donkfish. Its invention was an accident for the most part, as it was intended to be consumed in donk pockets. Unfortunately, it tastes horrible, so it has now become a pseudo-mascot." - icon_state = "donkfish" - random_case_rarity = FISH_RARITY_VERY_RARE - required_fluid_type = AQUARIUM_FLUID_FRESHWATER - stable_population = 4 - sprite_width = 5 - sprite_height = 4 - fillet_type = /obj/item/food/fishmeat/donkfish - fish_traits = list(/datum/fish_trait/yucky) - required_temperature_min = MIN_AQUARIUM_TEMP+15 - required_temperature_max = MIN_AQUARIUM_TEMP+28 - beauty = FISH_BEAUTY_EXCELLENT - -/obj/item/fish/emulsijack - name = "toxic emulsijack" - desc = "Ah, the terrifying emulsijack. Created in a laboratory, the only real use of this slimey, scaleless fish is for completely ruining a tank." - icon_state = "emulsijack" - random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS - required_fluid_type = AQUARIUM_FLUID_ANADROMOUS - stable_population = 3 - sprite_width = 7 - sprite_height = 3 - fish_traits = list(/datum/fish_trait/emulsijack) - required_temperature_min = MIN_AQUARIUM_TEMP+5 - required_temperature_max = MIN_AQUARIUM_TEMP+40 - beauty = FISH_BEAUTY_BAD - -/obj/item/fish/jumpercable - name = "monocloning jumpercable" - desc = "A surprisingly useful if nasty looking creation from the syndicate fish labs. Drop one in a tank, and \ - watch it self-feed and multiply. Generates more and more power as a growing swarm!" - icon_state = "jumpercable" - sprite_width = 16 - sprite_height = 5 - stable_population = 12 - average_size = 110 - average_weight = 6000 - random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS - required_temperature_min = MIN_AQUARIUM_TEMP+10 - required_temperature_max = MIN_AQUARIUM_TEMP+30 - favorite_bait = list(/obj/item/stock_parts/power_store/cell/lead) - fish_traits = list( - /datum/fish_trait/parthenogenesis, - /datum/fish_trait/mixotroph, - /datum/fish_trait/electrogenesis, - ) - beauty = FISH_BEAUTY_UGLY - -/obj/item/fish/ratfish - name = "ratfish" - desc = "A rat exposed to the murky waters of maintenance too long. Any higher power, if it revealed itself, would state that the ratfish's continued existence is extremely unwelcome." - icon_state = "ratfish" - sprite_width = 7 - sprite_height = 5 - random_case_rarity = FISH_RARITY_RARE - required_fluid_type = AQUARIUM_FLUID_FRESHWATER - stable_population = 10 //set by New, but this is the default config value - fillet_type = /obj/item/food/meat/slab/human/mutant/zombie //eww... - fish_traits = list(/datum/fish_trait/necrophage) - required_temperature_min = MIN_AQUARIUM_TEMP+15 - required_temperature_max = MIN_AQUARIUM_TEMP+35 - fish_movement_type = /datum/fish_movement/zippy - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = DAIRY - ) - ) - beauty = FISH_BEAUTY_DISGUSTING - -/obj/item/fish/ratfish/Initialize(mapload, apply_qualities = TRUE) - . = ..() - //stable pop reflects the config for how many mice migrate. powerful... - stable_population = CONFIG_GET(number/mice_roundstart) - -/obj/item/fish/sludgefish - name = "sludgefish" - desc = "A misshapen, fragile, loosely fish-like living goop, the only thing that'd ever thrive in the acidic and claustrophobic cavities of the station's organic waste disposal system." - icon_state = "sludgefish" - sprite_width = 7 - sprite_height = 6 - required_fluid_type = AQUARIUM_FLUID_SULPHWATEVER - stable_population = 8 - average_size = 20 - average_weight = 400 - health = 50 - breeding_timeout = 2.5 MINUTES - fish_traits = list(/datum/fish_trait/parthenogenesis, /datum/fish_trait/no_mating) - required_temperature_min = MIN_AQUARIUM_TEMP+10 - required_temperature_max = MIN_AQUARIUM_TEMP+40 - evolution_types = list(/datum/fish_evolution/purple_sludgefish) - beauty = FISH_BEAUTY_NULL - -/obj/item/fish/sludgefish/purple - name = "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 - fish_traits = list(/datum/fish_trait/parthenogenesis) - -/obj/item/fish/slimefish - name = "acquatic slime" - desc = "Kids, this is what happens when a slime overcomes its hydrophobic nature. It goes glug glug." - icon_state = "slimefish" - icon_state_dead = "slimefish_dead" - sprite_width = 7 - sprite_height = 7 - do_flop_animation = FALSE //it already has a cute bouncy wiggle. :3 - random_case_rarity = FISH_RARITY_VERY_RARE - required_fluid_type = AQUARIUM_FLUID_ANADROMOUS - stable_population = 4 - health = 150 - fillet_type = /obj/item/slime_extract/grey - grind_results = list(/datum/reagent/toxin/slimejelly = 10) - fish_traits = list(/datum/fish_trait/toxin_immunity, /datum/fish_trait/crossbreeder) - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = TOXIC, - ), - list( - "Type" = "Reagent", - "Value" = /datum/reagent/toxin, - "Amount" = 5, - ), - ) - required_temperature_min = MIN_AQUARIUM_TEMP+20 - beauty = FISH_BEAUTY_GREAT - -/obj/item/fish/boned - name = "unmarine bonemass" - desc = "What one could mistake for fish remains, is in reality a species that chose to discard its weak flesh a long time ago. A living fossil, in its most literal sense." - icon_state = "bonemass" - sprite_width = 10 - sprite_height = 7 - fish_movement_type = /datum/fish_movement/zippy - random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS - required_fluid_type = AQUARIUM_FLUID_ANY_WATER - min_pressure = HAZARD_LOW_PRESSURE - health = 150 - stable_population = 3 - grind_results = list(/datum/reagent/bone_dust = 10) - fillet_type = /obj/item/stack/sheet/bone - num_fillets = 2 - fish_traits = list(/datum/fish_trait/revival, /datum/fish_trait/carnivore) - average_size = 70 - average_weight = 2000 - death_text = "%SRC stops moving." //It's dead... or is it? - evolution_types = list(/datum/fish_evolution/mastodon) - beauty = FISH_BEAUTY_UGLY - -/obj/item/fish/mastodon - name = "unmarine mastodon" - desc = "A monster of exposed muscles and innards, wrapped in a fish-like skeleton. You don't remember ever seeing it on the catalog." - icon = 'icons/obj/aquarium/wide.dmi' - icon_state = "mastodon" - base_pixel_x = -16 - pixel_x = -16 - sprite_width = 12 - sprite_height = 7 - show_in_catalog = FALSE - random_case_rarity = FISH_RARITY_NOPE - fishing_difficulty_modifier = 30 - required_fluid_type = AQUARIUM_FLUID_ANY_WATER - min_pressure = HAZARD_LOW_PRESSURE - health = 300 - stable_population = 1 //This means they can only crossbreed. - grind_results = list(/datum/reagent/bone_dust = 5, /datum/reagent/consumable/liquidgibs = 5) - fillet_type = /obj/item/stack/sheet/bone - num_fillets = 2 - feeding_frequency = 2 MINUTES - breeding_timeout = 5 MINUTES - average_size = 180 - average_weight = 5000 - death_text = "%SRC stops moving." - fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/revival, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/aggressive) - beauty = FISH_BEAUTY_BAD - -/obj/item/fish/holo - name = "holographic goldfish" - desc = "A holographic representation of a common goldfish, slowly flickering out, removed from its holo-habitat." - icon_state = /obj/item/fish/goldfish::icon_state - show_in_catalog = FALSE - random_case_rarity = FISH_RARITY_NOPE - dedicated_in_aquarium_icon_state = /obj/item/fish/goldfish::dedicated_in_aquarium_icon_state - aquarium_vc_color = /obj/item/fish/goldfish::aquarium_vc_color - sprite_width = /obj/item/fish/goldfish::sprite_width - sprite_height = /obj/item/fish/goldfish::sprite_height - stable_population = 1 - average_size = /obj/item/fish/goldfish::average_size - average_weight = /obj/item/fish/goldfish::average_weight - required_fluid_type = AQUARIUM_FLUID_ANADROMOUS - grind_results = null - fillet_type = null - death_text = "%SRC gently disappears." - fish_traits = list(/datum/fish_trait/no_mating) //just to be sure, these shouldn't reproduce - experisci_scannable = FALSE - -/obj/item/fish/holo/Initialize(mapload, apply_qualities = TRUE) - . = ..() - var/area/station/holodeck/holo_area = get_area(src) - if(!istype(holo_area)) - addtimer(CALLBACK(src, PROC_REF(set_status), FISH_DEAD), 1 MINUTES) - return - holo_area.linked.add_to_spawned(src) - -/obj/item/fish/holo/set_status(new_status, silent = FALSE) - . = ..() - if(status == FISH_DEAD) - animate(src, alpha = 0, 3 SECONDS, easing = SINE_EASING) - QDEL_IN(src, 3 SECONDS) - -/obj/item/fish/holo/crab - name = "holographic crab" - desc = "A holographic represantion of a soul-crushingly soulless crab, unlike the cuter ones occasionally roaming around. It stares at you, with empty, beady eyes." - icon_state = "crab" - dedicated_in_aquarium_icon_state = null - aquarium_vc_color = null - average_size = 30 - average_weight = 1000 - sprite_height = 6 - sprite_width = 10 - -/obj/item/fish/holo/puffer - name = "holographic pufferfish" - desc ="A holographic representation of 100% safe-to-eat pufferfish... that is, if holographic fishes were even edible." - icon_state = /obj/item/fish/pufferfish::icon_state - dedicated_in_aquarium_icon_state = /obj/item/fish/pufferfish::dedicated_in_aquarium_icon_state - aquarium_vc_color = /obj/item/fish/pufferfish::aquarium_vc_color - average_size = /obj/item/fish/pufferfish::average_size - average_weight = /obj/item/fish/pufferfish::average_weight - sprite_height = /obj/item/fish/pufferfish::sprite_height - sprite_width = /obj/item/fish/pufferfish::sprite_width - beauty = FISH_BEAUTY_GOOD - -/obj/item/fish/holo/angel - name = "holographic angelfish" - desc = "A holographic representation of a angelfish. I got nothing snarky to say about this one." - icon_state = /obj/item/fish/angelfish::icon_state - dedicated_in_aquarium_icon_state = /obj/item/fish/angelfish::dedicated_in_aquarium_icon_state - aquarium_vc_color = /obj/item/fish/angelfish::aquarium_vc_color - average_size = /obj/item/fish/angelfish::average_size - average_weight = /obj/item/fish/angelfish::average_weight - sprite_height = /obj/item/fish/angelfish::sprite_height - sprite_width = /obj/item/fish/angelfish::sprite_width - -/obj/item/fish/holo/clown - name = "holographic clownfish" - icon_state = "holo_clownfish" - desc = "A holographic representation of a clownfish, or at least how they used to look like five centuries ago." - dedicated_in_aquarium_icon_state = null - aquarium_vc_color = /obj/item/fish/clownfish::aquarium_vc_color - average_size = /obj/item/fish/clownfish::average_size - average_weight = /obj/item/fish/clownfish::average_weight - sprite_height = /obj/item/fish/clownfish::sprite_height - sprite_width = /obj/item/fish/clownfish::sprite_width - -/obj/item/fish/holo/checkered - name = "unrendered holographic fish" - desc = "A checkered silhoutte of searing purple and pitch black presents itself before your eyes, like a tear in fabric of reality. It hurts to watch." - icon_state = "checkered" //it's a meta joke, buddy. - dedicated_in_aquarium_icon_state = null - aquarium_vc_color = null - average_size = 30 - average_weight = 500 - sprite_width = 4 - sprite_height = 3 - beauty = FISH_BEAUTY_NULL - -/obj/item/fish/holo/halffish - name = "holographic half-fish" - desc = "A holographic representation of... a fish reduced to all bones, except for its head. Isn't it supposed to be dead? Ehr, holo-dead?" - icon_state = "half_fish" - dedicated_in_aquarium_icon_state = null - aquarium_vc_color = null - sprite_height = 4 - sprite_width = 10 - average_size = 50 - average_weight = 500 - beauty = FISH_BEAUTY_UGLY - -/obj/item/fish/starfish - name = "cosmostarfish" - desc = "A peculiar, gravity-defying, echinoderm-looking critter from hyperspace." - icon_state = "starfish" - icon_state_dead = "starfish_dead" - sprite_width = 4 - sprite_height = 3 - average_size = 30 - average_weight = 300 - stable_population = 3 - required_fluid_type = AQUARIUM_FLUID_AIR - random_case_rarity = FISH_RARITY_NOPE - required_temperature_min = 0 - required_temperature_max = INFINITY - safe_air_limits = null - min_pressure = 0 - max_pressure = INFINITY - grind_results = list(/datum/reagent/bluespace = 10) - fillet_type = null - fish_traits = list(/datum/fish_trait/antigrav, /datum/fish_trait/mixotroph) - beauty = FISH_BEAUTY_GREAT - -/obj/item/fish/starfish/Initialize(mapload, apply_qualities = TRUE) - . = ..() - update_appearance(UPDATE_OVERLAYS) - -/obj/item/fish/starfish/update_overlays() - . = ..() - if(status == FISH_ALIVE) - . += emissive_appearance(icon, "starfish_emissive", src) - -///It spins, and dimly glows in the dark. -/obj/item/fish/starfish/flop_animation() - DO_FLOATING_ANIM(src) - -/obj/item/fish/lavaloop - name = "lavaloop fish" - desc = "Due to its curvature, it can be used as make-shift boomerang." - icon_state = "lava_loop" - sprite_width = 3 - sprite_height = 5 - average_size = 30 - average_weight = 500 - resistance_flags = FIRE_PROOF | LAVA_PROOF - required_fluid_type = AQUARIUM_FLUID_ANY_WATER //if we can survive hot lava and freezing plasrivers, we can survive anything - fish_movement_type = /datum/fish_movement/zippy - min_pressure = HAZARD_LOW_PRESSURE - required_temperature_min = MIN_AQUARIUM_TEMP+30 - required_temperature_max = MIN_AQUARIUM_TEMP+35 - fish_traits = list( - /datum/fish_trait/carnivore, - /datum/fish_trait/heavy, - ) - hitsound = null - throwforce = 5 - beauty = FISH_BEAUTY_GOOD - ///maximum bonus damage when winded up - var/maximum_bonus = 25 - -/obj/item/fish/lavaloop/Initialize(mapload, apply_qualities = TRUE) - . = ..() - ADD_TRAIT(src, TRAIT_BYPASS_RANGED_ARMOR, INNATE_TRAIT) - AddComponent(/datum/component/boomerang, throw_range, TRUE) - AddComponent(\ - /datum/component/throwbonus_on_windup,\ - maximum_bonus = maximum_bonus,\ - windup_increment_speed = 2,\ - throw_text = "starts cooking in your hands, it may explode soon!",\ - pass_maximum_callback = CALLBACK(src, PROC_REF(explode_on_user)),\ - apply_bonus_callback = CALLBACK(src, PROC_REF(on_fish_land)),\ - sound_on_success = 'sound/weapons/parry.ogg',\ - effect_on_success = /obj/effect/temp_visual/guardian/phase,\ - ) - -/obj/item/fish/lavaloop/proc/explode_on_user(mob/living/user) - var/obj/item/bodypart/arm/active_arm = user.get_active_hand() - active_arm?.dismember() - to_chat(user, span_warning("[src] explodes!")) - playsound(src, 'sound/effects/explosion1.ogg', 40, TRUE) - user.flash_act(1, 1) - qdel(src) - -/obj/item/fish/lavaloop/proc/on_fish_land(mob/living/target, bonus_value) - if(!istype(target)) - return FALSE - return (target.mob_size >= MOB_SIZE_LARGE) - -/obj/item/fish/lavaloop/plasma_river - maximum_bonus = 30 - -/obj/item/fish/lavaloop/plasma_river/explode_on_user(mob/living/user) - playsound(src, 'sound/effects/explosion1.ogg', 40, TRUE) - user.flash_act(1, 1) - user.apply_status_effect(/datum/status_effect/ice_block_talisman, 5 SECONDS) - qdel(src) - -/obj/item/fish/lavaloop/plasma_river/on_fish_land(mob/living/target, bonus_value) - if(!istype(target)) - return FALSE - if(target.mob_size < MOB_SIZE_LARGE) - return FALSE - var/freeze_timer = (bonus_value * 0.1) - if(freeze_timer <= 0) - return FALSE - target.apply_status_effect(/datum/status_effect/ice_block_talisman, freeze_timer SECONDS) - return FALSE - -/obj/item/fish/zipzap - name = "anxious zipzap" - desc = "A fish overflowing with crippling anxiety and electric potential. Worried about the walls of its tank closing in constantly. Both literally and as a general metaphorical unease about life's direction." - icon_state = "zipzap" - icon_state_dead = "zipzap_dead" - sprite_width = 6 - sprite_height = 3 - stable_population = 3 - average_size = 30 - average_weight = 500 - random_case_rarity = FISH_RARITY_VERY_RARE - favorite_bait = list(/obj/item/stock_parts/power_store/cell/lead) - required_temperature_min = MIN_AQUARIUM_TEMP+18 - required_temperature_max = MIN_AQUARIUM_TEMP+26 - fish_traits = list( - /datum/fish_trait/no_mating, - /datum/fish_trait/wary, - /datum/fish_trait/anxiety, - /datum/fish_trait/electrogenesis, - ) - //anxiety naturally limits the amount of zipzaps per tank, so they are stronger alone - electrogenesis_power = 20 MEGA JOULES - beauty = FISH_BEAUTY_GOOD - -/obj/item/fish/sockeye_salmon - name = "sockeye salmon" - desc = "A fairly common and iconic salmon endemic of the Pacific Ocean. At some point imported into outer space, where we're now." - icon_state = "sockeye" - sprite_width = 6 - sprite_height = 4 - stable_population = 6 - required_temperature_min = MIN_AQUARIUM_TEMP+3 - required_temperature_max = MIN_AQUARIUM_TEMP+19 - required_fluid_type = AQUARIUM_FLUID_ANADROMOUS - fillet_type = /obj/item/food/fishmeat/salmon - beauty = FISH_BEAUTY_GOOD - -/obj/item/fish/arctic_char - name = "arctic char" - desc = "A cold-water anadromous fish widespread around the Northern Hemisphere of Earth, yet it has somehow found a way here." - icon_state = "arctic_char" - sprite_width = 7 - sprite_height = 4 - stable_population = 6 - average_size = 60 - average_weight = 1200 - weight_size_deviation = 0.5 // known for their size dismophism - required_temperature_min = MIN_AQUARIUM_TEMP+3 - required_temperature_max = MIN_AQUARIUM_TEMP+19 - required_fluid_type = AQUARIUM_FLUID_ANADROMOUS - -/obj/item/fish/stingray - name = "stingray" - desc = "A type of ray, most known for its venomous stinger. Despite that, They're normally docile, if not a bit easily frightened." - icon_state = "stingray" - stable_population = 4 - sprite_height = 7 - sprite_width = 8 - average_size = 60 - average_weight = 700 - beauty = FISH_BEAUTY_GREAT - random_case_rarity = FISH_RARITY_RARE - required_fluid_type = AQUARIUM_FLUID_SALTWATER //Someone ought to add river rays later I guess. - fish_traits = list(/datum/fish_trait/stinger, /datum/fish_trait/toxic_barbs, /datum/fish_trait/wary, /datum/fish_trait/carnivore, /datum/fish_trait/predator) - -/obj/item/fish/sand_surfer - name = "sand surfer" - desc = "A bronze alien \"fish\" living and swimming underneath faraway sandy places." - icon_state = "sand_surfer" - sprite_height = 6 - sprite_width = 6 - stable_population = 5 - average_size = 65 - average_weight = 1100 - weight_size_deviation = 0.35 - random_case_rarity = FISH_RARITY_RARE - required_fluid_type = AQUARIUM_FLUID_AIR - required_temperature_min = MIN_AQUARIUM_TEMP+25 - required_temperature_max = MIN_AQUARIUM_TEMP+60 - fish_movement_type = /datum/fish_movement/plunger - fishing_difficulty_modifier = 5 - fish_traits = list(/datum/fish_trait/shiny_lover) - beauty = FISH_BEAUTY_GOOD - -/obj/item/fish/sand_crab - name = "burrower crab" - desc = "A sand-dwelling crustacean. It looks like a crab and tastes like a crab, but waddles like a fish." - icon_state = "crab" - sprite_height = 6 - sprite_width = 10 - average_size = 60 - average_weight = 1000 - weight_size_deviation = 0.1 - required_fluid_type = AQUARIUM_FLUID_SALTWATER - required_temperature_min = MIN_AQUARIUM_TEMP+20 - required_temperature_max = MIN_AQUARIUM_TEMP+40 - fillet_type = /obj/item/food/meat/slab/rawcrab - fish_traits = list(/datum/fish_trait/amphibious, /datum/fish_trait/shiny_lover, /datum/fish_trait/carnivore) - fish_movement_type = /datum/fish_movement/slow - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = SEAFOOD, - ), - ) - -/obj/item/fish/bumpy - name = "bump-fish" - desc = "An misshapen fish-thing all covered in stubby little tendrils" - icon_state = "bumpy" - sprite_height = 4 - sprite_width = 5 - stable_population = 4 - required_fluid_type = AQUARIUM_FLUID_ANY_WATER - required_temperature_min = MIN_AQUARIUM_TEMP+15 - required_temperature_max = MIN_AQUARIUM_TEMP+40 - beauty = FISH_BEAUTY_BAD - fish_traits = list(/datum/fish_trait/amphibious, /datum/fish_trait/vegan) - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = VEGETABLES, - ), - ) - -/obj/item/fish/swordfish - name = "swordfish" - desc = "A large billfish, most famous for its elongated bill, while also fairly popular for cooking, and as a fearsome weapon in the hands of a veteran spess-fisherman." - icon = 'icons/obj/aquarium/wide.dmi' - icon_state = "swordfish" - inhand_icon_state = "swordfish" - force = 18 - sharpness = SHARP_EDGED - attack_verb_continuous = list("slashes", "cuts", "pierces") - attack_verb_simple = list("slash", "cut", "pierce") - block_sound = 'sound/weapons/parry.ogg' - hitsound = 'sound/weapons/rapierhit.ogg' - demolition_mod = 0.75 - attack_speed = 1 SECONDS - block_chance = 50 - wound_bonus = 10 - bare_wound_bonus = 20 - armour_penetration = 75 - base_pixel_x = -18 - pixel_x = -18 - sprite_width = 13 - sprite_height = 6 - stable_population = 3 - average_size = 140 - average_weight = 4000 - breeding_timeout = 4.5 MINUTES - feeding_frequency = 4 MINUTES - health = 180 - beauty = FISH_BEAUTY_EXCELLENT - random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS - required_fluid_type = AQUARIUM_FLUID_SALTWATER - fish_movement_type = /datum/fish_movement/plunger - fishing_difficulty_modifier = 25 - fillet_type = /obj/item/food/fishmeat/quality - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = SEAFOOD, - ), - ) - fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/stinger) - -/obj/item/fish/swordfish/get_force_rank() - switch(w_class) - if(WEIGHT_CLASS_TINY) - force -= 11 - attack_speed -= 0.4 SECONDS - block_chance -= 45 - armour_penetration -= 20 - wound_bonus -= 15 - bare_wound_bonus -= 20 - if(WEIGHT_CLASS_SMALL) - force -= 8 - attack_speed -= 0.3 SECONDS - block_chance -= 30 - armour_penetration -= 15 - wound_bonus -= 10 - bare_wound_bonus -= 20 - if(WEIGHT_CLASS_NORMAL) - force -= 5 - attack_speed -= 0.2 SECONDS - block_chance -= 20 - armour_penetration -= 10 - wound_bonus -= 10 - bare_wound_bonus -= 15 - if(WEIGHT_CLASS_BULKY) - force -= 3 - attack_speed -= 0.1 SECONDS - block_chance -= 10 - armour_penetration -= 5 - wound_bonus -= 5 - bare_wound_bonus -= 10 - if(WEIGHT_CLASS_GIGANTIC) - force += 5 - attack_speed += 0.2 SECONDS - demolition_mod += 0.15 - block_chance += 10 - armour_penetration += 5 - wound_bonus += 5 - bare_wound_bonus += 10 - - if(status == FISH_DEAD) - force -= 4 + w_class - block_chance -= 25 - armour_penetration -= 30 - wound_bonus -= 10 - bare_wound_bonus -= 10 - -/obj/item/fish/swordfish/calculate_fish_force_bonus(bonus_malus) - . = ..() - armour_penetration += bonus_malus * 5 - wound_bonus += bonus_malus * 3 - bare_wound_bonus += bonus_malus * 5 - block_chance += bonus_malus * 7 - -/obj/item/fish/chainsawfish - name = "chainsawfish" - desc = "A very, very angry bioweapon, whose sole purpose is to rip and tear." - icon = 'icons/obj/aquarium/wide.dmi' - icon_state = "chainsawfish" - inhand_icon_state = "chainsawfish" - icon_state_dead = "chainsawfish_dead" - force = 22 - demolition_mod = 1.5 - block_chance = 15 - attack_verb_continuous = list("saws", "tears", "lacerates", "cuts", "chops", "dices") - attack_verb_simple = list("saw", "tear", "lacerate", "cut", "chop", "dice") - hitsound = 'sound/weapons/chainsawhit.ogg' - sharpness = SHARP_EDGED - tool_behaviour = TOOL_SAW - toolspeed = 0.5 - base_pixel_x = -16 - pixel_x = -16 - sprite_width = 8 - sprite_height = 5 - stable_population = 3 - average_size = 85 - average_weight = 2500 - breeding_timeout = 4.25 MINUTES - feeding_frequency = 3 MINUTES - health = 180 - beauty = FISH_BEAUTY_GREAT - random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS - required_fluid_type = AQUARIUM_FLUID_FRESHWATER - fish_movement_type = /datum/fish_movement/accelerando - fishing_difficulty_modifier = 30 - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = GORE - ), - ) - fish_traits = list(/datum/fish_trait/aggressive, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/stinger) - required_temperature_min = MIN_AQUARIUM_TEMP+18 - required_temperature_max = MIN_AQUARIUM_TEMP+26 - -/obj/item/fish/chainsawfish/Initialize(mapload) - . = ..() - AddElement(/datum/element/update_icon_updates_onmob) - -/obj/item/fish/chainsawfish/update_icon_state() - if(status == FISH_DEAD) - inhand_icon_state = "chainsawfish_dead" - else - inhand_icon_state = "chainsawfish" - if(HAS_TRAIT(src, TRAIT_WIELDED)) - inhand_icon_state = "[inhand_icon_state]_wielded" - return ..() - -/obj/item/fish/chainsawfish/get_force_rank() - switch(w_class) - if(WEIGHT_CLASS_TINY) - force -= 10 - attack_speed -= 0.2 SECONDS - demolition_mod -= 0.4 - block_chance -= 15 - armour_penetration -= 10 - wound_bonus -= 10 - bare_wound_bonus -= 10 - toolspeed += 0.6 - if(WEIGHT_CLASS_SMALL) - force -= 8 - attack_speed -= 0.1 SECONDS - demolition_mod -= 0.3 - block_chance -= 10 - armour_penetration -= 10 - wound_bonus -= 10 - bare_wound_bonus -= 10 - toolspeed += 0.4 - if(WEIGHT_CLASS_NORMAL) - force -= 5 - demolition_mod -= 0.15 - block_chance -= 5 - armour_penetration -= 5 - wound_bonus -= 5 - bare_wound_bonus -= 5 - toolspeed += 0.2 - if(WEIGHT_CLASS_HUGE) - force += 2 - attack_speed += 0.2 SECONDS - demolition_mod += 0.15 - armour_penetration += 10 - block_chance += 10 - wound_bonus += 10 - bare_wound_bonus += 5 - if(WEIGHT_CLASS_GIGANTIC) - force += 4 - attack_speed += 0.4 SECONDS - demolition_mod += 0.3 - block_chance += 20 - armour_penetration += 20 - wound_bonus += 15 - bare_wound_bonus += 10 - toolspeed -= 0.1 - - if(status == FISH_DEAD) - force -= 8 + w_class - hitsound = SFX_SWING_HIT - block_chance -= 25 - demolition_mod -= 0.3 - armour_penetration -= 15 - wound_bonus -= 5 - bare_wound_bonus -= 5 - toolspeed += 1 - -/obj/item/fish/chainsawfish/calculate_fish_force_bonus(bonus_malus) - . = ..() - armour_penetration += bonus_malus * 3 - wound_bonus += bonus_malus * 2 - bare_wound_bonus += bonus_malus * 3 - block_chance += bonus_malus * 2 - toolspeed -= bonus_malus * 0.1 - -/obj/item/fish/soul - name = "soulfish" - desc = "A distant yet vaguely close critter, like a long lost relative. You feel your soul rejuvenated just from looking at it... Also, what the fuck is this shit?!" - icon_state = "soulfish" - sprite_width = 7 - sprite_height = 6 - average_size = 60 - average_weight = 1200 - stable_population = 4 - show_in_catalog = FALSE - beauty = FISH_BEAUTY_EXCELLENT - fish_movement_type = /datum/fish_movement/choppy //Glideless legacy movement? in my fishing minigame? - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = FRIED - ), - ) - fillet_type = /obj/item/food/meat/cutlet/plain/human - required_temperature_min = MIN_AQUARIUM_TEMP+3 - required_temperature_max = MIN_AQUARIUM_TEMP+38 - random_case_rarity = FISH_RARITY_NOPE - -/obj/item/fish/skin_crab - name = "skin crab" - desc = "\"And on the eighth day, a demential mockery of both humanity and crabity was made.\" Fascinating." - icon_state = "skin_crab" - sprite_width = 7 - sprite_height = 6 - average_size = 40 - average_weight = 750 - stable_population = 5 - show_in_catalog = FALSE - beauty = FISH_BEAUTY_GREAT - favorite_bait = list( - list( - "Type" = "Foodtype", - "Value" = FRIED - ), - ) - fillet_type = /obj/item/food/meat/slab/rawcrab - random_case_rarity = FISH_RARITY_NOPE diff --git a/code/modules/fishing/fish/types/air_space.dm b/code/modules/fishing/fish/types/air_space.dm new file mode 100644 index 00000000000..f3b985edfa7 --- /dev/null +++ b/code/modules/fishing/fish/types/air_space.dm @@ -0,0 +1,95 @@ +/obj/item/fish/sand_surfer + name = "sand surfer" + desc = "A bronze alien \"fish\" living and swimming underneath faraway sandy places." + icon_state = "sand_surfer" + sprite_height = 6 + sprite_width = 6 + stable_population = 5 + average_size = 65 + average_weight = 1100 + weight_size_deviation = 0.35 + random_case_rarity = FISH_RARITY_RARE + required_fluid_type = AQUARIUM_FLUID_AIR + required_temperature_min = MIN_AQUARIUM_TEMP+25 + required_temperature_max = MIN_AQUARIUM_TEMP+60 + fish_movement_type = /datum/fish_movement/plunger + fishing_difficulty_modifier = 5 + fish_traits = list(/datum/fish_trait/shiny_lover) + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/sand_crab + name = "burrower crab" + desc = "A sand-dwelling crustacean. It looks like a crab and tastes like a crab, but waddles like a fish." + icon_state = "crab" + dedicated_in_aquarium_icon_state = "crab_small" + sprite_height = 6 + sprite_width = 10 + average_size = 60 + average_weight = 1000 + weight_size_deviation = 0.1 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + required_temperature_min = MIN_AQUARIUM_TEMP+20 + required_temperature_max = MIN_AQUARIUM_TEMP+40 + fillet_type = /obj/item/food/meat/slab/rawcrab + fish_traits = list(/datum/fish_trait/amphibious, /datum/fish_trait/shiny_lover, /datum/fish_trait/carnivore) + fish_movement_type = /datum/fish_movement/slow + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = SEAFOOD, + ), + ) + +/obj/item/fish/bumpy + name = "bump-fish" + desc = "An misshapen fish-thing all covered in stubby little tendrils" + icon_state = "bumpy" + sprite_height = 4 + sprite_width = 5 + stable_population = 4 + required_fluid_type = AQUARIUM_FLUID_ANY_WATER + required_temperature_min = MIN_AQUARIUM_TEMP+15 + required_temperature_max = MIN_AQUARIUM_TEMP+40 + beauty = FISH_BEAUTY_BAD + fish_traits = list(/datum/fish_trait/amphibious, /datum/fish_trait/vegan) + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = VEGETABLES, + ), + ) + +/obj/item/fish/starfish + name = "cosmostarfish" + desc = "A peculiar, gravity-defying, echinoderm-looking critter from hyperspace." + icon_state = "starfish" + icon_state_dead = "starfish_dead" + sprite_height = 3 + sprite_width = 4 + average_size = 30 + average_weight = 300 + stable_population = 3 + required_fluid_type = AQUARIUM_FLUID_AIR + random_case_rarity = FISH_RARITY_NOPE + required_temperature_min = 0 + required_temperature_max = INFINITY + safe_air_limits = null + min_pressure = 0 + max_pressure = INFINITY + grind_results = list(/datum/reagent/bluespace = 10) + fillet_type = null + fish_traits = list(/datum/fish_trait/antigrav, /datum/fish_trait/mixotroph) + beauty = FISH_BEAUTY_GREAT + +/obj/item/fish/starfish/Initialize(mapload, apply_qualities = TRUE) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/item/fish/starfish/update_overlays() + . = ..() + if(status == FISH_ALIVE) + . += emissive_appearance(icon, "starfish_emissive", src) + +///It spins, and dimly glows in the dark. +/obj/item/fish/starfish/flop_animation() + DO_FLOATING_ANIM(src) diff --git a/code/modules/fishing/fish/types/anadromous.dm b/code/modules/fishing/fish/types/anadromous.dm new file mode 100644 index 00000000000..ecdbda1fde7 --- /dev/null +++ b/code/modules/fishing/fish/types/anadromous.dm @@ -0,0 +1,62 @@ +/obj/item/fish/sockeye_salmon + name = "sockeye salmon" + desc = "A fairly common and iconic salmon endemic of the Pacific Ocean. At some point imported into outer space, where we're now." + icon_state = "sockeye" + sprite_width = 6 + sprite_height = 4 + stable_population = 6 + required_temperature_min = MIN_AQUARIUM_TEMP+3 + required_temperature_max = MIN_AQUARIUM_TEMP+19 + required_fluid_type = AQUARIUM_FLUID_ANADROMOUS + fillet_type = /obj/item/food/fishmeat/salmon + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/arctic_char + name = "arctic char" + desc = "A cold-water anadromous fish widespread around the Northern Hemisphere of Earth, yet it has somehow found a way here." + icon_state = "arctic_char" + sprite_width = 7 + sprite_height = 4 + stable_population = 6 + average_size = 60 + average_weight = 1200 + weight_size_deviation = 0.5 // known for their size dismophism + required_temperature_min = MIN_AQUARIUM_TEMP+3 + required_temperature_max = MIN_AQUARIUM_TEMP+19 + required_fluid_type = AQUARIUM_FLUID_ANADROMOUS + +/obj/item/fish/pike + name = "pike" + desc = "A long-bodied predator with a snout that almost looks like a beak. Definitely not a weapon to swing around." + icon = 'icons/obj/aquarium/wide.dmi' + icon_state = "pike" + inhand_icon_state = "pike" + base_pixel_x = -16 + pixel_x = -16 + stable_population = 4 + sprite_width = 10 + sprite_height = 3 + average_size = 100 + average_weight = 2000 + breeding_timeout = 4 MINUTES + health = 150 + beauty = FISH_BEAUTY_GOOD + required_fluid_type = AQUARIUM_FLUID_ANADROMOUS + random_case_rarity = FISH_RARITY_RARE + fish_movement_type = /datum/fish_movement/plunger + fishing_difficulty_modifier = 10 + required_temperature_min = MIN_AQUARIUM_TEMP+12 + required_temperature_max = MIN_AQUARIUM_TEMP+27 + fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/aggressive) + compatible_types = list(/obj/item/fish/pike/armored) + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = SEAFOOD|MEAT, + ), + /obj/item/fish, + ) + +/obj/item/fish/pike/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_FISH_SHOULD_TWOHANDED, INNATE_TRAIT) diff --git a/code/modules/fishing/fish/types/freshwater.dm b/code/modules/fishing/fish/types/freshwater.dm new file mode 100644 index 00000000000..129b738fc0a --- /dev/null +++ b/code/modules/fishing/fish/types/freshwater.dm @@ -0,0 +1,213 @@ +/obj/item/fish/goldfish + name = "goldfish" + desc = "Despite common belief, goldfish do not have three-second memories. \ + They can actually remember things that happened up to three months ago." + icon_state = "goldfish" + dedicated_in_aquarium_icon_state = "fish_greyscale" + aquarium_vc_color = "#D8540D" + sprite_width = 5 + sprite_height = 3 + stable_population = 9 + average_size = 20 + average_weight = 200 + weight_size_deviation = 0.35 + favorite_bait = list(/obj/item/food/bait/worm) + required_temperature_min = MIN_AQUARIUM_TEMP+18 + required_temperature_max = MIN_AQUARIUM_TEMP+26 + evolution_types = list(/datum/fish_evolution/three_eyes, /datum/fish_evolution/chainsawfish) + compatible_types = list(/obj/item/fish/goldfish/gill, /obj/item/fish/goldfish/three_eyes, /obj/item/fish/goldfish/three_eyes/gill) + +/obj/item/fish/goldfish/Initialize(mapload, apply_qualities = TRUE) + . = ..() + add_traits(list(TRAIT_FISHING_BAIT, TRAIT_GOOD_QUALITY_BAIT), INNATE_TRAIT) + +/obj/item/fish/goldfish/gill + name = "McGill" + desc = "A great rubber duck tool for Lawyers who can't get a grasp over their case." + stable_population = 1 + random_case_rarity = FISH_RARITY_NOPE + show_in_catalog = FALSE + beauty = FISH_BEAUTY_GOOD + compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/three_eyes) + fish_traits = list(/datum/fish_trait/recessive) + +/obj/item/fish/goldfish/three_eyes + name = "three-eyed goldfish" + desc = "A goldfish with an extra half a pair of eyes. You wonder what it's been feeding on lately..." + icon_state = "three_eyes" + stable_population = 4 + fish_traits = list(/datum/fish_trait/recessive, /datum/fish_trait/shiny_lover) + compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/gill, /obj/item/fish/goldfish/three_eyes/gill) + beauty = FISH_BEAUTY_GOOD + fishing_difficulty_modifier = 10 + random_case_rarity = FISH_RARITY_VERY_RARE + food = /datum/reagent/toxin/mutagen + favorite_bait = list( + list( + "Type" = "Reagent", + "Value" = /datum/reagent/toxin/mutagen, + "Amount" = 3, + ), + ) + +/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..." + compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/three_eyes) + beauty = FISH_BEAUTY_GREAT + show_in_catalog = FALSE + stable_population = 1 + random_case_rarity = FISH_RARITY_NOPE + +/obj/item/fish/angelfish + name = "angelfish" + desc = "Young Angelfish often live in groups, while adults prefer solitary life. They become territorial and aggressive toward other fish when they reach adulthood." + icon_state = "angelfish" + sprite_width = 4 + sprite_height = 7 + average_size = 30 + average_weight = 500 + stable_population = 3 + fish_traits = list(/datum/fish_trait/aggressive) + required_temperature_min = MIN_AQUARIUM_TEMP+22 + required_temperature_max = MIN_AQUARIUM_TEMP+30 + +/obj/item/fish/guppy + name = "guppy" + desc = "Guppy is also known as rainbow fish because of the brightly colored body and fins." + icon_state = "guppy" + sprite_width = 5 + sprite_height = 2 + sprite_width = 8 + sprite_height = 5 + average_size = 30 + average_weight = 500 + stable_population = 6 + required_temperature_min = MIN_AQUARIUM_TEMP+20 + required_temperature_max = MIN_AQUARIUM_TEMP+28 + +/obj/item/fish/plasmatetra + name = "plasma tetra" + desc = "Due to their small size, tetras are prey to many predators in their watery world, including eels, crustaceans, and invertebrates." + icon_state = "plastetra" + sprite_width = 4 + sprite_height = 2 + average_size = 30 + average_weight = 500 + stable_population = 3 + required_temperature_min = MIN_AQUARIUM_TEMP+20 + required_temperature_max = MIN_AQUARIUM_TEMP+28 + +/obj/item/fish/catfish + name = "catfish" + desc = "A catfish has about 100,000 taste buds, and their bodies are covered with them to help detect chemicals present in the water and also to respond to touch." + icon_state = "catfish" + sprite_width = 8 + sprite_height = 4 + average_size = 80 + average_weight = 1600 + weight_size_deviation = 0.35 + stable_population = 3 + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = JUNKFOOD + ) + ) + required_temperature_min = MIN_AQUARIUM_TEMP+12 + required_temperature_max = MIN_AQUARIUM_TEMP+30 + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/zipzap + name = "anxious zipzap" + desc = "A fish overflowing with crippling anxiety and electric potential. Worried about the walls of its tank closing in constantly. Both literally and as a general metaphorical unease about life's direction." + icon_state = "zipzap" + icon_state_dead = "zipzap_dead" + sprite_width = 6 + sprite_height = 3 + stable_population = 3 + average_size = 30 + average_weight = 500 + random_case_rarity = FISH_RARITY_VERY_RARE + favorite_bait = list(/obj/item/stock_parts/power_store/cell/lead) + required_temperature_min = MIN_AQUARIUM_TEMP+18 + required_temperature_max = MIN_AQUARIUM_TEMP+26 + fish_traits = list( + /datum/fish_trait/no_mating, + /datum/fish_trait/wary, + /datum/fish_trait/anxiety, + /datum/fish_trait/electrogenesis, + ) + //anxiety naturally limits the amount of zipzaps per tank, so they are stronger alone + electrogenesis_power = 20 MEGA JOULES + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/tadpole + name = "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 + average_weight = 10 + sprite_width = 3 + sprite_height = 1 + health = 50 + feeding_frequency = 1.5 MINUTES + required_temperature_min = MIN_AQUARIUM_TEMP+15 + required_temperature_max = MIN_AQUARIUM_TEMP+20 + fillet_type = null + fish_traits = list(/datum/fish_trait/no_mating) //They grow into frogs and that's it. + beauty = FISH_BEAUTY_NULL + random_case_rarity = FISH_RARITY_NOPE //Why would you want generic frog tadpoles you get from ponds inside fish cases? + /// Once dead, tadpoles disappear after a dozen seconds, since you can get infinite tadpoles. + var/del_timerid + +/obj/item/fish/tadpole/Initialize(mapload, apply_qualities = TRUE) + . = ..() + AddComponent(/datum/component/fish_growth, /mob/living/basic/frog, 100 / rand(2.5, 3 MINUTES) * 10) + RegisterSignal(src, COMSIG_FISH_BEFORE_GROWING, PROC_REF(growth_checks)) + RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth)) + +/obj/item/fish/tadpole/set_status(new_status, silent = FALSE) + . = ..() + if(status == FISH_DEAD) + del_timerid = QDEL_IN_STOPPABLE(src, 12 SECONDS) + else + deltimer(del_timerid) + +/obj/item/fish/tadpole/proc/growth_checks(datum/source, seconds_per_tick) + SIGNAL_HANDLER + var/hunger = CLAMP01((world.time - last_feeding) / feeding_frequency) + if(hunger >= 0.7) //too hungry to grow + return COMPONENT_DONT_GROW + var/obj/structure/aquarium/aquarium = loc + if(!aquarium.allow_breeding) //the aquarium has breeding disabled + return COMPONENT_DONT_GROW + +/obj/item/fish/tadpole/proc/on_growth(datum/source, mob/living/basic/frog/result) + SIGNAL_HANDLER + playsound(result, result.attack_sound, 50, TRUE) // reeeeeeeeeeeeeee... + +/obj/item/fish/tadpole/get_export_price(price, percent) + return 2 //two credits. Tadpoles aren't really that valueable. + +/obj/item/fish/perch + name = "perch" + desc = "An all around popular panfish, game fish and unfortunate prey to other, bigger predators." + icon_state = "perch" + dedicated_in_aquarium_icon_state = "fish_greyscale" + aquarium_vc_color = "#9D8C64" + sprite_width = 5 + sprite_height = 3 + stable_population = 7 + average_size = 25 + average_weight = 400 + required_temperature_min = MIN_AQUARIUM_TEMP+5 + required_temperature_max = MIN_AQUARIUM_TEMP+26 + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = BUGS, + ), + /obj/item/fish, + /obj/item/fishing_lure, //they love lures in general. + ) diff --git a/code/modules/fishing/fish/types/holographic.dm b/code/modules/fishing/fish/types/holographic.dm new file mode 100644 index 00000000000..dfcdbeedb6b --- /dev/null +++ b/code/modules/fishing/fish/types/holographic.dm @@ -0,0 +1,108 @@ + +/obj/item/fish/holo + name = "holographic goldfish" + desc = "A holographic representation of a common goldfish, slowly flickering out, removed from its holo-habitat." + icon_state = /obj/item/fish/goldfish::icon_state + show_in_catalog = FALSE + random_case_rarity = FISH_RARITY_NOPE + dedicated_in_aquarium_icon_state = /obj/item/fish/goldfish::dedicated_in_aquarium_icon_state + aquarium_vc_color = /obj/item/fish/goldfish::aquarium_vc_color + sprite_width = /obj/item/fish/goldfish::sprite_width + sprite_height = /obj/item/fish/goldfish::sprite_height + stable_population = 1 + average_size = /obj/item/fish/goldfish::average_size + average_weight = /obj/item/fish/goldfish::average_weight + required_fluid_type = AQUARIUM_FLUID_ANADROMOUS + grind_results = null + fillet_type = null + death_text = "%SRC gently disappears." + fish_traits = list(/datum/fish_trait/no_mating) //just to be sure, these shouldn't reproduce + experisci_scannable = FALSE + beauty = /obj/item/fish/goldfish::beauty + +/obj/item/fish/holo/Initialize(mapload, apply_qualities = TRUE) + . = ..() + var/area/station/holodeck/holo_area = get_area(src) + if(!istype(holo_area)) + addtimer(CALLBACK(src, PROC_REF(set_status), FISH_DEAD), 1 MINUTES) + return + holo_area.linked.add_to_spawned(src) + +/obj/item/fish/holo/set_status(new_status, silent = FALSE) + . = ..() + if(status == FISH_DEAD) + animate(src, alpha = 0, 3 SECONDS, easing = SINE_EASING) + QDEL_IN(src, 3 SECONDS) + +/obj/item/fish/holo/crab + name = "holographic crab" + desc = "A holographic represantion of a soul-crushingly soulless crab, unlike the cuter ones occasionally roaming around. It stares at you, with empty, beady eyes." + icon_state = "crab" + dedicated_in_aquarium_icon_state = null + aquarium_vc_color = null + average_size = 30 + average_weight = 1000 + sprite_height = 6 + sprite_width = 10 + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/holo/puffer + name = "holographic pufferfish" + desc ="A holographic representation of 100% safe-to-eat pufferfish... that is, if holographic fishes were even edible." + icon_state = /obj/item/fish/pufferfish::icon_state + dedicated_in_aquarium_icon_state = /obj/item/fish/pufferfish::dedicated_in_aquarium_icon_state + aquarium_vc_color = /obj/item/fish/pufferfish::aquarium_vc_color + average_size = /obj/item/fish/pufferfish::average_size + average_weight = /obj/item/fish/pufferfish::average_weight + sprite_height = /obj/item/fish/pufferfish::sprite_height + sprite_width = /obj/item/fish/pufferfish::sprite_width + beauty = /obj/item/fish/pufferfish::beauty + +/obj/item/fish/holo/angel + name = "holographic angelfish" + desc = "A holographic representation of a angelfish. I got nothing snarky to say about this one." + icon_state = /obj/item/fish/angelfish::icon_state + dedicated_in_aquarium_icon_state = /obj/item/fish/angelfish::dedicated_in_aquarium_icon_state + aquarium_vc_color = /obj/item/fish/angelfish::aquarium_vc_color + average_size = /obj/item/fish/angelfish::average_size + average_weight = /obj/item/fish/angelfish::average_weight + sprite_height = /obj/item/fish/angelfish::sprite_height + sprite_width = /obj/item/fish/angelfish::sprite_width + beauty = /obj/item/fish/angelfish::beauty + +/obj/item/fish/holo/clown + name = "holographic clownfish" + icon_state = "holo_clownfish" + desc = "A holographic representation of a clownfish, or at least how they used to look like five centuries ago." + dedicated_in_aquarium_icon_state = null + aquarium_vc_color = /obj/item/fish/clownfish::aquarium_vc_color + average_size = /obj/item/fish/clownfish::average_size + average_weight = /obj/item/fish/clownfish::average_weight + sprite_height = /obj/item/fish/clownfish::sprite_height + sprite_width = /obj/item/fish/clownfish::sprite_width + required_fluid_type = /obj/item/fish/clownfish::required_fluid_type + beauty = /obj/item/fish/clownfish::beauty + +/obj/item/fish/holo/checkered + name = "unrendered holographic fish" + desc = "A checkered silhoutte of searing purple and pitch black presents itself before your eyes, like a tear in fabric of reality. It hurts to watch." + icon_state = "checkered" //it's a meta joke, buddy. + dedicated_in_aquarium_icon_state = null + aquarium_vc_color = null + average_size = 30 + average_weight = 500 + sprite_width = 4 + sprite_height = 3 + beauty = FISH_BEAUTY_NULL + +/obj/item/fish/holo/halffish + name = "holographic half-fish" + desc = "A holographic representation of... a fish reduced to all bones, except for its head. Isn't it supposed to be dead? Ehr, holo-dead?" + icon_state = "half_fish" + dedicated_in_aquarium_icon_state = null + aquarium_vc_color = null + sprite_height = 4 + sprite_width = 10 + average_size = 50 + average_weight = 500 + beauty = FISH_BEAUTY_UGLY diff --git a/code/modules/fishing/fish/types/mining.dm b/code/modules/fishing/fish/types/mining.dm new file mode 100644 index 00000000000..7614f259e59 --- /dev/null +++ b/code/modules/fishing/fish/types/mining.dm @@ -0,0 +1,198 @@ +/// Commonly found on the mining fishing spots. Can be grown into lobstrosities +/obj/item/fish/chasm_crab + name = "chasm chrab" + desc = "The young of the lobstrosity mature in pools below the earth, eating what falls in until large enough to clamber out. Those found near the station are well-fed." + icon_state = "chrab" + sprite_height = 9 + sprite_width = 8 + stable_population = 4 + feeding_frequency = 10 MINUTES + random_case_rarity = FISH_RARITY_RARE + fillet_type = /obj/item/food/meat/slab/rawcrab + required_temperature_min = MIN_AQUARIUM_TEMP+9 + required_temperature_max = LAVALAND_MAX_TEMPERATURE+50 + min_pressure = HAZARD_LOW_PRESSURE + safe_air_limits = list( + /datum/gas/oxygen = list(2, 100), + /datum/gas/nitrogen, + /datum/gas/carbon_dioxide = list(0, 20), + /datum/gas/water_vapor, + /datum/gas/plasma = list(0, 5), + /datum/gas/bz = list(0, 5), + /datum/gas/miasma = list(0, 5), + ) + evolution_types = list(/datum/fish_evolution/ice_chrab) + compatible_types = list(/obj/item/fish/chasm_crab/ice) + beauty = FISH_BEAUTY_GOOD + favorite_bait = list(/obj/item/fish/lavaloop) + ///This value represents how much the crab needs aren't being met. Higher values translate to a more likely hostile lobstrosity. + var/anger = 0 + ///The lobstrosity type this matures into + var/lob_type = /mob/living/basic/mining/lobstrosity/juvenile/lava + ///at which rate the crab gains maturation + var/growth_rate = 100 / (10 MINUTES) * 10 + +/obj/item/fish/chasm_crab/Initialize(mapload, apply_qualities = TRUE) + . = ..() + RegisterSignal(src, COMSIG_FISH_BEFORE_GROWING, PROC_REF(growth_checks)) + RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth)) + +///A chasm crab growth speed is determined by its initial weight and size, ergo bigger crabs for faster lobstrosities +/obj/item/fish/chasm_crab/update_size_and_weight(new_size = average_size, new_weight = average_weight) + . = ..() + var/multiplier = 1 + switch(size) + if(0 to FISH_SIZE_TINY_MAX) + multiplier -= 0.2 + if(FISH_SIZE_SMALL_MAX to FISH_SIZE_NORMAL_MAX) + multiplier += 0.2 + if(FISH_SIZE_NORMAL_MAX to FISH_SIZE_BULKY_MAX) + multiplier += 0.5 + if(FISH_SIZE_BULKY_MAX to INFINITY) + multiplier += 0.8 + + if(weight <= 800) + multiplier -= 0.1 * round((1000 - weight) / 200) + else if(weight >= 1500) + multiplier += min(0.1 * round((weight - 1000) / 500), 2) + + AddComponent(/datum/component/fish_growth, lob_type, initial(growth_rate) * multiplier) + +/obj/item/fish/chasm_crab/proc/growth_checks(datum/source, seconds_per_tick) + SIGNAL_HANDLER + var/hunger = CLAMP01((world.time - last_feeding) / feeding_frequency) + if(health <= initial(health) * 0.6 || hunger >= 0.6) //if too hurt or hungry, don't grow. + anger += growth_rate * 2 * seconds_per_tick + return COMPONENT_DONT_GROW + + if(hunger >= 0.4) //I'm hungry and angry + anger += growth_rate * 0.6 * seconds_per_tick + + if(!isaquarium(loc)) + return + + var/obj/structure/aquarium/aquarium = loc + if(!aquarium.allow_breeding) //the aquarium has breeding disabled + return COMPONENT_DONT_GROW + if(!locate(/obj/item/aquarium_prop) in aquarium) //the aquarium deco is quite barren + anger += growth_rate * 0.25 * seconds_per_tick + var/fish_count = length(aquarium.get_fishes()) + if(!ISINRANGE(fish_count, 3, AQUARIUM_MAX_BREEDING_POPULATION * 0.5)) //too lonely or overcrowded + anger += growth_rate * 0.3 * seconds_per_tick + if(fish_count > AQUARIUM_MAX_BREEDING_POPULATION * 0.5) //check if there's enough room to maturate. + return COMPONENT_DONT_GROW + +/obj/item/fish/chasm_crab/proc/on_growth(datum/source, mob/living/basic/mining/lobstrosity/juvenile/result) + SIGNAL_HANDLER + if(!prob(anger)) + result.AddElement(/datum/element/ai_retaliate) + qdel(result.ai_controller) + result.ai_controller = new /datum/ai_controller/basic_controller/lobstrosity/juvenile/calm(result) + else if(anger < 30) //not really that mad, just a bit unstable. + qdel(result.ai_controller) + result.ai_controller = new /datum/ai_controller/basic_controller/lobstrosity/juvenile/capricious(result) + +/obj/item/fish/chasm_crab/ice + name = "arctic chrab" + desc = "A subspecies of chasm chrabs that has adapted to the cold climate and lack of abysmal holes of the icemoon." + icon_state = "arctic_chrab" + required_temperature_min = ICEBOX_MIN_TEMPERATURE-20 + required_temperature_max = MIN_AQUARIUM_TEMP+15 + evolution_types = list(/datum/fish_evolution/chasm_chrab) + compatible_types = list(/obj/item/fish/chasm_crab) + beauty = FISH_BEAUTY_GREAT + lob_type = /mob/living/basic/mining/lobstrosity/juvenile + +/obj/item/fish/boned + name = "unmarine bonemass" + desc = "What one could mistake for fish remains, is in reality a species that chose to discard its weak flesh a long time ago. A living fossil, in its most literal sense." + icon_state = "bonemass" + sprite_width = 10 + sprite_height = 7 + fish_movement_type = /datum/fish_movement/zippy + random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS + required_fluid_type = AQUARIUM_FLUID_ANY_WATER + min_pressure = HAZARD_LOW_PRESSURE + health = 150 + stable_population = 3 + grind_results = list(/datum/reagent/bone_dust = 10) + fillet_type = /obj/item/stack/sheet/bone + num_fillets = 2 + fish_traits = list(/datum/fish_trait/revival, /datum/fish_trait/carnivore) + average_size = 70 + average_weight = 2000 + death_text = "%SRC stops moving." //It's dead... or is it? + evolution_types = list(/datum/fish_evolution/mastodon) + beauty = FISH_BEAUTY_UGLY + +/obj/item/fish/lavaloop + name = "lavaloop fish" + desc = "Due to its curvature, it can be used as make-shift boomerang." + icon_state = "lava_loop" + sprite_width = 3 + sprite_height = 5 + average_size = 30 + average_weight = 500 + resistance_flags = FIRE_PROOF | LAVA_PROOF + required_fluid_type = AQUARIUM_FLUID_ANY_WATER //if we can survive hot lava and freezing plasrivers, we can survive anything + fish_movement_type = /datum/fish_movement/zippy + min_pressure = HAZARD_LOW_PRESSURE + required_temperature_min = MIN_AQUARIUM_TEMP+30 + required_temperature_max = MIN_AQUARIUM_TEMP+35 + fish_traits = list( + /datum/fish_trait/carnivore, + /datum/fish_trait/heavy, + ) + hitsound = null + throwforce = 5 + beauty = FISH_BEAUTY_GOOD + ///maximum bonus damage when winded up + var/maximum_bonus = 25 + +/obj/item/fish/lavaloop/Initialize(mapload, apply_qualities = TRUE) + . = ..() + add_traits(list(TRAIT_FISHING_BAIT, TRAIT_GOOD_QUALITY_BAIT, TRAIT_BYPASS_RANGED_ARMOR), INNATE_TRAIT) + AddComponent(/datum/component/boomerang, throw_range, TRUE) + AddComponent(\ + /datum/component/throwbonus_on_windup,\ + maximum_bonus = maximum_bonus,\ + windup_increment_speed = 2,\ + throw_text = "starts cooking in your hands, it may explode soon!",\ + pass_maximum_callback = CALLBACK(src, PROC_REF(explode_on_user)),\ + apply_bonus_callback = CALLBACK(src, PROC_REF(on_fish_land)),\ + sound_on_success = 'sound/weapons/parry.ogg',\ + effect_on_success = /obj/effect/temp_visual/guardian/phase,\ + ) + +/obj/item/fish/lavaloop/proc/explode_on_user(mob/living/user) + var/obj/item/bodypart/arm/active_arm = user.get_active_hand() + active_arm?.dismember() + to_chat(user, span_warning("[src] explodes!")) + playsound(src, 'sound/effects/explosion1.ogg', 40, TRUE) + user.flash_act(1, 1) + qdel(src) + +/obj/item/fish/lavaloop/proc/on_fish_land(mob/living/target, bonus_value) + if(!istype(target)) + return FALSE + return (target.mob_size >= MOB_SIZE_LARGE) + +/obj/item/fish/lavaloop/plasma_river + maximum_bonus = 30 + +/obj/item/fish/lavaloop/plasma_river/explode_on_user(mob/living/user) + playsound(src, 'sound/effects/explosion1.ogg', 40, TRUE) + user.flash_act(1, 1) + user.apply_status_effect(/datum/status_effect/ice_block_talisman, 5 SECONDS) + qdel(src) + +/obj/item/fish/lavaloop/plasma_river/on_fish_land(mob/living/target, bonus_value) + if(!istype(target)) + return FALSE + if(target.mob_size < MOB_SIZE_LARGE) + return FALSE + var/freeze_timer = (bonus_value * 0.1) + if(freeze_timer <= 0) + return FALSE + target.apply_status_effect(/datum/status_effect/ice_block_talisman, freeze_timer SECONDS) + return FALSE diff --git a/code/modules/fishing/fish/types/ruins.dm b/code/modules/fishing/fish/types/ruins.dm new file mode 100644 index 00000000000..da9e8388c0c --- /dev/null +++ b/code/modules/fishing/fish/types/ruins.dm @@ -0,0 +1,73 @@ +///From oil puddles from the elephant graveyard. Also an evolution of the "unmarine bonemass" +/obj/item/fish/mastodon + name = "unmarine mastodon" + desc = "A monster of exposed muscles and innards, wrapped in a fish-like skeleton. You don't remember ever seeing it on the catalog." + icon = 'icons/obj/aquarium/wide.dmi' + icon_state = "mastodon" + base_pixel_x = -16 + pixel_x = -16 + sprite_width = 12 + sprite_height = 7 + show_in_catalog = FALSE + random_case_rarity = FISH_RARITY_NOPE + fishing_difficulty_modifier = 30 + required_fluid_type = AQUARIUM_FLUID_ANY_WATER + min_pressure = HAZARD_LOW_PRESSURE + health = 300 + stable_population = 1 //This means they can only crossbreed. + grind_results = list(/datum/reagent/bone_dust = 5, /datum/reagent/consumable/liquidgibs = 5) + fillet_type = /obj/item/stack/sheet/bone + num_fillets = 2 + feeding_frequency = 2 MINUTES + breeding_timeout = 5 MINUTES + average_size = 180 + average_weight = 5000 + death_text = "%SRC stops moving." + fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/revival, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/aggressive) + beauty = FISH_BEAUTY_BAD + +///From the cursed spring +/obj/item/fish/soul + name = "soulfish" + desc = "A distant yet vaguely close critter, like a long lost relative. You feel your soul rejuvenated just from looking at it... Also, what the fuck is this shit?!" + icon_state = "soulfish" + sprite_width = 7 + sprite_height = 6 + average_size = 60 + average_weight = 1200 + stable_population = 4 + show_in_catalog = FALSE + beauty = FISH_BEAUTY_EXCELLENT + fish_movement_type = /datum/fish_movement/choppy //Glideless legacy movement? in my fishing minigame? + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = JUNKFOOD|FRIED, + ), + ) + fillet_type = /obj/item/food/meat/cutlet/plain/human + required_temperature_min = MIN_AQUARIUM_TEMP+3 + required_temperature_max = MIN_AQUARIUM_TEMP+38 + random_case_rarity = FISH_RARITY_NOPE + +///From the cursed spring +/obj/item/fish/skin_crab + name = "skin crab" + desc = "\"And on the eighth day, a demential mockery of both humanity and crabity was made.\" Fascinating." + icon_state = "skin_crab" + sprite_width = 7 + sprite_height = 6 + average_size = 40 + average_weight = 750 + stable_population = 5 + show_in_catalog = FALSE + beauty = FISH_BEAUTY_GREAT + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = JUNKFOOD|FRIED + ), + ) + fillet_type = /obj/item/food/meat/slab/rawcrab + random_case_rarity = FISH_RARITY_NOPE + diff --git a/code/modules/fishing/fish/types/saltwater.dm b/code/modules/fishing/fish/types/saltwater.dm new file mode 100644 index 00000000000..0271c7f3008 --- /dev/null +++ b/code/modules/fishing/fish/types/saltwater.dm @@ -0,0 +1,275 @@ +/obj/item/fish/clownfish + name = "clownfish" + desc = "Clownfish catch prey by swimming onto the reef, attracting larger fish, and luring them back to the anemone. The anemone will sting and eat the larger fish, leaving the remains for the clownfish." + icon_state = "clownfish" + required_fluid_type = AQUARIUM_FLUID_SALTWATER + sprite_width = 7 + sprite_height = 4 + average_size = 30 + average_weight = 500 + stable_population = 4 + fish_traits = list(/datum/fish_trait/picky_eater) + evolution_types = list(/datum/fish_evolution/lubefish) + compatible_types = list(/obj/item/fish/clownfish/lube) + required_temperature_min = MIN_AQUARIUM_TEMP+22 + required_temperature_max = MIN_AQUARIUM_TEMP+30 + +/obj/item/fish/clownfish/lube + name = "lubefish" + desc = "A clownfish exposed to cherry-flavored lube for far too long. First discovered the days following a cargo incident around the seas of Europa, when thousands of thousands of thousands..." + icon_state = "lubefish" + random_case_rarity = FISH_RARITY_VERY_RARE + fish_traits = list(/datum/fish_trait/picky_eater, /datum/fish_trait/lubed) + evolution_types = null + compatible_types = list(/obj/item/fish/clownfish) + food = /datum/reagent/lube + fishing_difficulty_modifier = 5 + beauty = FISH_BEAUTY_GREAT + +/obj/item/fish/cardinal + name = "cardinalfish" + desc = "Cardinalfish are often found near sea urchins, where the fish hide when threatened." + icon_state = "cardinalfish" + sprite_width = 6 + sprite_height = 3 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + average_size = 30 + average_weight = 500 + stable_population = 4 + fish_traits = list(/datum/fish_trait/vegan) + required_temperature_min = MIN_AQUARIUM_TEMP+22 + required_temperature_max = MIN_AQUARIUM_TEMP+30 + +/obj/item/fish/greenchromis + name = "green chromis" + desc = "The Chromis can vary in color from blue to green depending on the lighting and distance from the lights." + icon_state = "greenchromis" + sprite_width = 5 + sprite_height = 3 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + average_size = 30 + average_weight = 500 + stable_population = 5 + required_temperature_min = MIN_AQUARIUM_TEMP+23 + required_temperature_max = MIN_AQUARIUM_TEMP+28 + + fishing_difficulty_modifier = 5 // Bit harder + +/obj/item/fish/firefish + name = "firefish goby" + desc = "To communicate in the wild, the firefish uses its dorsal fin to alert others of potential danger." + icon_state = "firefish" + sprite_width = 5 + sprite_height = 3 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + average_size = 30 + average_weight = 500 + stable_population = 3 + disliked_bait = list(/obj/item/food/bait/worm, /obj/item/food/bait/doughball) + fish_movement_type = /datum/fish_movement/zippy + required_temperature_min = MIN_AQUARIUM_TEMP+23 + required_temperature_max = MIN_AQUARIUM_TEMP+28 + +/obj/item/fish/pufferfish + name = "pufferfish" + desc = "They say that one pufferfish contains enough toxins to kill 30 people, although in the last few decades they've been genetically engineered en masse to be less poisonous." + icon_state = "pufferfish" + required_fluid_type = AQUARIUM_FLUID_SALTWATER + sprite_width = 8 + sprite_height = 6 + average_size = 60 + average_weight = 1000 + stable_population = 3 + required_temperature_min = MIN_AQUARIUM_TEMP+23 + required_temperature_max = MIN_AQUARIUM_TEMP+28 + fillet_type = /obj/item/food/fishmeat/quality //Too bad they're poisonous + fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/toxic) + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/lanternfish + name = "lanternfish" + desc = "Typically found in areas below 6600 feet below the surface of the ocean, they live in complete darkness." + icon_state = "lanternfish" + required_fluid_type = AQUARIUM_FLUID_SALTWATER + random_case_rarity = FISH_RARITY_VERY_RARE + sprite_width = 6 + sprite_height = 5 + average_size = 50 + average_weight = 1000 + stable_population = 3 + fish_traits = list(/datum/fish_trait/nocturnal) + required_temperature_min = MIN_AQUARIUM_TEMP+2 //My source is that the water at a depth 6600 feet is pretty darn cold. + required_temperature_max = MIN_AQUARIUM_TEMP+18 + beauty = FISH_BEAUTY_NULL + +/obj/item/fish/stingray + name = "stingray" + desc = "A type of ray, most known for its venomous stinger. Despite that, They're normally docile, if not a bit easily frightened." + icon_state = "stingray" + stable_population = 4 + sprite_height = 7 + sprite_width = 8 + average_size = 60 + average_weight = 700 + beauty = FISH_BEAUTY_GREAT + random_case_rarity = FISH_RARITY_RARE + required_fluid_type = AQUARIUM_FLUID_SALTWATER //Someone ought to add river rays later I guess. + fish_traits = list(/datum/fish_trait/stinger, /datum/fish_trait/toxic_barbs, /datum/fish_trait/wary, /datum/fish_trait/carnivore, /datum/fish_trait/predator) + +/obj/item/fish/swordfish + name = "swordfish" + desc = "A large billfish, most famous for its elongated bill, while also fairly popular for cooking, and as a fearsome weapon in the hands of a veteran spess-fisherman." + icon = 'icons/obj/aquarium/wide.dmi' + icon_state = "swordfish" + inhand_icon_state = "swordfish" + force = 18 + sharpness = SHARP_EDGED + attack_verb_continuous = list("slashes", "cuts", "pierces") + attack_verb_simple = list("slash", "cut", "pierce") + block_sound = 'sound/weapons/parry.ogg' + hitsound = 'sound/weapons/rapierhit.ogg' + demolition_mod = 0.75 + attack_speed = 1 SECONDS + block_chance = 50 + wound_bonus = 10 + bare_wound_bonus = 20 + armour_penetration = 75 + base_pixel_x = -18 + pixel_x = -18 + sprite_width = 13 + sprite_height = 6 + stable_population = 3 + average_size = 140 + average_weight = 4000 + breeding_timeout = 4.5 MINUTES + feeding_frequency = 4 MINUTES + health = 180 + beauty = FISH_BEAUTY_EXCELLENT + random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS + required_fluid_type = AQUARIUM_FLUID_SALTWATER + fish_movement_type = /datum/fish_movement/plunger + fishing_difficulty_modifier = 25 + fillet_type = /obj/item/food/fishmeat/quality + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = SEAFOOD, + ), + /obj/item/fish, + ) + fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/stinger) + +/obj/item/fish/swordfish/get_force_rank() + switch(w_class) + if(WEIGHT_CLASS_TINY) + force -= 11 + attack_speed -= 0.4 SECONDS + block_chance -= 45 + armour_penetration -= 20 + wound_bonus -= 15 + bare_wound_bonus -= 20 + if(WEIGHT_CLASS_SMALL) + force -= 8 + attack_speed -= 0.3 SECONDS + block_chance -= 30 + armour_penetration -= 15 + wound_bonus -= 10 + bare_wound_bonus -= 20 + if(WEIGHT_CLASS_NORMAL) + force -= 5 + attack_speed -= 0.2 SECONDS + block_chance -= 20 + armour_penetration -= 10 + wound_bonus -= 10 + bare_wound_bonus -= 15 + if(WEIGHT_CLASS_BULKY) + force -= 3 + attack_speed -= 0.1 SECONDS + block_chance -= 10 + armour_penetration -= 5 + wound_bonus -= 5 + bare_wound_bonus -= 10 + if(WEIGHT_CLASS_GIGANTIC) + force += 5 + attack_speed += 0.2 SECONDS + demolition_mod += 0.15 + block_chance += 10 + armour_penetration += 5 + wound_bonus += 5 + bare_wound_bonus += 10 + + if(status == FISH_DEAD) + force -= 4 + w_class + block_chance -= 25 + armour_penetration -= 30 + wound_bonus -= 10 + bare_wound_bonus -= 10 + +/obj/item/fish/swordfish/calculate_fish_force_bonus(bonus_malus) + . = ..() + armour_penetration += bonus_malus * 5 + wound_bonus += bonus_malus * 3 + bare_wound_bonus += bonus_malus * 5 + block_chance += bonus_malus * 7 + +/obj/item/fish/squid + name = "squid" + desc = "An elongated mollusk with eight tentacles, natural camouflage and ink clouds to spray at predators. One of the most intelligent, well-equipped invertebrates out there." + icon_state = "squid" + sprite_width = 4 + sprite_height = 5 + stable_population = 6 + weight_size_deviation = 0.5 // They vary greatly in size. + average_weight = 500 //They're quite lighter than they're long. + required_fluid_type = AQUARIUM_FLUID_SALTWATER + beauty = FISH_BEAUTY_GOOD + required_temperature_min = MIN_AQUARIUM_TEMP+5 + required_temperature_max = MIN_AQUARIUM_TEMP+26 + fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/ink, /datum/fish_trait/camouflage, /datum/fish_trait/wary) + +/obj/item/fish/monkfish + name = "monkfish" + desc = "A member of the Lophiid family of anglerfish. It goes by several different names, however none of them will make it look any prettier, nor any less delicious." + icon_state = "monkfish" + required_fluid_type = AQUARIUM_FLUID_SALTWATER + sprite_height = 7 + sprite_width = 7 + beauty = FISH_BEAUTY_UGLY + required_temperature_min = MIN_AQUARIUM_TEMP+2 + required_temperature_max = MIN_AQUARIUM_TEMP+23 + average_size = 60 + average_weight = 1400 + stable_population = 4 + fish_traits = list(/datum/fish_trait/heavy) + fillet_type = /obj/item/food/fishmeat/quality + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = SEAFOOD|BUGS, + ), + ) + +/obj/item/fish/monkfish/Initialize(mapload, apply_qualities = TRUE) + . = ..() + name = pick("monkfish", "fishing-frog", "frog-fish", "sea-devil", "goosefish") + +/obj/item/fish/plaice + name = "plaice" + desc = "Perhaps the most prominent flatfish in the space-market. Nature really pulled out the rolling pin on this one." + icon_state = "plaice" + sprite_height = 7 + sprite_width = 6 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + required_temperature_min = MIN_AQUARIUM_TEMP+2 + required_temperature_max = MIN_AQUARIUM_TEMP+18 + average_size = 40 + average_weight = 700 + stable_population = 5 + fish_traits = list(/datum/fish_trait/heavy) + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = SEAFOOD|BUGS, + ), + ) + diff --git a/code/modules/fishing/fish/types/station.dm b/code/modules/fishing/fish/types/station.dm new file mode 100644 index 00000000000..8dd459452ab --- /dev/null +++ b/code/modules/fishing/fish/types/station.dm @@ -0,0 +1,80 @@ +/obj/item/fish/ratfish + name = "ratfish" + desc = "A rat exposed to the murky waters of maintenance too long. Any higher power, if it revealed itself, would state that the ratfish's continued existence is extremely unwelcome." + icon_state = "ratfish" + sprite_width = 7 + sprite_height = 5 + random_case_rarity = FISH_RARITY_RARE + required_fluid_type = AQUARIUM_FLUID_FRESHWATER + stable_population = 10 //set by New, but this is the default config value + fillet_type = /obj/item/food/meat/slab/human/mutant/zombie //eww... + fish_traits = list(/datum/fish_trait/necrophage) + required_temperature_min = MIN_AQUARIUM_TEMP+15 + required_temperature_max = MIN_AQUARIUM_TEMP+35 + fish_movement_type = /datum/fish_movement/zippy + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = DAIRY + ) + ) + beauty = FISH_BEAUTY_DISGUSTING + +/obj/item/fish/ratfish/Initialize(mapload, apply_qualities = TRUE) + . = ..() + //stable pop reflects the config for how many mice migrate. powerful... + stable_population = CONFIG_GET(number/mice_roundstart) + +/obj/item/fish/sludgefish + name = "sludgefish" + desc = "A misshapen, fragile, loosely fish-like living goop, the only thing that'd ever thrive in the acidic and claustrophobic cavities of the station's organic waste disposal system." + icon_state = "sludgefish" + sprite_width = 7 + sprite_height = 6 + required_fluid_type = AQUARIUM_FLUID_SULPHWATEVER + stable_population = 8 + average_size = 20 + average_weight = 400 + health = 50 + breeding_timeout = 2.5 MINUTES + fish_traits = list(/datum/fish_trait/parthenogenesis, /datum/fish_trait/no_mating) + required_temperature_min = MIN_AQUARIUM_TEMP+10 + required_temperature_max = MIN_AQUARIUM_TEMP+40 + evolution_types = list(/datum/fish_evolution/purple_sludgefish) + beauty = FISH_BEAUTY_NULL + +/obj/item/fish/sludgefish/purple + name = "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 + fish_traits = list(/datum/fish_trait/parthenogenesis) + +/obj/item/fish/slimefish + name = "acquatic slime" + desc = "Kids, this is what happens when a slime overcomes its hydrophobic nature. It goes glug glug." + icon_state = "slimefish" + icon_state_dead = "slimefish_dead" + sprite_width = 7 + sprite_height = 7 + do_flop_animation = FALSE //it already has a cute bouncy wiggle. :3 + random_case_rarity = FISH_RARITY_VERY_RARE + required_fluid_type = AQUARIUM_FLUID_ANADROMOUS + stable_population = 4 + health = 150 + fillet_type = /obj/item/slime_extract/grey + grind_results = list(/datum/reagent/toxin/slimejelly = 10) + fish_traits = list(/datum/fish_trait/toxin_immunity, /datum/fish_trait/crossbreeder) + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = TOXIC, + ), + list( + FISH_BAIT_TYPE = FISH_BAIT_REAGENT, + FISH_BAIT_VALUE = /datum/reagent/toxin, + FISH_BAIT_AMOUNT = 5, + ), + ) + required_temperature_min = MIN_AQUARIUM_TEMP+20 + beauty = FISH_BEAUTY_GREAT diff --git a/code/modules/fishing/fish/types/syndicate.dm b/code/modules/fishing/fish/types/syndicate.dm new file mode 100644 index 00000000000..a3e9a8519b4 --- /dev/null +++ b/code/modules/fishing/fish/types/syndicate.dm @@ -0,0 +1,256 @@ +///Contains fish that can be found in the syndicate fishing portal setting as well as the ominous fish case. +/obj/item/fish/emulsijack + name = "toxic emulsijack" + desc = "Ah, the terrifying emulsijack. Created in a laboratory, the only real use of this slimey, scaleless fish is for completely ruining a tank." + icon_state = "emulsijack" + random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS + required_fluid_type = AQUARIUM_FLUID_ANADROMOUS + stable_population = 3 + sprite_width = 7 + sprite_height = 3 + fish_traits = list(/datum/fish_trait/emulsijack) + required_temperature_min = MIN_AQUARIUM_TEMP+5 + required_temperature_max = MIN_AQUARIUM_TEMP+40 + beauty = FISH_BEAUTY_BAD + +/obj/item/fish/donkfish + name = "donk co. company patent donkfish" + desc = "A lab-grown donkfish. Its invention was an accident for the most part, as it was intended to be consumed in donk pockets. Unfortunately, it tastes horrible, so it has now become a pseudo-mascot." + icon_state = "donkfish" + random_case_rarity = FISH_RARITY_VERY_RARE + stable_population = 4 + sprite_width = 5 + sprite_height = 4 + fillet_type = /obj/item/food/fishmeat/donkfish + fish_traits = list(/datum/fish_trait/yucky) + required_temperature_min = MIN_AQUARIUM_TEMP+15 + required_temperature_max = MIN_AQUARIUM_TEMP+28 + beauty = FISH_BEAUTY_EXCELLENT + +/obj/item/fish/jumpercable + name = "monocloning jumpercable" + desc = "A surprisingly useful if nasty looking creation from the syndicate fish labs. Drop one in a tank, and \ + watch it self-feed and multiply. Generates more and more power as a growing swarm!" + icon_state = "jumpercable" + sprite_width = 16 + sprite_height = 5 + stable_population = 12 + average_size = 110 + average_weight = 6000 + random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS + required_temperature_min = MIN_AQUARIUM_TEMP+10 + required_temperature_max = MIN_AQUARIUM_TEMP+30 + favorite_bait = list(/obj/item/stock_parts/power_store/cell/lead) + fish_traits = list( + /datum/fish_trait/parthenogenesis, + /datum/fish_trait/mixotroph, + /datum/fish_trait/electrogenesis, + ) + beauty = FISH_BEAUTY_UGLY + +/obj/item/fish/chainsawfish + name = "chainsawfish" + desc = "A very, very angry bioweapon, whose sole purpose is to rip and tear." + icon = 'icons/obj/aquarium/wide.dmi' + icon_state = "chainsawfish" + inhand_icon_state = "chainsawfish" + icon_state_dead = "chainsawfish_dead" + force = 22 + demolition_mod = 1.5 + block_chance = 15 + attack_verb_continuous = list("saws", "tears", "lacerates", "cuts", "chops", "dices") + attack_verb_simple = list("saw", "tear", "lacerate", "cut", "chop", "dice") + hitsound = 'sound/weapons/chainsawhit.ogg' + sharpness = SHARP_EDGED + tool_behaviour = TOOL_SAW + toolspeed = 0.5 + base_pixel_x = -16 + pixel_x = -16 + sprite_width = 8 + sprite_height = 5 + stable_population = 3 + average_size = 85 + average_weight = 2500 + breeding_timeout = 4.25 MINUTES + feeding_frequency = 3 MINUTES + health = 180 + beauty = FISH_BEAUTY_GREAT + random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS + required_fluid_type = AQUARIUM_FLUID_FRESHWATER + fish_movement_type = /datum/fish_movement/accelerando + fishing_difficulty_modifier = 30 + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = GORE, + ), + ) + fish_traits = list(/datum/fish_trait/aggressive, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/stinger) + required_temperature_min = MIN_AQUARIUM_TEMP+18 + required_temperature_max = MIN_AQUARIUM_TEMP+26 + +/obj/item/fish/chainsawfish/Initialize(mapload) + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/fish/chainsawfish/update_icon_state() + if(status == FISH_DEAD) + inhand_icon_state = "chainsawfish_dead" + else + inhand_icon_state = "chainsawfish" + if(HAS_TRAIT(src, TRAIT_WIELDED)) + inhand_icon_state = "[inhand_icon_state]_wielded" + return ..() + +/obj/item/fish/chainsawfish/get_force_rank() + switch(w_class) + if(WEIGHT_CLASS_TINY) + force -= 10 + attack_speed -= 0.2 SECONDS + demolition_mod -= 0.4 + block_chance -= 15 + armour_penetration -= 10 + wound_bonus -= 10 + bare_wound_bonus -= 10 + toolspeed += 0.6 + if(WEIGHT_CLASS_SMALL) + force -= 8 + attack_speed -= 0.1 SECONDS + demolition_mod -= 0.3 + block_chance -= 10 + armour_penetration -= 10 + wound_bonus -= 10 + bare_wound_bonus -= 10 + toolspeed += 0.4 + if(WEIGHT_CLASS_NORMAL) + force -= 5 + demolition_mod -= 0.15 + block_chance -= 5 + armour_penetration -= 5 + wound_bonus -= 5 + bare_wound_bonus -= 5 + toolspeed += 0.2 + if(WEIGHT_CLASS_HUGE) + force += 2 + attack_speed += 0.2 SECONDS + demolition_mod += 0.15 + armour_penetration += 10 + block_chance += 10 + wound_bonus += 10 + bare_wound_bonus += 5 + if(WEIGHT_CLASS_GIGANTIC) + force += 4 + attack_speed += 0.4 SECONDS + demolition_mod += 0.3 + block_chance += 20 + armour_penetration += 20 + wound_bonus += 15 + bare_wound_bonus += 10 + toolspeed -= 0.1 + + if(status == FISH_DEAD) + force -= 8 + w_class + hitsound = SFX_SWING_HIT + block_chance -= 25 + demolition_mod -= 0.3 + armour_penetration -= 15 + wound_bonus -= 5 + bare_wound_bonus -= 5 + toolspeed += 1 + +/obj/item/fish/chainsawfish/calculate_fish_force_bonus(bonus_malus) + . = ..() + armour_penetration += bonus_malus * 3 + wound_bonus += bonus_malus * 2 + bare_wound_bonus += bonus_malus * 3 + block_chance += bonus_malus * 2 + toolspeed -= bonus_malus * 0.1 + +/obj/item/fish/pike/armored + name = "armored pike" + desc = "A long-bodied, metal-clad predator with a snout that almost looks like an halberd. Definitely a weapon to swing around." + icon_state = "armored_pike" + inhand_icon_state = "armored_pike" + attack_verb_continuous = list("attacks", "pokes", "jabs", "tears", "lacerates", "gores") + attack_verb_simple = list("attack", "poke", "jab", "tear", "lacerate", "gore") + hitsound = 'sound/weapons/bladeslice.ogg' + block_sound = 'sound/weapons/parry.ogg' + force = 20 + sharpness = SHARP_EDGED + wound_bonus = -15 + attack_speed = 1 SECONDS + block_chance = 25 + bare_wound_bonus = 15 + demolition_mod = 0.8 + armour_penetration = 10 + stable_population = 3 + average_weight = 3000 + breeding_timeout = 5 MINUTES + feeding_frequency = 4 MINUTES + health = 180 + random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS + beauty = FISH_BEAUTY_GREAT + fishing_difficulty_modifier = 20 + fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/aggressive, /datum/fish_trait/picky_eater, /datum/fish_trait/stinger) + compatible_types = list(/obj/item/fish/pike) + favorite_bait = list( + list( + FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE, + FISH_BAIT_VALUE = SEAFOOD, + ), + /obj/item/fish, + ) + +/obj/item/fish/swordfish/get_force_rank() + switch(w_class) + if(WEIGHT_CLASS_TINY) + force -= 11 + attack_speed -= 0.4 SECONDS + block_chance -= 25 + armour_penetration -= 15 + wound_bonus -= 15 + bare_wound_bonus -= 30 + if(WEIGHT_CLASS_SMALL) + force -= 6 + attack_speed -= 0.3 SECONDS + block_chance -= 20 + armour_penetration -= 10 + wound_bonus -= 10 + bare_wound_bonus -= 25 + if(WEIGHT_CLASS_NORMAL) + force -= 4 + attack_speed -= 0.2 SECONDS + block_chance -= 20 + armour_penetration -= 5 + wound_bonus -= 10 + bare_wound_bonus -= 15 + if(WEIGHT_CLASS_HUGE) + force += 3 + attack_speed += 0.2 SECONDS + block_chance += 10 + demolition_mod += 0.1 + armour_penetration += 5 + wound_bonus += 10 + bare_wound_bonus += 5 + if(WEIGHT_CLASS_GIGANTIC) + force += 7 + attack_speed += 0.3 SECONDS + demolition_mod += 0.2 + block_chance += 20 + armour_penetration += 10 + wound_bonus += 15 + bare_wound_bonus += 10 + + if(status == FISH_DEAD) + force -= 5 + w_class + block_chance -= 15 + armour_penetration -= 10 + wound_bonus -= 5 + bare_wound_bonus -= 15 + +/obj/item/fish/pike/armored/calculate_fish_force_bonus(bonus_malus) + . = ..() + armour_penetration += bonus_malus * 3 + wound_bonus += bonus_malus * 2 + bare_wound_bonus += bonus_malus * 4 + block_chance += bonus_malus * 4 diff --git a/code/modules/fishing/fish/types/tiziran.dm b/code/modules/fishing/fish/types/tiziran.dm new file mode 100644 index 00000000000..a39e82ce6c4 --- /dev/null +++ b/code/modules/fishing/fish/types/tiziran.dm @@ -0,0 +1,69 @@ +//Tiziran Fish. + +/obj/item/fish/dwarf_moonfish + name = "dwarf moonfish" + desc = "Ordinarily in the wild, the Zagoskian moonfish is around the size of a tuna, however through selective breeding a smaller breed suitable for being kept as an aquarium pet has been created." + icon_state = "dwarf_moonfish" + sprite_height = 6 + sprite_width = 6 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + stable_population = 2 + fillet_type = /obj/item/food/fishmeat/moonfish + average_size = 60 + average_weight = 1000 + required_temperature_min = MIN_AQUARIUM_TEMP+20 + required_temperature_max = MIN_AQUARIUM_TEMP+30 + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/gunner_jellyfish + name = "gunner jellyfish" + desc = "So called due to their resemblance to an artillery shell, the gunner jellyfish is native to Tizira, where it is enjoyed as a delicacy. Produces a mild hallucinogen that is destroyed by cooking." + icon_state = "gunner_jellyfish" + sprite_height = 4 + sprite_width = 5 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + stable_population = 4 + fillet_type = /obj/item/food/fishmeat/gunner_jellyfish + required_temperature_min = MIN_AQUARIUM_TEMP+24 + required_temperature_max = MIN_AQUARIUM_TEMP+32 + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/needlefish + name = "needlefish" + desc = "A tiny, transparent fish which resides in large schools in the oceans of Tizira. A common food for other, larger fish." + icon_state = "needlefish" + sprite_height = 3 + sprite_width = 7 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + stable_population = 12 + breeding_timeout = 1 MINUTES + fillet_type = null + average_size = 20 + average_weight = 300 + fish_traits = list(/datum/fish_trait/carnivore) + required_temperature_min = MIN_AQUARIUM_TEMP+10 + required_temperature_max = MIN_AQUARIUM_TEMP+32 + +/obj/item/fish/needlefish/Initialize(mapload, apply_qualities = TRUE) + . = ..() + add_traits(list(TRAIT_FISHING_BAIT, TRAIT_GOOD_QUALITY_BAIT), INNATE_TRAIT) + +/obj/item/fish/armorfish + name = "armorfish" + desc = "A small shellfish native to Tizira's oceans, known for its exceptionally hard shell. Consumed similarly to prawns." + icon_state = "armorfish" + sprite_height = 5 + sprite_width = 6 + average_size = 25 + average_weight = 350 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + stable_population = 10 + breeding_timeout = 1.25 MINUTES + fillet_type = /obj/item/food/fishmeat/armorfish + fish_movement_type = /datum/fish_movement/slow + required_temperature_min = MIN_AQUARIUM_TEMP+10 + required_temperature_max = MIN_AQUARIUM_TEMP+32 + +/obj/item/fish/armorfish/Initialize(mapload, apply_qualities = TRUE) + . = ..() + add_traits(list(TRAIT_FISHING_BAIT, TRAIT_GOOD_QUALITY_BAIT), INNATE_TRAIT) diff --git a/code/modules/fishing/fish_catalog.dm b/code/modules/fishing/fish_catalog.dm index 49a84413ded..f0880804f8e 100644 --- a/code/modules/fishing/fish_catalog.dm +++ b/code/modules/fishing/fish_catalog.dm @@ -90,7 +90,7 @@ if(source.catalog_description && (fish_type in source.fish_table)) spot_descriptions += source.catalog_description .["spots"] = english_list(spot_descriptions, nothing_text = "Unknown") - var/list/fish_list_properties = collect_fish_properties() + var/list/fish_list_properties = SSfishing.fish_properties var/list/fav_bait = fish_list_properties[fishy][NAMEOF(fishy, favorite_bait)] var/list/disliked_bait = fish_list_properties[fishy][NAMEOF(fishy, disliked_bait)] var/list/bait_list = list() diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm index 5408e411876..645ebed875d 100644 --- a/code/modules/fishing/fishing_equipment.dm +++ b/code/modules/fishing/fishing_equipment.dm @@ -52,7 +52,7 @@ */ /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." + desc = "A fishing line that automatically spins lures and begins reeling in fish the moment it bites. Also good for hurling things towards you." icon_state = "reel_auto" fishing_line_traits = FISHING_LINE_AUTOREEL line_color = "#F88414" @@ -163,6 +163,20 @@ rod_overlay_icon_state = "hook_treasure_overlay" chasm_detritus_type = /datum/chasm_detritus/restricted/objects +/obj/item/fishing_hook/magnet/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_FISHING_EQUIPMENT_SLOTTED, PROC_REF(hook_equipped)) + +///We make sure that the fishng rod doesn't need a bait to reliably catch non-fish loot. +/obj/item/fishing_hook/magnet/proc/hook_equipped(datum/source, obj/item/fishing_rod/rod) + SIGNAL_HANDLER + ADD_TRAIT(rod, TRAIT_ROD_REMOVE_FISHING_DUD, type) + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_removed)) + +/obj/item/fishing_hook/magnet/proc/on_removed(atom/movable/source, atom/old_loc, dir, forced) + SIGNAL_HANDLER + REMOVE_TRAIT(old_loc, TRAIT_ROD_REMOVE_FISHING_DUD, type) + UnregisterSignal(src, COMSIG_MOVABLE_MOVED) /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)) @@ -171,7 +185,6 @@ // We multiply the odds by five for everything that's not a fish nor a dud return MAGNET_HOOK_BONUS_MULTIPLIER - /obj/item/fishing_hook/shiny name = "shiny lure hook" icon_state = "gold_shiny" @@ -364,5 +377,42 @@ new /obj/item/storage/fish_case(src) new /obj/item/storage/fish_case(src) +/obj/item/storage/box/fishing_lures + name = "fishing lures set" + desc = "A small tackle box containing all the fishing lures you will ever need to curb randomness." + icon_state = "plasticbox" + foldable_result = null + illustration = "fish" + +/obj/item/storage/box/fishing_lures/PopulateContents() + new /obj/item/paper/lures_instructions(src) + var/list/typesof = typesof(/obj/item/fishing_lure) + for(var/type in typesof) + new type (src) + atom_storage.set_holdable(/obj/item/fishing_lure) //can only hold lures + //adds an extra slot, so we can put back the lures even if we didn't take out the instructions. + atom_storage.max_slots = length(typesof) + 1 + atom_storage.max_total_storage = WEIGHT_CLASS_SMALL * (atom_storage.max_slots + 1) + +/obj/item/paper/lures_instructions + name = "instructions paper" + icon_state = "slipfull" + show_written_words = FALSE + desc = "A piece of grey paper with an how-to for dummies about fishing lures printed on it. Smells cheap." + default_raw_text = "Thank you for buying this set.
\ + This a simple non-exhaustive set of instructions on how to use fishing lures, some information may \ + be slightly incorrect or oversimplified.

\ + + First and foremost, fishing lures are inedible, artificia baits, fairly sturdy so that \ + they won't be destroyed by the hungry fish. However, they need to be spun at intervals to replicate \ + the motion of a prey or organic bait to tempt the fish, since a piece of plastic and metal ins't \ + by itself all that tasty. Different lures can be used to catch different fish.

\ + + To help you, each lure comes with a small light that's attached to the float of your fishing rod. \ + For those who don't know it, the float is basically the thing bobbing up'n'down above the fishing spot. \ + The light will flash green and a sound cue will be played when the bait is ready to be spun. \ + Do not spin while the light is still red.

\ + That's all, best of luck to your angling journey.

" + #undef MAGNET_HOOK_BONUS_MULTIPLIER #undef RESCUE_HOOK_FISH_MULTIPLIER diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index f53437e3559..fc30d2aa697 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -1,4 +1,4 @@ -// Lure bobbing +// float bobbing #define WAIT_PHASE 1 // Click now to start tgui part #define BITING_PHASE 2 @@ -53,12 +53,14 @@ var/phase = WAIT_PHASE // Timer for the next phase var/next_phase_timer + // The last time we clicked during the baiting phase + var/last_baiting_click /// Fishing mob var/mob/user /// Rod that is used for the challenge var/obj/item/fishing_rod/used_rod - /// Lure visual - var/obj/effect/fishing_lure/lure + /// float visual + var/obj/effect/fishing_float/float /// Background icon state from fishing_hud.dmi var/background = "background_default" /// Fish icon state from fishing_hud.dmi @@ -110,7 +112,8 @@ src.reward_path = reward_path src.used_rod = rod var/atom/spot = comp.parent - lure = new(get_turf(spot), spot) + float = new(get_turf(spot), spot) + float.spin_frequency = rod.spin_frequency RegisterSignal(spot, COMSIG_QDELETING, PROC_REF(on_spot_gone)) RegisterSignal(comp.fish_source, COMSIG_FISHING_SOURCE_INTERRUPT_CHALLENGE, PROC_REF(interrupt_challenge)) comp.fish_source.RegisterSignal(src, COMSIG_FISHING_CHALLENGE_COMPLETED, TYPE_PROC_REF(/datum/fish_source, on_challenge_completed)) @@ -122,7 +125,7 @@ var/movement_path = initial(fish.fish_movement_type) mover = new movement_path(src) // Apply fish trait modifiers - var/list/fish_list_properties = collect_fish_properties() + var/list/fish_list_properties = SSfishing.fish_properties var/list/fish_traits = fish_list_properties[fish][NAMEOF(fish, fish_traits)] for(var/fish_trait in fish_traits) var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] @@ -178,8 +181,7 @@ //Stops the line snapped message from appearing everytime the minigame is over. UnregisterSignal(fishing_line, COMSIG_QDELETING) QDEL_NULL(fishing_line) - if(lure) - QDEL_NULL(lure) + QDEL_NULL(float) SStgui.close_uis(src) user = null used_rod = null @@ -187,8 +189,8 @@ return ..() /datum/fishing_challenge/proc/send_alert(message) - var/turf/lure_turf = get_turf(lure) - lure_turf?.balloon_alert(user, message) + var/turf/float_turf = get_turf(float) + float_turf?.balloon_alert(user, message) /datum/fishing_challenge/proc/on_spot_gone(datum/source) SIGNAL_HANDLER @@ -204,11 +206,14 @@ /datum/fishing_challenge/proc/start(mob/living/user) /// Create fishing line visuals if(!used_rod.internal) - fishing_line = used_rod.create_fishing_line(lure, user, target_py = 5) + fishing_line = used_rod.create_fishing_line(float, user, target_py = 5) + if(isnull(fishing_line)) //couldn't create a fishing line, probably because we don't have a good line of sight. + qdel(src) + return RegisterSignal(fishing_line, COMSIG_QDELETING, PROC_REF(on_line_deleted)) else //if the rod doesnt have a fishing line, then it ends when they move away - RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_lure_or_user_move)) - RegisterSignal(lure, COMSIG_MOVABLE_MOVED, PROC_REF(on_lure_or_user_move)) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_float_or_user_move)) + RegisterSignal(float, COMSIG_MOVABLE_MOVED, PROC_REF(on_float_or_user_move)) RegisterSignal(user, SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), PROC_REF(on_hands_blocked)) RegisterSignal(user, SIGNAL_REMOVETRAIT(TRAIT_PROFOUND_FISHER), PROC_REF(no_longer_fishing)) active_effects = bitfield_to_list(special_effects & FISHING_MINIGAME_ACTIVE_EFFECTS) @@ -219,19 +224,49 @@ RegisterSignal(user, COMSIG_MOB_CLICKON, PROC_REF(handle_click)) start_baiting_phase() to_chat(user, span_notice("You start fishing...")) - playsound(lure, 'sound/effects/splash.ogg', 100) + playsound(float, 'sound/effects/splash.ogg', 100) + +///Set the timers for lure that need to be spun at intervals. +/datum/fishing_challenge/proc/set_lure_timers() + float.spin_ready = FALSE + addtimer(CALLBACK(src, PROC_REF(set_lure_ready)), float.spin_frequency[1], TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_DELETE_ME) + addtimer(CALLBACK(src, PROC_REF(missed_lure)), float.spin_frequency[2], TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_DELETE_ME) + float.update_appearance(UPDATE_OVERLAYS) + +/datum/fishing_challenge/proc/set_lure_ready() + if(phase != WAIT_PHASE) + return + float.spin_ready = TRUE + float.update_appearance(UPDATE_OVERLAYS) + if(special_effects & FISHING_MINIGAME_AUTOREEL) + addtimer(CALLBACK(src, PROC_REF(auto_spin)), 0.2 SECONDS) + playsound(float, 'sound/machines/ping.ogg', 10, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + +/datum/fishing_challenge/proc/auto_spin() + if(phase != WAIT_PHASE || !float.spin_ready) + return + float.spin_ready = FALSE + float.update_appearance(UPDATE_OVERLAYS) + set_lure_timers() + send_alert("spun") + +/datum/fishing_challenge/proc/missed_lure() + if(phase != WAIT_PHASE) + return + send_alert("miss!") + start_baiting_phase(TRUE) //Add in another 3 to 5 seconds for not spinning the lure. /datum/fishing_challenge/proc/on_line_deleted(datum/source) SIGNAL_HANDLER fishing_line = null - ///The lure may be out of sight if the user has moed around a corner, so the message should be displayed over him instead. + ///The float may be out of sight if the user has moed around a corner, so the message should be displayed over him instead. user.balloon_alert(user, user.is_holding(used_rod) ? "line snapped" : "rod dropped") interrupt() -/datum/fishing_challenge/proc/on_lure_or_user_move(datum/source) +/datum/fishing_challenge/proc/on_float_or_user_move(datum/source) SIGNAL_HANDLER - if(!user.CanReach(lure)) + if(!user.CanReach(float)) user.balloon_alert(user, "too far!") interrupt() @@ -258,8 +293,20 @@ if(!HAS_TRAIT(source, TRAIT_PROFOUND_FISHER) && source.get_active_held_item() != used_rod) return if(phase == WAIT_PHASE) - send_alert("miss!") - start_baiting_phase(TRUE) //Add in another 3 to 5 seconds for that blunder. + if(world.time < last_baiting_click + 0.25 SECONDS) + return //Don't punish players if they accidentally double clicked. + if(float.spin_frequency) + if(!float.spin_ready) + send_alert("too early!") + start_baiting_phase(TRUE) //Add in another 3 to 5 seconds for that blunder. + else + send_alert("spun") + last_baiting_click = world.time + float.spin_ready = FALSE + set_lure_timers() + else + send_alert("miss!") + start_baiting_phase(TRUE) //Add in another 3 to 5 seconds for that blunder. else if(phase == BITING_PHASE) start_minigame_phase() return COMSIG_MOB_CANCEL_CLICKON @@ -298,30 +345,33 @@ user.client?.give_award(/datum/award/achievement/skill/legendary_fisher, user) if(win) if(reward_path != FISHING_DUD) - playsound(lure, 'sound/effects/bigsplash.ogg', 100) + playsound(float, 'sound/effects/bigsplash.ogg', 100) SEND_SIGNAL(src, COMSIG_FISHING_CHALLENGE_COMPLETED, user, win) if(!QDELETED(src)) qdel(src) /datum/fishing_challenge/proc/start_baiting_phase(penalty = FALSE) var/wait_time + last_baiting_click = world.time if(penalty) wait_time = min(timeleft(next_phase_timer) + rand(3 SECONDS, 5 SECONDS), 30 SECONDS) else - wait_time = rand(3 SECONDS, 25 SECONDS) + wait_time = float.spin_frequency ? rand(11 SECONDS, 17 SECONDS) : rand(3 SECONDS, 25 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(float, pixel_y = 1, time = 1 SECONDS, loop = -1, flags = ANIMATION_RELATIVE) animate(pixel_y = -1, time = 1 SECONDS, flags = ANIMATION_RELATIVE) - next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_biting_phase)), wait_time, TIMER_STOPPABLE) + next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_biting_phase)), wait_time, TIMER_STOPPABLE|TIMER_DELETE_ME) + if(float.spin_frequency) + set_lure_timers() /datum/fishing_challenge/proc/start_biting_phase() phase = BITING_PHASE // Trashing animation - playsound(lure, 'sound/effects/fish_splash.ogg', 100) + playsound(float, 'sound/effects/fish_splash.ogg', 100) if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH)) switch(fish_icon) if(FISH_ICON_DEF) @@ -356,13 +406,19 @@ send_alert("bottle!!!") else send_alert("!!!") - animate(lure, pixel_y = 3, time = 5, loop = -1, flags = ANIMATION_RELATIVE) + animate(float, 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 + addtimer(CALLBACK(src, PROC_REF(automatically_start_minigame)), 0.2 SECONDS) // Setup next phase - next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_baiting_phase)), BITING_TIME_WINDOW, TIMER_STOPPABLE) + next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_baiting_phase)), BITING_TIME_WINDOW, TIMER_STOPPABLE|TIMER_DELETE_ME) + ///If we're using a lure, we want the float to show a little green light during the minigame phase and not a red one. + float.spin_ready = TRUE + float.update_appearance(UPDATE_OVERLAYS) + +/datum/fishing_challenge/proc/automatically_start_minigame() + if(phase == BITING_PHASE) + start_minigame_phase(auto_reel = TRUE) ///The damage dealt per second to the fish when FISHING_MINIGAME_RULE_KILL is active. #define FISH_DAMAGE_PER_SECOND 2 @@ -403,6 +459,8 @@ var/diff_dist = 100 + difficulty bait_position = clamp(round(fish_position + rand(-diff_dist, diff_dist) - bait_height * 0.5), 0, FISHING_MINIGAME_AREA - bait_height) if(!prepare_minigame_hud()) + get_stack_trace("couldn't prepare minigame hud for a fishing challenge.") //just to be sure. This shouldn't happen. + qdel(src) return ADD_TRAIT(user, TRAIT_ACTIVELY_FISHING, WEAKREF(src)) phase = MINIGAME_PHASE @@ -410,10 +468,26 @@ if((FISHING_MINIGAME_RULE_KILL in special_effects) && ispath(reward_path,/obj/item/fish)) 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) + addtimer(CALLBACK(src, PROC_REF(win_anyway)), wait_time, TIMER_DELETE_ME) start_time = world.time experience_multiplier += difficulty * FISHING_SKILL_DIFFIULTY_EXP_MULT +///Throws a stack with prefixed text. +/datum/fishing_challenge/proc/get_stack_trace(init_text) + var/text = "[init_text] " + text += "used rod: [used_rod || "null"], " + if(used_rod) + text += "bait: [used_rod.bait || "null"], " + text += "reward: [reward_path || "null"], " + text += "user: [user || "null"]" + if(user) + if(QDELING(user)) + text += ", user qdeling" + else if(!user.client) + text += ", user clientless" + text += "." + stack_trace(text) + #undef FISH_DAMAGE_PER_SECOND ///Initialize the minigame hud and register some signals to make it work. @@ -475,6 +549,11 @@ fishing_hud.transform = fishing_hud.transform.Scale(1, -1) SEND_SOUND(user, sound('sound/effects/boing.ogg')) COOLDOWN_START(src, active_effect_cd, rand(5, 6) SECONDS) + if(FISHING_MINIGAME_RULE_CAMO) + fishing_hud.icon_state = "background_camo" + SEND_SOUND(user, sound('sound/effects/nightmare_poof.ogg', volume = 15)) + COOLDOWN_START(src, active_effect_cd, rand(6, 8) SECONDS) + animate(fishing_hud.hud_fish, alpha = 7, time = 2 SECONDS) return ///go back to normal @@ -487,6 +566,10 @@ if(FISHING_MINIGAME_RULE_FLIP) fishing_hud.transform = fishing_hud.transform.Scale(1, -1) COOLDOWN_START(src, active_effect_cd, rand(8, 12) SECONDS) + if(FISHING_MINIGAME_RULE_CAMO) + COOLDOWN_START(src, active_effect_cd, rand(9, 16) SECONDS) + SEND_SOUND(user, sound('sound/effects/nightmare_reappear.ogg', volume = 15)) + animate(fishing_hud.hud_fish, alpha = 255, time = 1.2 SECONDS) fishing_hud.icon_state = background current_active_effect = null @@ -647,21 +730,39 @@ icon_state = "completion_[FLOOR(challenge.completion, 5)]" /// The visual that appears over the fishing spot -/obj/effect/fishing_lure +/obj/effect/fishing_float + name = "float" icon = 'icons/obj/fishing.dmi' - icon_state = "lure_idle" + icon_state = "float" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + /** + * A list with two keys delimiting the spinning interval in which a mouse click has to be pressed while fishing. + * If set, an emissive overlay will be added, colored green when the lure is ready to be spun, otherwise red. + */ + var/list/spin_frequency + ///Is the bait ready to be spun? + var/spin_ready = FALSE -/obj/effect/fishing_lure/Initialize(mapload, atom/spot) +/obj/effect/fishing_float/Initialize(mapload, atom/spot) . = ..() - if(ismovable(spot)) // we want the lure and therefore the fishing line to stay connected with the fishing spot. + if(ismovable(spot)) // we want the float and therefore the fishing line to stay connected with the fishing spot. RegisterSignal(spot, COMSIG_MOVABLE_MOVED, PROC_REF(follow_movable)) -/obj/effect/fishing_lure/proc/follow_movable(atom/movable/source) +/obj/effect/fishing_float/proc/follow_movable(atom/movable/source) SIGNAL_HANDLER set_glide_size(source.glide_size) forceMove(source.loc) +/obj/effect/fishing_float/update_overlays() + . = ..() + if(!spin_frequency) + return + var/mutable_appearance/overlay = mutable_appearance(icon, "lure_light") + overlay.color = spin_ready ? COLOR_GREEN : COLOR_RED + . += overlay + . += emissive_appearance(icon, "lure_light_emissive", src, alpha = src.alpha) + #undef WAIT_PHASE #undef BITING_PHASE #undef MINIGAME_PHASE diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index 1c47b8f7ee3..28ae51d4236 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -42,6 +42,12 @@ ///The name of the icon state of the reel overlay var/reel_overlay = "reel_overlay" + /** + * A list with two keys delimiting the spinning interval in which a mouse click has to be pressed while fishing. + * Inherited from baits, passed down to the minigame lure. + */ + var/list/spin_frequency + ///Prevents spamming the line casting, without affecting the player's click cooldown. COOLDOWN_DECLARE(casting_cd) @@ -69,9 +75,11 @@ /obj/item/fishing_rod/add_item_context(obj/item/source, list/context, atom/target, mob/living/user) . = ..() - if(currently_hooked) - context[SCREENTIP_CONTEXT_LMB] = "Reel in" - context[SCREENTIP_CONTEXT_RMB] = "Unhook" + var/gone_fishing = HAS_TRAIT(user, TRAIT_GONE_FISHING) + if(currently_hooked || gone_fishing) + context[SCREENTIP_CONTEXT_LMB] = (gone_fishing && spin_frequency) ? "Spin" : "Reel in" + if(!gone_fishing) + context[SCREENTIP_CONTEXT_RMB] = "Unhook" return CONTEXTUAL_SCREENTIP_SET return NONE @@ -170,6 +178,8 @@ RegisterSignal(fishing_line, COMSIG_BEAM_BEFORE_DRAW, PROC_REF(check_los)) RegisterSignal(fishing_line, COMSIG_QDELETING, PROC_REF(clear_line)) INVOKE_ASYNC(fishing_line, TYPE_PROC_REF(/datum/beam/, Start)) + if(QDELETED(fishing_line)) + return null firer.update_held_items() return fishing_line @@ -419,23 +429,20 @@ if(user.transferItemToLoc(new_item,src)) set_slot(new_item, slot) balloon_alert(user, "[slot] installed") + else + balloon_alert(user, "stuck to your hands!") + return /// Trying to swap item else if(new_item && current_item) if(!slot_check(new_item,slot)) return - if(user.transferItemToLoc(new_item,src)) - switch(slot) - if(ROD_SLOT_BAIT) - bait = new_item - if(ROD_SLOT_HOOK) - hook = new_item - if(ROD_SLOT_LINE) - line = new_item - user.put_in_hands(current_item) - balloon_alert(user, "[slot] swapped") - - if(new_item) - SEND_SIGNAL(new_item, COMSIG_FISHING_EQUIPMENT_SLOTTED, src) + if(user.transferItemToLoc(new_item, src)) + user.put_in_hands(current_item) + set_slot(new_item, slot) + balloon_alert(user, "[slot] swapped") + else + balloon_alert(user, "stuck to your hands!") + return update_icon() playsound(src, 'sound/items/click.ogg', 50, TRUE) @@ -445,16 +452,23 @@ switch(slot) if(ROD_SLOT_BAIT) bait = equipment + if(!HAS_TRAIT(bait, TRAIT_BAIT_ALLOW_FISHING_DUD)) + ADD_TRAIT(src, TRAIT_ROD_REMOVE_FISHING_DUD, INNATE_TRAIT) if(ROD_SLOT_HOOK) hook = equipment if(ROD_SLOT_LINE) line = equipment cast_range += FISHING_ROD_REEL_CAST_RANGE + else + CRASH("set_slot called with an undefined slot: [slot]") + + SEND_SIGNAL(equipment, COMSIG_FISHING_EQUIPMENT_SLOTTED, src) /obj/item/fishing_rod/Exited(atom/movable/gone, direction) . = ..() if(gone == bait) bait = null + REMOVE_TRAIT(src, TRAIT_ROD_REMOVE_FISHING_DUD, INNATE_TRAIT) if(gone == line) cast_range -= FISHING_ROD_REEL_CAST_RANGE line = null diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index 5aa03ae8c0c..a63c087fcc6 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -31,6 +31,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) /obj/item/fish/jumpercable = FISH_ICON_ELECTRIC, /obj/item/fish/lavaloop = FISH_ICON_WEAPON, /obj/item/fish/mastodon = FISH_ICON_BONE, + /obj/item/fish/pike/armored = FISH_ICON_WEAPON, /obj/item/fish/pufferfish = FISH_ICON_CHUNKY, /obj/item/fish/sand_crab = FISH_ICON_CRAB, /obj/item/fish/skin_crab = FISH_ICON_CRAB, @@ -139,8 +140,8 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) // In the future non-fish rewards can have variable difficulty calculated here return - var/list/fish_list_properties = collect_fish_properties() var/obj/item/fish/caught_fish = result + var/list/fish_properties = SSfishing.fish_properties[caught_fish] // Baseline fish difficulty . += initial(caught_fish.fishing_difficulty_modifier) @@ -148,18 +149,18 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) if(rod.bait) var/obj/item/bait = rod.bait //Fav bait makes it easier - var/list/fav_bait = fish_list_properties[caught_fish][NAMEOF(caught_fish, favorite_bait)] + var/list/fav_bait = fish_properties[FISH_PROPERTIES_FAV_BAIT] for(var/bait_identifer in fav_bait) if(is_matching_bait(bait, bait_identifer)) . += FAV_BAIT_DIFFICULTY_MOD //Disliked bait makes it harder - var/list/disliked_bait = fish_list_properties[caught_fish][NAMEOF(caught_fish, disliked_bait)] + var/list/disliked_bait = fish_properties[FISH_PROPERTIES_BAD_BAIT] for(var/bait_identifer in disliked_bait) if(is_matching_bait(bait, bait_identifer)) . += DISLIKED_BAIT_DIFFICULTY_MOD // Matching/not matching fish traits and equipment - var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fish_traits)] + var/list/fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] var/additive_mod = 0 var/multiplicative_mod = 1 @@ -173,8 +174,8 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) . *= multiplicative_mod /// In case you want more complex rules for specific spots -/datum/fish_source/proc/roll_reward(obj/item/fishing_rod/rod, mob/fisherman) - return pick_weight(get_modified_fish_table(rod,fisherman)) +/datum/fish_source/proc/roll_reward(obj/item/fishing_rod/rod, mob/fisherman, atom/location) + return pick_weight(get_modified_fish_table(rod, fisherman, location)) || FISHING_DUD /** * Used to register signals or add traits and the such right after conditions have been cleared @@ -198,7 +199,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) return var/obj/item/fish/caught = source.reward_path user.add_mob_memory(/datum/memory/caught_fish, protagonist = user, deuteragonist = initial(caught.name)) - var/turf/fishing_spot = get_turf(source.lure) + var/turf/fishing_spot = get_turf(source.float) var/atom/movable/reward = dispense_reward(source.reward_path, user, fishing_spot) if(source.used_rod) SEND_SIGNAL(source.used_rod, COMSIG_FISHING_ROD_CAUGHT_FISH, reward, user) @@ -259,23 +260,6 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) caught_fish.randomize_size_and_weight() return reward -/// Cached fish list properties so we don't have to initalize fish every time, init deffered -GLOBAL_LIST(fishing_property_cache) - -/// Awful workaround around initial(x.list_variable) not being a thing while trying to keep some semblance of being structured -/proc/collect_fish_properties() - if(GLOB.fishing_property_cache == null) - var/list/fish_property_table = list() - for(var/fish_type in subtypesof(/obj/item/fish)) - var/obj/item/fish/fish = new fish_type(null, FALSE) - fish_property_table[fish_type] = list() - fish_property_table[fish_type][NAMEOF(fish, favorite_bait)] = fish.favorite_bait.Copy() - fish_property_table[fish_type][NAMEOF(fish, disliked_bait)] = fish.disliked_bait.Copy() - fish_property_table[fish_type][NAMEOF(fish, fish_traits)] = fish.fish_traits.Copy() - QDEL_NULL(fish) - GLOB.fishing_property_cache = fish_property_table - return GLOB.fishing_property_cache - /// Returns the fish table, with with the unavailable items from fish_counts removed. /datum/fish_source/proc/get_fish_table() var/list/table = fish_table.Copy() @@ -285,7 +269,7 @@ GLOBAL_LIST(fishing_property_cache) return table /// Builds a fish weights table modified by bait/rod/user properties -/datum/fish_source/proc/get_modified_fish_table(obj/item/fishing_rod/rod, mob/fisherman) +/datum/fish_source/proc/get_modified_fish_table(obj/item/fishing_rod/rod, mob/fisherman, atom/location) var/obj/item/bait = rod.bait ///An exponent used to level out the difference in probabilities between fishes/mobs on the table depending on bait quality. var/leveling_exponent = 0 @@ -305,9 +289,9 @@ GLOBAL_LIST(fishing_property_cache) else if(HAS_TRAIT(bait, TRAIT_BASIC_QUALITY_BAIT)) result_multiplier = 2 leveling_exponent = 0.1 - final_table -= FISHING_DUD - var/list/fish_list_properties = collect_fish_properties() + if(HAS_TRAIT(rod, TRAIT_ROD_REMOVE_FISHING_DUD)) + final_table -= FISHING_DUD if(HAS_TRAIT(fisherman, TRAIT_PROFOUND_FISHER) && !fisherman.client) @@ -317,37 +301,17 @@ GLOBAL_LIST(fishing_property_cache) final_table[result] += rod.hook?.get_hook_bonus_additive(result)//Decide on order here so it can be multiplicative if(ispath(result, /obj/item/fish)) - //Modify fish roll chance - var/obj/item/fish/caught_fish = result - if(bait) final_table[result] = round(final_table[result] * result_multiplier, 1) - if(!HAS_TRAIT(bait, TRAIT_OMNI_BAIT)) - //Bait matching likes doubles the chance - var/list/fav_bait = fish_list_properties[result][NAMEOF(caught_fish, favorite_bait)] - for(var/bait_identifer in fav_bait) - if(is_matching_bait(bait, bait_identifer)) - final_table[result] *= 2 - //Bait matching dislikes - var/list/disliked_bait = fish_list_properties[result][NAMEOF(caught_fish, disliked_bait)] - for(var/bait_identifer in disliked_bait) - if(is_matching_bait(bait, bait_identifer)) - final_table[result] = round(final_table[result] * 0.5, 1) + var/mult = bait.check_bait(result) + final_table[result] = round(final_table[result] * mult, 1) + if(mult > 1 && HAS_TRAIT(bait, TRAIT_BAIT_ALLOW_FISHING_DUD)) + final_table -= FISHING_DUD else final_table[result] = round(final_table[result] * 0.15, 1) //Fishing without bait is not going to be easy // Apply fish trait modifiers - var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fish_traits)] - var/additive_mod = 0 - var/multiplicative_mod = 1 - 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, fisherman) - additive_mod += mod[ADDITIVE_FISHING_MOD] - multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD] - - final_table[result] += additive_mod - final_table[result] = round(final_table[result] * multiplicative_mod, 1) + final_table[result] = get_fish_trait_catch_mods(final_table[result], result, rod, fisherman, location) if(final_table[result] <= 0) final_table -= result @@ -371,6 +335,63 @@ GLOBAL_LIST(fishing_property_cache) return final_table +/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)) + return weight + var/multiplier = 1 + for(var/fish_trait in SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS]) + var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] + var/list/mod = trait.catch_weight_mod(rod, user, location, 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)) + continue + var/obj/item/fish/prototype = reward + if(initial(prototype.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/obj/item/fishing_rod/rod = user.get_active_held_item() + if(!istype(rod)) + rod = null + + for(var/reward in fish_table) + if(!ispath(reward, /obj/item/fish)) + continue + var/obj/item/fish/prototype = reward + if(initial(prototype.show_in_catalog)) + var/init_name = initial(prototype.name) + if(rod) + var/init_weight = fish_table[reward] + var/weight = (rod.bait ? rod.bait.check_bait(prototype) : 1) + weight = get_fish_trait_catch_mods(weight, reward, rod, user, location) + if(weight > init_weight) + init_name = span_bold(init_name) + if(weight/init_weight >= 3.5) + init_name = "init_name" + else if(weight < init_weight) + init_name = span_small(reward) + known_fishes += init_name + + if(!length(known_fishes)) + return + + 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) + examine_text += span_info("[info]: [english_list(known_fishes)].") + /datum/fish_source/proc/spawn_reward_from_explosion(atom/location, severity) if(!explosive_malus) explosive_spawn(location, severity) @@ -388,7 +409,7 @@ GLOBAL_LIST(fishing_property_cache) explosive_spawn(turf, exploded_turfs[turf], multiplier) exploded_turfs = null -/datum/fish_source/proc/explosive_spawn(location, severity, multiplier = 1) +/datum/fish_source/proc/explosive_spawn(atom/location, severity, multiplier = 1) for(var/i in 1 to (severity + 2)) if(!prob((100 + 100 * severity)/i * multiplier)) continue diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index 52388685687..6aea78b9943 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -1,17 +1,22 @@ /datum/fish_source/ocean fish_table = list( - FISHING_DUD = 11, + FISHING_DUD = 10, /obj/effect/spawner/message_in_a_bottle = 4, - /obj/item/coin/gold = 7, - /obj/item/fish/clownfish = 15, - /obj/item/fish/pufferfish = 15, - /obj/item/fish/cardinal = 15, - /obj/item/fish/greenchromis = 15, + /obj/item/coin/gold = 6, + /obj/item/fish/clownfish = 11, + /obj/item/fish/pufferfish = 11, + /obj/item/fish/cardinal = 11, + /obj/item/fish/greenchromis = 11, + /obj/item/fish/squid = 11, + /obj/item/fish/stingray = 8, + /obj/item/fish/plaice = 8, + /obj/item/fish/monkfish = 5, /obj/item/fish/stingray = 10, /obj/item/fish/lanternfish = 7, /obj/item/fish/zipzap = 7, /obj/item/fish/clownfish/lube = 5, /obj/item/fish/swordfish = 5, + /obj/item/fish/swordfish = 3, /obj/structure/mystery_box/fishing = 2, ) fish_counts = list( @@ -46,13 +51,22 @@ FISHING_DUD = 4, /obj/item/fish/goldfish = 5, /obj/item/fish/guppy = 5, + /obj/item/fish/perch = 4, /obj/item/fish/angelfish = 4, /obj/item/fish/catfish = 4, + /obj/item/fish/perch = 5, /obj/item/fish/slimefish = 2, /obj/item/fish/sockeye_salmon = 1, /obj/item/fish/arctic_char = 1, + /obj/item/fish/pike = 1, /obj/item/fish/goldfish/three_eyes = 1, ) + fish_counts = list( + /obj/item/fish/pike = 3, + ) + fish_count_regen = list( + /obj/item/fish/pike = 4 MINUTES, + ) fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5 /datum/fish_source/sand @@ -85,6 +99,7 @@ /obj/item/fish/goldfish = 10, /obj/item/fish/guppy = 10, /obj/item/fish/angelfish = 10, + /obj/item/fish/perch = 5, /obj/item/fish/goldfish/three_eyes = 3, ) catalog_description = "Aquarium dimension (Fishing portal generator)" @@ -103,6 +118,8 @@ /obj/item/fish/pufferfish = 10, /obj/item/fish/cardinal = 10, /obj/item/fish/greenchromis = 10, + /obj/item/fish/squid = 8, + /obj/item/fish/plaice = 8, ) catalog_description = "Beach dimension (Fishing portal generator)" radial_name = "Beach" @@ -141,6 +158,7 @@ /obj/item/fish/armorfish = 5, /obj/item/fish/zipzap = 5, /obj/item/fish/stingray = 4, + /obj/item/fish/monkfish = 4, /obj/item/fish/swordfish = 3, ) fish_counts = list( @@ -185,13 +203,16 @@ /obj/item/fish/donkfish = 5, /obj/item/fish/emulsijack = 5, /obj/item/fish/jumpercable = 5, - /obj/item/fish/chainsawfish = 3, + /obj/item/fish/chainsawfish = 2, + /obj/item/fish/pike/armored = 2, ) fish_counts = list( /obj/item/fish/chainsawfish = 1, + /obj/item/fish/pike/armored = 1, ) fish_count_regen = list( /obj/item/fish/chainsawfish = 7 MINUTES, + /obj/item/fish/pike/armored = 7 MINUTES, ) catalog_description = "Syndicate dimension (Fishing portal generator)" radial_name = "Syndicate" diff --git a/code/modules/food_and_drinks/recipes/processor_recipes.dm b/code/modules/food_and_drinks/recipes/processor_recipes.dm index 3ff29e194c7..14f93b45cd8 100644 --- a/code/modules/food_and_drinks/recipes/processor_recipes.dm +++ b/code/modules/food_and_drinks/recipes/processor_recipes.dm @@ -139,3 +139,7 @@ output = /obj/item/popsicle_stick food_multiplier = 3 preserve_materials = FALSE + +/datum/food_processor_process/canned_ink + input = /obj/item/food/ink_sac + output = /obj/item/food/canned/squid_ink diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm index f59adad7839..aa63f5d41f1 100644 --- a/code/modules/hydroponics/plant_genes.dm +++ b/code/modules/hydroponics/plant_genes.dm @@ -202,7 +202,7 @@ return RegisterSignal(our_plant, COMSIG_PLANT_ON_SLIP, PROC_REF(squash_plant)) - RegisterSignal(our_plant, COMSIG_MOVABLE_IMPACT, PROC_REF(squash_plant)) + RegisterSignal(our_plant, COMSIG_MOVABLE_IMPACT, PROC_REF(squash_plant_if_not_caught)) RegisterSignal(our_plant, COMSIG_ITEM_ATTACK_SELF, PROC_REF(squash_plant)) /* @@ -239,6 +239,10 @@ qdel(our_plant) +/datum/plant_gene/trait/squash/proc/squash_plant_if_not_caught(datum/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) + if(!caught) + squash_plant(source, hit_atom) + /* * Makes plant slippery, unless it has a grown-type trash. Then the trash gets slippery. * Applies other trait effects (teleporting, etc) to the target by signal. diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 7b734c15306..1d913f4a236 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -91,7 +91,7 @@ /obj/item/paperplane/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) if(iscarbon(hit_atom) && HAS_TRAIT(hit_atom, TRAIT_PAPER_MASTER)) var/mob/living/carbon/hit_carbon = hit_atom - if(hit_carbon.can_catch_item(TRUE)) + if(hit_carbon.can_catch_item(src, skip_throw_mode_check = TRUE)) hit_carbon.throw_mode_on(THROW_MODE_TOGGLE) . = ..() diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index 22306cffe71..90b8acee031 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -1012,7 +1012,7 @@ /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." + desc = "An advanced line reel which can be used speed up fishing or casually snag 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) diff --git a/icons/hud/fishing_hud.dmi b/icons/hud/fishing_hud.dmi index db36e241a2f..f9d2d2ff9c4 100644 Binary files a/icons/hud/fishing_hud.dmi and b/icons/hud/fishing_hud.dmi differ diff --git a/icons/mob/effects/creampie.dmi b/icons/mob/effects/face_decal.dmi similarity index 100% rename from icons/mob/effects/creampie.dmi rename to icons/mob/effects/face_decal.dmi diff --git a/icons/mob/inhands/fish_lefthand.dmi b/icons/mob/inhands/fish_lefthand.dmi index b61231c6b9d..bd712791b92 100644 Binary files a/icons/mob/inhands/fish_lefthand.dmi and b/icons/mob/inhands/fish_lefthand.dmi differ diff --git a/icons/mob/inhands/fish_righthand.dmi b/icons/mob/inhands/fish_righthand.dmi index 48ede802899..09e97a31969 100644 Binary files a/icons/mob/inhands/fish_righthand.dmi and b/icons/mob/inhands/fish_righthand.dmi differ diff --git a/icons/obj/aquarium/fish.dmi b/icons/obj/aquarium/fish.dmi index 069c92c9a3e..e3b41f4cf90 100644 Binary files a/icons/obj/aquarium/fish.dmi and b/icons/obj/aquarium/fish.dmi differ diff --git a/icons/obj/aquarium/wide.dmi b/icons/obj/aquarium/wide.dmi index 61dfbe12718..5334fcf1808 100644 Binary files a/icons/obj/aquarium/wide.dmi and b/icons/obj/aquarium/wide.dmi differ diff --git a/icons/obj/fishing.dmi b/icons/obj/fishing.dmi index b1d5a787b20..2344d2b8ed9 100644 Binary files a/icons/obj/fishing.dmi and b/icons/obj/fishing.dmi differ diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index ad7421dbf6d..7b6afcc37ff 100644 Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ diff --git a/strings/fishing_tips.txt b/strings/fishing_tips.txt index 66e52a2ba88..eda70bb4252 100644 --- a/strings/fishing_tips.txt +++ b/strings/fishing_tips.txt @@ -39,4 +39,6 @@ The sludgefish from the toilets can be used as a steady supply of cheap fish and In a jiffy, you can scoop tadpoles from ponds with your bare hands, place them inside aquariums and quickly raise them into frogs. The legendary fishing hat isn't just cosmetic. Space carps (as well as young lobstrosities and frogs) do truly fear those who wear it. Have you ever heard a lobster or crab talk? Well, neither have I, but they say they're quite the fishy punsters. -You can get an experiscanner from science to perform fish scanning experiments, which can unlock more modules for the fishing portal, as well as fishing technology nodes (better equipment) for research. \ No newline at end of file +You can get an experiscanner from science to perform fish scanning experiments, which can unlock more modules for the fishing portal, as well as fishing technology nodes (better equipment) for research. +If you have enough credits, you can buy a set of fishing lures from cargo. Each lure allows you to catch different species of fish and won't get consumed, however they need to be spun at intervals to work. +This may sound silly, but squids and their ink sacs can be used as weapons to temporarily blind people. \ No newline at end of file diff --git a/tgstation.dme b/tgstation.dme index e2e1a44bf54..f8e32dacc97 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1233,6 +1233,7 @@ #include "code\datums\components\spin2win.dm" #include "code\datums\components\spinny.dm" #include "code\datums\components\spirit_holding.dm" +#include "code\datums\components\splat.dm" #include "code\datums\components\splattercasting.dm" #include "code\datums\components\squashable.dm" #include "code\datums\components\squeak.dm" @@ -4165,7 +4166,16 @@ #include "code\modules\fishing\fish\chasm_detritus.dm" #include "code\modules\fishing\fish\fish_evolution.dm" #include "code\modules\fishing\fish\fish_traits.dm" -#include "code\modules\fishing\fish\fish_types.dm" +#include "code\modules\fishing\fish\types\air_space.dm" +#include "code\modules\fishing\fish\types\anadromous.dm" +#include "code\modules\fishing\fish\types\freshwater.dm" +#include "code\modules\fishing\fish\types\holographic.dm" +#include "code\modules\fishing\fish\types\mining.dm" +#include "code\modules\fishing\fish\types\ruins.dm" +#include "code\modules\fishing\fish\types\saltwater.dm" +#include "code\modules\fishing\fish\types\station.dm" +#include "code\modules\fishing\fish\types\syndicate.dm" +#include "code\modules\fishing\fish\types\tiziran.dm" #include "code\modules\fishing\sources\_fish_source.dm" #include "code\modules\fishing\sources\source_types.dm" #include "code\modules\flufftext\Dreaming.dm"