[READY] Fishing and aquarium expansion. (#76531)

Listing the changes, off the top of my head:
- Resprited fishing rods, hooks, and the worm bait!
- Added a new, telescopic fishing rod, that can be bought as a goodie.
The master rod is also telescopic now.
- Added a couple hooks. One that lets you move the bait up and down,
otherwise keeping it in place, and another that stops the fish from
escaping, but slowly kills it. The former from the bepis fishing tech
node, the latter frm the black market.
- Added a fishing skill and relative legendary reward: A fishing hat,
like the one that recites "women fear me, fish fear me"
- You can now stop fishing by activating the fishing rod in your hand,
and stops it from stealing all clicks on other things if it isn't in
your active hand.
- Reworked fishing traits into fish traits, which can apply to fish
after it has been caught.
- Expanded the fish breeding system. Traits may be passed down to
offsprings, and offsprings may evolve (mutate?) into different kind of
fishes if conditions when conditions are met.
- Added half a dozen new fishes, each with its own traits: lubefish,
sludgefish (and its purple variant), slimefish, unmarine bonemass and
unmarine mastodon. Also, holodeck fish, as a joke.
- New traits: lubed skin, parthenogenesis, toxic (new reagent), toxin
immunity, predator, necrophage, no mating, crossbreeder, aggressive and
revival. Converted Emulsijack's ability and Donkfish's yuckiness into
traits as well.
- Added a fish analyzer that you can scan aquariums and fishes with.
- Fish can now be blended if you really want to. The number of reagents
from blending, w_class, and the number of fillets you get from cutting
fish now scale with size and weight.
- fish feed is no longer infinite (but it should still be plenty).
- Implemented temperature requirements for aquarium fish.
- You can now buy (dead) fish from the black market for dirt cheap.
- Last but now least, toilets are now valid fishing spots.
This commit is contained in:
Ghom
2023-07-27 22:50:36 +02:00
committed by GitHub
parent 61bef09b24
commit d9c8bd9bae
87 changed files with 2653 additions and 576 deletions

View File

@@ -22,6 +22,9 @@
"t" = ( "t" = (
/turf/open/floor/holofloor/carpet, /turf/open/floor/holofloor/carpet,
/area/template_noop) /area/template_noop)
"B" = (
/turf/open/floor/holofloor/beach/water,
/area/template_noop)
"H" = ( "H" = (
/obj/effect/holodeck_effect/mobspawner/monkey, /obj/effect/holodeck_effect/mobspawner/monkey,
/turf/open/floor/holofloor/beach, /turf/open/floor/holofloor/beach,
@@ -64,8 +67,8 @@ R
R R
R R
R R
R
a a
B
"} "}
(2,1,1) = {" (2,1,1) = {"
R R
@@ -76,8 +79,8 @@ R
S S
R R
Q Q
R
a a
B
"} "}
(3,1,1) = {" (3,1,1) = {"
X X
@@ -88,8 +91,8 @@ R
t t
t t
R R
R
a a
B
"} "}
(4,1,1) = {" (4,1,1) = {"
R R
@@ -100,8 +103,8 @@ h
R R
R R
R R
R
a a
B
"} "}
(5,1,1) = {" (5,1,1) = {"
R R
@@ -112,8 +115,8 @@ R
t t
t t
R R
R
a a
B
"} "}
(6,1,1) = {" (6,1,1) = {"
R R
@@ -124,8 +127,8 @@ X
g g
R R
q q
R
a a
B
"} "}
(7,1,1) = {" (7,1,1) = {"
R R
@@ -136,8 +139,8 @@ R
R R
R R
f f
R
a a
B
"} "}
(8,1,1) = {" (8,1,1) = {"
W W
@@ -148,8 +151,8 @@ H
R R
M M
R R
R
a a
B
"} "}
(9,1,1) = {" (9,1,1) = {"
R R
@@ -160,6 +163,6 @@ R
R R
R R
R R
R
a a
B
"} "}

View File

@@ -50,6 +50,7 @@
//Skill medal hub IDs //Skill medal hub IDs
#define MEDAL_LEGENDARY_MINER "Legendary Miner" #define MEDAL_LEGENDARY_MINER "Legendary Miner"
#define MEDAL_LEGENDARY_FISHER "Legendary Fisher"
//Mafia medal hub IDs (wins) //Mafia medal hub IDs (wins)
#define MAFIA_MEDAL_ASSISTANT "Assistant" #define MAFIA_MEDAL_ASSISTANT "Assistant"

View File

@@ -13,9 +13,20 @@
#define FISH_ALIVE "alive" #define FISH_ALIVE "alive"
#define FISH_DEAD "dead" #define FISH_DEAD "dead"
#define FISH_SIZE_TINY_MAX 30
#define FISH_SIZE_SMALL_MAX 50
#define FISH_SIZE_NORMAL_MAX 90
#define FISH_SIZE_BULKY_MAX 130
#define FISH_GRIND_RESULTS_WEIGHT_DIVISOR 500
#define FISH_FILLET_NUMBER_SIZE_DIVISOR 30
#define NEW_FISH_BREEDING_TIMEOUT_MULT 2
#define NEW_FISH_LAST_FEEDING_MULT 0.5
#define MIN_AQUARIUM_TEMP T0C #define MIN_AQUARIUM_TEMP T0C
#define MAX_AQUARIUM_TEMP (T0C + 100) #define MAX_AQUARIUM_TEMP (T0C + 100)
#define DEFAULT_AQUARIUM_TEMP 300 #define DEFAULT_AQUARIUM_TEMP (T0C + 24)
#define FISH_RARITY_BASIC 1000 #define FISH_RARITY_BASIC 1000
#define FISH_RARITY_RARE 400 #define FISH_RARITY_RARE 400
@@ -27,5 +38,6 @@
#define AQUARIUM_FLUID_SULPHWATEVER "Sulfuric Water" #define AQUARIUM_FLUID_SULPHWATEVER "Sulfuric Water"
#define AQUARIUM_FLUID_AIR "Air" #define AQUARIUM_FLUID_AIR "Air"
#define AQUARIUM_FLUID_ANADROMOUS "Adaptive to both Freshwater and Saltwater" #define AQUARIUM_FLUID_ANADROMOUS "Adaptive to both Freshwater and Saltwater"
#define AQUARIUM_FLUID_ANY_WATER "Adaptive to all kind of water"
#define AQUARIUM_COMPANY "Aquatech Ltd." #define AQUARIUM_COMPANY "Aquatech Ltd."

View File

@@ -56,6 +56,7 @@
#define COLOR_VIBRANT_LIME "#00FF00" #define COLOR_VIBRANT_LIME "#00FF00"
#define COLOR_SERVICE_LIME "#58C800" #define COLOR_SERVICE_LIME "#58C800"
#define COLOR_JADE "#5EFB6E" #define COLOR_JADE "#5EFB6E"
#define COLOR_EMERALD "#00CC66"
#define COLOR_LIME "#32CD32" #define COLOR_LIME "#32CD32"
#define COLOR_DARK_LIME "#00aa00" #define COLOR_DARK_LIME "#00aa00"
#define COLOR_VERY_PALE_LIME_GREEN "#DDFFD3" #define COLOR_VERY_PALE_LIME_GREEN "#DDFFD3"

View File

@@ -76,7 +76,7 @@
#define COMPONENT_ALLOW_REACH (1<<0) #define COMPONENT_ALLOW_REACH (1<<0)
///for when an atom has been created through processing (atom/original_atom, list/chosen_processing_option) ///for when an atom has been created through processing (atom/original_atom, list/chosen_processing_option)
#define COMSIG_ATOM_CREATEDBY_PROCESSING "atom_createdby_processing" #define COMSIG_ATOM_CREATEDBY_PROCESSING "atom_createdby_processing"
///when an atom is processed (mob/living/user, obj/item/I, list/atom/results) ///when an atom is processed (mob/living/user, obj/item/process_item, list/atom/results)
#define COMSIG_ATOM_PROCESSED "atom_processed" #define COMSIG_ATOM_PROCESSED "atom_processed"
///called when teleporting into a possibly protected turf: (channel, turf/origin, turf/destination) ///called when teleporting into a possibly protected turf: (channel, turf/origin, turf/destination)
#define COMSIG_ATOM_INTERCEPT_TELEPORTING "intercept_teleporting" #define COMSIG_ATOM_INTERCEPT_TELEPORTING "intercept_teleporting"

View File

@@ -5,9 +5,18 @@
// Fish signals // Fish signals
#define COMSIG_FISH_STATUS_CHANGED "fish_status_changed" #define COMSIG_FISH_STATUS_CHANGED "fish_status_changed"
#define COMSIG_FISH_STIRRED "fish_stirred" #define COMSIG_FISH_STIRRED "fish_stirred"
///From /obj/item/fish/process: (seconds_per_tick)
#define COMSIG_FISH_LIFE "fish_life"
///From /datum/fish_trait/eat_fish: (predator)
#define COMSIG_FISH_EATEN_BY_OTHER_FISH "fish_eaten_by_other_fish"
///From /obj/item/fish/feed: (fed_reagents, fed_reagent_type)
#define COMSIG_FISH_FED "fish_on_fed"
/// Fishing challenge completed /// Fishing challenge completed
#define COMSIG_FISHING_CHALLENGE_COMPLETED "fishing_completed" #define COMSIG_FISHING_CHALLENGE_COMPLETED "fishing_completed"
/// Sent to the fisherman when the reward is dispensed: (reward)
#define COMSIG_MOB_FISHING_REWARD_DISPENSED "mob_fishing_reward_dispensed"
/// Called when you try to use fishing rod on anything /// Called when you try to use fishing rod on anything
#define COMSIG_PRE_FISHING "pre_fishing" #define COMSIG_PRE_FISHING "pre_fishing"
@@ -17,3 +26,9 @@
/// Sent when fishing line is snapped /// Sent when fishing line is snapped
#define COMSIG_FISHING_LINE_SNAPPED "fishing_line_interrupted" #define COMSIG_FISHING_LINE_SNAPPED "fishing_line_interrupted"
/// Sent when the challenge is to be interrupted: (reason)
#define COMSIG_FISHING_SOURCE_INTERRUPT_CHALLENGE "fishing_spot_interrupt_challenge"
/// Sent to the reward after it's been generated by fishing: (fishing_spot)
#define COMSIG_ATOM_FISHING_REWARD "atom_fishing_reward"

View File

@@ -104,6 +104,10 @@
///from base of obj/item/equipped(): (mob/equipper, slot) ///from base of obj/item/equipped(): (mob/equipper, slot)
#define COMSIG_ITEM_EQUIPPED "item_equip" #define COMSIG_ITEM_EQUIPPED "item_equip"
///From base of obj/item/on_equipped() (mob/equipped, slot)
#define COMSIG_ITEM_POST_EQUIPPED "item_post_equipped"
/// This will make the on_equipped proc return FALSE.
#define COMPONENT_EQUIPPED_FAILED (1<<0)
/// A mob has just equipped an item. Called on [/mob] from base of [/obj/item/equipped()]: (/obj/item/equipped_item, slot) /// A mob has just equipped an item. Called on [/mob] from base of [/obj/item/equipped()]: (/obj/item/equipped_item, slot)
#define COMSIG_MOB_EQUIPPED_ITEM "mob_equipped_item" #define COMSIG_MOB_EQUIPPED_ITEM "mob_equipped_item"
/// A mob has just unequipped an item. /// A mob has just unequipped an item.

View File

@@ -35,18 +35,39 @@
/// Fishing hook trait that's used to make the bait more weighted, for the /// Fishing hook trait that's used to make the bait more weighted, for the
/// fishing minigame itself. /// fishing minigame itself.
#define FISHING_HOOK_WEIGHTED (1 << 1) #define FISHING_HOOK_WEIGHTED (1 << 1)
/**
* During the fishing minigame, it stops the bait from being pulled down by gravity,
* while also allowing the player to move it down with right-click.
*/
#define FISHING_HOOK_BIDIRECTIONAL (1 << 2)
///Prevents the user from losing the game by letting the fish get away.
#define FISHING_HOOK_NO_ESCAPE (1 << 3)
///Limits the completion loss of the minigame when the fsh is not on the bait area.
#define FISHING_HOOK_ENSNARE (1 << 4)
///Slowly damages the fish, until it dies, then it's victory.
#define FISHING_HOOK_KILL (1 << 5)
///Reduces the difficulty of the minigame
#define FISHING_LINE_CLOAKED (1 << 0) #define FISHING_LINE_CLOAKED (1 << 0)
///Required to cast a line on lava.
#define FISHING_LINE_REINFORCED (1 << 1) #define FISHING_LINE_REINFORCED (1 << 1)
/// Much like FISHING_HOOK_ENSNARE but for the reel.
#define FISHING_LINE_BOUNCY (1 << 2) #define FISHING_LINE_BOUNCY (1 << 2)
#define FISHING_SPOT_PRESET_BEACH "beach" #define FISHING_SPOT_PRESET_BEACH "beach"
#define FISHING_SPOT_PRESET_LAVALAND_LAVA "lavaland lava" #define FISHING_SPOT_PRESET_LAVALAND_LAVA "lavaland lava"
#define FISHING_SPOT_PRESET_ICEMOON_PLASMA "icemoon plasma"
#define FISHING_SPOT_PRESET_CHASM "chasm" #define FISHING_SPOT_PRESET_CHASM "chasm"
#define FISHING_SPOT_PRESET_TOILET "toilet"
#define FISHING_MINIGAME_RULE_HEAVY_FISH "heavy" #define FISHING_MINIGAME_RULE_HEAVY_FISH "heavy"
#define FISHING_MINIGAME_RULE_LUBED_FISH "lubed"
#define FISHING_MINIGAME_RULE_WEIGHTED_BAIT "weighted" #define FISHING_MINIGAME_RULE_WEIGHTED_BAIT "weighted"
#define FISHING_MINIGAME_RULE_LIMIT_LOSS "limit_loss" #define FISHING_MINIGAME_RULE_LIMIT_LOSS "limit_loss"
#define FISHING_MINIGAME_RULE_BIDIRECTIONAL "bidirectional"
#define FISHING_MINIGAME_RULE_NO_ESCAPE "no_escape"
#define FISHING_MINIGAME_RULE_KILL "kill"
#define FISHING_MINIGAME_RULE_NO_EXP "no_exp"
/// The default additive value for fishing hook catch weight modifiers. /// The default additive value for fishing hook catch weight modifiers.
#define FISHING_DEFAULT_HOOK_BONUS_ADDITIVE 0 #define FISHING_DEFAULT_HOOK_BONUS_ADDITIVE 0

View File

@@ -190,6 +190,8 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list(
#define isitem(A) (istype(A, /obj/item)) #define isitem(A) (istype(A, /obj/item))
#define isfish(A) (istype(A, /obj/item/fish))
#define isstack(A) (istype(A, /obj/item/stack)) #define isstack(A) (istype(A, /obj/item/stack))
#define isgrenade(A) (istype(A, /obj/item/grenade)) #define isgrenade(A) (istype(A, /obj/item/grenade))
@@ -204,6 +206,8 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list(
#define isstructure(A) (istype(A, /obj/structure)) #define isstructure(A) (istype(A, /obj/structure))
#define isaquarium(A) (istype(A, /obj/structure/aquarium))
#define ismachinery(A) (istype(A, /obj/machinery)) #define ismachinery(A) (istype(A, /obj/machinery))
#define isvendor(A) (istype(A, /obj/machinery/vending)) #define isvendor(A) (istype(A, /obj/machinery/vending))

View File

@@ -24,9 +24,14 @@
#define SKILL_EXP_LIST list(SKILL_EXP_NONE, SKILL_EXP_NOVICE, SKILL_EXP_APPRENTICE, SKILL_EXP_JOURNEYMAN, SKILL_EXP_EXPERT, SKILL_EXP_MASTER, SKILL_EXP_LEGENDARY) #define SKILL_EXP_LIST list(SKILL_EXP_NONE, SKILL_EXP_NOVICE, SKILL_EXP_APPRENTICE, SKILL_EXP_JOURNEYMAN, SKILL_EXP_EXPERT, SKILL_EXP_MASTER, SKILL_EXP_LEGENDARY)
//Skill modifier types //Skill modifier types
#define SKILL_SPEED_MODIFIER "skill_speed_modifier"//ideally added/subtracted in speed calculations to make you do stuff faster ///ideally added/subtracted in speed calculations to make you do stuff faster
#define SKILL_PROBS_MODIFIER "skill_probability_modifier"//ideally added/subtracted where beneficial in prob(x) calls #define SKILL_SPEED_MODIFIER "skill_speed_modifier"
#define SKILL_RANDS_MODIFIER "skill_randomness_modifier"//ideally added/subtracted where beneficial in rand(x,y) calls ///ideally added/subtracted where beneficial in prob(x) calls
#define SKILL_PROBS_MODIFIER "skill_probability_modifier"
///ideally added/subtracted where beneficial in rand(x,y) calls
#define SKILL_RANDS_MODIFIER "skill_randomness_modifier"
///ideally for addittive operations
#define SKILL_VALUE_MODIFIER "skill_value_modifier"
// Gets the reference for the skill type that was given // Gets the reference for the skill type that was given
#define GetSkillRef(A) (SSskills.all_skills[A]) #define GetSkillRef(A) (SSskills.all_skills[A])
@@ -34,3 +39,9 @@
//number defines //number defines
#define CLEAN_SKILL_BEAUTY_ADJUSTMENT -15//It's a denominator so no 0. Higher number = less cleaning xp per cleanable. Negative value means cleanables with negative beauty give xp. #define CLEAN_SKILL_BEAUTY_ADJUSTMENT -15//It's a denominator so no 0. Higher number = less cleaning xp per cleanable. Negative value means cleanables with negative beauty give xp.
#define CLEAN_SKILL_GENERIC_WASH_XP 1.5//Value. Higher number = more XP when cleaning non-cleanables (walls/floors/lips) #define CLEAN_SKILL_GENERIC_WASH_XP 1.5//Value. Higher number = more XP when cleaning non-cleanables (walls/floors/lips)
///The multiplier of the extra experience given by the fishing minigame based on difficulty. At the default difficulty of 15, the bonus will be of 21%.
#define FISHING_SKILL_DIFFIULTY_EXP_MULT 0.015
///How much exp one would gain per spent playing the fishing minigame at minimum difficulty.
#define FISHING_SKILL_EXP_PER_SECOND (SKILL_EXP_LEGENDARY / (22 MINUTES))
///The maximum amount of experience one can get per fishing minigame. I appreciate the effort though.
#define FISHING_SKILL_EXP_CAP_PER_GAME (SKILL_EXP_LEGENDARY / 5)

View File

@@ -852,6 +852,17 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Trait given to mechs that can have orebox functionality on movement /// Trait given to mechs that can have orebox functionality on movement
#define TRAIT_OREBOX_FUNCTIONAL "orebox_functional" #define TRAIT_OREBOX_FUNCTIONAL "orebox_functional"
///fish traits
#define TRAIT_RESIST_EMULSIFY "resist_emulsify"
#define TRAIT_FISH_SELF_REPRODUCE "fish_self_reproduce"
#define TRAIT_FISH_NO_MATING "fish_no_mating"
#define TRAIT_YUCKY_FISH "yucky_fish"
#define TRAIT_FISH_TOXIN_IMMUNE "fish_toxin_immune"
#define TRAIT_FISH_CROSSBREEDER "fish_crossbreeder"
#define TRAIT_FISH_AMPHIBIOUS "fish_amphibious"
///Trait needed for the lubefish evolution
#define TRAIT_FISH_FED_LUBE "fish_fed_lube"
// common trait sources // common trait sources
#define TRAIT_GENERIC "generic" #define TRAIT_GENERIC "generic"
#define UNCONSCIOUS_TRAIT "unconscious" #define UNCONSCIOUS_TRAIT "unconscious"
@@ -1189,6 +1200,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// the object has a label applied /// the object has a label applied
#define TRAIT_HAS_LABEL "labeled" #define TRAIT_HAS_LABEL "labeled"
///coming from a fish trait datum.
#define FISH_TRAIT_DATUM "fish_trait_datum"
///coming from a fish evolution datum
#define FISH_EVOLUTION "fish_evolution"
/// some trait sorces dirived from bodyparts BODYPART_TRAIT is generic. /// some trait sorces dirived from bodyparts BODYPART_TRAIT is generic.
#define BODYPART_TRAIT "bodypart" #define BODYPART_TRAIT "bodypart"
#define HEAD_TRAIT "head" #define HEAD_TRAIT "head"

View File

@@ -171,3 +171,31 @@ GLOBAL_LIST_EMPTY(gas_handbook)
return object return object
return null return null
/**
* A simple helped proc that checks if the contents of a list of gases are within acceptable terms.
*
* Arguments:
* * gases: The list of gases which contents are being checked
* * gases to check: An associated list of gas types and acceptable boundaries in moles. e.g. /datum/gas/oxygen = list(16, 30)
* * * if the assoc list is null, then it'll be considered a safe gas and won't return FALSE.
* * extraneous_gas_limit: If a gas not in gases is found, this is the limit above which the proc will return FALSE.
*/
/proc/check_gases(list/gases, list/gases_to_check, extraneous_gas_limit = 0.1)
gases_to_check = gases_to_check.Copy()
for(var/id in gases)
var/gas_moles = gases[id][MOLES]
if(!(id in gases_to_check))
if(gas_moles > extraneous_gas_limit)
return FALSE
continue
var/list/boundaries = gases_to_check[id]
if(boundaries && !ISINRANGE(gas_moles, boundaries[1], boundaries[2]))
return FALSE
gases_to_check -= id
///Check that gases absent from the turf have a lower boundary of zero or none at all, otherwise return FALSE
for(var/id in gases_to_check)
var/list/boundaries = gases_to_check[id]
if(boundaries && boundaries[1] > 0)
return FALSE
return TRUE

View File

@@ -301,7 +301,7 @@ GLOBAL_LIST_INIT(rarity_loot, list(//rare: really good items
/obj/item/shield/buckler = 1, /obj/item/shield/buckler = 1,
/obj/item/throwing_star = 1, /obj/item/throwing_star = 1,
/obj/item/weldingtool/hugetank = 1, /obj/item/weldingtool/hugetank = 1,
/obj/item/fishing_rod/master = 1, /obj/item/fishing_rod/telescopic/master = 1,
/obj/item/spess_knife = 1, /obj/item/spess_knife = 1,
) = 1, ) = 1,

View File

@@ -254,6 +254,15 @@ GLOBAL_LIST_INIT(traits_by_type, list(
/obj/item/card/id = list( /obj/item/card/id = list(
"TRAIT_MAGNETIC_ID_CARD" = TRAIT_MAGNETIC_ID_CARD, "TRAIT_MAGNETIC_ID_CARD" = TRAIT_MAGNETIC_ID_CARD,
), ),
/obj/item/fish = list(
"TRAIT_RESIST_EMULSIFY" = TRAIT_RESIST_EMULSIFY,
"TRAIT_FISH_SELF_REPRODUCE" = TRAIT_FISH_SELF_REPRODUCE,
"TRAIT_FISH_NO_MATING" = TRAIT_FISH_NO_MATING,
"TRAIT_YUCKY_FISH" = TRAIT_YUCKY_FISH,
"TRAIT_FISH_TOXIN_IMMUNE" = TRAIT_FISH_TOXIN_IMMUNE,
"TRAIT_FISH_CROSSBREEDER" = TRAIT_FISH_CROSSBREEDER,
"TRAIT_FISH_FED_LUBE" = TRAIT_FISH_FED_LUBE,
),
)) ))
/// value -> trait name, generated on use from trait_by_type global /// value -> trait name, generated on use from trait_by_type global

View File

@@ -32,6 +32,13 @@
/datum/radial_menu/persistent/element_chosen(choice_id, mob/user, params) /datum/radial_menu/persistent/element_chosen(choice_id, mob/user, params)
select_proc_callback.Invoke(choices_values[choice_id], params) select_proc_callback.Invoke(choices_values[choice_id], params)
///Version of wait used by persistent radial menus.
/datum/radial_menu/persistent/wait()
while(!QDELETED(src))
if(custom_check_callback && next_check < world.time)
custom_check_callback.Invoke()
next_check = world.time + check_delay
stoplag(1)
/datum/radial_menu/persistent/proc/change_choices(list/newchoices, tooltips = FALSE, animate = FALSE, keep_same_page = FALSE) /datum/radial_menu/persistent/proc/change_choices(list/newchoices, tooltips = FALSE, animate = FALSE, keep_same_page = FALSE)
if(!newchoices.len) if(!newchoices.len)
@@ -55,7 +62,7 @@
Select_proc is the proc to be called each time an element on the menu is clicked, and should accept the chosen element as its final argument Select_proc is the proc to be called each time an element on the menu is clicked, and should accept the chosen element as its final argument
Clicking the center button will return a choice of null Clicking the center button will return a choice of null
*/ */
/proc/show_radial_menu_persistent(mob/user, atom/anchor, list/choices, datum/callback/select_proc, uniqueid, radius, tooltips = FALSE, radial_slice_icon = "radial_slice") /proc/show_radial_menu_persistent(mob/user, atom/anchor, list/choices, datum/callback/select_proc, uniqueid, radius, tooltips = FALSE, radial_slice_icon = "radial_slice", datum/callback/custom_check)
if(!user || !anchor || !length(choices) || !select_proc) if(!user || !anchor || !length(choices) || !select_proc)
return return
if(!uniqueid) if(!uniqueid)
@@ -71,9 +78,13 @@
menu.radius = radius menu.radius = radius
menu.radial_slice_icon = radial_slice_icon menu.radial_slice_icon = radial_slice_icon
menu.select_proc_callback = select_proc menu.select_proc_callback = select_proc
if(istype(custom_check))
menu.custom_check_callback = custom_check
menu.anchor = anchor menu.anchor = anchor
menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud
menu.set_choices(choices, tooltips) menu.set_choices(choices, tooltips)
menu.show_to(user) menu.show_to(user)
if(menu.custom_check_callback)
menu.next_check = world.time + menu.check_delay
INVOKE_ASYNC(menu, TYPE_PROC_REF(/datum/radial_menu, wait))
return menu return menu

View File

@@ -7,3 +7,8 @@
database_id = MEDAL_LEGENDARY_MINER database_id = MEDAL_LEGENDARY_MINER
icon = "mining" icon = "mining"
/datum/award/achievement/skill/legendary_fisher
name = "Legendary fisher"
desc = "Give a spaceman a fish and you feed him for a while; teach a spaceman to fish and you feed him until the shuttle arrives."
database_id = MEDAL_LEGENDARY_FISHER
icon = "fishing_hat"

View File

@@ -95,6 +95,8 @@
aquarium_vc_color = fish.aquarium_vc_color aquarium_vc_color = fish.aquarium_vc_color
if(fish.dedicated_in_aquarium_icon_state) if(fish.dedicated_in_aquarium_icon_state)
if(fish.dedicated_in_aquarium_icon)
icon = fish.dedicated_in_aquarium_icon
icon_state = fish.dedicated_in_aquarium_icon_state icon_state = fish.dedicated_in_aquarium_icon_state
base_transform = matrix() base_transform = matrix()
else else

View File

@@ -1,6 +1,3 @@
/// List of weakrefs to containers for things which have fallen into chasms
GLOBAL_LIST_INIT(chasm_storage, list())
// Used by /turf/open/chasm and subtypes to implement the "dropping" mechanic // Used by /turf/open/chasm and subtypes to implement the "dropping" mechanic
/datum/component/chasm /datum/component/chasm
var/turf/target_turf var/turf/target_turf
@@ -35,6 +32,8 @@ GLOBAL_LIST_INIT(chasm_storage, list())
)) ))
/datum/component/chasm/Initialize(turf/target, mapload) /datum/component/chasm/Initialize(turf/target, mapload)
if(!isturf(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_stopped)) RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_stopped))
RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_no_longer_stopped)) RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_no_longer_stopped))
target_turf = target target_turf = target
@@ -45,24 +44,10 @@ GLOBAL_LIST_INIT(chasm_storage, list())
//otherwise don't do anything because turfs and areas are initialized before movables. //otherwise don't do anything because turfs and areas are initialized before movables.
if(!mapload) if(!mapload)
addtimer(CALLBACK(src, PROC_REF(drop_stuff)), 0) addtimer(CALLBACK(src, PROC_REF(drop_stuff)), 0)
src.parent.AddElement(/datum/element/lazy_fishing_spot, FISHING_SPOT_PRESET_CHASM) parent.AddElement(/datum/element/lazy_fishing_spot, FISHING_SPOT_PRESET_CHASM)
/datum/component/chasm/UnregisterFromParent() /datum/component/chasm/UnregisterFromParent()
remove_storage() storage = null
/**
* Deletes the chasm storage object and removes empty weakrefs from global list
*/
/datum/component/chasm/proc/remove_storage()
if (!storage)
return
QDEL_NULL(storage)
var/list/chasm_storage = list()
for (var/datum/weakref/ref as anything in GLOB.chasm_storage)
if (!ref.resolve())
continue
chasm_storage += ref
GLOB.chasm_storage = chasm_storage
/datum/component/chasm/proc/entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) /datum/component/chasm/proc/entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER SIGNAL_HANDLER
@@ -197,9 +182,7 @@ GLOBAL_LIST_INIT(chasm_storage, list())
return return
if(!storage) if(!storage)
storage = new(get_turf(parent)) storage = (locate() in parent) || new(parent)
RegisterSignal(storage, COMSIG_ATOM_EXITED, PROC_REF(left_chasm))
GLOB.chasm_storage += WEAKREF(storage)
if(storage.contains(dropped_thing)) if(storage.contains(dropped_thing))
return return
@@ -209,14 +192,11 @@ GLOBAL_LIST_INIT(chasm_storage, list())
dropped_thing.transform = oldtransform dropped_thing.transform = oldtransform
dropped_thing.pixel_y = oldoffset dropped_thing.pixel_y = oldoffset
if(dropped_thing.forceMove(storage)) if(!dropped_thing.forceMove(storage))
if (isliving(dropped_thing))
RegisterSignal(dropped_thing, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
else
parent.visible_message(span_boldwarning("[parent] spits out [dropped_thing]!")) parent.visible_message(span_boldwarning("[parent] spits out [dropped_thing]!"))
dropped_thing.throw_at(get_edge_target_turf(parent, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10)) dropped_thing.throw_at(get_edge_target_turf(parent, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
if(isliving(dropped_thing)) else if(isliving(dropped_thing))
var/mob/living/fallen_mob = dropped_thing var/mob/living/fallen_mob = dropped_thing
fallen_mob.notransform = FALSE fallen_mob.notransform = FALSE
if (fallen_mob.stat != DEAD) if (fallen_mob.stat != DEAD)
@@ -237,27 +217,8 @@ GLOBAL_LIST_INIT(chasm_storage, list())
SIGNAL_HANDLER SIGNAL_HANDLER
UnregisterSignal(gone, COMSIG_LIVING_REVIVE) UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
#define CHASM_TRAIT "chasm trait" ///Global list needed to let fishermen with a rescue hook fish fallen mobs from any place
GLOBAL_LIST_EMPTY(chasm_fallen_mobs)
/**
* Called if something comes back to life inside the pit. Expected sources are badmins and changelings.
* Ethereals should take enough damage to be smashed and not revive.
*
* Arguments
* * escapee - Lucky guy who just came back to life at the bottom of a hole.
*/
/datum/component/chasm/proc/on_revive(mob/living/escapee)
SIGNAL_HANDLER
var/atom/parent = src.parent
parent.visible_message(span_boldwarning("After a long climb, [escapee] leaps out of [parent]!"))
ADD_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT) //Otherwise they instantly fall back in
escapee.forceMove(get_turf(parent))
escapee.throw_at(get_edge_target_turf(parent, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
REMOVE_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT)
escapee.Paralyze(20 SECONDS, TRUE)
UnregisterSignal(escapee, COMSIG_LIVING_REVIVE)
#undef CHASM_TRAIT
/** /**
* An abstract object which is basically just a bag that the chasm puts people inside * An abstract object which is basically just a bag that the chasm puts people inside
@@ -271,3 +232,40 @@ GLOBAL_LIST_INIT(chasm_storage, list())
/obj/effect/abstract/chasm_storage/Initialize(mapload) /obj/effect/abstract/chasm_storage/Initialize(mapload)
. = ..() . = ..()
ADD_TRAIT(src, TRAIT_SECLUDED_LOCATION, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_SECLUDED_LOCATION, INNATE_TRAIT)
/obj/effect/abstract/chasm_storage/Entered(atom/movable/arrived)
. = ..()
if (isliving(arrived))
RegisterSignal(arrived, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
GLOB.chasm_fallen_mobs += arrived
/obj/effect/abstract/chasm_storage/Exited(atom/movable/gone)
. = ..()
if (isliving(gone))
UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
GLOB.chasm_fallen_mobs -= gone
#define CHASM_TRAIT "chasm trait"
/**
* Called if something comes back to life inside the pit. Expected sources are badmins and changelings.
* Ethereals should take enough damage to be smashed and not revive.
* Arguments
* escapee - Lucky guy who just came back to life at the bottom of a hole.
*/
/obj/effect/abstract/chasm_storage/proc/on_revive(mob/living/escapee)
SIGNAL_HANDLER
var/turf/turf = get_turf(src)
if(turf.GetComponent(/datum/component/chasm))
turf.visible_message(span_boldwarning("After a long climb, [escapee] leaps out of [turf]!"))
else
playsound(turf, 'sound/effects/bang.ogg', 50, TRUE)
turf.visible_message(span_boldwarning("[escapee] busts through [turf], leaping out of the chasm below"))
turf.ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
ADD_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT) //Otherwise they instantly fall back in
escapee.forceMove(turf)
escapee.throw_at(get_edge_target_turf(turf, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
REMOVE_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT)
escapee.Paralyze(20 SECONDS, TRUE)
UnregisterSignal(escapee, COMSIG_LIVING_REVIVE)
#undef CHASM_TRAIT

View File

@@ -51,14 +51,6 @@
/datum/component/fishing_spot/proc/start_fishing_challenge(obj/item/fishing_rod/rod, mob/user) /datum/component/fishing_spot/proc/start_fishing_challenge(obj/item/fishing_rod/rod, mob/user)
/// Roll what we caught based on modified table /// Roll what we caught based on modified table
var/result = fish_source.roll_reward(rod, user) var/result = fish_source.roll_reward(rod, user)
var/datum/fishing_challenge/challenge = new(parent, result, rod, user) var/datum/fishing_challenge/challenge = new(src, result, rod, user)
challenge.background = fish_source.background fish_source.pre_challenge_started(rod, user)
challenge.difficulty = fish_source.calculate_difficulty(result, rod, user)
RegisterSignal(challenge, COMSIG_FISHING_CHALLENGE_COMPLETED, PROC_REF(fishing_completed))
challenge.start(user) challenge.start(user)
/datum/component/fishing_spot/proc/fishing_completed(datum/fishing_challenge/source, mob/user, success, perfect)
if(success)
var/obj/item/fish/caught = source.reward_path
user.add_mob_memory(/datum/memory/caught_fish, protagonist = user, deuteragonist = initial(caught.name))
fish_source.dispense_reward(source.reward_path, user)

View File

@@ -0,0 +1,48 @@
///An element that forbids mobs without a required skill level from equipping the item.
/datum/element/skill_reward
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
///The required skill the user has to have to equip the item.
var/associated_skill
/datum/element/skill_reward/Attach(datum/target, associated_skill)
. = ..()
if(!isitem(target))
return ELEMENT_INCOMPATIBLE
src.associated_skill = associated_skill
RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(target, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
RegisterSignal(target, COMSIG_ITEM_POST_EQUIPPED, PROC_REF(drop_if_unworthy))
/datum/element/skill_reward/proc/on_examine(datum/source, mob/user, list/examine_list)
SIGNAL_HANDLER
examine_list += span_notice("You notice a powerful aura about this item, suggesting that only the truly experienced may wield it.")
/datum/element/skill_reward/proc/on_attack_hand(datum/source, mob/living/user, list/modifiers)
SIGNAL_HANDLER
if(!LAZYACCESS(modifiers, CTRL_CLICK) && !check_equippable(user)) //Allows other players to drag it around at least.
to_chat(user, span_warning("You feel completely and utterly unworthy to even touch \the [source]."))
return COMPONENT_CANCEL_ATTACK_CHAIN
///We check if the item can be equipped, otherwise we drop it.
/datum/element/skill_reward/proc/drop_if_unworthy(datum/source, mob/living/user)
SIGNAL_HANDLER
if(check_equippable(user) | !(source in user.get_equipped_items(TRUE)))
return
to_chat(user, span_warning("You feel completely and utterly unworthy to even touch \the [source]."))
user.dropItemToGround(src, TRUE)
return COMPONENT_EQUIPPED_FAILED
/datum/element/skill_reward/proc/check_equippable(mob/living/user)
return user.mind?.get_skill_level(associated_skill) >= SKILL_LEVEL_LEGENDARY
/**
* Welp, the code is pretty much the same, except for one tiny detail, I suppose it's ok to make a subtype of this element.
* That tiny detail is that we don't check for skills, but if the player has played for thousands of hours.
*/
/datum/element/skill_reward/veteran
element_flags = NONE
/datum/element/skill_reward/veteran/check_equippable(mob/user)
return user.client?.is_veteran()

View File

@@ -42,6 +42,21 @@
icon_file = 'icons/obj/clothing/belt_overlays.dmi' icon_file = 'icons/obj/clothing/belt_overlays.dmi'
json_config = 'code/datums/greyscale/json_configs/screwdriver_worn.json' json_config = 'code/datums/greyscale/json_configs/screwdriver_worn.json'
/datum/greyscale_config/fish_analyzer_inhand_left
name = "Held Fish Analyzer, Left"
icon_file = 'icons/mob/inhands/items_lefthand.dmi'
json_config = 'code/datums/greyscale/json_configs/fish_analyzer_worn.json'
/datum/greyscale_config/fish_analyzer_inhand_right
name = "Held Fish Analyzer, Right"
icon_file = 'icons/mob/inhands/items_righthand.dmi'
json_config = 'code/datums/greyscale/json_configs/fish_analyzer_worn.json'
/datum/greyscale_config/fish_analyzer_worn
name = "Worn Fish Analyzer"
icon_file = 'icons/mob/clothing/belt.dmi'
json_config = 'code/datums/greyscale/json_configs/fish_analyzer_worn.json'
// //
// WEAPONS // WEAPONS
// //

View File

@@ -0,0 +1,15 @@
{
"fish_analyzer": [
{
"type": "icon_state",
"icon_state": "fish_analyzer_outer",
"blend_mode": "overlay",
"color_ids": [ 1 ]
},
{
"type": "icon_state",
"icon_state": "fish_analyzer_inner",
"blend_mode": "overlay"
}
]
}

View File

@@ -131,23 +131,13 @@
return return
var/list/floor_gases = floor_gas_mixture.gases var/list/floor_gases = floor_gas_mixture.gases
var/list/gases_to_check = list(/datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, /datum/gas/plasma) var/static/list/gases_to_check = list(
var/trace_gases /datum/gas/oxygen = list(16, 100),
for(var/id in floor_gases) /datum/gas/nitrogen,
if(id in gases_to_check) /datum/gas/carbon_dioxide = list(0, 10)
continue )
trace_gases = TRUE if(!check_gases(floor_gases, gases_to_check))
break return FALSE
// Can most things breathe?
if(trace_gases)
return
if(!(floor_gases[/datum/gas/oxygen] && floor_gases[/datum/gas/oxygen][MOLES] >= 16))
return
if(floor_gases[/datum/gas/plasma])
return
if(floor_gases[/datum/gas/carbon_dioxide] && floor_gases[/datum/gas/carbon_dioxide][MOLES] >= 10)
return
// Aim for goldilocks temperatures and pressure // Aim for goldilocks temperatures and pressure
if((floor_gas_mixture.temperature <= 270) || (floor_gas_mixture.temperature >= 360)) if((floor_gas_mixture.temperature <= 270) || (floor_gas_mixture.temperature >= 360))

View File

@@ -6,8 +6,8 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill))
var/desc = "the art of doing things" var/desc = "the art of doing things"
///Dictionary of modifier type - list of modifiers (indexed by level). 7 entries in each list for all 7 skill levels. ///Dictionary of modifier type - list of modifiers (indexed by level). 7 entries in each list for all 7 skill levels.
var/modifiers = list(SKILL_SPEED_MODIFIER = list(1, 1, 1, 1, 1, 1, 1)) //Dictionary of modifier type - list of modifiers (indexed by level). 7 entries in each list for all 7 skill levels. var/modifiers = list(SKILL_SPEED_MODIFIER = list(1, 1, 1, 1, 1, 1, 1)) //Dictionary of modifier type - list of modifiers (indexed by level). 7 entries in each list for all 7 skill levels.
///List Path pointing to the skill cape reward that will appear when a user finishes leveling up a skill ///List Path pointing to the skill item reward that will appear when a user finishes leveling up a skill
var/skill_cape_path var/skill_item_path
///List associating different messages that appear on level up with different levels ///List associating different messages that appear on level up with different levels
var/list/levelUpMessages = list() var/list/levelUpMessages = list()
///List associating different messages that appear on level up with different levels ///List associating different messages that appear on level up with different levels
@@ -66,7 +66,7 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill))
/datum/skill/proc/try_skill_reward(datum/mind/mind, new_level) /datum/skill/proc/try_skill_reward(datum/mind/mind, new_level)
if (new_level != SKILL_LEVEL_LEGENDARY) if (new_level != SKILL_LEVEL_LEGENDARY)
return return
if (!ispath(skill_cape_path)) if (!ispath(skill_item_path))
to_chat(mind.current, span_nicegreen("My legendary [name] skill is quite impressive, though it seems the Professional [title] Association doesn't have any status symbols to commemorate my abilities with. I should let Centcom know of this travesty, maybe they can do something about it.")) to_chat(mind.current, span_nicegreen("My legendary [name] skill is quite impressive, though it seems the Professional [title] Association doesn't have any status symbols to commemorate my abilities with. I should let Centcom know of this travesty, maybe they can do something about it."))
return return
if (LAZYFIND(mind.skills_rewarded, src.type)) if (LAZYFIND(mind.skills_rewarded, src.type))
@@ -75,7 +75,7 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill))
podspawn(list( podspawn(list(
"target" = get_turf(mind.current), "target" = get_turf(mind.current),
"style" = STYLE_BLUESPACE, "style" = STYLE_BLUESPACE,
"spawn" = skill_cape_path, "spawn" = skill_item_path,
"delays" = list(POD_TRANSIT = 150, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) "delays" = list(POD_TRANSIT = 150, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30)
)) ))
to_chat(mind.current, span_nicegreen("My legendary skill has attracted the attention of the Professional [title] Association. It seems they are sending me a status symbol to commemorate my abilities.")) to_chat(mind.current, span_nicegreen("My legendary skill has attracted the attention of the Professional [title] Association. It seems they are sending me a status symbol to commemorate my abilities."))

View File

@@ -3,4 +3,4 @@
title = "Cleaner" title = "Cleaner"
desc = "Its not who I am underneath, but what I mop up that defines me." desc = "Its not who I am underneath, but what I mop up that defines me."
modifiers = list(SKILL_SPEED_MODIFIER = list(1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.36)) //speed also touches probability in using up a soap's charge modifiers = list(SKILL_SPEED_MODIFIER = list(1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.36)) //speed also touches probability in using up a soap's charge
skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/cleaning skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/cleaning

View File

@@ -0,0 +1,10 @@
/**
* skill associated with the fishing feature. It modifies the fishing minigame difficulty
* and is gained each time one is completed.
*/
/datum/skill/fishing
name = "Fishing"
title = "Fisher"
desc = "How empty and alone you are on this barren Earth."
modifiers = list(SKILL_VALUE_MODIFIER = list(1, 1, 0, -1, -2, -4, -6))
skill_item_path = /obj/item/clothing/head/soft/fishing_hat

View File

@@ -4,7 +4,7 @@
desc = "My proficiency as a gamer. This helps me beat bosses with ease, powergame in Orion Trail, and makes me wanna slam some gamer fuel." desc = "My proficiency as a gamer. This helps me beat bosses with ease, powergame in Orion Trail, and makes me wanna slam some gamer fuel."
modifiers = list(SKILL_PROBS_MODIFIER = list(0, 5, 10, 15, 15, 20, 25), modifiers = list(SKILL_PROBS_MODIFIER = list(0, 5, 10, 15, 15, 20, 25),
SKILL_RANDS_MODIFIER = list(0, 1, 2, 3, 4, 5, 7)) SKILL_RANDS_MODIFIER = list(0, 1, 2, 3, 4, 5, 7))
skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/gaming skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/gaming
/datum/skill/gaming/New() /datum/skill/gaming/New()
. = ..() . = ..()

View File

@@ -3,4 +3,4 @@
title = "Miner" title = "Miner"
desc = "A dwarf's biggest skill, after drinking." desc = "A dwarf's biggest skill, after drinking."
modifiers = list(SKILL_SPEED_MODIFIER = list(1, 0.95, 0.9, 0.85, 0.75, 0.6, 0.5),SKILL_PROBS_MODIFIER=list(10, 15, 20, 25, 30, 35, 40)) modifiers = list(SKILL_SPEED_MODIFIER = list(1, 0.95, 0.9, 0.85, 0.75, 0.6, 0.5),SKILL_PROBS_MODIFIER=list(10, 15, 20, 25, 30, 35, 40))
skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/mining skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/mining

View File

@@ -0,0 +1,33 @@
/datum/storage/fish_case
max_slots = 1
max_specific_storage = WEIGHT_CLASS_HUGE
can_hold_trait = TRAIT_FISH_CASE_COMPATIBILE
can_hold_description = "fish and aquarium equipment"
/**
* Change the size of the storage item to match the inserted item's
* Because of that, we also check if conditions to keep it inside another storage or pockets are still met.
*/
/datum/storage/fish_case/handle_enter(obj/item/storage/fish_case/source, obj/item/arrived)
. = ..()
if(!istype(arrived) || arrived.w_class == source.w_class)
return
source.w_class = arrived.w_class
var/obj/item/resolve_parent = parent?.resolve()
if(resolve_parent?.item_flags & IN_STORAGE)
source.moveToNullspace() //temporarily remove source from its location so that attempt_insert may work correctly.
if(!resolve_parent.atom_storage?.attempt_insert(source, override = TRUE))
source.forceMove(resolve_parent.drop_location())
source.visible_message("[source] spills out of [resolve_parent] as it expands to hold [arrived]", vision_distance = 1)
else if(!isliving(source.loc))
return
var/mob/living/living_loc = source.loc
var/equipped_slot = living_loc.get_slot_by_item(source)
if(equipped_slot & (ITEM_SLOT_RPOCKET|ITEM_SLOT_LPOCKET) && source.w_class > WEIGHT_CLASS_SMALL)
source.forceMove(living_loc.drop_location())
to_chat(living_loc, "[source] drops out of your pockets as it expands to hold [arrived]")
/datum/storage/fish_case/handle_exit(obj/item/storage/fish_case/source, obj/item/gone)
. = ..()
if(istype(gone))
source.w_class = initial(source.w_class)

View File

@@ -641,6 +641,22 @@
/obj/item/proc/on_found(mob/finder) /obj/item/proc/on_found(mob/finder)
return return
/**
* Called after an item is placed in an equipment slot. Runs equipped(), then sends a signal.
* This should be called last or near-to-last, after all other inventory code stuff is handled.
*
* Arguments:
* * user is mob that equipped it
* * slot uses the slot_X defines found in setup.dm for items that can be placed in multiple slots
* * initial is used to indicate whether or not this is the initial equipment (job datums etc) or just a player doing it
*/
/obj/item/proc/on_equipped(mob/user, slot, initial = FALSE)
SHOULD_NOT_OVERRIDE(TRUE)
equipped(user, slot, initial)
if(SEND_SIGNAL(src, COMSIG_ITEM_POST_EQUIPPED, user, slot) && COMPONENT_EQUIPPED_FAILED)
return FALSE
return TRUE
/** /**
* To be overwritten to only perform visual tasks; * To be overwritten to only perform visual tasks;
* this is directly called instead of `equipped` on visual-only features like human dummies equipping outfits. * this is directly called instead of `equipped` on visual-only features like human dummies equipping outfits.
@@ -652,7 +668,7 @@
return return
/** /**
* Called after an item is placed in an equipment slot. * Called by on_equipped. Don't call this directly, we want the ITEM_POST_EQUIPPED signal to be sent after everything else.
* *
* Note that hands count as slots. * Note that hands count as slots.
* *
@@ -663,7 +679,7 @@
*/ */
/obj/item/proc/equipped(mob/user, slot, initial = FALSE) /obj/item/proc/equipped(mob/user, slot, initial = FALSE)
SHOULD_CALL_PARENT(TRUE) SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(user, COMSIG_HUMAN_EQUIPPING_ITEM, src, slot) PROTECTED_PROC(TRUE)
visual_equipped(user, slot, initial) visual_equipped(user, slot, initial)
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot)
SEND_SIGNAL(user, COMSIG_MOB_EQUIPPED_ITEM, src, slot) SEND_SIGNAL(user, COMSIG_MOB_EQUIPPED_ITEM, src, slot)

View File

@@ -81,11 +81,11 @@
icon_state = "armorfish_fillet" icon_state = "armorfish_fillet"
food_reagents = list(/datum/reagent/consumable/nutriment/protein = 3) food_reagents = list(/datum/reagent/consumable/nutriment/protein = 3)
///donkfish fillets. The yuck reagent is now added by the fish trait of the same name.
/obj/item/food/fishmeat/donkfish /obj/item/food/fishmeat/donkfish
name = "donkfillet" name = "donkfillet"
desc = "The dreaded donkfish fillet. No sane spaceman would eat this, and it does not get better when cooked." desc = "The dreaded donkfish fillet. No sane spaceman would eat this, and it does not get better when cooked."
icon_state = "donkfillet" icon_state = "donkfillet"
food_reagents = list(/datum/reagent/yuck = 3)
/obj/item/food/fishfingers /obj/item/food/fishfingers
name = "fish fingers" name = "fish fingers"

View File

@@ -165,3 +165,12 @@
/obj/item/storage/wallet/random/PopulateContents() /obj/item/storage/wallet/random/PopulateContents()
new /obj/item/holochip(src, rand(5, 30)) new /obj/item/holochip(src, rand(5, 30))
new /obj/effect/spawner/random/entertainment/wallet_storage(src) new /obj/effect/spawner/random/entertainment/wallet_storage(src)
///Used by the toilet fish source.
/obj/item/storage/wallet/money
desc = "It can hold a few small and personal things. This one reeks of toilet water."
/obj/item/storage/wallet/money/PopulateContents()
for(var/iteration in 1 to pick(3, 4))
new /obj/item/holochip(src, rand(50, 450))

View File

@@ -16,7 +16,8 @@
. = ..() . = ..()
open = round(rand(0, 1)) open = round(rand(0, 1))
update_appearance() update_appearance()
if(mapload && SSmapping.level_trait(z, ZTRAIT_STATION))
AddElement(/datum/element/lazy_fishing_spot, FISHING_SPOT_PRESET_TOILET)
/obj/structure/toilet/attack_hand(mob/living/user, list/modifiers) /obj/structure/toilet/attack_hand(mob/living/user, list/modifiers)
. = ..() . = ..()
@@ -120,8 +121,9 @@
return return
w_items += I.w_class w_items += I.w_class
to_chat(user, span_notice("You carefully place [I] into the cistern.")) to_chat(user, span_notice("You carefully place [I] into the cistern."))
return
else if(is_reagent_container(I) && !user.combat_mode) if(is_reagent_container(I) && !user.combat_mode)
if (!open) if (!open)
return return
if(istype(I, /obj/item/food/monkeycube)) if(istype(I, /obj/item/food/monkeycube))

View File

@@ -38,10 +38,13 @@
var/mask_icon = 'icons/turf/floors.dmi' var/mask_icon = 'icons/turf/floors.dmi'
/// The icon state that covers the lava bits of our turf /// The icon state that covers the lava bits of our turf
var/mask_state = "lava-lightmask" var/mask_state = "lava-lightmask"
/// The configuration key for the preset fishing spot for this type of turf.
var/fish_source_type = FISHING_SPOT_PRESET_LAVALAND_LAVA
/turf/open/lava/Initialize(mapload) /turf/open/lava/Initialize(mapload)
. = ..() . = ..()
AddElement(/datum/element/lazy_fishing_spot, FISHING_SPOT_PRESET_LAVALAND_LAVA) if(fish_source_type)
AddElement(/datum/element/lazy_fishing_spot, fish_source_type)
refresh_light() refresh_light()
if(!smoothing_flags) if(!smoothing_flags)
update_appearance() update_appearance()
@@ -342,6 +345,7 @@
icon_state = "liquidplasma" icon_state = "liquidplasma"
initial_gas_mix = BURNING_COLD initial_gas_mix = BURNING_COLD
baseturfs = /turf/open/lava/plasma baseturfs = /turf/open/lava/plasma
fish_source_type = FISHING_SPOT_PRESET_ICEMOON_PLASMA
light_range = 3 light_range = 3
light_power = 0.75 light_power = 0.75
@@ -423,8 +427,10 @@
initial_gas_mix = OPENTURF_DEFAULT_ATMOS initial_gas_mix = OPENTURF_DEFAULT_ATMOS
baseturfs = /turf/open/lava/plasma/mafia baseturfs = /turf/open/lava/plasma/mafia
slowdown = 0 slowdown = 0
fish_source_type = null
//basketball specific lava (normal atmos, no slowdown) //basketball specific lava (normal atmos, no slowdown)
/turf/open/lava/smooth/basketball /turf/open/lava/smooth/basketball
initial_gas_mix = OPENTURF_DEFAULT_ATMOS initial_gas_mix = OPENTURF_DEFAULT_ATMOS
slowdown = 0 slowdown = 0
fish_source_type = null

View File

@@ -236,6 +236,12 @@
cost = PAYCHECK_CREW cost = PAYCHECK_CREW
contains = list(/obj/item/bait_can/worm/premium) contains = list(/obj/item/bait_can/worm/premium)
/datum/supply_pack/goody/telescopic_fishing_rod
name = "Telescopic Fishing Rod"
desc = "A collapsible fishing rod that can fit within a backpack."
cost = PAYCHECK_CREW * 8
contains = list(/obj/item/fishing_rod/telescopic)
/datum/supply_pack/goody/coffee_mug /datum/supply_pack/goody/coffee_mug
name = "Coffee Mug" name = "Coffee Mug"
desc = "A bog standard coffee mug, for drinking coffee." desc = "A bog standard coffee mug, for drinking coffee."

View File

@@ -95,6 +95,13 @@
stock_max = 3 stock_max = 3
availability_prob = 50 availability_prob = 50
/datum/market_item/misc/jawed_hook
name = "Jawed Fishing Hook"
desc = "The thing ya use if y'are strugglin' with fishes. Just rememeber to whoop yer rod before it's too late, 'cause this thing's gonna hurt them like an Arkansas toothpick."
price_min = CARGO_CRATE_VALUE * 0.75
price_max = CARGO_CRATE_VALUE * 2
stock_max = 3
availability_prob = 70
/datum/market_item/misc/v8_engine /datum/market_item/misc/v8_engine
name = "Genuine V8 Engine (Perserved)" name = "Genuine V8 Engine (Perserved)"
@@ -104,3 +111,13 @@
price_max = CARGO_CRATE_VALUE * 6 price_max = CARGO_CRATE_VALUE * 6
stock_max = 1 stock_max = 1
availability_prob = 15 availability_prob = 15
/datum/market_item/misc/fish
name = "Fish"
desc = "Fish! Fresh fish! Fish you can cut, grind and even keep in aquarium if you want to! Get some before the next fight at my village breaks out!"
price_min = PAYCHECK_CREW * 0.5
price_max = PAYCHECK_CREW * 1.2
item = /obj/item/storage/fish_case/blackmarket
stock_min = 3
stock_max = 8
availability_prob = 90

View File

@@ -68,6 +68,13 @@
contains = list(/obj/item/storage/fish_case/tiziran = 2) contains = list(/obj/item/storage/fish_case/tiziran = 2)
crate_name = "tiziran fish crate" crate_name = "tiziran fish crate"
/datum/supply_pack/misc/fish_analyzers
name = "Fish Analyzers"
desc = "A pack containing three analyzers to monitor fish's status and traits with."
cost = CARGO_CRATE_VALUE * 2.5
contains = list(/obj/item/fish_analyzer = 3)
crate_name = "fish analyzers crate"
/datum/supply_pack/misc/bicycle /datum/supply_pack/misc/bicycle
name = "Bicycle" name = "Bicycle"
desc = "Nanotrasen reminds all employees to never toy with powers outside their control." desc = "Nanotrasen reminds all employees to never toy with powers outside their control."

View File

@@ -6,13 +6,14 @@
icon_state = "cargosoft" icon_state = "cargosoft"
inhand_icon_state = "greyscale_softcap" //todo wip inhand_icon_state = "greyscale_softcap" //todo wip
var/soft_type = "cargo" var/soft_type = "cargo"
var/soft_suffix = "soft"
dog_fashion = /datum/dog_fashion/head/cargo_tech dog_fashion = /datum/dog_fashion/head/cargo_tech
var/flipped = FALSE var/flipped = FALSE
/obj/item/clothing/head/soft/dropped() /obj/item/clothing/head/soft/dropped()
icon_state = "[soft_type]soft" icon_state = "[soft_type][soft_suffix]"
flipped = FALSE flipped = FALSE
..() ..()
@@ -33,10 +34,10 @@
if(!user.incapacitated()) if(!user.incapacitated())
flipped = !flipped flipped = !flipped
if(flipped) if(flipped)
icon_state = "[soft_type]soft_flipped" icon_state = "[soft_type][soft_suffix]_flipped"
to_chat(user, span_notice("You flip the hat backwards.")) to_chat(user, span_notice("You flip the hat backwards."))
else else
icon_state = "[soft_type]soft" icon_state = "[soft_type][soft_suffix]"
to_chat(user, span_notice("You flip the hat back in normal position.")) to_chat(user, span_notice("You flip the hat back in normal position."))
usr.update_worn_head() //so our mob-overlays update usr.update_worn_head() //so our mob-overlays update
@@ -139,3 +140,24 @@
icon_state = "paramedicsoft" icon_state = "paramedicsoft"
soft_type = "paramedic" soft_type = "paramedic"
dog_fashion = null dog_fashion = null
/obj/item/clothing/head/soft/fishing_hat
name = "legendary fishing hat"
desc = "An ancient relic of a bygone era of bountiful catches and endless rivers. Printed on the front is a poem:<i>\n\
Women Fear Me\n\
Fish Fear Me\n\
Men Turn Their Eyes Away From Me\n\
As I Walk No Beast Dares Make A Sound In My Presence\n\
I Am Alone On This Barren Earth.</i>"
icon_state = "fishing_hat"
soft_type = "fishing_hat"
inhand_icon_state = "fishing_hat"
soft_suffix = null
worn_y_offset = 5
clothing_flags = SNUG_FIT
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE
dog_fashion = null
/obj/item/clothing/head/soft/fishing_hat/Initialize(mapload)
. = ..()
AddElement(/datum/element/skill_reward, /datum/skill/fishing)

View File

@@ -55,31 +55,12 @@
/obj/item/clothing/neck/cloak/skill_reward /obj/item/clothing/neck/cloak/skill_reward
var/associated_skill_path = /datum/skill var/associated_skill_path = /datum/skill
var/element_type = /datum/element/skill_reward
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE
/obj/item/clothing/neck/cloak/skill_reward/examine(mob/user) /obj/item/clothing/neck/cloak/skill_reward/Initialize(mapload)
. = ..() . = ..()
. += span_notice("You notice a powerful aura about this cloak, suggesting that only the truly experienced may wield it.") AddElement(element_type, associated_skill_path)
/obj/item/clothing/neck/cloak/skill_reward/proc/check_wearable(mob/user)
return user.mind?.get_skill_level(associated_skill_path) >= SKILL_LEVEL_LEGENDARY
/obj/item/clothing/neck/cloak/skill_reward/proc/unworthy_unequip(mob/user)
to_chat(user, span_warning("You feel completely and utterly unworthy to even touch \the [src]."))
var/hand_index = user.get_held_index_of_item(src)
if (hand_index)
user.dropItemToGround(src, TRUE)
return FALSE
/obj/item/clothing/neck/cloak/skill_reward/equipped(mob/user, slot)
if (!check_wearable(user))
unworthy_unequip(user)
return ..()
/obj/item/clothing/neck/cloak/skill_reward/attack_hand(mob/user, list/modifiers)
if (!check_wearable(user))
unworthy_unequip(user)
return ..()
/obj/item/clothing/neck/cloak/skill_reward/gaming /obj/item/clothing/neck/cloak/skill_reward/gaming
name = "legendary gamer's cloak" name = "legendary gamer's cloak"
@@ -103,6 +84,5 @@
name = "legendary veteran's cloak" name = "legendary veteran's cloak"
desc = "Worn by the wisest of veteran employees, this legendary cloak is only attainable by maintaining a living employment agreement with Nanotrasen for over <b>five thousand hours</b>. This status symbol represents a being is better than you in nearly every quantifiable way, simple as that." desc = "Worn by the wisest of veteran employees, this legendary cloak is only attainable by maintaining a living employment agreement with Nanotrasen for over <b>five thousand hours</b>. This status symbol represents a being is better than you in nearly every quantifiable way, simple as that."
icon_state = "playercloak" icon_state = "playercloak"
element_type = /datum/element/skill_reward/veteran
/obj/item/clothing/neck/cloak/skill_reward/playing/check_wearable(mob/user)
return user.client?.is_veteran()

View File

@@ -37,22 +37,39 @@
///Current layers in use by aquarium contents ///Current layers in use by aquarium contents
var/list/used_layers = list() var/list/used_layers = list()
/// /obj/item/fish in the aquarium - does not include things with aquarium visuals that are not fish /// /obj/item/fish in the aquarium, sorted by type - does not include things with aquarium visuals that are not fish
var/list/tracked_fish = list() var/list/tracked_fish_by_type
/obj/structure/aquarium/Initialize(mapload) /obj/structure/aquarium/Initialize(mapload)
. = ..() . = ..()
update_appearance() update_appearance()
RegisterSignal(src,COMSIG_ATOM_ATTACKBY, PROC_REF(feed_feedback)) RegisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(track_if_fish))
AddElement(/datum/element/relay_attackers)
RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked))
/obj/structure/aquarium/proc/track_if_fish(atom/source, atom/initialized)
SIGNAL_HANDLER
if(isfish(initialized))
LAZYADDASSOCLIST(tracked_fish_by_type, initialized.type, initialized)
/obj/structure/aquarium/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) /obj/structure/aquarium/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
. = ..() . = ..()
if(istype(arrived,/obj/item/fish)) if(isfish(arrived))
tracked_fish += arrived LAZYADDASSOCLIST(tracked_fish_by_type, arrived.type, arrived)
/obj/structure/aquarium/Exited(atom/movable/gone, direction) /obj/structure/aquarium/Exited(atom/movable/gone, direction)
. = ..() . = ..()
tracked_fish -= gone LAZYREMOVEASSOC(tracked_fish_by_type, gone.type, gone)
/// Returns tracked_fish_by_type but flattened and without the items in the blacklist, also shuffled if shuffle is TRUE.
/obj/structure/aquarium/proc/get_fishes(shuffle = FALSE, blacklist)
. = list()
for(var/fish_type in tracked_fish_by_type)
. += tracked_fish_by_type[fish_type]
. -= blacklist
if(shuffle)
. = shuffle(.)
return .
/obj/structure/aquarium/proc/request_layer(layer_type) /obj/structure/aquarium/proc/request_layer(layer_type)
/** /**
@@ -111,9 +128,9 @@
default_unfasten_wrench(user, tool) default_unfasten_wrench(user, tool)
return TOOL_ACT_TOOLTYPE_SUCCESS return TOOL_ACT_TOOLTYPE_SUCCESS
/obj/structure/aquarium/attackby(obj/item/I, mob/living/user, params) /obj/structure/aquarium/attackby(obj/item/item, mob/living/user, params)
if(broken) if(broken)
var/obj/item/stack/sheet/glass/glass = I var/obj/item/stack/sheet/glass/glass = item
if(istype(glass)) if(istype(glass))
if(glass.get_amount() < 2) if(glass.get_amount() < 2)
to_chat(user, span_warning("You need two glass sheets to fix the case!")) to_chat(user, span_warning("You need two glass sheets to fix the case!"))
@@ -126,20 +143,27 @@
update_appearance() update_appearance()
return TRUE return TRUE
else else
var/datum/component/aquarium_content/content_component = I.GetComponent(/datum/component/aquarium_content) var/datum/component/aquarium_content/content_component = item.GetComponent(/datum/component/aquarium_content)
if(content_component && content_component.is_ready_to_insert(src)) if(content_component && content_component.is_ready_to_insert(src) && user.transferItemToLoc(item, src))
if(user.transferItemToLoc(I,src)) update_appearance()
update_appearance() return TRUE
return TRUE
else if(istype(item, /obj/item/fish_feed))
return ..() if(!item.reagents.total_volume)
to_chat(user, span_warning("[item] is empty."))
return TRUE
var/list/fishes = get_fishes()
for(var/obj/item/fish/fish as anything in fishes)
fish.feed(item.reagents)
to_chat(user, span_notice("You feed the fish."))
return TRUE
return ..() return ..()
/obj/structure/aquarium/proc/feed_feedback(datum/source, obj/item/thing, mob/user, params) /obj/structure/aquarium/proc/on_attacked(datum/source, mob/attacker, attack_flags)
SIGNAL_HANDLER var/list/fishes = get_fishes()
if(istype(thing, /obj/item/fish_feed)) //I wish this were an aquarium signal, but the aquarium_content component got in the way.
to_chat(user,span_notice("You feed the fish.")) for(var/obj/item/fish/fish as anything in fishes)
return NONE SEND_SIGNAL(fish, COMSIG_FISH_STIRRED)
/obj/structure/aquarium/interact(mob/user) /obj/structure/aquarium/interact(mob/user)
if(!broken && user.pulling && isliving(user.pulling)) if(!broken && user.pulling && isliving(user.pulling))
@@ -176,6 +200,7 @@
if(do_after(user, 5 SECONDS, target = src)) if(do_after(user, 5 SECONDS, target = src))
var/alive_fish = 0 var/alive_fish = 0
var/dead_fish = 0 var/dead_fish = 0
var/list/tracked_fish = get_fishes()
for(var/obj/item/fish/fish in tracked_fish) for(var/obj/item/fish/fish in tracked_fish)
if(fish.status == FISH_ALIVE) if(fish.status == FISH_ALIVE)
alive_fish++ alive_fish++

View File

@@ -1,7 +1,7 @@
///Fish feed can ///Fish feed can
/obj/item/fish_feed /obj/item/fish_feed
name = "fish feed can" name = "fish feed can"
desc = "Autogenerates nutritious fish feed based on sample inside." desc = "A refillable can that dispenses nutritious fish feed."
icon = 'icons/obj/aquarium.dmi' icon = 'icons/obj/aquarium.dmi'
icon_state = "fish_feed" icon_state = "fish_feed"
w_class = WEIGHT_CLASS_TINY w_class = WEIGHT_CLASS_TINY
@@ -9,57 +9,85 @@
/obj/item/fish_feed/Initialize(mapload) /obj/item/fish_feed/Initialize(mapload)
. = ..() . = ..()
create_reagents(5, OPENCONTAINER) create_reagents(5, OPENCONTAINER)
reagents.add_reagent(/datum/reagent/consumable/nutriment, 1) //Default fish diet reagents.add_reagent(/datum/reagent/consumable/nutriment, 2.5) //Default fish diet
///Stasis fish case container for moving fish between aquariums safely. /**
* Stasis fish case container for moving fish between aquariums safely.
* Their w_class scales with that of the fish inside it.
* Most subtypes of this also start with a fish already inside.
*/
/obj/item/storage/fish_case /obj/item/storage/fish_case
name = "stasis fish case" name = "stasis fish case"
desc = "A small case keeping the fish inside in stasis." desc = "A resizable case keeping the fish inside in stasis."
icon = 'icons/obj/storage/case.dmi' icon = 'icons/obj/storage/case.dmi'
icon_state = "fishbox" icon_state = "fishbox"
inhand_icon_state = "syringe_kit" inhand_icon_state = "syringe_kit"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
storage_type = /datum/storage/fish_case
/obj/item/storage/fish_case/Initialize(mapload) /obj/item/storage/fish_case/Initialize(mapload)
ADD_TRAIT(src, TRAIT_FISH_SAFE_STORAGE, TRAIT_GENERIC) // Before populate so fish instatiates in ready container already ADD_TRAIT(src, TRAIT_FISH_SAFE_STORAGE, TRAIT_GENERIC) // Before populate so fish instatiates in ready container already
. = ..() return ..()
create_storage(max_slots = 1) /obj/item/storage/fish_case/PopulateContents()
atom_storage.can_hold_trait = TRAIT_FISH_CASE_COMPATIBILE var/fish_type = get_fish_type()
atom_storage.can_hold_description = "fish and aquarium equipment" if(fish_type)
var/obj/item/fish/spawned_fish = new fish_type(null)
spawned_fish.forceMove(src) // trigger storage.handle_entered
///Fish case with single random fish inside. /obj/item/storage/fish_case/proc/get_fish_type()
/obj/item/storage/fish_case/random/PopulateContents() return
. = ..()
var/fish_type = select_fish_type()
new fish_type(src)
/obj/item/storage/fish_case/random/proc/select_fish_type() /obj/item/storage/fish_case/random
return random_fish_type()
/obj/item/storage/fish_case/random/freshwater/select_fish_type() var/fluid_type
return random_fish_type(required_fluid=AQUARIUM_FLUID_FRESHWATER)
/obj/item/storage/fish_case/random/saltwater/select_fish_type() /obj/item/storage/fish_case/random/get_fish_type()
return random_fish_type(required_fluid=AQUARIUM_FLUID_SALTWATER) return random_fish_type(required_fluid = fluid_type)
/obj/item/storage/fish_case/random/freshwater
fluid_type = AQUARIUM_FLUID_FRESHWATER
/obj/item/storage/fish_case/random/saltwater
fluid_type = AQUARIUM_FLUID_SALTWATER
/obj/item/storage/fish_case/syndicate /obj/item/storage/fish_case/syndicate
name = "ominous fish case" name = "ominous fish case"
/obj/item/storage/fish_case/syndicate/PopulateContents() /obj/item/storage/fish_case/syndicate/get_fish_type()
. = ..() return pick(/obj/item/fish/donkfish, /obj/item/fish/emulsijack)
var/fish_type = pick(/obj/item/fish/donkfish, /obj/item/fish/emulsijack)
new fish_type(src)
/obj/item/storage/fish_case/tiziran /obj/item/storage/fish_case/tiziran
name = "imported fish case" name = "imported fish case"
/obj/item/storage/fish_case/tiziran/PopulateContents() /obj/item/storage/fish_case/tiziran/get_fish_type()
return pick(/obj/item/fish/dwarf_moonfish, /obj/item/fish/gunner_jellyfish, /obj/item/fish/needlefish, /obj/item/fish/armorfish)
///Subtype bought from the blackmarket at a gratuitously cheap price. The catch? The fish inside it is dead.
/obj/item/storage/fish_case/blackmarket
name = "ominous fish case"
desc = "A resizable case keeping the fish inside in stasis. This one holds a faint cadaverine smell."
/obj/item/storage/fish_case/blackmarket/get_fish_type()
var/static/list/weighted_list = list(
/obj/item/fish/boned = 1,
/obj/item/fish/clownfish/lube = 3,
/obj/item/fish/emulsijack = 1,
/obj/item/fish/sludgefish/purple = 1,
/obj/item/fish/pufferfish = 3,
/obj/item/fish/slimefish = 2,
/obj/item/fish/ratfish = 2,
/obj/item/fish/chasm_crab/ice = 2,
/obj/item/fish/chasm_crab = 2,
)
return pick_weight(weighted_list)
/obj/item/storage/fish_cas/blackmarket/Initialize(mapload)
. = ..() . = ..()
var/fish_type = pick(/obj/item/fish/dwarf_moonfish, /obj/item/fish/gunner_jellyfish, /obj/item/fish/needlefish, /obj/item/fish/armorfish) for(var/obj/item/fish/fish as anything in contents)
new fish_type(src) fish.set_status(FISH_DEAD)
/obj/item/aquarium_kit /obj/item/aquarium_kit
name = "DIY Aquarium Construction Kit" name = "DIY Aquarium Construction Kit"
@@ -72,7 +100,6 @@
. = ..() . = ..()
to_chat(user,span_notice("There's instruction and tools necessary to build aquarium inside. All you need is to start crafting.")) to_chat(user,span_notice("There's instruction and tools necessary to build aquarium inside. All you need is to start crafting."))
/obj/item/aquarium_prop /obj/item/aquarium_prop
name = "generic aquarium prop" name = "generic aquarium prop"
desc = "very boring" desc = "very boring"

View File

@@ -0,0 +1,241 @@
///An item that can be used to gather information on the fish, such as but not limited to: health, hunger and traits.
/obj/item/fish_analyzer
name = "fish analyzer"
icon = 'icons/obj/device.dmi'
icon_state = "fish_analyzer_map"
base_icon_state = "fish_analyzer"
inhand_icon_state = "fish_analyzer"
worn_icon_state = "fish_analyzer"
desc = "A fish-shaped scanner used to monitor fish's status and evolutionary traits."
flags_1 = CONDUCT_1
item_flags = NOBLUDGEON
slot_flags = ITEM_SLOT_BELT
throwforce = 3
w_class = WEIGHT_CLASS_TINY
throw_speed = 3
throw_range = 7
custom_materials = list(/datum/material/iron= SMALL_MATERIAL_AMOUNT *2)
greyscale_config_inhand_left = /datum/greyscale_config/fish_analyzer_inhand_left
greyscale_config_inhand_right = /datum/greyscale_config/fish_analyzer_inhand_right
greyscale_config_worn = /datum/greyscale_config/fish_analyzer_worn
///The color of the case. Used by grayscale configs and update_overlays()
var/case_color
/**
* The radial menu shown when analyzing aquariums. Having a persistent one allows us
* to update it whenever fish come and go, and is also required since we have a select callback
* used to check right clicks for scanning traits instead of status.
*/
var/datum/radial_menu/persistent/fish_menu
/// A cached list of the current choices for the aforedefined radial menu.
var/list/radial_choices
/obj/item/fish_analyzer/Initialize(mapload)
case_color = rgb(rand(16, 255), rand(16, 255), rand(16, 255))
set_greyscale(colors = list(case_color))
. = ..()
register_item_context()
update_appearance()
/obj/item/fish_analyzer/Destroy()
if(fish_menu)
QDEL_NULL(fish_menu)
radial_choices = null
return ..()
/obj/item/fish_analyzer/update_icon_state()
. = ..()
icon_state = base_icon_state
/obj/item/fish_analyzer/update_overlays()
. = ..()
var/mutable_appearance/case = mutable_appearance(icon, "fish_analyzer_case")
case.color = case_color
. += case
. += emissive_appearance(icon, "fish_analyzer_emissive", src)
/obj/item/fish_analyzer/add_item_context(obj/item/source, list/context, atom/target)
if (isfish(target))
context[SCREENTIP_CONTEXT_LMB] = "Analyze status"
context[SCREENTIP_CONTEXT_RMB] = "Analyze traits"
return CONTEXTUAL_SCREENTIP_SET
else if(isaquarium(target))
context[SCREENTIP_CONTEXT_LMB] = "Open radial menu"
return CONTEXTUAL_SCREENTIP_SET
return NONE
/obj/item/fish_analyzer/afterattack(atom/target, mob/user, proximity)
. = ..()
if(!proximity || !user.can_read(src) || user.is_blind())
return
if(isfish(target))
balloon_alert(user, "analyzing stats")
user.visible_message(span_notice("[user] analyzes [target]."), span_notice("You analyze [target]."))
analyze_status(target, user)
else if(istype(target, /obj/structure/aquarium))
scan_aquarium(target, user)
/obj/item/fish_analyzer/afterattack_secondary(atom/target, mob/user, proximity_flag, click_parameters)
if(!isfish(target))
return
if(!proximity_flag || !user.can_read(src) || user.is_blind())
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
balloon_alert(user, "analyzing traits")
analyze_traits(target, user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
///Instantiates the radial menu, populates the list of choices, shows it and register signals on the aquarium.
/obj/item/fish_analyzer/proc/scan_aquarium(obj/structure/aquarium/aquarium, mob/user)
if(fish_menu)
balloon_alert(user, "already scanning")
return
var/list/fishes = aquarium.get_fishes()
if(!length(fishes))
balloon_alert(user, "no fish to scan")
return
radial_choices = list()
for(var/obj/item/fish/fish as anything in fishes)
radial_choices(fish)
fish_menu = show_radial_menu_persistent(user, aquarium, radial_choices, select_proc = CALLBACK(src, PROC_REF(choice_selected), user, aquarium), tooltips = TRUE, custom_check = CALLBACK(src, PROC_REF(can_select_fish), user, aquarium))
RegisterSignal(aquarium, COMSIG_ATOM_ABSTRACT_ENTERED, PROC_REF(on_aquarium_entered))
RegisterSignal(aquarium, COMSIG_ATOM_ABSTRACT_EXITED, PROC_REF(on_aquarium_exited))
RegisterSignal(aquarium, COMSIG_QDELETING, PROC_REF(delete_radial))
///Instantiates a radial menu choice datum for the current fish and adds it to the list of choices.
/obj/item/fish_analyzer/proc/radial_choices(obj/item/fish/fish)
var/datum/radial_menu_choice/menu_choice = new
menu_choice.name = fish.name
menu_choice.info = "[fish.status == FISH_ALIVE ? "Alive" : "Dead"]\n[fish.size] cm\n[fish.weight] g\nProgenitors: [fish.progenitors]\nRight-click to analyze traits"
var/mutable_appearance/fish_appearance = new(fish)
fish_appearance.layer = FLOAT_LAYER
fish_appearance.plane = FLOAT_PLANE
menu_choice.image = fish_appearance
radial_choices[fish] = menu_choice
///Called when the user has selected a choice. If it's a right click, analyze the traits, else the status
/obj/item/fish_analyzer/proc/choice_selected(mob/user, obj/structure/aquarium/aquarium, obj/item/fish/choice, params)
if(!choice || !can_select_fish(user, aquarium))
delete_radial(aquarium)
return
var/is_right_clicking = LAZYACCESS(params2list(params), RIGHT_CLICK)
user.visible_message(span_notice("[user] analyzes [choice] inside [aquarium]."), span_notice("You analyze [choice] inside [aquarium]."))
if(is_right_clicking)
analyze_traits(choice, user)
else
analyze_status(choice, user)
///Whether the item should continue to show its radial menu or delete it.
/obj/item/fish_analyzer/proc/can_select_fish(mob/user, obj/structure/aquarium/aquarium)
if(!user.is_holding(src) || !user?.CanReach(aquarium) || IS_DEAD_OR_INCAP(user))
delete_radial(aquarium)
return FALSE
return TRUE
///Called when something enters the aquarium. If it's a fish, update the choices.
/obj/item/fish_analyzer/proc/on_aquarium_entered(obj/structure/aquarium/source, atom/movable/arrived)
SIGNAL_HANDLER
if(isfish(arrived))
radial_choices(arrived)
fish_menu.change_choices(radial_choices, tooltips = TRUE, animate = TRUE)
///Called when something exits the aquarium. If it's a fish, update the choices.
/obj/item/fish_analyzer/proc/on_aquarium_exited(obj/structure/aquarium/source, atom/movable/gone)
SIGNAL_HANDLER
if(!isfish(gone))
return
radial_choices -= gone
if(!length(radial_choices))
delete_radial(source)
return
fish_menu.change_choices(radial_choices, tooltips = TRUE, animate = TRUE)
///Unregisters signals, delete the radial menu, unsets the choices.
/obj/item/fish_analyzer/proc/delete_radial(obj/structure/aquarium/source)
SIGNAL_HANDLER
UnregisterSignal(source, list(COMSIG_ATOM_ABSTRACT_EXITED, COMSIG_ATOM_ABSTRACT_ENTERED, COMSIG_QDELETING))
QDEL_NULL(fish_menu)
radial_choices = null
/**
* Called when a fish or a menu choice is left-clicked.
* This returns the fish's status, size, weight, feed type, hunger, breeding timeout.
*/
/obj/item/fish_analyzer/proc/analyze_status(obj/item/fish/fish, mob/user)
// the final list of strings to render
var/render_list = list()
var/fish_status = fish.status == FISH_DEAD ? span_alert("<b>Deceased</b>") : "<b>[PERCENT(fish.health/initial(fish.health))]% healthy</b>"
render_list += "[span_info("Analyzing status for [fish]:")]\n<span class='info ml-1'>Overrall status: [fish_status]</span>\n"
render_list += "<span class='info ml-1'>Size: [fish.size] cm - Weight: [fish.weight] g</span>\n"
render_list += "<span class='info ml-1'>Required feed type: <font color='[initial(fish.food.color)]'>[initial(fish.food.name)]</font></span>\n"
render_list += "<span class='info ml-1'>Safe temperature: [fish.required_temperature_min] - [fish.required_temperature_max]K"
if(isaquarium(fish.loc))
var/obj/structure/aquarium/aquarium = fish.loc
if(!ISINRANGE(aquarium.fluid_temp, fish.required_temperature_min, fish.required_temperature_max))
render_list += span_alert("(OUT OF RANGE)")
render_list += "</span>\n"
render_list += "<span class='info ml-1'>Safe fluid type: [fish.required_fluid_type]"
if(isaquarium(fish.loc))
var/obj/structure/aquarium/aquarium = fish.loc
if(!compatible_fluid_type(fish.required_fluid_type, aquarium.fluid_type))
render_list += span_alert("(IN UNSAFE FLUID)")
render_list += "</span>"
if(fish.status != FISH_DEAD)
render_list += "\n"
var/hunger = PERCENT(min((world.time - fish.last_feeding) / fish.feeding_frequency, 1))
var/hunger_string = "[hunger]%"
switch(hunger)
if(0 to 60)
hunger_string = span_info(hunger_string)
if(60 to 90)
hunger_string = span_warning(hunger_string)
if(90 to 100)
hunger_string = span_alert(hunger_string)
render_list += "<span class='info ml-1'>Hunger: [hunger_string]</span>\n"
var/time_left = round(max(fish.breeding_wait - world.time, 0)/10)
render_list += "<span class='info ml-1'>Time until it can breed: [time_left] seconds</span>"
to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO)
/**
* Called when a fish or a menu choice is left-clicked.
* This returns the fish's progenitors, traits and their inheritability.
*/
/obj/item/fish_analyzer/proc/analyze_traits(obj/item/fish/fish, mob/user)
// the final list of strings to render
var/render_list = list()
render_list += "[span_info("Analyzing traits for [fish]:")]\n<span class='info ml-1'>Progenitor species: [fish.progenitors]</span>\n"
if(!length(fish.fish_traits))
render_list += "<span class='info ml-1'>This fish has no trait to speak of...</span>\n"
else
render_list += "<span class='info ml-1'>Traits:</span>\n"
for(var/trait_type in fish.fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
var/tooltipped_trait = span_tooltip(trait.catalog_description, trait.name)
render_list += "<span class='info ml-2'>[tooltipped_trait] - Inheritabilities: <font color='[COLOR_EMERALD]'>[trait.inheritability]%</font> - <font color='[COLOR_BRIGHT_ORANGE]'>[trait.diff_traits_inheritability]%</font></span>\n"
var/evolution_len = length(fish.evolution_types)
if(!evolution_len)
render_list += "<span class='info ml-1'>This fish has no evolution to speak of...</span>"
for(var/index in 1 to evolution_len)
var/datum/fish_evolution/evolution = GLOB.fish_evolutions[fish.evolution_types[index]]
var/evolution_name = evolution.name
var/evolution_tooltip = evolution.get_evolution_tooltip()
if(evolution_tooltip)
evolution_name = span_tooltip(evolution_tooltip, evolution_name)
render_list += "<span class='info ml-2'>[evolution_name] - Base Probability: [evolution.probability]%</span>"
if(index != evolution_len)
render_list += "\n"
to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO)

View File

@@ -4,8 +4,16 @@
desc = "very bland" desc = "very bland"
icon = 'icons/obj/aquarium.dmi' icon = 'icons/obj/aquarium.dmi'
icon_state = "bugfish" icon_state = "bugfish"
lefthand_file = 'icons/mob/inhands/fish_lefthand.dmi'
w_class = WEIGHT_CLASS_TINY righthand_file = 'icons/mob/inhands/fish_righthand.dmi'
inhand_icon_state = "fish_normal"
force = 6
attack_verb_continuous = list("slaps", "whacks")
attack_verb_simple = list("slap", "whack")
hitsound = 'sound/weapons/slap.ogg'
///The grind results of the fish. They scale with the weight of the fish.
grind_results = list(/datum/reagent/blood = 20, /datum/reagent/consumable/liquidgibs = 5)
obj_flags = UNIQUE_RENAME
/// Resulting width of aquarium visual icon - default size of "fish_greyscale" state /// Resulting width of aquarium visual icon - default size of "fish_greyscale" state
var/sprite_width = 3 var/sprite_width = 3
@@ -17,6 +25,11 @@
/// Original height of aquarium visual icon - used to calculate scaledown factor /// Original height of aquarium visual icon - used to calculate scaledown factor
var/source_height = 32 var/source_height = 32
/**
* If present and it also has a dedicated icon state, this icon file will
* be used for in-aquarium visual for the fish instead of its icon
*/
var/dedicated_in_aquarium_icon
/// If present this icon will be used for in-aquarium visual for the fish instead of icon_state /// If present this icon will be used for in-aquarium visual for the fish instead of icon_state
var/dedicated_in_aquarium_icon_state var/dedicated_in_aquarium_icon_state
@@ -31,7 +44,7 @@
var/required_temperature_max = MAX_AQUARIUM_TEMP var/required_temperature_max = MAX_AQUARIUM_TEMP
/// What type of reagent this fish needs to be fed. /// What type of reagent this fish needs to be fed.
var/food = /datum/reagent/consumable/nutriment var/datum/reagent/food = /datum/reagent/consumable/nutriment
/// How often the fish needs to be fed /// How often the fish needs to be fed
var/feeding_frequency = 5 MINUTES var/feeding_frequency = 5 MINUTES
/// Time of last feedeing /// Time of last feedeing
@@ -39,9 +52,15 @@
/// Fish status /// Fish status
var/status = FISH_ALIVE var/status = FISH_ALIVE
///icon used when the fish is dead, ifset.
var/icon_state_dead
///If this fish should do the flopping animation
var/do_flop_animation = TRUE
/// Current fish health. Dies at 0. /// Current fish health. Dies at 0.
var/health = 100 var/health = 100
/// The message shown when the fish dies.
var/death_text = "%SRC dies."
/// Should this fish type show in fish catalog /// Should this fish type show in fish catalog
var/show_in_catalog = TRUE var/show_in_catalog = TRUE
@@ -52,13 +71,21 @@
/// Fish autogenerated from this behaviour will be processable into this /// Fish autogenerated from this behaviour will be processable into this
var/fillet_type = /obj/item/food/fishmeat var/fillet_type = /obj/item/food/fishmeat
/// number of fillets given by the fish. It scales with its size.
var/num_fillets = 1
/// Won't breed more than this amount in single aquarium. /// Won't breed more than this amount in single aquarium.
var/stable_population = 1 var/stable_population = 1
/// Last time new fish was created /// The time limit before new fish can be created
var/last_breeding var/breeding_wait
/// How long it takes to produce new fish /// How long it takes to produce new fish
var/breeding_timeout = 2 MINUTES var/breeding_timeout = 2 MINUTES
/// If set, the fish can also breed with these fishes types
var/list/compatible_types
/// A list of possible evolutions. If set, offsprings may be of a different, new fish type if conditions are met.
var/list/evolution_types
/// The species' name(s) of the parents of the fish. Shown by the fish analyzer.
var/progenitors
var/flopping = FALSE var/flopping = FALSE
@@ -66,8 +93,11 @@
// Fishing related properties // Fishing related properties
/// List of fishing trait types, these modify probabilty/difficulty depending on rod/user properties /**
var/list/fishing_traits = list() * List of fish trait types, these may modify probabilty/difficulty depending on rod/user properties
* or dictate how the fish behaves or some of its qualities.
*/
var/list/fish_traits = list()
/// Fishing behaviour /// Fishing behaviour
var/fish_ai_type = FISH_AI_DUMB var/fish_ai_type = FISH_AI_DUMB
@@ -87,32 +117,44 @@
*/ */
var/list/disliked_bait = list() var/list/disliked_bait = list()
/// Size in centimeters /// Size in centimeters. Item size class scales with it.
var/size = 50 var/size = 50
/// Average size for this fish type in centimeters. Will be used as gaussian distribution with 20% deviation for fishing, bought fish are always standard size /// Average size for this fish type in centimeters. Will be used as gaussian distribution with 20% deviation for fishing, bought fish are always standard size
var/average_size = 50 var/average_size = 50
/// Weight in grams /// Weight in grams. number of fillets and grind results scale with it, just don't think too hard how someone could manage to fit a trout in a blender.
var/weight = 1000 var/weight = 1000
/// Average weight for this fish type in grams /// Average weight for this fish type in grams
var/average_weight = 1000 var/average_weight = 1000
/obj/item/fish/Initialize(mapload, apply_qualities = TRUE)
/obj/item/fish/Initialize(mapload)
. = ..() . = ..()
if(fillet_type)
AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, 1, 5, screentip_verb = "Cut")
AddComponent(/datum/component/aquarium_content, PROC_REF(get_aquarium_animation), list(COMSIG_FISH_STATUS_CHANGED,COMSIG_FISH_STIRRED)) AddComponent(/datum/component/aquarium_content, PROC_REF(get_aquarium_animation), list(COMSIG_FISH_STATUS_CHANGED,COMSIG_FISH_STIRRED))
RegisterSignal(src, COMSIG_ATOM_TEMPORARY_ANIMATION_START, PROC_REF(on_temp_animation))
RegisterSignal(src, COMSIG_ATOM_ON_LAZARUS_INJECTOR, PROC_REF(use_lazarus))
check_environment_after_movement() RegisterSignal(src, COMSIG_ATOM_ON_LAZARUS_INJECTOR, PROC_REF(use_lazarus))
if(do_flop_animation)
RegisterSignal(src, COMSIG_ATOM_TEMPORARY_ANIMATION_START, PROC_REF(on_temp_animation))
check_environment()
if(status != FISH_DEAD) if(status != FISH_DEAD)
START_PROCESSING(SSobj, src) START_PROCESSING(SSobj, src)
size = average_size //stops new fish from being able to reproduce right away.
weight = average_weight breeding_wait = world.time + (breeding_timeout * NEW_FISH_BREEDING_TIMEOUT_MULT)
last_feeding = world.time - (feeding_frequency * NEW_FISH_LAST_FEEDING_MULT)
if(apply_qualities)
apply_traits() //Make sure traits are applied before size and weight.
update_size_and_weight(first_run = TRUE)
progenitors = full_capitalize(name) //default value
register_evolutions()
/obj/item/fish/update_icon_state()
if(status == FISH_DEAD && icon_state_dead)
icon_state = icon_state_dead
else
icon_state = initial(icon_state)
return ..()
/obj/item/fish/examine(mob/user) /obj/item/fish/examine(mob/user)
. = ..() . = ..()
@@ -120,18 +162,111 @@
. += span_notice("It's [size] cm long.") . += span_notice("It's [size] cm long.")
. += span_notice("It weighs [weight] g.") . += span_notice("It weighs [weight] g.")
/obj/item/fish/proc/randomize_weight_and_size(modifier = 0) ///Randomizes weight and size.
var/size_deviation = 0.2 * average_size /obj/item/fish/proc/randomize_size_and_weight(avg_size = average_size, avg_weight = average_weight, deviation = 0.2, first_run = FALSE)
var/size_mod = modifier * average_size var/size_deviation = 0.2 * avg_size
size = max(1,gaussian(average_size + size_mod, size_deviation)) var/new_size = round(max(1,gaussian(avg_size, size_deviation)), 1)
var/weight_deviation = 0.2 * average_weight var/weight_deviation = 0.2 * avg_weight
var/weight_mod = modifier * average_weight var/new_weight = round(max(1,gaussian(avg_weight, weight_deviation)), 1)
weight = max(1,gaussian(average_weight + weight_mod, weight_deviation))
update_size_and_weight(new_size, new_weight, first_run)
///Updates weight and size, along with weight class, number of fillets you can get and grind results.
/obj/item/fish/proc/update_size_and_weight(new_size = average_size, new_weight = average_weight, first_run = FALSE)
if(!first_run && fillet_type)
RemoveElement(/datum/element/processable, TOOL_KNIFE, fillet_type, num_fillets, 0.5 SECONDS, screentip_verb = "Cut")
size = new_size
switch(size)
if(0 to FISH_SIZE_TINY_MAX)
w_class = WEIGHT_CLASS_TINY
inhand_icon_state = "fish_small"
if(FISH_SIZE_TINY_MAX to FISH_SIZE_SMALL_MAX)
inhand_icon_state = "fish_small"
w_class = WEIGHT_CLASS_SMALL
if(FISH_SIZE_SMALL_MAX to FISH_SIZE_NORMAL_MAX)
inhand_icon_state = "fish_normal"
w_class = WEIGHT_CLASS_NORMAL
if(FISH_SIZE_NORMAL_MAX to FISH_SIZE_BULKY_MAX)
inhand_icon_state = "fish_bulky"
w_class = WEIGHT_CLASS_BULKY
if(FISH_SIZE_BULKY_MAX to INFINITY)
inhand_icon_state = "fish_huge"
w_class = WEIGHT_CLASS_HUGE
if(fillet_type)
var/init_fillets = initial(num_fillets)
var/amount = max(round(init_fillets * size / FISH_FILLET_NUMBER_SIZE_DIVISOR, 1), 1)
num_fillets = amount
AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, num_fillets, 0.5 SECONDS, screentip_verb = "Cut")
if(!first_run)
for(var/reagent_type in grind_results)
grind_results[reagent_type] /= FLOOR(weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR, 0.1)
weight = new_weight
for(var/reagent_type in grind_results)
grind_results[reagent_type] *= FLOOR(weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR, 0.1)
/**
* This proc has fish_traits list populated with fish_traits paths from three different lists:
* traits from x_traits and y_traits are compared, and inserted if conditions are met;
* traits from fixed_traits are inserted unconditionally.
* traits from removed_traits will be removed from the for loop.
*
* This proc should only be called if the fish was spawned with the apply_qualities arg set to FALSE
* and hasn't had inherited traits already.
*/
/obj/item/fish/proc/inherit_traits(list/x_traits, list/y_traits, list/fixed_traits, list/removed_traits)
fish_traits = fixed_traits?.Copy() || list()
var/list/same_traits = x_traits & y_traits
var/list/all_traits = (x_traits|y_traits)-removed_traits
/**
* Traits that the fish is guaranteed to inherit will be inherited,
* with the assertion that they're compatible anyway.
*/
for(var/trait_type in all_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
if(type in trait.guaranteed_inheritance_types)
fish_traits |= trait_type
all_traits -= trait_type
///Build a list of incompatible traits. Don't let any such trait pass onto the fish.
var/list/incompatible_traits = list()
for(var/trait_type in fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
incompatible_traits |= trait.incompatible_traits
/**
* shuffle the traits, so, in the case of incompatible traits, we don't have to choose which to discard.
* Instead we let the random numbers do it for us in a first come, first served basis.
*/
for(var/trait_type in shuffle(all_traits))
if(trait_type in fish_traits)
continue //likely a fixed trait
if(trait_type in incompatible_traits)
continue
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
if(length(fish_traits & trait.incompatible_traits))
continue
if((trait_type in same_traits) ? prob(trait.inheritability) : prob(trait.diff_traits_inheritability))
fish_traits |= trait_type
incompatible_traits |= trait.incompatible_traits
apply_traits()
/obj/item/fish/proc/apply_traits()
for(var/fish_trait_type in fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait_type]
trait.apply_to_fish(src)
/obj/item/fish/proc/register_evolutions()
for(var/evolution_type in evolution_types)
var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type]
evolution.register_fish(src)
/obj/item/fish/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) /obj/item/fish/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..() . = ..()
check_environment_after_movement() check_environment()
/obj/item/fish/proc/enter_stasis() /obj/item/fish/proc/enter_stasis()
in_stasis = TRUE in_stasis = TRUE
@@ -144,49 +279,35 @@
if(status != FISH_DEAD) if(status != FISH_DEAD)
START_PROCESSING(SSobj, src) START_PROCESSING(SSobj, src)
/obj/item/fish/proc/on_aquarium_insertion(obj/structure/aquarium) ///Feed the fishes with the contents of the fish feed
if(isnull(last_feeding)) //Fish start fed. /obj/item/fish/proc/feed(datum/reagents/fed_reagents)
last_feeding = world.time if(status != FISH_ALIVE)
RegisterSignal(aquarium, COMSIG_ATOM_EXITED, PROC_REF(aquarium_exited))
RegisterSignal(aquarium, COMSIG_ATOM_ATTACKBY, PROC_REF(attack_reaction))
/obj/item/fish/proc/aquarium_exited(datum/source, atom/movable/gone, direction)
SIGNAL_HANDLER
if(src != gone)
return return
UnregisterSignal(source,list(COMSIG_ATOM_EXITED,COMSIG_ATOM_ATTACKBY)) var/fed_reagent_type
if(fed_reagents.remove_reagent(food, 0.1))
/// Our aquarium is hit with stuff fed_reagent_type = food
/obj/item/fish/proc/attack_reaction(datum/source, obj/item/thing, mob/user, params)
SIGNAL_HANDLER
if(is_food(thing))
on_feeding(thing.reagents)
return COMPONENT_NO_AFTERATTACK
else
//stirred effect
SEND_SIGNAL(src, COMSIG_FISH_STIRRED)
/obj/item/fish/proc/is_food(obj/item/thing)
return istype(thing, /obj/item/fish_feed)
/obj/item/fish/proc/on_feeding(datum/reagents/feed_reagents)
if(feed_reagents.has_reagent(food))
last_feeding = world.time last_feeding = world.time
else
var/datum/reagent/wrong_reagent = pick(fed_reagents.reagent_list)
fed_reagent_type = wrong_reagent.type
fed_reagents.remove_reagent(fed_reagent_type, 0.1)
SEND_SIGNAL(src, COMSIG_FISH_FED, fed_reagents, fed_reagent_type)
/obj/item/fish/proc/check_environment_after_movement() /obj/item/fish/proc/check_environment(stasis_check = TRUE)
if(QDELETED(src)) //we don't care anymore if(QDELETED(src)) //we don't care anymore
return return
// Apply/remove stasis as needed if(stasis_check)
if(loc && HAS_TRAIT(loc, TRAIT_FISH_SAFE_STORAGE)) // Apply/remove stasis as needed
enter_stasis() if(loc && HAS_TRAIT(loc, TRAIT_FISH_SAFE_STORAGE))
else if(in_stasis) enter_stasis()
exit_stasis() else if(in_stasis)
exit_stasis()
if(!do_flop_animation)
return
// Do additional stuff // Do additional stuff
var/in_aquarium = istype(loc,/obj/structure/aquarium) var/in_aquarium = isaquarium(loc)
if(in_aquarium)
on_aquarium_insertion(loc)
// Start flopping if outside of fish container // Start flopping if outside of fish container
var/should_be_flopping = status == FISH_ALIVE && loc && !HAS_TRAIT(loc,TRAIT_FISH_SAFE_STORAGE) && !in_aquarium var/should_be_flopping = status == FISH_ALIVE && loc && !HAS_TRAIT(loc,TRAIT_FISH_SAFE_STORAGE) && !in_aquarium
@@ -203,22 +324,28 @@
if(ready_to_reproduce()) if(ready_to_reproduce())
try_to_reproduce() try_to_reproduce()
SEND_SIGNAL(src, COMSIG_FISH_LIFE, seconds_per_tick)
/obj/item/fish/proc/set_status(new_status) /obj/item/fish/proc/set_status(new_status)
if(status == new_status)
return
switch(new_status) switch(new_status)
if(FISH_ALIVE) if(FISH_ALIVE)
status = FISH_ALIVE status = FISH_ALIVE
health = initial(health) // since the fishe has been revived health = initial(health) // since the fishe has been revived
start_flopping() last_feeding = world.time //reset hunger
check_environment(FALSE)
START_PROCESSING(SSobj, src) START_PROCESSING(SSobj, src)
if(FISH_DEAD) if(FISH_DEAD)
status = FISH_DEAD status = FISH_DEAD
STOP_PROCESSING(SSobj, src) STOP_PROCESSING(SSobj, src)
stop_flopping() stop_flopping()
var/message = span_notice("\The [name] dies.") var/message = span_notice(replacetext(death_text, "%SRC", "[src]"))
if(istype(loc,/obj/structure/aquarium)) if(isaquarium(loc))
loc.visible_message(message) loc.visible_message(message)
else else
visible_message(message) visible_message(message)
update_appearance()
SEND_SIGNAL(src, COMSIG_FISH_STATUS_CHANGED) SEND_SIGNAL(src, COMSIG_FISH_STATUS_CHANGED)
/obj/item/fish/proc/use_lazarus(datum/source, obj/item/lazarus_injector/injector, mob/user) /obj/item/fish/proc/use_lazarus(datum/source, obj/item/lazarus_injector/injector, mob/user)
@@ -243,16 +370,31 @@
/// Checks if our current environment lets us live. /// Checks if our current environment lets us live.
/obj/item/fish/proc/proper_environment() /obj/item/fish/proc/proper_environment()
var/obj/structure/aquarium/aquarium = loc var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium)) if(istype(aquarium))
return FALSE if(!compatible_fluid_type(required_fluid_type, aquarium.fluid_type))
if(aquarium.fluid_type != AQUARIUM_FLUID_AIR || !HAS_TRAIT(src, TRAIT_FISH_AMPHIBIOUS))
return FALSE
if(!ISINRANGE(aquarium.fluid_temp, required_temperature_min, required_temperature_max))
return FALSE
return TRUE
if(required_fluid_type != AQUARIUM_FLUID_ANADROMOUS) if(required_fluid_type != AQUARIUM_FLUID_AIR && !HAS_TRAIT(src, TRAIT_FISH_AMPHIBIOUS))
if(aquarium.fluid_type != required_fluid_type) return FALSE
return FALSE var/datum/gas_mixture/mixture = loc.return_air()
else if(!mixture)
if(aquarium.fluid_type != AQUARIUM_FLUID_SALTWATER && aquarium.fluid_type != AQUARIUM_FLUID_FRESHWATER) return FALSE
return FALSE var/static/list/gases_to_check = list(
if(aquarium.fluid_temp < required_temperature_min || aquarium.fluid_temp > required_temperature_max) /datum/gas/oxygen = list(12, 100),
/datum/gas/nitrogen,
/datum/gas/carbon_dioxide = list(0, 10),
/datum/gas/water_vapor,
)
if(!check_gases(mixture.gases, gases_to_check))
return FALSE
if(!ISINRANGE(mixture.temperature, required_temperature_min, required_temperature_max))
return FALSE
var/pressure = mixture.return_pressure()
if((pressure <= 20) || (pressure >= 550))
return FALSE return FALSE
return TRUE return TRUE
@@ -272,37 +414,109 @@
set_status(FISH_DEAD) set_status(FISH_DEAD)
/obj/item/fish/proc/ready_to_reproduce() //Fish breeding stops if fish count exceeds this.
#define AQUARIUM_MAX_BREEDING_POPULATION 20
/obj/item/fish/proc/ready_to_reproduce(being_targetted = FALSE)
var/obj/structure/aquarium/aquarium = loc var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium)) if(!istype(aquarium))
return FALSE return FALSE
return aquarium.allow_breeding && health == initial(health) && stable_population > 1 && world.time - last_breeding >= breeding_timeout if(being_targetted && HAS_TRAIT(src, TRAIT_FISH_NO_MATING))
return FALSE
if(!being_targetted && length(aquarium.get_fishes()) >= AQUARIUM_MAX_BREEDING_POPULATION)
return FALSE
return aquarium.allow_breeding && health >= initial(health) * 0.8 && stable_population > 1 && world.time >= breeding_wait
#undef AQUARIUM_MAX_BREEDING_POPULATION
//Fish breeding stops if fish count exceeds this.
#define AQUARIUM_MAX_BREEDING_POPULATION 20
/obj/item/fish/proc/try_to_reproduce() /obj/item/fish/proc/try_to_reproduce()
var/obj/structure/aquarium/aquarium = loc var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium)) if(!istype(aquarium))
return return FALSE
if(length(aquarium.tracked_fish) >= AQUARIUM_MAX_BREEDING_POPULATION) //so aquariums full of fish don't need to do these expensive checks
return
var/list/other_fish_of_same_type = list()
for(var/obj/item/fish/fish_in_aquarium in aquarium)
if(fish_in_aquarium == src || fish_in_aquarium.type != type)
continue
other_fish_of_same_type += fish_in_aquarium
if(length(other_fish_of_same_type) >= stable_population)
return
var/obj/item/fish/second_fish var/obj/item/fish/second_fish
for(var/obj/item/fish/other_fish in other_fish_of_same_type)
if(other_fish.ready_to_reproduce()) /**
second_fish = other_fish * Fishes with this trait cannot mate, but could still reproduce asexually, so don't early return.
break * Also mating takes priority over that.
if(second_fish) */
new type(loc) //could use child_type var if(!HAS_TRAIT(src, TRAIT_FISH_NO_MATING))
last_breeding = world.time var/list/available_fishes = list()
second_fish.last_breeding = world.time var/types_to_mate_with = aquarium.tracked_fish_by_type
#undef AQUARIUM_MAX_BREEDING_POPULATION if(!HAS_TRAIT(src, TRAIT_FISH_CROSSBREEDER))
var/list/types_to_check = list(src)
if(compatible_types)
types_to_check |= compatible_types
types_to_mate_with = types_to_mate_with & types_to_check
for(var/obj/item/fish/fish_type as anything in types_to_mate_with)
var/list/type_fishes = types_to_mate_with[fish_type]
if(length(type_fishes) >= initial(fish_type.stable_population))
continue
available_fishes += type_fishes
available_fishes -= src //no self-mating.
if(length(available_fishes))
for(var/obj/item/fish/other_fish as anything in shuffle(available_fishes))
if(other_fish.ready_to_reproduce(TRUE))
second_fish = other_fish
break
if(!second_fish && !HAS_TRAIT(src, TRAIT_FISH_SELF_REPRODUCE))
return FALSE
var/chosen_type
var/datum/fish_evolution/chosen_evolution
if(PERFORM_ALL_TESTS(fish_breeding) && second_fish && !length(evolution_types))
chosen_type = second_fish.type
else
var/list/possible_evolutions = list()
for(var/evolution_type in evolution_types)
var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type]
if(evolution.check_conditions(src, second_fish, aquarium))
possible_evolutions += evolution
if(second_fish?.evolution_types)
var/secondary_evolutions = (second_fish.evolution_types - evolution_types)
for(var/evolution_type in secondary_evolutions)
var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type]
if(evolution.check_conditions(second_fish, src, aquarium))
possible_evolutions += evolution
if(length(possible_evolutions))
chosen_evolution = pick(possible_evolutions)
chosen_type = chosen_evolution.new_fish_type
else if(second_fish)
if(length(aquarium.tracked_fish_by_type[type]) >= stable_population)
chosen_type = second_fish.type
else
chosen_type = pick(second_fish.type, type)
else
chosen_type = type
return create_offspring(chosen_type, second_fish, chosen_evolution)
/obj/item/fish/proc/create_offspring(chosen_type, obj/item/fish/partner, datum/fish_evolution/evolution)
var/obj/item/fish/new_fish = new chosen_type (loc, FALSE)
//Try to pass down compatible traits based on inheritability
new_fish.inherit_traits(fish_traits, partner?.fish_traits, evolution?.new_traits, evolution?.removed_traits)
if(partner)
var/mean_size = (size + partner.size)/2
var/mean_weight = (weight + partner.weight)/2
new_fish.randomize_size_and_weight(mean_size, mean_weight, 0.3, TRUE)
partner.breeding_wait = world.time + breeding_timeout
else //Make a close of this fish.
new_fish.update_size_and_weight(size, weight, TRUE)
new_fish.progenitors = initial(name)
if(partner && type != partner.type)
var/string = "[initial(name)] - [initial(partner.name)]"
new_fish.progenitors = full_capitalize(string)
else
new_fish.progenitors = full_capitalize(initial(name))
breeding_wait = world.time + breeding_timeout
return new_fish
#define PAUSE_BETWEEN_PHASES 15 #define PAUSE_BETWEEN_PHASES 15
#define PAUSE_BETWEEN_FLOPS 2 #define PAUSE_BETWEEN_FLOPS 2
@@ -345,9 +559,10 @@
/// Starts flopping animation /// Starts flopping animation
/obj/item/fish/proc/start_flopping() /obj/item/fish/proc/start_flopping()
if(!flopping) //Requires update_transform/animate_wrappers to be less restrictive. if(flopping) //Requires update_transform/animate_wrappers to be less restrictive.
flopping = TRUE return
flop_animation(src) flopping = TRUE
flop_animation(src)
/// Stops flopping animation /// Stops flopping animation
/obj/item/fish/proc/stop_flopping() /obj/item/fish/proc/stop_flopping()
@@ -374,11 +589,20 @@
var/chance_table = list() var/chance_table = list()
for(var/_fish_type in subtypesof(/obj/item/fish)) for(var/_fish_type in subtypesof(/obj/item/fish))
var/obj/item/fish/fish = _fish_type var/obj/item/fish/fish = _fish_type
if(required_fluid && initial(fish.required_fluid_type) != required_fluid) if(required_fluid)
continue var/init_fish_fluid_type = initial(fish.required_fluid_type)
if(!compatible_fluid_type(init_fish_fluid_type, required_fluid))
continue
if(initial(fish.available_in_random_cases) || !case_fish_only) if(initial(fish.available_in_random_cases) || !case_fish_only)
chance_table[fish] = initial(fish.random_case_rarity) chance_table[fish] = initial(fish.random_case_rarity)
probability_table[argkey] = chance_table probability_table[argkey] = chance_table
return pick_weight(probability_table[argkey]) return pick_weight(probability_table[argkey])
/proc/compatible_fluid_type(fish_fluid_type, fluid_type)
switch(fish_fluid_type)
if(AQUARIUM_FLUID_ANY_WATER)
return fluid_type != AQUARIUM_FLUID_AIR
if(AQUARIUM_FLUID_ANADROMOUS)
return fluid_type == AQUARIUM_FLUID_SALTWATER || fluid_type == AQUARIUM_FLUID_FRESHWATER
else
return fish_fluid_type == fluid_type

View File

@@ -40,15 +40,7 @@
if (prob(default_contents_chance)) if (prob(default_contents_chance))
create_default_object() create_default_object()
return return
RegisterSignal(src, COMSIG_ATOM_FISHING_REWARD, PROC_REF(find_chasm_contents))
var/list/chasm_stuff = find_chasm_contents()
if (!chasm_stuff.len)
create_default_object()
return
var/atom/movable/detritus = determine_detritus(chasm_stuff)
detritus.forceMove(get_turf(src))
qdel(src)
/// Returns the chosen detritus from the given list of things to choose from /// Returns the chosen detritus from the given list of things to choose from
/obj/item/chasm_detritus/proc/determine_detritus(list/chasm_stuff) /obj/item/chasm_detritus/proc/determine_detritus(list/chasm_stuff)
@@ -60,19 +52,24 @@
new contents_type(get_turf(src)) new contents_type(get_turf(src))
qdel(src) qdel(src)
/// Returns a list of every object which is currently inside of a chasm. /// Returns an objected which is currently inside of a nearby chasm.
/obj/item/chasm_detritus/proc/find_chasm_contents() /obj/item/chasm_detritus/proc/find_chasm_contents(datum/source, turf/fishing_spot)
var/list/chasm_contents = list() SIGNAL_HANDLER
if (!GLOB.chasm_storage.len) var/list/chasm_contents = get_chasm_contents(fishing_spot)
return chasm_contents
var/list/chasm_storage_resolved = recursive_list_resolve(GLOB.chasm_storage) if (length(chasm_contents))
for (var/obj/storage as anything in chasm_storage_resolved) create_default_object()
return
var/atom/movable/detritus = determine_detritus(chasm_contents)
detritus.forceMove(get_turf(src))
qdel(src)
/obj/item/chasm_detritus/proc/get_chasm_contents(turf/fishing_spot)
. = list()
for (var/obj/effect/abstract/chasm_storage/storage in range(5, fishing_spot))
for (var/thing as anything in storage.contents) for (var/thing as anything in storage.contents)
chasm_contents += thing . += thing
return chasm_contents
/// Variant of the chasm detritus that allows for an easier time at fishing out /// Variant of the chasm detritus that allows for an easier time at fishing out
/// bodies, and sometimes less desireable monsters too. /// bodies, and sometimes less desireable monsters too.
@@ -81,21 +78,27 @@
/// contained in the `GLOB.chasm_storage` global list in `find_chasm_contents()`. /// contained in the `GLOB.chasm_storage` global list in `find_chasm_contents()`.
var/chasm_storage_restricted_type = /obj var/chasm_storage_restricted_type = /obj
/obj/item/chasm_detritus/restricted/get_chasm_contents(turf/fishing_spot)
/obj/item/chasm_detritus/restricted/find_chasm_contents() . = list()
var/list/chasm_contents = list() for (var/obj/effect/abstract/chasm_storage/storage in range(5, fishing_spot))
if (!GLOB.chasm_storage.len)
return chasm_contents
var/list/chasm_storage_resolved = recursive_list_resolve(GLOB.chasm_storage)
for (var/obj/storage as anything in chasm_storage_resolved)
for (var/thing as anything in storage.contents) for (var/thing as anything in storage.contents)
if(!istype(thing, chasm_storage_restricted_type)) if(!istype(thing, chasm_storage_restricted_type))
continue continue
. += thing
chasm_contents += thing /obj/item/chasm_detritus/restricted/objects
default_contents_chance = 12.5
default_contents_key = NO_CORPSES
return chasm_contents /obj/item/chasm_detritus/restricted/bodies
default_contents_chance = 12.5
default_contents_key = BODIES_ONLY
chasm_storage_restricted_type = /mob
/// This also includes all mobs fallen into chasms, regardless of distance
/obj/item/chasm_detritus/restricted/bodies/get_chasm_contents(turf/fishing_spot)
. = ..()
. |= GLOB.chasm_fallen_mobs
/// Body detritus is selected in favor of bodies belonging to sentient mobs /// Body detritus is selected in favor of bodies belonging to sentient mobs
/// The first sentient body found in the list of contents is returned, otherwise /// The first sentient body found in the list of contents is returned, otherwise
@@ -106,17 +109,6 @@
return fallen_mob return fallen_mob
return ..() return ..()
/obj/item/chasm_detritus/restricted/objects
default_contents_chance = 12.5
default_contents_key = NO_CORPSES
/obj/item/chasm_detritus/restricted/bodies
default_contents_chance = 12.5
default_contents_key = BODIES_ONLY
chasm_storage_restricted_type = /mob
#undef NORMAL_CONTENTS #undef NORMAL_CONTENTS
#undef BODIES_ONLY #undef BODIES_ONLY
#undef NO_CORPSES #undef NO_CORPSES

View File

@@ -0,0 +1,113 @@
GLOBAL_LIST_INIT(fish_evolutions, init_fish_evolutions())
/proc/init_fish_evolutions()
. = list()
for(var/datum/fish_evolution/evolution as anything in subtypesof(/datum/fish_evolution))
//Skip abstract types.
if(!initial(evolution.probability) || !initial(evolution.new_fish_type))
continue
.[evolution] = new evolution
/**
* Fish evolution datums
*
* If present in a fish's evolution_types list, and other conditions are met in check_conditions()
* then there's a chance the offspring may be of a new type rather than the same as its source or mate (if any).
*/
/datum/fish_evolution
var/name
var/probability = 0
///The obj/item/fish path of the new fish
var/obj/item/fish/new_fish_type = /obj/item/fish
///The minimum required temperature for the evolved fish to spawn
var/required_temperature_min = MIN_AQUARIUM_TEMP
///The maximum required temperature for the evolved fish to spawn
var/required_temperature_max = MAX_AQUARIUM_TEMP
///A list of traits added to the new fish. These take priority over the parents' traits.
var/list/new_traits
///If set, these traits will be removed from the new fish.
var/list/removed_traits
///A text string shown in the catalog, containing information on conditions specific to this evolution.
var/conditions_note
/datum/fish_evolution/New()
if(!ispath(new_fish_type, /obj/item/fish))
stack_trace("[type] instantiated with a new fish type of [new_fish_type]. That's not a fish, hun, things will break.")
if(!name)
name = full_capitalize(initial(new_fish_type.name))
/**
* The main proc that checks whether this can happen or not.
* Please do keep in mind a mate may not be present for fish with the
* self-reproductive trait.
*/
/datum/fish_evolution/proc/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium)
SHOULD_CALL_PARENT(TRUE)
//chances are halved if only one parent has this evolution.
var/real_probability = (mate && (type in mate.evolution_types)) ? probability : probability/2
if(!prob(real_probability))
return FALSE
if(!ISINRANGE(aquarium.fluid_temp, required_temperature_min, required_temperature_max))
return FALSE
return TRUE
///Called by the fish analyzer right click function. Returns a text string used as tooltip.
/datum/fish_evolution/proc/get_evolution_tooltip()
. = ""
if(required_temperature_min != MIN_AQUARIUM_TEMP || required_temperature_max != MAX_AQUARIUM_TEMP)
. = "An aquarium temperature between [required_temperature_min] and [required_temperature_max] is required."
if(conditions_note)
. += " [conditions_note]"
return .
///Proc called to let evolution register signals that are needed for various conditions.
/datum/fish_evolution/proc/register_fish(obj/item/fish/fish)
return
/datum/fish_evolution/lubefish
probability = 25
new_fish_type = /obj/item/fish/clownfish/lube
new_traits = list(/datum/fish_trait/lubed)
conditions_note = "The fish must be fed lube beforehand."
/datum/fish_evolution/lubefish/register_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_FISH_FED, PROC_REF(check_for_lube))
/datum/fish_evolution/lubefish/proc/check_for_lube(obj/item/fish/source, datum/reagents/fed_reagents, wrong_reagent_type)
SIGNAL_HANDLER
if((wrong_reagent_type == /datum/reagent/lube) || fed_reagents.remove_reagent(/datum/reagent/lube, 0.1))
ADD_TRAIT(source, TRAIT_FISH_FED_LUBE, FISH_EVOLUTION)
addtimer(TRAIT_CALLBACK_REMOVE(source, TRAIT_FISH_FED_LUBE, FISH_EVOLUTION), source.feeding_frequency)
/datum/fish_evolution/lubefish/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium)
if(!HAS_TRAIT(source, TRAIT_FISH_FED_LUBE))
return FALSE
return ..()
/datum/fish_evolution/purple_sludgefish
probability = 5
new_fish_type = /obj/item/fish/sludgefish/purple
removed_traits = list(/datum/fish_trait/no_mating)
/datum/fish_evolution/mastodon
name = "???" //The resulting fish is not shown on the catalog.
probability = 40
new_fish_type = /obj/item/fish/mastodon
new_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/predator, /datum/fish_trait/aggressive)
conditions_note = "The fish (and its mate) need to be unusually big both in size and weight."
/datum/fish_evolution/mastodon/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium)
if((source.size < 144 || source.weight < 4000) || (mate && (mate.size < 144 || mate.weight < 4000)))
return FALSE
return ..()
/datum/fish_evolution/chasm_chrab
probability = 50
new_fish_type = /obj/item/fish/chasm_crab
required_temperature_min = MIN_AQUARIUM_TEMP+14
required_temperature_max = MIN_AQUARIUM_TEMP+15
/datum/fish_evolution/ice_chrab
probability = 50
new_fish_type = /obj/item/fish/chasm_crab/ice
required_temperature_min = MIN_AQUARIUM_TEMP+9
required_temperature_max = MIN_AQUARIUM_TEMP+10

View File

@@ -0,0 +1,338 @@
GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list()))
/datum/fish_trait
var/name = "Unnamed Trait"
/// Description of the trait in the fishing catalog and scanner
var/catalog_description = "Uh uh, someone has forgotten to set description to this trait. Yikes!"
///A list of traits fish cannot have in conjunction with this trait.
var/list/incompatible_traits
/// The probability this trait can be inherited by offsprings when both mates have it
var/inheritability = 100
/// Same as above, but for when only one has it.
var/diff_traits_inheritability = 50
/// fishes of types within this list are granted to have this trait, no matter the probability
var/list/guaranteed_inheritance_types
/// Difficulty modifier from this mod, needs to return a list with two values
/datum/fish_trait/proc/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
SHOULD_CALL_PARENT(TRUE) //Technically it doesn't but this makes it saner without custom unit test
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)
SHOULD_CALL_PARENT(TRUE)
return list(ADDITIVE_FISHING_MOD = 0, MULTIPLICATIVE_FISHING_MOD = 1)
/// Returns special minigame rules applied by this trait
/datum/fish_trait/proc/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman)
return list()
/// Applies some special qualities to the fish that has been spawned
/datum/fish_trait/proc/apply_to_fish(obj/item/fish/fish)
return
/// Proc used by both the predator and necrophage traits.
/datum/fish_trait/proc/eat_fish(obj/item/fish/predator, obj/item/fish/prey)
predator.last_feeding = world.time
var/message = prey.status == FISH_DEAD ? "[src] eats [prey]'s carcass." : "[src] hunts down and eats [prey]."
predator.loc.visible_message(span_warning(message))
SEND_SIGNAL(prey, COMSIG_FISH_EATEN_BY_OTHER_FISH, predator)
qdel(prey)
/datum/fish_trait/wary
name = "Wary"
catalog_description = "This fish will avoid visible fish lines, cloaked line recommended."
/datum/fish_trait/wary/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
// Wary fish require transparent line or they're harder
if(!rod.line || !(rod.line.fishing_line_traits & FISHING_LINE_CLOAKED))
.[ADDITIVE_FISHING_MOD] = -FISH_TRAIT_MINOR_DIFFICULTY_BOOST
/datum/fish_trait/shiny_lover
name = "Shiny Lover"
catalog_description = "This fish loves shiny things, shiny lure recommended."
/datum/fish_trait/shiny_lover/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
// These fish are easier to catch with shiny lure
if(rod.hook && rod.hook.fishing_hook_traits & FISHING_HOOK_SHINY)
.[ADDITIVE_FISHING_MOD] = FISH_TRAIT_MINOR_DIFFICULTY_BOOST
/datum/fish_trait/picky_eater
name = "Picky Eater"
catalog_description = "This fish is very picky and will ignore low quality bait."
/datum/fish_trait/picky_eater/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
if(!rod.bait || !(HAS_TRAIT(rod.bait, GOOD_QUALITY_BAIT_TRAIT) || HAS_TRAIT(rod.bait, GREAT_QUALITY_BAIT_TRAIT)))
.[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)
. = ..()
var/turf/turf = get_turf(fisherman)
var/light_amount = turf.get_lumcount()
if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD)
.[MULTIPLICATIVE_FISHING_MOD] = 0
/datum/fish_trait/nocturnal/apply_to_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(check_light))
/datum/fish_trait/nocturnal/proc/check_light(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(isturf(source.loc) || isaquarium(source))
var/turf/turf = get_turf(source)
var/light_amount = turf.get_lumcount()
if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD)
source.adjust_health(source.health - 0.5 * seconds_per_tick)
/datum/fish_trait/heavy
name = "Heavy"
catalog_description = "This fish tends to stay near the waterbed.";
/datum/fish_trait/heavy/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman)
return list(FISHING_MINIGAME_RULE_HEAVY_FISH)
/datum/fish_trait/carnivore
name = "Carnivore"
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)
. = ..()
.[MULTIPLICATIVE_FISHING_MOD] = 0
if(rod.bait && istype(rod.bait, /obj/item/food))
var/obj/item/food/food_bait = rod.bait
if(food_bait.foodtypes & MEAT)
.[MULTIPLICATIVE_FISHING_MOD] = 1
/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)
. = ..()
.[MULTIPLICATIVE_FISHING_MOD] = 0
if(rod.bait && istype(rod.bait, /obj/item/food/grown))
.[MULTIPLICATIVE_FISHING_MOD] = 1
/datum/fish_trait/emulsijack
name = "Emulsifier"
catalog_description = "This fish emits an invisible toxin that emulsifies other fish for it to feed on."
/datum/fish_trait/emulsijack/apply_to_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(emulsify))
ADD_TRAIT(fish, TRAIT_RESIST_EMULSIFY, FISH_TRAIT_DATUM)
/datum/fish_trait/emulsijack/proc/emulsify(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(!isaquarium(source.loc))
return
var/emulsified = FALSE
for(var/obj/item/fish/victim in source.loc)
if(HAS_TRAIT(victim, TRAIT_RESIST_EMULSIFY) || HAS_TRAIT(victim, TRAIT_FISH_TOXIN_IMMUNE)) //no team killing
continue
victim.adjust_health(victim.health - 3 * seconds_per_tick) //the victim may heal a bit but this will quickly kill
emulsified = TRUE
if(emulsified)
source.adjust_health(source.health + 3 * seconds_per_tick)
source.last_feeding = world.time //it feeds on the emulsion!
/datum/fish_trait/necrophage
name = "Necrophage"
catalog_description = "This fish will eat the carcasses of dead fishes when hungry."
incompatible_traits = list(/datum/fish_trait/vegan)
/datum/fish_trait/necrophage/apply_to_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(eat_dead_fishes))
/datum/fish_trait/necrophage/proc/eat_dead_fishes(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(world.time - source.last_feeding >= source.feeding_frequency || !isaquarium(source.loc))
return
for(var/obj/item/fish/victim in source.loc)
if(victim.status != FISH_DEAD || victim == source || HAS_TRAIT(victim, TRAIT_YUCKY_FISH))
continue
eat_fish(source, victim)
return
/datum/fish_trait/parthenogenesis
name = "Parthenogenesis"
catalog_description = "This fish can reproduce asexually, without the need of a mate."
inheritability = 80
diff_traits_inheritability = 25
/datum/fish_trait/parthenogenesis/apply_to_fish(obj/item/fish/fish)
ADD_TRAIT(fish, TRAIT_FISH_SELF_REPRODUCE, FISH_TRAIT_DATUM)
/**
* Useful for those species with the parthenogenesis trait if you don't want them to mate with each other,
* or for similar shenanigeans, I don't know.
* Otherwise you could just set the stable_population to 1.
*/
/datum/fish_trait/no_mating
name = "Mateless"
catalog_description = "This fish cannot reproduce with other fishes."
incompatible_traits = list(/datum/fish_trait/crossbreeder)
/datum/fish_trait/no_mating/apply_to_fish(obj/item/fish/fish)
ADD_TRAIT(fish, TRAIT_FISH_NO_MATING, FISH_TRAIT_DATUM)
/datum/fish_trait/revival
diff_traits_inheritability = 15
name = "Self-Revival"
catalog_description = "This fish shows a peculiar ability of reviving itself a minute or two after death."
guaranteed_inheritance_types = list(/obj/item/fish/boned, /obj/item/fish/mastodon)
/datum/fish_trait/revival/apply_to_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(check_status))
/datum/fish_trait/revival/proc/check_status(obj/item/fish/source)
SIGNAL_HANDLER
if(source.status == FISH_DEAD)
addtimer(CALLBACK(src, PROC_REF(revive), source), rand(1 MINUTES, 2 MINUTES))
/datum/fish_trait/revival/proc/revive(obj/item/fish/source)
if(QDELETED(source) || source.status != FISH_DEAD)
return
source.set_status(FISH_ALIVE)
var/message = span_nicegreen("[source] twitches. It's alive!")
if(isaquarium(source.loc))
source.loc.visible_message(message)
else
source.visible_message(message)
/datum/fish_trait/predator
name = "Predator"
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/apply_to_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(eat_fishes))
/datum/fish_trait/predator/proc/eat_fishes(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(world.time - source.last_feeding >= source.feeding_frequency || !isaquarium(source.loc))
return
var/obj/structure/aquarium/aquarium = source.loc
for(var/obj/item/fish/victim in aquarium.get_fishes(TRUE, source))
if(victim.size < source.size * 0.75) // It's a big fish eat small fish world
continue
if(victim.status != FISH_ALIVE || victim == source || HAS_TRAIT(victim, TRAIT_YUCKY_FISH) || SPT_PROB(80, seconds_per_tick))
continue
eat_fish(source, victim)
return
/datum/fish_trait/yucky
name = "Yucky"
catalog_description = "This fish tastes so repulsive, other fishes won't try to eat it."
/datum/fish_trait/yucky/apply_to_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_ATOM_PROCESSED, PROC_REF(add_yuck))
ADD_TRAIT(fish, TRAIT_YUCKY_FISH, FISH_TRAIT_DATUM)
LAZYSET(fish.grind_results, /datum/reagent/yuck, 3)
/datum/fish_trait/yucky/proc/add_yuck(obj/item/fish/source, mob/living/user, obj/item/process_item, list/results)
var/amount = source.grind_results[/datum/reagent/yuck] / length(results)
for(var/atom/result as anything in results)
result.reagents?.add_reagent(/datum/reagent/yuck, amount)
/datum/fish_trait/toxic
name = "Toxic"
catalog_description = "This fish contains toxins in its liver. Feeding it to predatory fishes or people is not reccomended."
diff_traits_inheritability = 25
/datum/fish_trait/toxic/apply_to_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_ATOM_PROCESSED, PROC_REF(add_toxin))
RegisterSignal(fish, COMSIG_FISH_EATEN_BY_OTHER_FISH, PROC_REF(on_eaten))
LAZYSET(fish.grind_results, /datum/reagent/toxin/tetrodotoxin, 0.5)
/datum/fish_trait/toxic/proc/add_toxin(obj/item/fish/source, mob/living/user, obj/item/process_item, list/results)
var/amount = source.grind_results[ /datum/reagent/toxin/tetrodotoxin] / length(results)
for(var/atom/result as anything in results)
result.reagents?.add_reagent( /datum/reagent/toxin/tetrodotoxin, amount)
/datum/fish_trait/toxic/proc/on_eaten(obj/item/fish/source, obj/item/fish/predator)
if(HAS_TRAIT(predator, TRAIT_FISH_TOXIN_IMMUNE))
return
RegisterSignal(predator, COMSIG_FISH_LIFE, PROC_REF(damage_predator), TRUE)
RegisterSignal(predator, COMSIG_FISH_STATUS_CHANGED, PROC_REF(stop_damaging), TRUE)
/datum/fish_trait/toxic/proc/damage_predator(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
source.adjust_health(source.health - 3 * seconds_per_tick)
/datum/fish_trait/toxic/proc/stop_damaging(obj/item/fish/source)
SIGNAL_HANDLER
if(source.status == FISH_DEAD)
UnregisterSignal(source, list(COMSIG_FISH_LIFE, COMSIG_FISH_STATUS_CHANGED))
/datum/fish_trait/toxin_immunity
name = "Toxin Immunity"
catalog_description = "This fish has developed an ample-spected immunity to toxins."
diff_traits_inheritability = 40
/datum/fish_trait/toxin_immunity/apply_to_fish(obj/item/fish/fish)
ADD_TRAIT(fish, TRAIT_FISH_TOXIN_IMMUNE, FISH_TRAIT_DATUM)
/datum/fish_trait/crossbreeder
name = "Crossbreeder"
catalog_description = "This fish's adaptive genetics allows it to crossbreed with other fish species."
inheritability = 80
diff_traits_inheritability = 20
incompatible_traits = list(/datum/fish_trait/no_mating)
/datum/fish_trait/crossbreeder/apply_to_fish(obj/item/fish/fish)
ADD_TRAIT(fish, TRAIT_FISH_CROSSBREEDER, FISH_TRAIT_DATUM)
/datum/fish_trait/aggressive
name = "Aggressive"
inheritability = 80
diff_traits_inheritability = 40
catalog_description = "This fish is agressively territorial, and may attack fish that come close to it."
/datum/fish_trait/aggressive/apply_to_fish(obj/item/fish/fish)
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(try_attack_fish))
/datum/fish_trait/aggressive/proc/try_attack_fish(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(!isaquarium(source.loc) || !SPT_PROB(1, seconds_per_tick))
return
var/obj/structure/aquarium/aquarium = source.loc
for(var/obj/item/fish/victim in aquarium.get_fishes(TRUE, source))
if(victim.status != FISH_ALIVE)
continue
aquarium.visible_message(span_warning("[source] violently [pick("whips", "bites", "attacks", "slams")] [victim]"))
var/damage = round(rand(4, 20) * (source.size / victim.size)) //smaller fishes take extra damage.
victim.adjust_health(victim.health - damage)
return
/datum/fish_trait/lubed
name = "Lubed"
inheritability = 90
diff_traits_inheritability = 45
guaranteed_inheritance_types = list(/obj/item/fish/clownfish/lube)
catalog_description = "This fish exudes a viscous, slippery lubrificant. It's reccomended not to step on it."
/datum/fish_trait/lubed/apply_to_fish(obj/item/fish/fish)
fish.AddComponent(/datum/component/slippery, 8 SECONDS, SLIDE|GALOSHES_DONT_HELP)
/datum/fish_trait/lubed/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman)
return list(FISHING_MINIGAME_RULE_LUBED_FISH)
/datum/fish_trait/amphibious
name = "Amphibious"
inheritability = 80
diff_traits_inheritability = 40
catalog_description = "This fish has developed a primitive adaptation to life on both land and water."
/datum/fish_trait/amphibious/apply_to_fish(obj/item/fish/fish)
ADD_TRAIT(fish, TRAIT_FISH_AMPHIBIOUS, FISH_TRAIT_DATUM)
if(fish.required_fluid_type == AQUARIUM_FLUID_AIR)
fish.required_fluid_type = AQUARIUM_FLUID_FRESHWATER

View File

@@ -10,6 +10,8 @@
average_size = 30 average_size = 30
average_weight = 500 average_weight = 500
favorite_bait = list(/obj/item/food/bait/worm) favorite_bait = list(/obj/item/food/bait/worm)
required_temperature_min = MIN_AQUARIUM_TEMP+18
required_temperature_max = MIN_AQUARIUM_TEMP+26
/obj/item/fish/angelfish /obj/item/fish/angelfish
name = "angelfish" name = "angelfish"
@@ -21,6 +23,9 @@
average_size = 30 average_size = 30
average_weight = 500 average_weight = 500
stable_population = 3 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 /obj/item/fish/guppy
name = "guppy" name = "guppy"
@@ -33,6 +38,8 @@
average_size = 30 average_size = 30
average_weight = 500 average_weight = 500
stable_population = 6 stable_population = 6
required_temperature_min = MIN_AQUARIUM_TEMP+20
required_temperature_max = MIN_AQUARIUM_TEMP+28
/obj/item/fish/plasmatetra /obj/item/fish/plasmatetra
name = "plasma tetra" name = "plasma tetra"
@@ -43,6 +50,8 @@
average_size = 30 average_size = 30
average_weight = 500 average_weight = 500
stable_population = 3 stable_population = 3
required_temperature_min = MIN_AQUARIUM_TEMP+20
required_temperature_max = MIN_AQUARIUM_TEMP+28
/obj/item/fish/catfish /obj/item/fish/catfish
name = "cory catfish" name = "cory catfish"
@@ -59,6 +68,8 @@
"Value" = JUNKFOOD "Value" = JUNKFOOD
) )
) )
required_temperature_min = MIN_AQUARIUM_TEMP+12
required_temperature_max = MIN_AQUARIUM_TEMP+30
// Saltwater fish below // Saltwater fish below
@@ -73,8 +84,23 @@
average_size = 30 average_size = 30
average_weight = 500 average_weight = 500
stable_population = 4 stable_population = 4
grind_results = list(/datum/reagent/blood = 20, /datum/reagent/consumable/liquidgibs = 2)
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
fishing_traits = list(/datum/fishing_trait/picky_eater) /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
dedicated_in_aquarium_icon_state = "lubefish_small"
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
/obj/item/fish/cardinal /obj/item/fish/cardinal
name = "cardinalfish" name = "cardinalfish"
@@ -85,7 +111,9 @@
average_size = 30 average_size = 30
average_weight = 500 average_weight = 500
stable_population = 4 stable_population = 4
fishing_traits = list(/datum/fishing_trait/vegan) 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 /obj/item/fish/greenchromis
name = "green chromis" name = "green chromis"
@@ -97,6 +125,8 @@
average_size = 30 average_size = 30
average_weight = 500 average_weight = 500
stable_population = 5 stable_population = 5
required_temperature_min = MIN_AQUARIUM_TEMP+23
required_temperature_max = MIN_AQUARIUM_TEMP+28
fishing_difficulty_modifier = 5 // Bit harder fishing_difficulty_modifier = 5 // Bit harder
@@ -112,10 +142,12 @@
stable_population = 3 stable_population = 3
disliked_bait = list(/obj/item/food/bait/worm, /obj/item/food/bait/doughball) disliked_bait = list(/obj/item/food/bait/worm, /obj/item/food/bait/doughball)
fish_ai_type = FISH_AI_ZIPPY fish_ai_type = FISH_AI_ZIPPY
required_temperature_min = MIN_AQUARIUM_TEMP+23
required_temperature_max = MIN_AQUARIUM_TEMP+28
/obj/item/fish/pufferfish /obj/item/fish/pufferfish
name = "pufferfish" name = "pufferfish"
desc = "One Pufferfish contains enough toxins in its liver to kill 30 people." 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" icon_state = "pufferfish"
required_fluid_type = AQUARIUM_FLUID_SALTWATER required_fluid_type = AQUARIUM_FLUID_SALTWATER
sprite_width = 8 sprite_width = 8
@@ -123,8 +155,10 @@
average_size = 60 average_size = 60
average_weight = 1000 average_weight = 1000
stable_population = 3 stable_population = 3
required_temperature_min = MIN_AQUARIUM_TEMP+23
required_temperature_max = MIN_AQUARIUM_TEMP+28
fishing_traits = list(/datum/fishing_trait/heavy) fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/toxic)
/obj/item/fish/lanternfish /obj/item/fish/lanternfish
name = "lanternfish" name = "lanternfish"
@@ -139,8 +173,9 @@
average_size = 100 average_size = 100
average_weight = 1500 average_weight = 1500
stable_population = 3 stable_population = 3
fish_traits = list(/datum/fish_trait/nocturnal)
fishing_traits = list(/datum/fishing_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
//Tiziran Fish //Tiziran Fish
/obj/item/fish/dwarf_moonfish /obj/item/fish/dwarf_moonfish
@@ -152,6 +187,8 @@
fillet_type = /obj/item/food/fishmeat/moonfish fillet_type = /obj/item/food/fishmeat/moonfish
average_size = 100 average_size = 100
average_weight = 2000 average_weight = 2000
required_temperature_min = MIN_AQUARIUM_TEMP+20
required_temperature_max = MIN_AQUARIUM_TEMP+30
/obj/item/fish/gunner_jellyfish /obj/item/fish/gunner_jellyfish
name = "gunner jellyfish" name = "gunner jellyfish"
@@ -160,6 +197,8 @@
required_fluid_type = AQUARIUM_FLUID_SALTWATER required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 4 stable_population = 4
fillet_type = /obj/item/food/fishmeat/gunner_jellyfish fillet_type = /obj/item/food/fishmeat/gunner_jellyfish
required_temperature_min = MIN_AQUARIUM_TEMP+24
required_temperature_max = MIN_AQUARIUM_TEMP+32
/obj/item/fish/needlefish /obj/item/fish/needlefish
name = "needlefish" name = "needlefish"
@@ -168,9 +207,11 @@
required_fluid_type = AQUARIUM_FLUID_SALTWATER required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 12 stable_population = 12
fillet_type = null fillet_type = null
average_size = 30 average_size = 20
average_weight = 300 average_weight = 300
fishing_traits = list(/datum/fishing_trait/carnivore) 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 /obj/item/fish/armorfish
name = "armorfish" name = "armorfish"
@@ -180,6 +221,8 @@
stable_population = 10 stable_population = 10
fillet_type = /obj/item/food/fishmeat/armorfish fillet_type = /obj/item/food/fishmeat/armorfish
fish_ai_type = FISH_AI_SLOW fish_ai_type = FISH_AI_SLOW
required_temperature_min = MIN_AQUARIUM_TEMP+10
required_temperature_max = MIN_AQUARIUM_TEMP+32
//Chasm fish //Chasm fish
/obj/item/fish/chasm_crab /obj/item/fish/chasm_crab
@@ -195,6 +238,20 @@
feeding_frequency = 15 MINUTES feeding_frequency = 15 MINUTES
random_case_rarity = FISH_RARITY_RARE random_case_rarity = FISH_RARITY_RARE
fillet_type = /obj/item/food/meat/slab/rawcrab fillet_type = /obj/item/food/meat/slab/rawcrab
required_temperature_min = MIN_AQUARIUM_TEMP+9
required_temperature_max = MAX_AQUARIUM_TEMP+150
evolution_types = list(/datum/fish_evolution/ice_chrab)
compatible_types = list(/obj/item/fish/chasm_crab/ice)
/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"
dedicated_in_aquarium_icon_state = "ice_chrab_small"
required_temperature_min = MIN_AQUARIUM_TEMP-150
required_temperature_max = MIN_AQUARIUM_TEMP+15
evolution_types = list(/datum/fish_evolution/chasm_chrab)
compatible_types = list(/obj/item/fish/chasm_crab)
/obj/item/storage/box/fish_debug /obj/item/storage/box/fish_debug
name = "box full of fish" name = "box full of fish"
@@ -211,28 +268,20 @@
required_fluid_type = AQUARIUM_FLUID_FRESHWATER required_fluid_type = AQUARIUM_FLUID_FRESHWATER
stable_population = 4 stable_population = 4
fillet_type = /obj/item/food/fishmeat/donkfish 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
/obj/item/fish/emulsijack /obj/item/fish/emulsijack
name = "toxic emulsijack" name = "toxic emulsijack"
desc = "Ah, the terrifying emulsijack. Created in a laboratory, this slimey, scaleless fish emits an invisible toxin that emulsifies other fish for it to feed on. Its only real use is for completely ruining a tank." 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" icon_state = "emulsijack"
random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS
required_fluid_type = AQUARIUM_FLUID_ANADROMOUS required_fluid_type = AQUARIUM_FLUID_ANADROMOUS
stable_population = 3 stable_population = 3
fish_traits = list(/datum/fish_trait/emulsijack)
/obj/item/fish/emulsijack/process(seconds_per_tick) required_temperature_min = MIN_AQUARIUM_TEMP+5
var/emulsified = FALSE required_temperature_max = MIN_AQUARIUM_TEMP+40
var/obj/structure/aquarium/aquarium = loc
if(istype(aquarium))
for(var/obj/item/fish/victim in aquarium)
if(istype(victim, /obj/item/fish/emulsijack))
continue //no team killing
victim.adjust_health((victim.health - 3) * seconds_per_tick) //the victim may heal a bit but this will quickly kill
emulsified = TRUE
if(emulsified)
adjust_health((health + 3) * seconds_per_tick)
last_feeding = world.time //emulsijack feeds on the emulsion!
..()
/obj/item/fish/ratfish /obj/item/fish/ratfish
name = "ratfish" name = "ratfish"
@@ -242,6 +291,9 @@
required_fluid_type = AQUARIUM_FLUID_FRESHWATER required_fluid_type = AQUARIUM_FLUID_FRESHWATER
stable_population = 10 //set by New, but this is the default config value stable_population = 10 //set by New, but this is the default config value
fillet_type = /obj/item/food/meat/slab/human/mutant/zombie //eww... 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_ai_type = FISH_AI_ZIPPY fish_ai_type = FISH_AI_ZIPPY
favorite_bait = list( favorite_bait = list(
@@ -255,3 +307,186 @@
. = ..() . = ..()
//stable pop reflects the config for how many mice migrate. powerful... //stable pop reflects the config for how many mice migrate. powerful...
stable_population = CONFIG_GET(number/mice_roundstart) 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"
dedicated_in_aquarium_icon_state = "sludgefish_small"
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 = 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)
/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"
dedicated_in_aquarium_icon_state = "sludgefish_purple_small"
available_in_random_cases = FALSE
random_case_rarity = FISH_RARITY_VERY_RARE
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"
dedicated_in_aquarium_icon_state = "slimefish_small"
sprite_width = 7
sprite_height = 7
do_flop_animation = FALSE //it already has our 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
/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"
dedicated_in_aquarium_icon_state = "bonemass_small"
sprite_width = 10
sprite_height = 7
fish_ai_type = FISH_AI_ZIPPY
random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS
required_fluid_type = AQUARIUM_FLUID_ANY_WATER
health = 150
stable_population = 3
grind_results = list(/datum/reagent/bone_dust = 20)
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)
/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"
dedicated_in_aquarium_icon = 'icons/obj/aquarium.dmi'
dedicated_in_aquarium_icon_state = "mastodon_small"
base_pixel_x = -16
pixel_x = -16
sprite_width = 12
sprite_height = 7
show_in_catalog = FALSE
available_in_random_cases = FALSE
random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS
fishing_difficulty_modifier = 5
required_fluid_type = AQUARIUM_FLUID_ANY_WATER
health = 300
stable_population = 2 //This means they can only crossbreed.
grind_results = list(/datum/reagent/bone_dust = 15, /datum/reagent/consumable/liquidgibs = 5)
fillet_type = /obj/item/stack/sheet/bone
num_fillets = 2
feeding_frequency = 2 MINUTES
breeding_timeout = 10 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)
/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 = "goldfish"
show_in_catalog = FALSE
available_in_random_cases = FALSE
sprite_width = 8
sprite_height = 8
stable_population = 1
average_size = 30
average_weight = 500
required_fluid_type = AQUARIUM_FLUID_ANADROMOUS
grind_results = null
fillet_type = null
death_text = "%SRC disappears."
fish_traits = list(/datum/fish_trait/no_mating) //just to be sure, these shouldn't reproduce
/obj/item/fish/holo/Initialize(mapload)
. = ..()
var/area/station/holodeck/holo_area = get_area(src)
if(!istype(holo_area))
return
holo_area.linked.add_to_spawned(src)
/obj/item/fish/holo/set_status(new_status)
. = ..()
if(status == FISH_DEAD)
qdel(src)
/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 = "crab_small"
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."
sprite_width = 8
sprite_height = 8
average_size = 60
average_weight = 1000
/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 = "angelfish"
dedicated_in_aquarium_icon_state = "bigfish"
sprite_height = 7
/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 = "holo_clownfish_small"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
sprite_width = 8
sprite_height = 5
/obj/item/fish/holo/checkered
name = "unrendered holographic fish" //it's a meta joke, buddy.
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"
dedicated_in_aquarium_icon_state = "checkered_small"
sprite_width = 4
/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 = "half_fish_small"
sprite_height = 4
sprite_width = 10
average_size = 50

View File

@@ -91,10 +91,10 @@
.["disliked_bait"] = english_list(bait_list, nothing_text = "None") .["disliked_bait"] = english_list(bait_list, nothing_text = "None")
// Fish traits description // Fish traits description
var/list/trait_descriptions = list() var/list/trait_descriptions = list()
var/list/fish_traits = fish_list_properties[fishy][NAMEOF(fishy, fishing_traits)] var/list/fish_traits = fish_list_properties[fishy][NAMEOF(fishy, fish_traits)]
for(var/fish_trait in fish_traits) for(var/fish_trait in fish_traits)
var/datum/fishing_trait/trait = fish_trait var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait]
trait_descriptions += initial(trait.catalog_description) trait_descriptions += trait.catalog_description
if(!length(trait_descriptions)) if(!length(trait_descriptions))
trait_descriptions += "This fish exhibits no special behavior." trait_descriptions += "This fish exhibits no special behavior."
.["traits"] = trait_descriptions .["traits"] = trait_descriptions

View File

@@ -150,7 +150,7 @@
/obj/item/fishing_hook/bone /obj/item/fishing_hook/bone
name = "bone hook" name = "bone hook"
desc = "a simple hook carved from sharpened bone" desc = "A simple hook carved from sharpened bone"
icon_state = "hook_bone" icon_state = "hook_bone"
/datum/crafting_recipe/bone_hook /datum/crafting_recipe/bone_hook
@@ -160,6 +160,24 @@
time = 2 SECONDS time = 2 SECONDS
category = CAT_TOOLS category = CAT_TOOLS
/obj/item/fishing_hook/stabilized
name = "gyro-stabilized hook"
desc = "A quirky hook that grants the user a better control of the tool, allowing them to move the hook both and up and down when reeling in, otherwise keeping it stabilized."
icon_state = "gyro"
fishing_hook_traits = FISHING_HOOK_BIDIRECTIONAL
rod_overlay_icon_state = "hook_gyro_overlay"
/obj/item/fishing_hook/stabilized/examine(mob/user)
. = ..()
. += span_notice("While fishing, you can press the Ctrl key down to move the bait down, rather than up.")
/obj/item/fishing_hook/jaws
name = "jawed hook"
desc = "Despite hints of rust, this gritty beartrap-like hook hybrid manages to look even more threating than the real thing. May neptune have mercy of whatever is caught by its jaws."
icon_state = "jaws"
fishing_hook_traits = FISHING_HOOK_NO_ESCAPE|FISHING_HOOK_ENSNARE|FISHING_HOOK_KILL
rod_overlay_icon_state = "hook_jaws_overlay"
/obj/item/storage/toolbox/fishing /obj/item/storage/toolbox/fishing
name = "fishing toolbox" name = "fishing toolbox"
desc = "Contains everything you need for your fishing trip." desc = "Contains everything you need for your fishing trip."
@@ -197,6 +215,5 @@
new /obj/item/fishing_line/reinforced(src) new /obj/item/fishing_line/reinforced(src)
new /obj/item/fishing_line/cloaked(src) new /obj/item/fishing_line/cloaked(src)
#undef MAGNET_HOOK_BONUS_MULTIPLIER #undef MAGNET_HOOK_BONUS_MULTIPLIER
#undef RESCUE_HOOK_FISH_MULTIPLIER #undef RESCUE_HOOK_FISH_MULTIPLIER

View File

@@ -33,34 +33,51 @@
/// Background image from /datum/asset/simple/fishing_minigame /// Background image from /datum/asset/simple/fishing_minigame
var/background = "default" var/background = "default"
/// Max distance we can move from the spot
var/max_distance = 5
/// Fishing line visual /// Fishing line visual
var/datum/beam/fishing_line var/datum/beam/fishing_line
/datum/fishing_challenge/New(atom/spot, reward_path, obj/item/fishing_rod/rod, mob/user) var/experience_multiplier = 1
/datum/fishing_challenge/New(datum/component/fishing_spot/comp, reward_path, obj/item/fishing_rod/rod, mob/user)
src.user = user src.user = user
src.reward_path = reward_path src.reward_path = reward_path
src.used_rod = rod src.used_rod = rod
lure = new(get_turf(spot)) var/atom/spot = comp.parent
lure = new(get_turf(spot), spot)
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))
background = comp.fish_source.background
/// Fish minigame properties /// Fish minigame properties
if(ispath(reward_path,/obj/item/fish)) if(ispath(reward_path,/obj/item/fish))
var/obj/item/fish/fish = reward_path var/obj/item/fish/fish = reward_path
fish_ai = initial(fish.fish_ai_type) fish_ai = initial(fish.fish_ai_type)
// Apply fishing trait modifiers // Apply fish trait modifiers
var/list/fish_list_properties = collect_fish_properties() var/list/fish_list_properties = collect_fish_properties()
var/list/fish_traits = fish_list_properties[fish][NAMEOF(fish, fishing_traits)] var/list/fish_traits = fish_list_properties[fish][NAMEOF(fish, fish_traits)]
for(var/fish_trait in fish_traits) for(var/fish_trait in fish_traits)
var/datum/fishing_trait/trait = new fish_trait var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait]
special_effects += trait.minigame_mod(rod, user) special_effects += trait.minigame_mod(rod, user)
/// Enable special parameters /// Enable special parameters
if(rod.line) if(rod.line)
if(rod.line.fishing_line_traits & FISHING_LINE_BOUNCY) if(rod.line.fishing_line_traits & FISHING_LINE_BOUNCY)
special_effects += FISHING_MINIGAME_RULE_LIMIT_LOSS special_effects |= FISHING_MINIGAME_RULE_LIMIT_LOSS
if(rod.hook) if(rod.hook)
if(rod.hook.fishing_hook_traits & FISHING_HOOK_WEIGHTED) if(rod.hook.fishing_hook_traits & FISHING_HOOK_WEIGHTED)
special_effects += FISHING_MINIGAME_RULE_WEIGHTED_BAIT special_effects |= FISHING_MINIGAME_RULE_WEIGHTED_BAIT
if(rod.hook.fishing_hook_traits & FISHING_HOOK_BIDIRECTIONAL)
special_effects |= FISHING_MINIGAME_RULE_BIDIRECTIONAL
if(rod.hook.fishing_hook_traits & FISHING_HOOK_NO_ESCAPE)
special_effects |= FISHING_MINIGAME_RULE_NO_ESCAPE
if(rod.hook.fishing_hook_traits & FISHING_HOOK_ENSNARE)
special_effects |= FISHING_MINIGAME_RULE_LIMIT_LOSS
if(rod.hook.fishing_hook_traits & FISHING_HOOK_KILL)
special_effects |= FISHING_MINIGAME_RULE_KILL
if((FISHING_MINIGAME_RULE_KILL in special_effects) && ispath(reward_path,/obj/item/fish))
RegisterSignal(user, COMSIG_MOB_FISHING_REWARD_DISPENSED, PROC_REF(hurt_fish))
difficulty += comp.fish_source.calculate_difficulty(reward_path, rod, user, src)
/datum/fishing_challenge/Destroy(force, ...) /datum/fishing_challenge/Destroy(force, ...)
if(!completed) if(!completed)
@@ -69,13 +86,29 @@
QDEL_NULL(fishing_line) QDEL_NULL(fishing_line)
if(lure) if(lure)
QDEL_NULL(lure) QDEL_NULL(lure)
. = ..() user = null
used_rod = null
return ..()
/datum/fishing_challenge/proc/send_alert(message)
var/turf/lure_turf = get_turf(lure)
lure_turf?.balloon_alert(user, message)
/datum/fishing_challenge/proc/on_spot_gone(datum/source)
send_alert("fishing spot gone!")
interrupt(balloon_alert = FALSE)
/datum/fishing_challenge/proc/interrupt_challenge(datum/source, reason)
if(reason)
send_alert(reason)
interrupt(balloon_alert = FALSE)
/datum/fishing_challenge/proc/start(mob/living/user) /datum/fishing_challenge/proc/start(mob/living/user)
/// Create fishing line visuals /// Create fishing line visuals
fishing_line = used_rod.create_fishing_line(lure, target_py = 5) fishing_line = used_rod.create_fishing_line(lure, target_py = 5)
// If fishing line breaks los / rod gets dropped / deleted // If fishing line breaks los / rod gets dropped / deleted
RegisterSignal(fishing_line, COMSIG_FISHING_LINE_SNAPPED, PROC_REF(interrupt)) RegisterSignal(fishing_line, COMSIG_FISHING_LINE_SNAPPED, PROC_REF(interrupt))
RegisterSignal(used_rod, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_attack_self))
ADD_TRAIT(user, TRAIT_GONE_FISHING, REF(src)) ADD_TRAIT(user, TRAIT_GONE_FISHING, REF(src))
user.add_mood_event("fishing", /datum/mood_event/fishing) user.add_mood_event("fishing", /datum/mood_event/fishing)
RegisterSignal(user, COMSIG_MOB_CLICKON, PROC_REF(handle_click)) RegisterSignal(user, COMSIG_MOB_CLICKON, PROC_REF(handle_click))
@@ -83,40 +116,53 @@
to_chat(user, span_notice("You start fishing...")) to_chat(user, span_notice("You start fishing..."))
playsound(lure, 'sound/effects/splash.ogg', 100) playsound(lure, 'sound/effects/splash.ogg', 100)
/datum/fishing_challenge/proc/handle_click() /datum/fishing_challenge/proc/handle_click(mob/source, atom/target, modifiers)
SIGNAL_HANDLER
//You need to be holding the rod to use it.
if(!source.get_active_held_item(used_rod) || LAZYACCESS(modifiers, SHIFT_CLICK))
return
if(phase == WAIT_PHASE) //Reset wait if(phase == WAIT_PHASE) //Reset wait
lure.balloon_alert(user, "miss!") send_alert("miss!")
start_baiting_phase() start_baiting_phase()
else if(phase == BITING_PHASE) else if(phase == BITING_PHASE)
start_minigame_phase() INVOKE_ASYNC(src, PROC_REF(start_minigame_phase))
return COMSIG_MOB_CANCEL_CLICKON return COMSIG_MOB_CANCEL_CLICKON
/datum/fishing_challenge/proc/check_distance()
SIGNAL_HANDLER
if(get_dist(user,lure) > max_distance)
interrupt()
/// Challenge interrupted by something external /// Challenge interrupted by something external
/datum/fishing_challenge/proc/interrupt() /datum/fishing_challenge/proc/interrupt(datum/source, balloon_alert = TRUE)
SIGNAL_HANDLER SIGNAL_HANDLER
if(!completed) if(!completed)
experience_multiplier *= 0.5
if(balloon_alert)
send_alert(user.is_holding(used_rod) ? "line snapped" : "tool dropped")
complete(FALSE)
/datum/fishing_challenge/proc/on_attack_self(obj/item/source, mob/user)
SIGNAL_HANDLER
INVOKE_ASYNC(src, PROC_REF(stop_fishing), source, user)
/datum/fishing_challenge/proc/stop_fishing(obj/item/rod, mob/user)
if((phase != MINIGAME_PHASE || do_after(user, 3 SECONDS, rod)) && !QDELETED(src) && !completed)
experience_multiplier *= 0.5
send_alert("stopped fishing")
complete(FALSE) complete(FALSE)
/datum/fishing_challenge/proc/complete(win = FALSE, perfect_win = FALSE) /datum/fishing_challenge/proc/complete(win = FALSE, perfect_win = FALSE)
deltimer(next_phase_timer) deltimer(next_phase_timer)
completed = TRUE completed = TRUE
if(user) if(user)
UnregisterSignal(user, list(COMSIG_MOB_CLICKON, COMSIG_MOVABLE_MOVED))
REMOVE_TRAIT(user, TRAIT_GONE_FISHING, REF(src)) REMOVE_TRAIT(user, TRAIT_GONE_FISHING, REF(src))
if(used_rod) if(start_time)
UnregisterSignal(used_rod, COMSIG_ITEM_DROPPED) var/seconds_spent = (world.time - start_time)/10
if(phase == MINIGAME_PHASE) if(!(FISHING_MINIGAME_RULE_NO_EXP in special_effects))
used_rod.consume_bait() user.mind?.adjust_experience(/datum/skill/fishing, min(round(seconds_spent * FISHING_SKILL_EXP_PER_SECOND * experience_multiplier), FISHING_SKILL_EXP_CAP_PER_GAME))
if(win && user.mind?.get_skill_level(/datum/skill/fishing) >= SKILL_LEVEL_LEGENDARY)
user.client?.give_award(/datum/award/achievement/skill/legendary_fisher, user)
if(used_rod && phase == MINIGAME_PHASE)
used_rod.consume_bait()
if(win) if(win)
if(reward_path != FISHING_DUD) if(reward_path != FISHING_DUD)
playsound(lure, 'sound/effects/bigsplash.ogg', 100) playsound(lure, 'sound/effects/bigsplash.ogg', 100)
else
user.balloon_alert(user, "it got away")
SEND_SIGNAL(src, COMSIG_FISHING_CHALLENGE_COMPLETED, user, win, perfect_win) SEND_SIGNAL(src, COMSIG_FISHING_CHALLENGE_COMPLETED, user, win, perfect_win)
qdel(src) qdel(src)
@@ -134,19 +180,41 @@
phase = BITING_PHASE phase = BITING_PHASE
// Trashing animation // Trashing animation
playsound(lure, 'sound/effects/fish_splash.ogg', 100) playsound(lure, 'sound/effects/fish_splash.ogg', 100)
lure.balloon_alert(user, "!!!") send_alert("!!!")
animate(lure, pixel_y = 3, time = 5, loop = -1, flags = ANIMATION_RELATIVE) animate(lure, pixel_y = 3, time = 5, loop = -1, flags = ANIMATION_RELATIVE)
animate(pixel_y = -3, time = 5, flags = ANIMATION_RELATIVE) animate(pixel_y = -3, time = 5, flags = ANIMATION_RELATIVE)
// Setup next phase // Setup next phase
var/wait_time = rand(3 SECONDS, 6 SECONDS) var/wait_time = rand(3 SECONDS, 6 SECONDS)
next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_baiting_phase)), wait_time, TIMER_STOPPABLE) next_phase_timer = addtimer(CALLBACK(src, PROC_REF(start_baiting_phase)), wait_time, TIMER_STOPPABLE)
///The damage dealt per second to the fish when FISHING_MINIGAME_RULE_KILL is active.
#define FISH_DAMAGE_PER_SECOND 2
/datum/fishing_challenge/proc/start_minigame_phase() /datum/fishing_challenge/proc/start_minigame_phase()
phase = MINIGAME_PHASE phase = MINIGAME_PHASE
deltimer(next_phase_timer) deltimer(next_phase_timer)
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)
start_time = world.time start_time = world.time
experience_multiplier += difficulty * FISHING_SKILL_DIFFIULTY_EXP_MULT
ui_interact(user) ui_interact(user)
/datum/fishing_challenge/proc/win_anyway()
if(!completed)
//winning by timeout or idling around shouldn't give as much experience.
experience_multiplier *= 0.5
complete(TRUE)
/datum/fishing_challenge/proc/hurt_fish(datum/source, obj/item/fish/reward)
SIGNAL_HANDLER
if(istype(reward))
var/damage = CEILING((world.time - start_time)/10 * FISH_DAMAGE_PER_SECOND, 1)
reward.adjust_health(reward.health - damage)
#undef FISH_DAMAGE_PER_SECOND
/datum/fishing_challenge/ui_interact(mob/user, datum/tgui/ui) /datum/fishing_challenge/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui) ui = SStgui.try_update_ui(user, src, ui)
if(!ui) if(!ui)
@@ -162,11 +230,12 @@
/datum/fishing_challenge/ui_close(mob/user) /datum/fishing_challenge/ui_close(mob/user)
. = ..() . = ..()
if(!completed) if(!completed)
send_alert("stopped fishing")
complete(FALSE) complete(FALSE)
/datum/fishing_challenge/ui_static_data(mob/user) /datum/fishing_challenge/ui_static_data(mob/user)
. = ..() . = ..()
.["difficulty"] = max(1,min(difficulty,100)) .["difficulty"] = clamp(difficulty, 1, 100)
.["fish_ai"] = fish_ai .["fish_ai"] = fish_ai
.["special_effects"] = special_effects .["special_effects"] = special_effects
.["background_image"] = background .["background_image"] = background
@@ -176,7 +245,7 @@
/datum/fishing_challenge/ui_status(mob/user, datum/ui_state/state) /datum/fishing_challenge/ui_status(mob/user, datum/ui_state/state)
return min( return min(
get_dist(user, lure) > max_distance ? UI_CLOSE : UI_INTERACTIVE, get_dist(user, lure) > 5 ? UI_CLOSE : UI_INTERACTIVE,
ui_status_user_has_free_hands(user), ui_status_user_has_free_hands(user),
ui_status_user_is_abled(user, lure), ui_status_user_is_abled(user, lure),
) )
@@ -193,6 +262,7 @@
if("win") if("win")
complete(win = TRUE, perfect_win = params["perfect"]) complete(win = TRUE, perfect_win = params["perfect"])
if("lose") if("lose")
send_alert("it got away")
complete(win = FALSE) complete(win = FALSE)
/// The visual that appears over the fishing spot /// The visual that appears over the fishing spot
@@ -200,6 +270,15 @@
icon = 'icons/obj/fishing.dmi' icon = 'icons/obj/fishing.dmi'
icon_state = "lure_idle" icon_state = "lure_idle"
/obj/effect/fishing_lure/Initialize(mapload, atom/spot)
. = ..()
if(ismovable(spot)) // we want the lure 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)
set_glide_size(source.glide_size)
forceMove(source.loc)
#undef WAIT_PHASE #undef WAIT_PHASE
#undef BITING_PHASE #undef BITING_PHASE
#undef MINIGAME_PHASE #undef MINIGAME_PHASE

View File

@@ -38,12 +38,17 @@
/// List of fishing line beams /// List of fishing line beams
var/list/fishing_lines = list() var/list/fishing_lines = list()
/// The default color for the reel overlay if no line is equipped.
var/default_line_color = "gray" var/default_line_color = "gray"
///The name of the icon state of the reel overlay
var/reel_overlay = "reel_overlay"
/obj/item/fishing_rod/Initialize(mapload) /obj/item/fishing_rod/Initialize(mapload)
. = ..() . = ..()
register_context() register_context()
register_item_context() register_item_context()
update_appearance()
/obj/item/fishing_rod/add_context(atom/source, list/context, obj/item/held_item, mob/user) /obj/item/fishing_rod/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(src == held_item) if(src == held_item)
@@ -96,10 +101,7 @@
* * target_fish_source - The /datum/fish_source we're trying to fish in. * * target_fish_source - The /datum/fish_source we're trying to fish in.
*/ */
/obj/item/fishing_rod/proc/reason_we_cant_fish(datum/fish_source/target_fish_source) /obj/item/fishing_rod/proc/reason_we_cant_fish(datum/fish_source/target_fish_source)
if(!hook) return hook?.reason_we_cant_fish(target_fish_source)
return null
return hook.reason_we_cant_fish(target_fish_source)
/obj/item/fishing_rod/proc/consume_bait() /obj/item/fishing_rod/proc/consume_bait()
@@ -201,23 +203,27 @@
reel(user) reel(user)
return . return .
/// If the line to whatever that is is clear and we're not already busy, try fishing in it cast_line(target, user, proximity_flag)
if(!casting && !currently_hooked_item && !proximity_flag && CheckToolReach(user, target, cast_range))
/// Annoyingly pre attack is only called in melee
SEND_SIGNAL(target, COMSIG_PRE_FISHING)
casting = TRUE
var/obj/projectile/fishing_cast/cast_projectile = new(get_turf(src))
cast_projectile.range = cast_range
cast_projectile.owner = src
cast_projectile.original = target
cast_projectile.fired_from = src
cast_projectile.firer = user
cast_projectile.impacted = list(user = TRUE)
cast_projectile.preparePixelProjectile(target, user)
cast_projectile.fire()
return . return .
///Called by afterattack(). If the line to whatever that is is clear and we're not already busy, try fishing in it
/obj/item/fishing_rod/proc/cast_line(atom/target, mob/user, proximity_flag)
if(casting || currently_hooked_item || proximity_flag || !CheckToolReach(user, target, cast_range))
return
/// Annoyingly pre attack is only called in melee
SEND_SIGNAL(target, COMSIG_PRE_FISHING)
casting = TRUE
var/obj/projectile/fishing_cast/cast_projectile = new(get_turf(src))
cast_projectile.range = cast_range
cast_projectile.owner = src
cast_projectile.original = target
cast_projectile.fired_from = src
cast_projectile.firer = user
cast_projectile.impacted = list(user = TRUE)
cast_projectile.preparePixelProjectile(target, user)
cast_projectile.fire()
/// Called by hook projectile when hitting things /// Called by hook projectile when hitting things
/obj/item/fishing_rod/proc/hook_hit(atom/atom_hit_by_hook_projectile) /obj/item/fishing_rod/proc/hook_hit(atom/atom_hit_by_hook_projectile)
var/mob/user = loc var/mob/user = loc
@@ -237,11 +243,15 @@
/obj/item/fishing_rod/update_overlays() /obj/item/fishing_rod/update_overlays()
. = ..() . = ..()
. += get_fishing_overlays()
/obj/item/fishing_rod/proc/get_fishing_overlays()
. = list()
var/line_color = line?.line_color || default_line_color var/line_color = line?.line_color || default_line_color
/// Line part by the rod, always visible /// Line part by the rod, always visible
var/mutable_appearance/reel_overlay = mutable_appearance(icon, "reel_overlay") var/mutable_appearance/reel_appearance = mutable_appearance(icon, reel_overlay)
reel_overlay.color = line_color; reel_appearance.color = line_color;
. += reel_overlay . += reel_appearance
// Line & hook is also visible when only bait is equipped but it uses default appearances then // Line & hook is also visible when only bait is equipped but it uses default appearances then
if(hook || bait) if(hook || bait)
@@ -260,6 +270,10 @@
/obj/item/fishing_rod/worn_overlays(mutable_appearance/standing, isinhands, icon_file) /obj/item/fishing_rod/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..() . = ..()
. += get_fishing_worn_overlays(standing, isinhands, icon_file)
/obj/item/fishing_rod/proc/get_fishing_worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = list()
var/line_color = line?.line_color || default_line_color var/line_color = line?.line_color || default_line_color
var/mutable_appearance/reel_overlay = mutable_appearance(icon_file, "reel_overlay") var/mutable_appearance/reel_overlay = mutable_appearance(icon_file, "reel_overlay")
reel_overlay.appearance_flags |= RESET_COLOR reel_overlay.appearance_flags |= RESET_COLOR
@@ -401,6 +415,8 @@
name = "bone fishing rod" name = "bone fishing rod"
desc = "A humble rod, made with whatever happened to be on hand." desc = "A humble rod, made with whatever happened to be on hand."
icon_state = "fishing_rod_bone" icon_state = "fishing_rod_bone"
reel_overlay = "reel_bone"
default_line_color = "red"
/datum/crafting_recipe/bone_rod /datum/crafting_recipe/bone_rod
name = "Bone Fishing Rod" name = "Bone Fishing Rod"
@@ -411,19 +427,84 @@
/obj/item/stack/sheet/bone = 2) /obj/item/stack/sheet/bone = 2)
category = CAT_TOOLS category = CAT_TOOLS
/obj/item/fishing_rod/master /obj/item/fishing_rod/telescopic
icon_state = "fishing_rod_telescopic"
desc = "A lightweight, ergonomic, easy to store telescopic fishing rod. "
inhand_icon_state = null
force = 0
w_class = WEIGHT_CLASS_NORMAL
ui_description = "A collapsible fishing rod that can fit within a backpack."
reel_overlay = "reel_telescopic"
///Whether the rod is exteded or not. Tied to the transforming element.
var/active = FALSE
///The force of the item when extended.
var/active_force = 8
/obj/item/fishing_rod/telescopic/Initialize(mapload)
. = ..()
AddComponent(/datum/component/transforming, force_on = 8, hitsound_on = hitsound, w_class_on = WEIGHT_CLASS_HUGE, clumsy_check = FALSE)
RegisterSignal(src, COMSIG_TRANSFORMING_PRE_TRANSFORM, PROC_REF(pre_transform))
RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
/obj/item/fishing_rod/telescopic/reason_we_cant_fish(datum/fish_source/target_fish_source)
if(!active)
return "You need to extend your fishing rod before you can cast the line."
return ..()
/obj/item/fishing_rod/telescopic/cast_line(atom/target, mob/user, proximity_flag)
if(!active)
to_chat(user, "You need to extend your fishing rod before you can cast the line.")
return
return ..()
/obj/item/fishing_rod/telescopic/get_fishing_overlays()
if(!active)
return list()
return ..()
/obj/item/fishing_rod/telescopic/get_fishing_worn_overlays(mutable_appearance/standing, isinhands, icon_file)
if(!active)
return list()
return ..()
///Stops the fishing rod from being collapsed while fishing.
/obj/item/fishing_rod/telescopic/proc/pre_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
if(active)
return
//the fishing minigame uses the attack_self signal to let the user end it early without having to drop the rod.
if(HAS_TRAIT(user, TRAIT_GONE_FISHING))
return COMPONENT_BLOCK_TRANSFORM
///Gives feedback to the user, makes it show up inhand, toggles whether it can be used for fishing.
/obj/item/fishing_rod/telescopic/proc/on_transform(obj/item/source, mob/user, active)
SIGNAL_HANDLER
src.active = active
inhand_icon_state = active ? "rod" : null // When inactive, there is no inhand icon_state.
if(user)
balloon_alert(user, active ? "extended" : "collapsed")
playsound(src, 'sound/weapons/batonextend.ogg', 50, TRUE)
update_appearance(UPDATE_OVERLAYS)
if(currently_hooked_item)
clear_hooked_item()
return COMPONENT_NO_DEFAULT_MESSAGE
/obj/item/fishing_rod/telescopic/master
name = "master fishing rod" name = "master fishing rod"
desc = "The mythical rod of a lost fisher king. Said to be imbued with un-paralleled fishing power. There's writing on the back of the pole. \"中国航天制造\"" desc = "The mythical rod of a lost fisher king. Said to be imbued with un-paralleled fishing power. There's writing on the back of the pole. \"中国航天制造\""
difficulty_modifier = -10 difficulty_modifier = -10
ui_description = "This rods makes fishing easy even for an absolute beginner." ui_description = "This rods makes fishing easy even for an absolute beginner."
icon_state = "fishing_rod_master" icon_state = "fishing_rod_master"
reel_overlay = "reel_master"
active_force = 13 //It's that sturdy
/obj/item/fishing_rod/tech /obj/item/fishing_rod/tech
name = "advanced fishing rod" name = "advanced fishing rod"
desc = "An embedded universal constructor along with micro-fusion generator makes this marvel of technology never run out of bait. Interstellar treaties prevent using it outside of recreational fishing. And you can fish with this. " desc = "An embedded universal constructor along with micro-fusion generator makes this marvel of technology never run out of bait. Interstellar treaties prevent using it outside of recreational fishing. And you can fish with this. "
ui_description = "This rod has an infinite supply of synthetic bait." ui_description = "This rod has an infinite supply of synthetic bait."
icon_state = "fishing_rod_science" icon_state = "fishing_rod_science"
reel_overlay = "reel_science"
/obj/item/fishing_rod/tech/Initialize(mapload) /obj/item/fishing_rod/tech/Initialize(mapload)
. = ..() . = ..()

View File

@@ -1,82 +0,0 @@
/datum/fishing_trait
/// Description of the trait in the fishing catalog
var/catalog_description
/// Difficulty modifier from this mod, needs to return a list with two values
/datum/fishing_trait/proc/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
SHOULD_CALL_PARENT(TRUE) //Technically it doesn't but this makes it saner without custom unit test
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/fishing_trait/proc/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman)
SHOULD_CALL_PARENT(TRUE)
return list(ADDITIVE_FISHING_MOD = 0, MULTIPLICATIVE_FISHING_MOD = 1)
/// Returns special minigame rules applied by this trait
/datum/fishing_trait/proc/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman)
return list()
/datum/fishing_trait/wary
catalog_description = "This fish will avoid visible fish lines, cloaked line recommended."
/datum/fishing_trait/wary/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
// Wary fish require transparent line or they're harder
if(!rod.line || !(rod.line.fishing_line_traits & FISHING_LINE_CLOAKED))
.[ADDITIVE_FISHING_MOD] = -FISH_TRAIT_MINOR_DIFFICULTY_BOOST
/datum/fishing_trait/shiny_lover
catalog_description = "This fish loves shiny things, shiny lure recommended."
/datum/fishing_trait/shiny_lover/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
// These fish are easier to catch with shiny lure
if(rod.hook && rod.hook.fishing_hook_traits & FISHING_HOOK_SHINY)
.[ADDITIVE_FISHING_MOD] = FISH_TRAIT_MINOR_DIFFICULTY_BOOST
/datum/fishing_trait/picky_eater
catalog_description = "This fish is very picky and will ignore low quality bait."
/datum/fishing_trait/picky_eater/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
if(!rod.bait || !(HAS_TRAIT(rod.bait, GOOD_QUALITY_BAIT_TRAIT) || HAS_TRAIT(rod.bait, GREAT_QUALITY_BAIT_TRAIT)))
.[MULTIPLICATIVE_FISHING_MOD] = 0
/datum/fishing_trait/nocturnal
catalog_description = "This fish avoids bright lights, fishing in darkness recommended."
/datum/fishing_trait/nocturnal/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
var/turf/T = get_turf(fisherman)
var/light_amount = T.get_lumcount()
if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD)
.[MULTIPLICATIVE_FISHING_MOD] = 0
/datum/fishing_trait/heavy
catalog_description = "This fish tends to stay near the waterbed.";
/datum/fishing_trait/heavy/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman)
return list(FISHING_MINIGAME_RULE_HEAVY_FISH)
/datum/fishing_trait/carnivore
catalog_description = "This fish can only be baited with meat."
/datum/fishing_trait/carnivore/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
.[MULTIPLICATIVE_FISHING_MOD] = 0
if(rod.bait && istype(rod.bait, /obj/item/food))
var/obj/item/food/food_bait = rod.bait
if(food_bait.foodtypes & MEAT)
.[MULTIPLICATIVE_FISHING_MOD] = 1
/datum/fishing_trait/vegan
catalog_description = "This fish can only be baited with fresh produce."
/datum/fishing_trait/vegan/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
.[MULTIPLICATIVE_FISHING_MOD] = 0
if(rod.bait && istype(rod.bait, /obj/item/food/grown))
.[MULTIPLICATIVE_FISHING_MOD] = 1

View File

@@ -11,9 +11,15 @@ GLOBAL_LIST_INIT(preset_fish_sources,init_fishing_configurations())
var/datum/fish_source/lavaland/lava_preset = new var/datum/fish_source/lavaland/lava_preset = new
.[FISHING_SPOT_PRESET_LAVALAND_LAVA] = lava_preset .[FISHING_SPOT_PRESET_LAVALAND_LAVA] = lava_preset
var/datum/fish_source/lavaland/icemoon/plasma_preset = new
.[FISHING_SPOT_PRESET_ICEMOON_PLASMA] = plasma_preset
var/datum/fish_source/chasm/chasm_preset = new var/datum/fish_source/chasm/chasm_preset = new
.[FISHING_SPOT_PRESET_CHASM] = chasm_preset .[FISHING_SPOT_PRESET_CHASM] = chasm_preset
var/datum/fish_source/toilet/toilet_preset = new
.[FISHING_SPOT_PRESET_TOILET] = toilet_preset
/// Where the fish actually come from - every fishing spot has one assigned but multiple fishing holes can share single source, ie single shared one for ocean/lavaland river /// Where the fish actually come from - every fishing spot has one assigned but multiple fishing holes can share single source, ie single shared one for ocean/lavaland river
/datum/fish_source /datum/fish_source
/// Fish catch weight table - these are relative weights /// Fish catch weight table - these are relative weights
@@ -35,7 +41,7 @@ GLOBAL_LIST_INIT(preset_fish_sources,init_fishing_configurations())
/// DIFFICULTY = (SPOT_BASE_VALUE + FISH_MODIFIER + ROD_MODIFIER + FAV/DISLIKED_BAIT_MODIFIER + TRAITS_ADDITIVE) * TRAITS_MULTIPLICATIVE , For non-fish it's just SPOT_BASE_VALUE /// DIFFICULTY = (SPOT_BASE_VALUE + FISH_MODIFIER + ROD_MODIFIER + FAV/DISLIKED_BAIT_MODIFIER + TRAITS_ADDITIVE) * TRAITS_MULTIPLICATIVE , For non-fish it's just SPOT_BASE_VALUE
/datum/fish_source/proc/calculate_difficulty(result, obj/item/fishing_rod/rod, mob/fisherman) /datum/fish_source/proc/calculate_difficulty(result, obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/challenge)
. = fishing_difficulty . = fishing_difficulty
if(!ispath(result,/obj/item/fish)) if(!ispath(result,/obj/item/fish))
@@ -55,21 +61,22 @@ GLOBAL_LIST_INIT(preset_fish_sources,init_fishing_configurations())
for(var/bait_identifer in fav_bait) for(var/bait_identifer in fav_bait)
if(is_matching_bait(bait, bait_identifer)) if(is_matching_bait(bait, bait_identifer))
. += FAV_BAIT_DIFFICULTY_MOD . += FAV_BAIT_DIFFICULTY_MOD
break
//Disliked bait makes it harder //Disliked bait makes it harder
var/list/disliked_bait = fish_list_properties[caught_fish][NAMEOF(caught_fish, disliked_bait)] var/list/disliked_bait = fish_list_properties[caught_fish][NAMEOF(caught_fish, disliked_bait)]
for(var/bait_identifer in disliked_bait) for(var/bait_identifer in disliked_bait)
if(is_matching_bait(bait, bait_identifer)) if(is_matching_bait(bait, bait_identifer))
. += DISLIKED_BAIT_DIFFICULTY_MOD . += DISLIKED_BAIT_DIFFICULTY_MOD
break
if(!challenge || !(FISHING_MINIGAME_RULE_NO_EXP in challenge.special_effects))
. += fisherman.mind?.get_skill_modifier(/datum/skill/fishing, SKILL_VALUE_MODIFIER)
// Matching/not matching fish traits and equipment // Matching/not matching fish traits and equipment
var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fishing_traits)] var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fish_traits)]
var/additive_mod = 0 var/additive_mod = 0
var/multiplicative_mod = 1 var/multiplicative_mod = 1
for(var/fish_trait in fish_traits) for(var/fish_trait in fish_traits)
var/datum/fishing_trait/trait = new fish_trait var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait]
var/list/mod = trait.difficulty_mod(rod, fisherman) var/list/mod = trait.difficulty_mod(rod, fisherman)
additive_mod += mod[ADDITIVE_FISHING_MOD] additive_mod += mod[ADDITIVE_FISHING_MOD]
multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD] multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD]
@@ -81,29 +88,57 @@ GLOBAL_LIST_INIT(preset_fish_sources,init_fishing_configurations())
/datum/fish_source/proc/roll_reward(obj/item/fishing_rod/rod, mob/fisherman) /datum/fish_source/proc/roll_reward(obj/item/fishing_rod/rod, mob/fisherman)
return pick_weight(get_modified_fish_table(rod,fisherman)) return pick_weight(get_modified_fish_table(rod,fisherman))
/**
* Used to register signals or add traits and the such right after conditions have been cleared
* and before the minigame starts.
*/
/datum/fish_source/proc/pre_challenge_started(obj/item/fishing_rod/rod, mob/user)
return
///Proc called when the challenge is interrupted within the fish source code.
/datum/fish_source/proc/interrupt_challenge(reason)
SEND_SIGNAL(src, COMSIG_FISHING_SOURCE_INTERRUPT_CHALLENGE, reason)
/**
* Proc called when the COMSIG_FISHING_CHALLENGE_COMPLETED signal is sent.
* Check if we've succeeded. If so, write into memory and dispense the reward.
*/
/datum/fish_source/proc/on_challenge_completed(datum/fishing_challenge/source, mob/user, success, perfect)
SIGNAL_HANDLER
SHOULD_CALL_PARENT(TRUE)
if(!success)
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)
dispense_reward(source.reward_path, user, fishing_spot)
/// Gives out the reward if possible /// Gives out the reward if possible
/datum/fish_source/proc/dispense_reward(reward_path, mob/fisherman) /datum/fish_source/proc/dispense_reward(reward_path, mob/fisherman, fishing_spot)
if((reward_path in fish_counts)) // This is limited count result if((reward_path in fish_counts)) // This is limited count result
if(fish_counts[reward_path] > 0) if(fish_counts[reward_path] > 0)
fish_counts[reward_path] -= 1 fish_counts[reward_path] -= 1
else else
reward_path = FISHING_DUD //Ran out of these since rolling (multiple fishermen on same source most likely) reward_path = FISHING_DUD //Ran out of these since rolling (multiple fishermen on same source most likely)
var/atom/movable/reward
if(ispath(reward_path)) if(ispath(reward_path))
reward = new reward_path(get_turf(fisherman))
if(ispath(reward_path,/obj/item)) if(ispath(reward_path,/obj/item))
var/obj/item/reward = new reward_path(get_turf(fisherman))
if(ispath(reward_path,/obj/item/fish)) if(ispath(reward_path,/obj/item/fish))
var/obj/item/fish/caught_fish = reward var/obj/item/fish/caught_fish = reward
caught_fish.randomize_weight_and_size() caught_fish.randomize_size_and_weight()
//fish caught signal if needed goes here and/or fishing achievements //fish caught signal if needed goes here and/or fishing achievements
//Try to put it in hand //Try to put it in hand
fisherman.put_in_hands(reward) INVOKE_ASYNC(fisherman, TYPE_PROC_REF(/mob, put_in_hands), reward)
fisherman.balloon_alert(fisherman, "caught [reward]!") fisherman.balloon_alert(fisherman, "caught [reward]!")
else //If someone adds fishing out carp/chests/singularities or whatever just plop it down on the fisher's turf else //If someone adds fishing out carp/chests/singularities or whatever just plop it down on the fisher's turf
fisherman.balloon_alert(fisherman, "caught something!") fisherman.balloon_alert(fisherman, "caught something!")
new reward_path(get_turf(fisherman))
else if (reward_path == FISHING_DUD) else if (reward_path == FISHING_DUD)
//baloon alert instead //baloon alert instead
fisherman.balloon_alert(fisherman,pick(duds)) fisherman.balloon_alert(fisherman,pick(duds))
SEND_SIGNAL(fisherman, COMSIG_MOB_FISHING_REWARD_DISPENSED, reward)
if(reward)
SEND_SIGNAL(reward, COMSIG_ATOM_FISHING_REWARD, fishing_spot)
/// Cached fish list properties so we don't have to initalize fish every time, init deffered /// Cached fish list properties so we don't have to initalize fish every time, init deffered
GLOBAL_LIST(fishing_property_cache) GLOBAL_LIST(fishing_property_cache)
@@ -113,11 +148,11 @@ GLOBAL_LIST(fishing_property_cache)
if(GLOB.fishing_property_cache == null) if(GLOB.fishing_property_cache == null)
var/list/fish_property_table = list() var/list/fish_property_table = list()
for(var/fish_type in subtypesof(/obj/item/fish)) for(var/fish_type in subtypesof(/obj/item/fish))
var/obj/item/fish/fish = new fish_type(null) var/obj/item/fish/fish = new fish_type(null, FALSE)
fish_property_table[fish_type] = list() 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, 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, disliked_bait)] = fish.disliked_bait.Copy()
fish_property_table[fish_type][NAMEOF(fish, fishing_traits)] = fish.fishing_traits.Copy() fish_property_table[fish_type][NAMEOF(fish, fish_traits)] = fish.fish_traits.Copy()
QDEL_NULL(fish) QDEL_NULL(fish)
GLOB.fishing_property_cache = fish_property_table GLOB.fishing_property_cache = fish_property_table
return GLOB.fishing_property_cache return GLOB.fishing_property_cache
@@ -132,6 +167,8 @@ GLOBAL_LIST(fishing_property_cache)
if("Foodtype") if("Foodtype")
var/obj/item/food/food_bait = bait var/obj/item/food/food_bait = bait
return istype(food_bait) && food_bait.foodtypes & special_identifier["Value"] 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 else
CRASH("Unknown bait identifier in fish favourite/disliked list") CRASH("Unknown bait identifier in fish favourite/disliked list")
else else
@@ -173,20 +210,18 @@ GLOBAL_LIST(fishing_property_cache)
for(var/bait_identifer in fav_bait) for(var/bait_identifer in fav_bait)
if(is_matching_bait(bait, bait_identifer)) if(is_matching_bait(bait, bait_identifer))
final_table[result] *= 2 final_table[result] *= 2
break // could compound possibly
//Bait matching dislikes //Bait matching dislikes
var/list/disliked_bait = fish_list_properties[result][NAMEOF(caught_fish, disliked_bait)] var/list/disliked_bait = fish_list_properties[result][NAMEOF(caught_fish, disliked_bait)]
for(var/bait_identifer in disliked_bait) for(var/bait_identifer in disliked_bait)
if(is_matching_bait(bait, bait_identifer)) if(is_matching_bait(bait, bait_identifer))
final_table[result] *= 0.5 final_table[result] *= 0.5
break // same question as above
// Apply fishing trait modifiers // Apply fish trait modifiers
var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fishing_traits)] var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fish_traits)]
var/additive_mod = 0 var/additive_mod = 0
var/multiplicative_mod = 1 var/multiplicative_mod = 1
for(var/fish_trait in fish_traits) for(var/fish_trait in fish_traits)
var/datum/fishing_trait/trait = new fish_trait var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait]
var/list/mod = trait.catch_weight_mod(rod, fisherman) var/list/mod = trait.catch_weight_mod(rod, fisherman)
additive_mod += mod[ADDITIVE_FISHING_MOD] additive_mod += mod[ADDITIVE_FISHING_MOD]
multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD] multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD]

View File

@@ -8,6 +8,9 @@
/obj/item/fish/greenchromis = 15, /obj/item/fish/greenchromis = 15,
/obj/item/fish/lanternfish = 5 /obj/item/fish/lanternfish = 5
) )
fish_counts = list(
/obj/item/fish/clownfish/lube = 2,
)
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5 fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5
/datum/fish_source/ocean/beach /datum/fish_source/ocean/beach
@@ -32,7 +35,6 @@
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5 fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5
/datum/fish_source/chasm/roll_reward(obj/item/fishing_rod/rod, mob/fisherman) /datum/fish_source/chasm/roll_reward(obj/item/fishing_rod/rod, mob/fisherman)
var/rolled_reward = ..() var/rolled_reward = ..()
@@ -41,7 +43,6 @@
return rod.hook.chasm_detritus_type return rod.hook.chasm_detritus_type
/datum/fish_source/lavaland /datum/fish_source/lavaland
catalog_description = "Lava vents" catalog_description = "Lava vents"
background = "fishing_background_lavaland" background = "fishing_background_lavaland"
@@ -65,11 +66,86 @@
if(!rod.line || !(rod.line.fishing_line_traits & FISHING_LINE_REINFORCED)) if(!rod.line || !(rod.line.fishing_line_traits & FISHING_LINE_REINFORCED))
return "You'll need reinforced fishing line to fish in there" return "You'll need reinforced fishing line to fish in there"
/datum/fish_source/lavaland/icemoon
catalog_description = "Liquid plasma vents"
fish_table = list(
FISHING_DUD = 5,
/obj/item/fish/chasm_crab/ice = 15,
/mob/living/simple_animal/hostile/asteroid/lobstrosity = 1,
/obj/effect/decal/remains/plasma = 1,
/obj/item/stack/ore/plasma = 3,
/obj/item/coin/plasma = 3,
)
fish_counts = list(
/obj/item/stack/sheet/mineral/adamantine = 3,
/obj/item/stack/sheet/mineral/mythril = 2,
)
/datum/fish_source/moisture_trap /datum/fish_source/moisture_trap
catalog_description = "moisture trap basins" catalog_description = "Moisture trap basins"
fish_table = list( fish_table = list(
FISHING_DUD = 20, FISHING_DUD = 20,
/obj/item/fish/ratfish = 10 /obj/item/fish/ratfish = 10,
/obj/item/fish/slimefish = 4
) )
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 10 fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 10
/datum/fish_source/toilet
catalog_description = "Station toilets"
duds = list("ewww... nothing", "it was nothing", "it was toilet paper", "it was flushed away", "the hook is empty", "where's the damn money?!")
fish_table = list(
FISHING_DUD = 18,
/obj/item/fish/sludgefish = 18,
/obj/item/fish/slimefish = 2,
)
fish_counts = list(
/obj/item/storage/wallet/money = 2,
)
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY - 5 //For beginners
/datum/fish_source/holographic
catalog_description = "Holographic water"
fish_table = list(
/obj/item/fish/holo = 2,
/obj/item/fish/holo/crab = 2,
/obj/item/fish/holo/puffer = 2,
/obj/item/fish/holo/angel = 2,
/obj/item/fish/holo/clown = 2,
)
fish_counts = list(
/obj/item/fish/holo/checkered = 1,
/obj/item/fish/holo/halffish = 1,
)
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY - 5
/datum/fish_source/holographic/reason_we_cant_fish(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
if(!istype(get_area(fisherman), /area/station/holodeck))
return "You need to be inside the Holodeck to catch holographic fish."
/datum/fish_source/holographic/pre_challenge_started(obj/item/fishing_rod/rod, mob/user)
RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(check_area))
/datum/fish_source/holographic/proc/check_area(mob/user)
SIGNAL_HANDLER
if(!istype(get_area(user), /area/station/holodeck))
interrupt_challenge("exited holodeck")
/datum/fish_source/holographic/on_challenge_completed(datum/fishing_challenge/source, mob/user, success, perfect)
. = ..()
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
/datum/fish_source/oil_well
catalog_description = "Oil wells"
fish_table = list(
FISHING_DUD = 5,
/obj/item/fish/boned = 10,
/obj/item/stack/sheet/bone = 2,
)
fish_counts = list(
/obj/item/clothing/gloves/bracer = 1,
/obj/effect/decal/remains/human = 1,
/obj/item/fish/mastodon = 1,
)
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 15

View File

@@ -269,42 +269,54 @@ GLOBAL_LIST_INIT(typecache_holodeck_linked_floorcheck_ok, typecacheof(list(/turf
if(QDELETED(holo_atom)) if(QDELETED(holo_atom))
spawned -= holo_atom spawned -= holo_atom
continue continue
finalize_spawned(holo_atom)
RegisterSignal(holo_atom, COMSIG_QDELETING, PROC_REF(remove_from_holo_lists))
holo_atom.flags_1 |= HOLOGRAM_1
if(isholoeffect(holo_atom))//activates holo effects and transfers them from the spawned list into the effects list
var/obj/effect/holodeck_effect/holo_effect = holo_atom
effects += holo_effect
spawned -= holo_effect
var/atom/holo_effect_product = holo_effect.activate(src)//change name
if(istype(holo_effect_product))
spawned += holo_effect_product // we want mobs or objects spawned via holoeffects to be tracked as objects
RegisterSignal(holo_effect_product, COMSIG_QDELETING, PROC_REF(remove_from_holo_lists))
if(islist(holo_effect_product))
for(var/atom/atom_product as anything in holo_effect_product)
RegisterSignal(atom_product, COMSIG_QDELETING, PROC_REF(remove_from_holo_lists))
continue
if(isobj(holo_atom))
var/obj/holo_object = holo_atom
holo_object.resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
if(isstructure(holo_object))
holo_object.flags_1 |= NODECONSTRUCT_1
continue
if(ismachinery(holo_object))
var/obj/machinery/holo_machine = holo_object
holo_machine.flags_1 |= NODECONSTRUCT_1
holo_machine.power_change()
if(istype(holo_machine, /obj/machinery/button))
var/obj/machinery/button/holo_button = holo_machine
holo_button.setup_device()
spawning_simulation = FALSE spawning_simulation = FALSE
/obj/machinery/computer/holodeck/proc/finalize_spawned(atom/holo_atom)
RegisterSignal(holo_atom, COMSIG_QDELETING, PROC_REF(remove_from_holo_lists))
holo_atom.flags_1 |= HOLOGRAM_1
if(isholoeffect(holo_atom))//activates holo effects and transfers them from the spawned list into the effects list
var/obj/effect/holodeck_effect/holo_effect = holo_atom
effects += holo_effect
spawned -= holo_effect
var/atom/holo_effect_product = holo_effect.activate(src)//change name
if(istype(holo_effect_product))
spawned += holo_effect_product // we want mobs or objects spawned via holoeffects to be tracked as objects
RegisterSignal(holo_effect_product, COMSIG_QDELETING, PROC_REF(remove_from_holo_lists))
if(islist(holo_effect_product))
for(var/atom/atom_product as anything in holo_effect_product)
RegisterSignal(atom_product, COMSIG_QDELETING, PROC_REF(remove_from_holo_lists))
return
if(isobj(holo_atom))
var/obj/holo_object = holo_atom
holo_object.resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
if(isstructure(holo_object))
holo_object.flags_1 |= NODECONSTRUCT_1
return
if(ismachinery(holo_object))
var/obj/machinery/holo_machine = holo_object
holo_machine.flags_1 |= NODECONSTRUCT_1
holo_machine.power_change()
if(istype(holo_machine, /obj/machinery/button))
var/obj/machinery/button/holo_button = holo_machine
holo_button.setup_device()
/**
* A separate proc for objects that weren't loaded by the template nor spawned by holo effects
* yet need to be added to the list of spawned objects. (e.g. holographic fishes)
*/
/obj/machinery/computer/holodeck/proc/add_to_spawned(atom/holo_atom)
spawned |= holo_atom
if(!(obj_flags & EMAGGED) && isitem(holo_atom))
var/obj/item/to_be_nerfed = holo_atom
to_be_nerfed.damtype = STAMINA
finalize_spawned(holo_atom)
///this qdels holoitems that should no longer exist for whatever reason ///this qdels holoitems that should no longer exist for whatever reason
/obj/machinery/computer/holodeck/proc/derez(atom/movable/holo_atom, silent = TRUE, forced = FALSE) /obj/machinery/computer/holodeck/proc/derez(atom/movable/holo_atom, silent = TRUE, forced = FALSE)
spawned -= holo_atom spawned -= holo_atom

View File

@@ -91,6 +91,10 @@
icon_state = "water" icon_state = "water"
bullet_sizzle = TRUE bullet_sizzle = TRUE
/turf/open/floor/holofloor/beach/water/Initialize(mapload)
. = ..()
AddComponent(/datum/component/fishing_spot, /datum/fish_source/holographic)
/turf/open/floor/holofloor/asteroid /turf/open/floor/holofloor/asteroid
gender = PLURAL gender = PLURAL
name = "asteroid sand" name = "asteroid sand"

View File

@@ -90,6 +90,9 @@
.=..() .=..()
create_reagents(20) create_reagents(20)
reagents.add_reagent(dispensedreagent, 20) reagents.add_reagent(dispensedreagent, 20)
//I'm pretty much aware that, because how oil wells and sinks work, attackby() won't work unless in combat mode.
//Thankfully, the user can cast the line from a distance.
AddComponent(/datum/component/fishing_spot, /datum/fish_source/oil_well)
/obj/structure/sink/oil_well/attack_hand(mob/user, list/modifiers) /obj/structure/sink/oil_well/attack_hand(mob/user, list/modifiers)
flick("puddle-oil-splash",src) flick("puddle-oil-splash",src)

View File

@@ -163,15 +163,16 @@
I.forceMove(src) I.forceMove(src)
held_items[hand_index] = I held_items[hand_index] = I
SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src) SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src)
I.equipped(src, ITEM_SLOT_HANDS)
if(QDELETED(I)) // this is here because some ABSTRACT items like slappers and circle hands could be moved from hand to hand then delete, which meant you'd have a null in your hand until you cleared it (say, by dropping it)
held_items[hand_index] = null
return FALSE
if(I.pulledby) if(I.pulledby)
I.pulledby.stop_pulling() I.pulledby.stop_pulling()
if(!I.on_equipped(src, ITEM_SLOT_HANDS))
return FALSE
update_held_items() update_held_items()
I.pixel_x = I.base_pixel_x I.pixel_x = I.base_pixel_x
I.pixel_y = I.base_pixel_y I.pixel_y = I.base_pixel_y
if(QDELETED(I)) // this is here because some ABSTRACT items like slappers and circle hands could be moved from hand to hand then delete, which meant you'd have a null in your hand until you cleared it (say, by dropping it)
held_items[hand_index] = null
return FALSE
return hand_index return hand_index
//Puts the item into the first available left hand if possible and calls all necessary triggers/updates. returns 1 on success. //Puts the item into the first available left hand if possible and calls all necessary triggers/updates. returns 1 on success.

View File

@@ -130,7 +130,7 @@
/// This proc is called after an item has been successfully handled and equipped to a slot. /// This proc is called after an item has been successfully handled and equipped to a slot.
/mob/living/carbon/proc/has_equipped(obj/item/item, slot, initial = FALSE) /mob/living/carbon/proc/has_equipped(obj/item/item, slot, initial = FALSE)
return item.equipped(src, slot, initial) return item.on_equipped(src, slot, initial)
/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) /mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE)
. = ..() //Sets the default return value to what the parent returns. . = ..() //Sets the default return value to what the parent returns.

View File

@@ -64,7 +64,6 @@
item_module.screen_loc = inv3.screen_loc item_module.screen_loc = inv3.screen_loc
held_items[module_num] = item_module held_items[module_num] = item_module
item_module.equipped(src, ITEM_SLOT_HANDS)
item_module.mouse_opacity = initial(item_module.mouse_opacity) item_module.mouse_opacity = initial(item_module.mouse_opacity)
SET_PLANE_EXPLICIT(item_module, ABOVE_HUD_PLANE, src) SET_PLANE_EXPLICIT(item_module, ABOVE_HUD_PLANE, src)
item_module.forceMove(src) item_module.forceMove(src)
@@ -78,6 +77,7 @@
if(storage_was_closed) if(storage_was_closed)
hud_used.toggle_show_robot_modules() hud_used.toggle_show_robot_modules()
item_module.on_equipped(src, ITEM_SLOT_HANDS)
return TRUE return TRUE
/** /**

View File

@@ -80,7 +80,7 @@
return return
//Call back for item being equipped to drone //Call back for item being equipped to drone
equipping.equipped(src, slot) equipping.on_equipped(src, slot)
/mob/living/simple_animal/drone/getBackSlot() /mob/living/simple_animal/drone/getBackSlot()
return ITEM_SLOT_DEX_STORAGE return ITEM_SLOT_DEX_STORAGE

View File

@@ -450,8 +450,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
equipping.screen_loc = null // will get moved if inventory is visible equipping.screen_loc = null // will get moved if inventory is visible
equipping.forceMove(src) equipping.forceMove(src)
equipping.equipped(src, slot)
SET_PLANE_EXPLICIT(equipping, ABOVE_HUD_PLANE, src) SET_PLANE_EXPLICIT(equipping, ABOVE_HUD_PLANE, src)
equipping.on_equipped(src, slot)
/mob/living/simple_animal/hostile/guardian/proc/apply_overlay(cache_index) /mob/living/simple_animal/hostile/guardian/proc/apply_overlay(cache_index)
if((. = guardian_overlays[cache_index])) if((. = guardian_overlays[cache_index]))

View File

@@ -449,20 +449,27 @@
* Reagent takes a PATH to a reagent. * Reagent takes a PATH to a reagent.
* Amount checks for having a specific amount of that chemical. * Amount checks for having a specific amount of that chemical.
* Needs matabolizing takes into consideration if the chemical is matabolizing when it's checked. * Needs matabolizing takes into consideration if the chemical is matabolizing when it's checked.
* Check subtypes controls whether it should it should also include subtypes: ispath(type, reagent) versus type == reagent.
*/ */
/datum/reagents/proc/has_reagent(reagent, amount = -1, needs_metabolizing = FALSE) /datum/reagents/proc/has_reagent(reagent, amount = -1, needs_metabolizing = FALSE, check_subtypes = FALSE)
var/list/cached_reagents = reagent_list var/list/cached_reagents = reagent_list
for(var/datum/reagent/holder_reagent as anything in cached_reagents) for(var/datum/reagent/holder_reagent as anything in cached_reagents)
if (holder_reagent.type == reagent) if (check_subtypes ? ispath(holder_reagent.type, reagent) : holder_reagent.type == reagent)
if(!amount) if(!amount)
if(needs_metabolizing && !holder_reagent.metabolizing) if(needs_metabolizing && !holder_reagent.metabolizing)
if(check_subtypes)
continue
return FALSE return FALSE
return holder_reagent return holder_reagent
else else
if(round(holder_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) >= amount) if(round(holder_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) >= amount)
if(needs_metabolizing && !holder_reagent.metabolizing) if(needs_metabolizing && !holder_reagent.metabolizing)
if(check_subtypes)
continue
return FALSE return FALSE
return holder_reagent return holder_reagent
else if(!check_subtypes)
return FALSE
return FALSE return FALSE
/** /**

View File

@@ -1264,3 +1264,106 @@
/datum/reagent/toxin/viperspider/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) /datum/reagent/toxin/viperspider/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
affected_mob.adjust_hallucinations(10 SECONDS * REM * seconds_per_tick) affected_mob.adjust_hallucinations(10 SECONDS * REM * seconds_per_tick)
return ..() return ..()
/datum/reagent/toxin/tetrodotoxin
name = "Tetrodotoxin"
description = "A colorless, oderless, tasteless neurotoxin usually carried by livers of animals of the Tetraodontiformes order."
silent_toxin = TRUE
reagent_state = SOLID
color = "#EEEEEE"
metabolization_rate = 0.1 * REAGENTS_METABOLISM
toxpwr = 0
taste_mult = 0
chemical_flags = REAGENT_NO_RANDOM_RECIPE
var/list/traits_not_applied = list(
TRAIT_PARALYSIS_L_ARM = BODY_ZONE_L_ARM,
TRAIT_PARALYSIS_R_ARM = BODY_ZONE_R_ARM,
TRAIT_PARALYSIS_L_LEG = BODY_ZONE_L_LEG,
TRAIT_PARALYSIS_R_LEG = BODY_ZONE_R_LEG,
)
/datum/reagent/toxin/tetrodotoxin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
//be ready for a cocktail of symptoms, including:
//numbness, nausea, vomit, breath loss, weakness, paralysis and nerve damage/impairment and eventually a heart attack if enough time passes.
switch(current_cycle)
if(6 to 12)
if(SPT_PROB(20, seconds_per_tick))
affected_mob.set_jitter_if_lower(rand(2 SECONDS, 3 SECONDS) * REM * seconds_per_tick)
if(SPT_PROB(5, seconds_per_tick))
var/obj/item/organ/internal/tongue/tongue = affected_mob.get_organ_slot(ORGAN_SLOT_TONGUE)
if(tongue)
to_chat(affected_mob, span_warning("your [tongue.name] feels numb..."))
affected_mob.set_slurring_if_lower(5 SECONDS * REM * seconds_per_tick)
affected_mob.adjust_disgust(3.5 * REM * seconds_per_tick)
if(12 to 20)
silent_toxin = FALSE
toxpwr = 0.5
affected_mob.adjustStaminaLoss(2.5 * REM * seconds_per_tick, 0)
if(SPT_PROB(20, seconds_per_tick))
affected_mob.losebreath += 1 * REM * seconds_per_tick
if(SPT_PROB(40, seconds_per_tick))
affected_mob.set_jitter_if_lower(rand(2 SECONDS, 3 SECONDS) * REM * seconds_per_tick)
affected_mob.adjust_disgust(3 * REM * seconds_per_tick)
affected_mob.set_slurring_if_lower(1 SECONDS * REM * seconds_per_tick)
affected_mob.adjustStaminaLoss(2 * REM * seconds_per_tick, 0)
if(SPT_PROB(4, seconds_per_tick))
paralyze_limb(affected_mob)
if(SPT_PROB(10, seconds_per_tick))
affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS))
if(20 to 28)
toxpwr = 1
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5)
if(SPT_PROB(40, seconds_per_tick))
affected_mob.losebreath += 2 * REM * seconds_per_tick
affected_mob.adjust_disgust(3 * REM * seconds_per_tick)
affected_mob.set_slurring_if_lower(3 SECONDS * REM * seconds_per_tick)
if(SPT_PROB(5, seconds_per_tick))
to_chat(affected_mob, span_danger("you feel horribly weak."))
affected_mob.adjustStaminaLoss(5 * REM * seconds_per_tick, 0)
if(SPT_PROB(8, seconds_per_tick))
paralyze_limb(affected_mob)
if(SPT_PROB(10, seconds_per_tick))
affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS))
if(28 to INFINITY)
toxpwr = 1.5
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, BRAIN_DAMAGE_DEATH)
affected_mob.set_silence_if_lower(3 SECONDS * REM * seconds_per_tick)
affected_mob.adjustStaminaLoss(5 * REM * seconds_per_tick, 0)
affected_mob.adjust_disgust(2 * REM * seconds_per_tick)
if(SPT_PROB(15, seconds_per_tick))
paralyze_limb(affected_mob)
if(SPT_PROB(10, seconds_per_tick))
affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS))
if(current_cycle >= 38 && !length(traits_not_applied) && SPT_PROB(5, seconds_per_tick) && !affected_mob.undergoing_cardiac_arrest())
affected_mob.set_heartattack(TRUE)
to_chat(affected_mob, span_danger("you feel a burning pain spread throughout your chest, oh no..."))
return ..()
/datum/reagent/toxin/tetrodotoxin/proc/paralyze_limb(mob/living/affected_mob)
if(!length(traits_not_applied))
return
var/added_trait = pick(traits_not_applied)
ADD_TRAIT(affected_mob, added_trait, REF(src))
traits_not_applied -= added_trait
/datum/reagent/toxin/tetrodotoxin/on_mob_metabolize(mob/living/affected_mob)
RegisterSignal(affected_mob, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath))
/datum/reagent/toxin/tetrodotoxin/on_mob_end_metabolize(mob/living/affected_mob)
UnregisterSignal(affected_mob, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath))
// the initial() proc doesn't work for lists.
var/list/initial_list = list(
TRAIT_PARALYSIS_L_ARM = BODY_ZONE_L_ARM,
TRAIT_PARALYSIS_R_ARM = BODY_ZONE_R_ARM,
TRAIT_PARALYSIS_L_LEG = BODY_ZONE_L_LEG,
TRAIT_PARALYSIS_R_LEG = BODY_ZONE_R_LEG,
)
affected_mob.remove_traits(initial_list, REF(src))
traits_not_applied = initial_list
/datum/reagent/toxin/tetrodotoxin/proc/block_breath(mob/living/source)
SIGNAL_HANDLER
if(current_cycle >= 28)
return COMSIG_CARBON_BLOCK_BREATH

View File

@@ -979,6 +979,7 @@
/datum/design/fishing_rod_tech /datum/design/fishing_rod_tech
name = "Advanced Fishing Rod" name = "Advanced Fishing Rod"
desc = "A fishing rod with an embedded generator dispensing an infinite supply of fishing baits."
id = "fishing_rod_tech" id = "fishing_rod_tech"
build_type = PROTOLATHE | AWAY_LATHE build_type = PROTOLATHE | AWAY_LATHE
materials = list(/datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plastic =SHEET_MATERIAL_AMOUNT) materials = list(/datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plastic =SHEET_MATERIAL_AMOUNT)
@@ -988,6 +989,30 @@
) )
departmental_flags = DEPARTMENT_BITFLAG_SERVICE departmental_flags = DEPARTMENT_BITFLAG_SERVICE
/datum/design/stabilized_hook
name = "Gyro-Stabilized Hook"
desc = "An advanced fishing hook that gives the user a tighter control on the fish when reeling in."
id = "stabilized_hook"
build_type = PROTOLATHE | AWAY_LATHE
materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 5, /datum/material/gold = SMALL_MATERIAL_AMOUNT * 3, /datum/material/titanium = SMALL_MATERIAL_AMOUNT * 2)
build_path = /obj/item/fishing_hook/stabilized
category = list(
RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE
)
departmental_flags = DEPARTMENT_BITFLAG_SERVICE
/datum/design/fish_analyzer
name = "Fish Analyzer"
desc = "An analyzer used to monitor fish's status and traits with."
id = "fish_analyzer"
build_type = PROTOLATHE | AWAY_LATHE
materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 5, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 0.5)
build_path = /obj/item/fish_analyzer
category = list(
RND_CATEGORY_EQUIPMENT + RND_SUBCATEGORY_EQUIPMENT_SERVICE
)
departmental_flags = DEPARTMENT_BITFLAG_SERVICE
///////////////////////////////////////// /////////////////////////////////////////
/////////Coffeemaker Stuff/////////////// /////////Coffeemaker Stuff///////////////
///////////////////////////////////////// /////////////////////////////////////////

View File

@@ -2328,7 +2328,9 @@
description = "Cutting edge fishing advancements." description = "Cutting edge fishing advancements."
prereq_ids = list("base") prereq_ids = list("base")
design_ids = list( design_ids = list(
"fishing_rod_tech" "fishing_rod_tech",
"stabilized_hook",
"fish_analyzer",
) )
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
hidden = TRUE hidden = TRUE

View File

@@ -26,6 +26,18 @@
var/initial_inline_css var/initial_inline_css
var/mouse_event_macro_set = FALSE var/mouse_event_macro_set = FALSE
/**
* Static list used to map in macros that will then emit execute events to the tgui window
* A small disclaimer though I'm no tech wiz: I don't think it's possible to map in right or middle
* clicks in the current state, as they're keywords rather than modifiers.
*/
var/static/list/byondToTguiEventMap = list(
"MouseDown" = "byond/mousedown",
"MouseUp" = "byond/mouseup",
"Ctrl" = "byond/ctrldown",
"Ctrl+UP" = "byond/ctrlup",
)
/** /**
* public * public
* *
@@ -381,11 +393,6 @@
if(mouse_event_macro_set) if(mouse_event_macro_set)
return return
var/list/byondToTguiEventMap = list(
"MouseDown" = "byond/mousedown",
"MouseUp" = "byond/mouseup"
)
for(var/mouseMacro in byondToTguiEventMap) for(var/mouseMacro in byondToTguiEventMap)
var/command_template = ".output CONTROL PAYLOAD" var/command_template = ".output CONTROL PAYLOAD"
var/event_message = TGUI_CREATE_MESSAGE(byondToTguiEventMap[mouseMacro], null) var/event_message = TGUI_CREATE_MESSAGE(byondToTguiEventMap[mouseMacro], null)
@@ -406,10 +413,6 @@
/datum/tgui_window/proc/remove_mouse_macro() /datum/tgui_window/proc/remove_mouse_macro()
if(!mouse_event_macro_set) if(!mouse_event_macro_set)
stack_trace("Unsetting mouse macro on tgui window that has none") stack_trace("Unsetting mouse macro on tgui window that has none")
var/list/byondToTguiEventMap = list(
"MouseDown" = "byond/mousedown",
"MouseUp" = "byond/mouseup"
)
for(var/mouseMacro in byondToTguiEventMap) for(var/mouseMacro in byondToTguiEventMap)
winset(client, null, "[mouseMacro]Window[id]Macro.parent=null") winset(client, null, "[mouseMacro]Window[id]Macro.parent=null")
mouse_event_macro_set = FALSE mouse_event_macro_set = FALSE

View File

@@ -122,6 +122,7 @@
#include "egg_glands.dm" #include "egg_glands.dm"
#include "emoting.dm" #include "emoting.dm"
#include "explosion_action.dm" #include "explosion_action.dm"
#include "fish_unit_tests.dm"
#include "focus_only_tests.dm" #include "focus_only_tests.dm"
#include "font_awesome_icons.dm" #include "font_awesome_icons.dm"
#include "food_edibility_check.dm" #include "food_edibility_check.dm"

View File

@@ -0,0 +1,127 @@
#define TRAIT_FISH_TESTING "made_you_read_this"
///Checks that things associated with fish size and weight work correctly.
/datum/unit_test/fish_size_weight
/datum/unit_test/fish_size_weight/Run()
var/obj/item/fish/fish = allocate(/obj/item/fish/testdummy)
TEST_ASSERT_EQUAL(fish.grind_results[/datum/reagent], 20, "the test fish has [fish.grind_results[/datum/reagent]] units of reagent when it should have 20")
TEST_ASSERT_EQUAL(fish.w_class, WEIGHT_CLASS_BULKY, "the test fish has w_class of [fish.w_class] when it should have been [WEIGHT_CLASS_BULKY]")
var/expected_num_fillets = round(FISH_SIZE_BULKY_MAX / FISH_FILLET_NUMBER_SIZE_DIVISOR * 2, 1)
TEST_ASSERT_EQUAL(fish.num_fillets, expected_num_fillets, "the test fish has [fish.num_fillets] number of fillets when it should have [expected_num_fillets]")
///Checks that fish breeding works correctly.
/datum/unit_test/fish_breeding
/datum/unit_test/fish_breeding/Run()
var/obj/item/fish/fish = allocate(/obj/item/fish/testdummy)
///Check if the fishes can generate offsprings at all.
var/obj/item/fish/fish_two = allocate(/obj/item/fish/testdummy/two)
var/obj/item/fish/new_fish = fish.create_offspring(fish_two.type, fish_two)
TEST_ASSERT(new_fish, "the two test fishes couldn't generate an offspring")
var/traits_len = length(new_fish.fish_traits)
TEST_ASSERT_NOTEQUAL(traits_len, 2, "the offspring of the test fishes has both parents' traits, which are incompatible with each other")
TEST_ASSERT_NOTEQUAL(traits_len, 0, "the offspring has neither of the parents' traits")
TEST_ASSERT(HAS_TRAIT(new_fish, TRAIT_FISH_TESTING), "The offspring doesn't have the relative datum trait associated with its fish trait")
///Check that crossbreeder, no-mating and self-reproductive fish traits work correctly.
var/obj/structure/aquarium/traits/aquarium = allocate(/obj/structure/aquarium/traits)
TEST_ASSERT(!aquarium.sterile.try_to_reproduce(), "The test aquarium's sterile fish managed to reproduce when it shouldn't have")
var/obj/item/fish/crossbreeder_jr = aquarium.crossbreeder.try_to_reproduce()
TEST_ASSERT(crossbreeder_jr, "The test aquarium's crossbreeder fish didn't manage to reproduce when it should have.")
TEST_ASSERT_EQUAL(crossbreeder_jr.type, aquarium.cloner.type, "The test aquarium's crossbreeder fish mated with the wrong type of fish")
var/obj/item/fish/cloner_jr = aquarium.cloner.try_to_reproduce()
TEST_ASSERT(cloner_jr, "The test aquarium's cloner fish didn't manage to reproduce when it should have.")
TEST_ASSERT_NOTEQUAL(cloner_jr.type, aquarium.sterile.type, "The test aquarium's cloner fish mated with the sterile fish")
///Checks that fish evolutions work correctly.
/datum/unit_test/fish_evolution
/datum/unit_test/fish_evolution/Run()
var/obj/structure/aquarium/evolution/aquarium = allocate(/obj/structure/aquarium/evolution)
var/obj/item/fish/evolve_jr = aquarium.evolve.try_to_reproduce()
TEST_ASSERT(evolve_jr, "The test aquarium's evolution fish didn't manage to reproduce when it should have")
TEST_ASSERT_NOTEQUAL(evolve_jr.type, /obj/item/fish/goldfish, "The test aquarium's evolution fish managed to pass the conditions of an impossible evolution.")
TEST_ASSERT_EQUAL(evolve_jr.type, /obj/item/fish/clownfish, "The test aquarium's evolution fish's offspring isn't of the expected type")
TEST_ASSERT(!(/datum/fish_trait/dummy in evolve_jr.fish_traits), "The test aquarium's evolution fish's offspring still has the old trait that ought to be removed by the evolution datum")
TEST_ASSERT(/datum/fish_trait/dummy/two in evolve_jr.fish_traits, "The test aquarium's evolution fish's offspring doesn't have the evolution trait")
///dummy fish item used for the tests, as well with related subtypes and datums.
/obj/item/fish/testdummy
grind_results = list()
average_weight = FISH_GRIND_RESULTS_WEIGHT_DIVISOR * 2
average_size = FISH_SIZE_BULKY_MAX
num_fillets = 2
fish_traits = list(/datum/fish_trait/dummy)
stable_population = INFINITY
breeding_timeout = 0
/obj/item/fish/testdummy/two
fish_traits = list(/datum/fish_trait/dummy/two)
/datum/fish_trait/dummy
incompatible_traits = list(/datum/fish_trait/dummy/two)
inheritability = 100
diff_traits_inheritability = 100
/datum/fish_trait/dummy/apply_to_fish(obj/item/fish/fish)
ADD_TRAIT(fish, TRAIT_FISH_TESTING, FISH_TRAIT_DATUM)
fish.grind_results[/datum/reagent] = 10
/datum/fish_trait/dummy/two
incompatible_traits = list(/datum/fish_trait/dummy)
/obj/structure/aquarium/traits
allow_breeding = TRUE
var/obj/item/fish/testdummy/crossbreeder/crossbreeder
var/obj/item/fish/testdummy/cloner/cloner
var/obj/item/fish/testdummy/sterile/sterile
/obj/structure/aquarium/traits/Initialize(mapload)
. = ..()
crossbreeder = new(src)
cloner = new(src)
sterile = new(src)
/obj/item/fish/testdummy/crossbreeder
fish_traits = list(/datum/fish_trait/crossbreeder)
/obj/item/fish/testdummy/cloner
fish_traits = list(/datum/fish_trait/parthenogenesis)
/obj/item/fish/testdummy/sterile
fish_traits = list(/datum/fish_trait/no_mating)
/obj/structure/aquarium/evolution
allow_breeding = TRUE
var/obj/item/fish/testdummy/evolve/evolve
var/obj/item/fish/testdummy/evolve_two/evolve_two
/obj/structure/aquarium/evolution/Initialize(mapload)
. = ..()
evolve = new(src)
evolve_two = new(src)
/obj/item/fish/testdummy/evolve
compatible_types = list(/obj/item/fish/testdummy/evolve_two)
evolution_types = list(/datum/fish_evolution/dummy)
/obj/item/fish/testdummy/evolve_two
compatible_types = list(/obj/item/fish/testdummy/evolve)
evolution_types = list(/datum/fish_evolution/dummy/two)
/datum/fish_evolution/dummy
probability = 200 //Guaranteed chance even if halved.
new_fish_type = /obj/item/fish/clownfish
new_traits = list(/datum/fish_trait/dummy/two)
removed_traits = list(/datum/fish_trait/dummy)
/datum/fish_evolution/dummy/two
new_fish_type = /obj/item/fish/goldfish
/datum/fish_evolution/dummy/two/New()
. = ..()
probability = 0 //works around the global list initialization skipping abstract/impossible evolutions.
#undef TRAIT_FISH_TESTING

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
icons/obj/aquarium_wide.dmi Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 218 KiB

View File

@@ -880,7 +880,7 @@
#include "code\datums\components\aggro_emote.dm" #include "code\datums\components\aggro_emote.dm"
#include "code\datums\components\ai_retaliate_advanced.dm" #include "code\datums\components\ai_retaliate_advanced.dm"
#include "code\datums\components\anti_magic.dm" #include "code\datums\components\anti_magic.dm"
#include "code\datums\components\aquarium.dm" #include "code\datums\components\aquarium_content.dm"
#include "code\datums\components\area_based_godmode.dm" #include "code\datums\components\area_based_godmode.dm"
#include "code\datums\components\area_sound_manager.dm" #include "code\datums\components\area_sound_manager.dm"
#include "code\datums\components\areabound.dm" #include "code\datums\components\areabound.dm"
@@ -1283,6 +1283,7 @@
#include "code\datums\elements\shatters_when_thrown.dm" #include "code\datums\elements\shatters_when_thrown.dm"
#include "code\datums\elements\sideway_movement.dm" #include "code\datums\elements\sideway_movement.dm"
#include "code\datums\elements\simple_flying.dm" #include "code\datums\elements\simple_flying.dm"
#include "code\datums\elements\skill_reward.dm"
#include "code\datums\elements\skittish.dm" #include "code\datums\elements\skittish.dm"
#include "code\datums\elements\snail_crawl.dm" #include "code\datums\elements\snail_crawl.dm"
#include "code\datums\elements\soft_landing.dm" #include "code\datums\elements\soft_landing.dm"
@@ -1458,6 +1459,7 @@
#include "code\datums\screentips\item_context.dm" #include "code\datums\screentips\item_context.dm"
#include "code\datums\skills\_skill.dm" #include "code\datums\skills\_skill.dm"
#include "code\datums\skills\cleaning.dm" #include "code\datums\skills\cleaning.dm"
#include "code\datums\skills\fishing.dm"
#include "code\datums\skills\gaming.dm" #include "code\datums\skills\gaming.dm"
#include "code\datums\skills\mining.dm" #include "code\datums\skills\mining.dm"
#include "code\datums\station_traits\_station_trait.dm" #include "code\datums\station_traits\_station_trait.dm"
@@ -1505,6 +1507,7 @@
#include "code\datums\storage\subtypes\cards.dm" #include "code\datums\storage\subtypes\cards.dm"
#include "code\datums\storage\subtypes\duffel_bag.dm" #include "code\datums\storage\subtypes\duffel_bag.dm"
#include "code\datums\storage\subtypes\extract_inventory.dm" #include "code\datums\storage\subtypes\extract_inventory.dm"
#include "code\datums\storage\subtypes\fish_case.dm"
#include "code\datums\storage\subtypes\implant.dm" #include "code\datums\storage\subtypes\implant.dm"
#include "code\datums\storage\subtypes\organ_box.dm" #include "code\datums\storage\subtypes\organ_box.dm"
#include "code\datums\storage\subtypes\pockets.dm" #include "code\datums\storage\subtypes\pockets.dm"
@@ -3503,11 +3506,13 @@
#include "code\modules\fishing\fishing_minigame.dm" #include "code\modules\fishing\fishing_minigame.dm"
#include "code\modules\fishing\fishing_portal_machine.dm" #include "code\modules\fishing\fishing_portal_machine.dm"
#include "code\modules\fishing\fishing_rod.dm" #include "code\modules\fishing\fishing_rod.dm"
#include "code\modules\fishing\fishing_traits.dm"
#include "code\modules\fishing\aquarium\aquarium.dm" #include "code\modules\fishing\aquarium\aquarium.dm"
#include "code\modules\fishing\aquarium\aquarium_kit.dm" #include "code\modules\fishing\aquarium\aquarium_kit.dm"
#include "code\modules\fishing\aquarium\fish_analyzer.dm"
#include "code\modules\fishing\fish\_fish.dm" #include "code\modules\fishing\fish\_fish.dm"
#include "code\modules\fishing\fish\chasm_detritus.dm" #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\fish_types.dm"
#include "code\modules\fishing\sources\_fish_source.dm" #include "code\modules\fishing\sources\_fish_source.dm"
#include "code\modules\fishing\sources\source_types.dm" #include "code\modules\fishing\sources\source_types.dm"

View File

@@ -146,6 +146,14 @@ export const backendMiddleware = (store) => {
globalEvents.emit('byond/mouseup'); globalEvents.emit('byond/mouseup');
} }
if (type === 'byond/ctrldown') {
globalEvents.emit('byond/ctrldown');
}
if (type === 'byond/ctrlup') {
globalEvents.emit('byond/ctrlup');
}
if (type === 'backend/suspendStart' && !suspendInterval) { if (type === 'backend/suspendStart' && !suspendInterval) {
logger.log(`suspending (${Byond.windowId})`); logger.log(`suspending (${Byond.windowId})`);
// Keep sending suspend messages until it succeeds. // Keep sending suspend messages until it succeeds.

View File

@@ -96,7 +96,7 @@ export const FishCatalog = (props, context) => {
<LabeledList.Item label="Average weight"> <LabeledList.Item label="Average weight">
{currentFish.weight} g {currentFish.weight} g
</LabeledList.Item> </LabeledList.Item>
<LabeledList.Item label="Fishing tips"> <LabeledList.Item label="Fishing and Aquarium tips">
<LabeledList> <LabeledList>
<LabeledList.Item label="Fishing locations"> <LabeledList.Item label="Fishing locations">
{currentFish.fishing_tips.spots} {currentFish.fishing_tips.spots}

View File

@@ -1,11 +1,12 @@
import { clamp } from 'common/math'; import { clamp } from 'common/math';
import { KEY_CTRL } from 'common/keycodes';
import { randomInteger, randomNumber, randomPick, randomProb } from 'common/random'; import { randomInteger, randomNumber, randomPick, randomProb } from 'common/random';
import { useDispatch } from 'common/redux'; import { useDispatch } from 'common/redux';
import { Component } from 'inferno'; import { Component } from 'inferno';
import { resolveAsset } from '../assets'; import { resolveAsset } from '../assets';
import { backendSuspendStart, useBackend } from '../backend'; import { backendSuspendStart, useBackend } from '../backend';
import { Icon } from '../components'; import { Icon, KeyListener } from '../components';
import { globalEvents } from '../events'; import { globalEvents, KeyEvent } from '../events';
import { Window } from '../layouts'; import { Window } from '../layouts';
type Bait = { type Bait = {
@@ -26,6 +27,7 @@ type FishAI = 'dumb' | 'zippy' | 'slow';
enum ReelingState { enum ReelingState {
Idle, Idle,
Reeling, Reeling,
ReelingDown,
} }
type FishingMinigameProps = { type FishingMinigameProps = {
@@ -43,7 +45,13 @@ type FishingMinigameState = {
fish: Fish; fish: Fish;
}; };
type SpecialRule = 'weighted' | 'limit_loss' | 'heavy'; type SpecialRule =
| 'weighted'
| 'limit_loss'
| 'heavy'
| 'bidirectional'
| 'no_escape'
| 'lubed';
class FishingMinigame extends Component< class FishingMinigame extends Component<
FishingMinigameProps, FishingMinigameProps,
@@ -63,6 +71,9 @@ class FishingMinigame extends Component<
longJumpVelocityLimit: number = 200; longJumpVelocityLimit: number = 200;
shortJumpVelocityLimit: number = 400; shortJumpVelocityLimit: number = 400;
idleVelocity: number = 0; idleVelocity: number = 0;
accel_up_coeff: number = 1;
bidirectional: boolean = false;
no_escape: boolean = false;
baseLongJumpChancePerSecond: number = 0.0075; baseLongJumpChancePerSecond: number = 0.0075;
baseShortJumpChancePerSecond: number = 0.255; baseShortJumpChancePerSecond: number = 0.255;
@@ -82,6 +93,9 @@ class FishingMinigame extends Component<
: -6; : -6;
this.baitBounceCoeff = props.special_rules.includes('weighted') ? 0.1 : 0.6; this.baitBounceCoeff = props.special_rules.includes('weighted') ? 0.1 : 0.6;
this.idleVelocity = props.special_rules.includes('heavy') ? 10 : 0; this.idleVelocity = props.special_rules.includes('heavy') ? 10 : 0;
this.bidirectional = props.special_rules.includes('bidirectional');
this.no_escape = props.special_rules.includes('no_escape');
this.accel_up_coeff = props.special_rules.includes('lubed') ? 1.4 : 1;
switch (props.fish_ai) { switch (props.fish_ai) {
case 'dumb': case 'dumb':
@@ -117,6 +131,10 @@ class FishingMinigame extends Component<
this.handle_mousedown = this.handle_mousedown.bind(this); this.handle_mousedown = this.handle_mousedown.bind(this);
this.handle_mouseup = this.handle_mouseup.bind(this); this.handle_mouseup = this.handle_mouseup.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handle_ctrldown = this.handle_ctrldown.bind(this);
this.handle_ctrlup = this.handle_ctrlup.bind(this);
this.updateAnimation = this.updateAnimation.bind(this); this.updateAnimation = this.updateAnimation.bind(this);
this.moveFish = this.moveFish.bind(this); this.moveFish = this.moveFish.bind(this);
this.moveBait = this.moveBait.bind(this); this.moveBait = this.moveBait.bind(this);
@@ -130,6 +148,8 @@ class FishingMinigame extends Component<
this.animation_id = window.requestAnimationFrame(this.updateAnimation); this.animation_id = window.requestAnimationFrame(this.updateAnimation);
globalEvents.on('byond/mousedown', this.handle_mousedown); globalEvents.on('byond/mousedown', this.handle_mousedown);
globalEvents.on('byond/mouseup', this.handle_mouseup); globalEvents.on('byond/mouseup', this.handle_mouseup);
globalEvents.on('byond/ctrldown', this.handle_ctrldown);
globalEvents.on('byond/ctrlup', this.handle_ctrlup);
} }
componentWillUnmount() { componentWillUnmount() {
@@ -138,6 +158,8 @@ class FishingMinigame extends Component<
window.cancelAnimationFrame(this.animation_id); window.cancelAnimationFrame(this.animation_id);
globalEvents.off('byond/mousedown', this.handle_mousedown); globalEvents.off('byond/mousedown', this.handle_mousedown);
globalEvents.off('byond/mouseup', this.handle_mouseup); globalEvents.off('byond/mouseup', this.handle_mouseup);
globalEvents.off('byond/ctrldown', this.handle_ctrldown);
globalEvents.off('byond/ctrlup', this.handle_ctrlup);
} }
updateAnimation(timestamp: DOMHighResTimeStamp) { updateAnimation(timestamp: DOMHighResTimeStamp) {
@@ -275,7 +297,7 @@ class FishingMinigame extends Component<
const { fish, bait } = this.state; const { fish, bait } = this.state;
// Speedup when reeling // Speedup when reeling
const acceleration_up = -1500; const acceleration_up = -1500 * this.accel_up_coeff;
// Gravity // Gravity
const acceleration_down = 1000; const acceleration_down = 1000;
// Velocity is multiplied by this when bouncing off the bottom/top // Velocity is multiplied by this when bouncing off the bottom/top
@@ -298,17 +320,31 @@ class FishingMinigame extends Component<
// Bottom bound // Bottom bound
if (newPosition + bait.height > this.area_height) { if (newPosition + bait.height > this.area_height) {
newPosition = this.area_height - bait.height; newPosition = this.area_height - bait.height;
newVelocity = -bait.velocity * bounce_coeff; if (this.reeling === ReelingState.ReelingDown) {
newVelocity = 0;
} else {
newVelocity = -bait.velocity * bounce_coeff;
}
} }
const acceleration = const acceleration =
this.reeling === ReelingState.Reeling this.reeling === ReelingState.Reeling
? acceleration_up ? acceleration_up
: acceleration_down; : this.reeling === ReelingState.ReelingDown
const velocity_change = acceleration * seconds; ? -acceleration_up
: acceleration_down;
// Slowdown both ways when on fish // Slowdown both ways when on fish
if (this.fishOnBait(fish, bait)) { const velocity_change =
newVelocity += on_point_coeff * velocity_change; acceleration *
seconds *
(this.fishOnBait(fish, bait) ? on_point_coeff : 1);
if (this.bidirectional && this.reeling === ReelingState.Idle) {
if (newVelocity < 0) {
newVelocity = Math.min(newVelocity + velocity_change, 0);
} else {
newVelocity = Math.max(newVelocity - velocity_change, 0);
}
} else { } else {
newVelocity += velocity_change; newVelocity += velocity_change;
} }
@@ -351,7 +387,7 @@ class FishingMinigame extends Component<
const dispatch = useDispatch(this.context); const dispatch = useDispatch(this.context);
if (newCompletion <= 0) { if (newCompletion <= 0 && !this.no_escape) {
this.props.lose(); this.props.lose();
dispatch(backendSuspendStart()); dispatch(backendSuspendStart());
} else if (newCompletion >= 100) { } else if (newCompletion >= 100) {
@@ -377,7 +413,33 @@ class FishingMinigame extends Component<
} }
handle_mouseup(event: MouseEvent) { handle_mouseup(event: MouseEvent) {
this.reeling = ReelingState.Idle; if (this.reeling === ReelingState.Reeling) {
this.reeling = ReelingState.Idle;
}
}
handleKeyDown(keyEvent: KeyEvent) {
if (keyEvent.code === KEY_CTRL) {
this.handle_ctrldown();
}
}
handleKeyUp(keyEvent: KeyEvent) {
if (keyEvent.code === KEY_CTRL) {
this.handle_ctrlup();
}
}
handle_ctrldown() {
if (this.bidirectional && this.reeling === ReelingState.Idle) {
this.reeling = ReelingState.ReelingDown;
}
}
handle_ctrlup() {
if (this.bidirectional && this.reeling === ReelingState.ReelingDown) {
this.reeling = ReelingState.Idle;
}
} }
render() { render() {
@@ -386,6 +448,10 @@ class FishingMinigame extends Component<
const background_image = resolveAsset(this.props.background); const background_image = resolveAsset(this.props.background);
return ( return (
<div class="fishing"> <div class="fishing">
<KeyListener
onKeyDown={this.handleKeyDown}
onKeyUp={this.handleKeyUp}
/>
<div class="main"> <div class="main">
<div <div
class="background" class="background"