[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.
@@ -22,6 +22,9 @@
|
||||
"t" = (
|
||||
/turf/open/floor/holofloor/carpet,
|
||||
/area/template_noop)
|
||||
"B" = (
|
||||
/turf/open/floor/holofloor/beach/water,
|
||||
/area/template_noop)
|
||||
"H" = (
|
||||
/obj/effect/holodeck_effect/mobspawner/monkey,
|
||||
/turf/open/floor/holofloor/beach,
|
||||
@@ -64,8 +67,8 @@ R
|
||||
R
|
||||
R
|
||||
R
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
(2,1,1) = {"
|
||||
R
|
||||
@@ -76,8 +79,8 @@ R
|
||||
S
|
||||
R
|
||||
Q
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
(3,1,1) = {"
|
||||
X
|
||||
@@ -88,8 +91,8 @@ R
|
||||
t
|
||||
t
|
||||
R
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
(4,1,1) = {"
|
||||
R
|
||||
@@ -100,8 +103,8 @@ h
|
||||
R
|
||||
R
|
||||
R
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
(5,1,1) = {"
|
||||
R
|
||||
@@ -112,8 +115,8 @@ R
|
||||
t
|
||||
t
|
||||
R
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
(6,1,1) = {"
|
||||
R
|
||||
@@ -124,8 +127,8 @@ X
|
||||
g
|
||||
R
|
||||
q
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
(7,1,1) = {"
|
||||
R
|
||||
@@ -136,8 +139,8 @@ R
|
||||
R
|
||||
R
|
||||
f
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
(8,1,1) = {"
|
||||
W
|
||||
@@ -148,8 +151,8 @@ H
|
||||
R
|
||||
M
|
||||
R
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
(9,1,1) = {"
|
||||
R
|
||||
@@ -160,6 +163,6 @@ R
|
||||
R
|
||||
R
|
||||
R
|
||||
R
|
||||
a
|
||||
B
|
||||
"}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
|
||||
//Skill medal hub IDs
|
||||
#define MEDAL_LEGENDARY_MINER "Legendary Miner"
|
||||
#define MEDAL_LEGENDARY_FISHER "Legendary Fisher"
|
||||
|
||||
//Mafia medal hub IDs (wins)
|
||||
#define MAFIA_MEDAL_ASSISTANT "Assistant"
|
||||
|
||||
@@ -13,9 +13,20 @@
|
||||
#define FISH_ALIVE "alive"
|
||||
#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 MAX_AQUARIUM_TEMP (T0C + 100)
|
||||
#define DEFAULT_AQUARIUM_TEMP 300
|
||||
#define DEFAULT_AQUARIUM_TEMP (T0C + 24)
|
||||
|
||||
#define FISH_RARITY_BASIC 1000
|
||||
#define FISH_RARITY_RARE 400
|
||||
@@ -27,5 +38,6 @@
|
||||
#define AQUARIUM_FLUID_SULPHWATEVER "Sulfuric Water"
|
||||
#define AQUARIUM_FLUID_AIR "Air"
|
||||
#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."
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
#define COLOR_VIBRANT_LIME "#00FF00"
|
||||
#define COLOR_SERVICE_LIME "#58C800"
|
||||
#define COLOR_JADE "#5EFB6E"
|
||||
#define COLOR_EMERALD "#00CC66"
|
||||
#define COLOR_LIME "#32CD32"
|
||||
#define COLOR_DARK_LIME "#00aa00"
|
||||
#define COLOR_VERY_PALE_LIME_GREEN "#DDFFD3"
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
#define COMPONENT_ALLOW_REACH (1<<0)
|
||||
///for when an atom has been created through processing (atom/original_atom, list/chosen_processing_option)
|
||||
#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"
|
||||
///called when teleporting into a possibly protected turf: (channel, turf/origin, turf/destination)
|
||||
#define COMSIG_ATOM_INTERCEPT_TELEPORTING "intercept_teleporting"
|
||||
|
||||
@@ -5,9 +5,18 @@
|
||||
// Fish signals
|
||||
#define COMSIG_FISH_STATUS_CHANGED "fish_status_changed"
|
||||
#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
|
||||
#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
|
||||
#define COMSIG_PRE_FISHING "pre_fishing"
|
||||
|
||||
@@ -17,3 +26,9 @@
|
||||
|
||||
/// Sent when fishing line is snapped
|
||||
#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"
|
||||
|
||||
@@ -104,6 +104,10 @@
|
||||
|
||||
///from base of obj/item/equipped(): (mob/equipper, slot)
|
||||
#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)
|
||||
#define COMSIG_MOB_EQUIPPED_ITEM "mob_equipped_item"
|
||||
/// A mob has just unequipped an item.
|
||||
|
||||
@@ -35,18 +35,39 @@
|
||||
/// Fishing hook trait that's used to make the bait more weighted, for the
|
||||
/// fishing minigame itself.
|
||||
#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)
|
||||
///Required to cast a line on lava.
|
||||
#define FISHING_LINE_REINFORCED (1 << 1)
|
||||
/// Much like FISHING_HOOK_ENSNARE but for the reel.
|
||||
#define FISHING_LINE_BOUNCY (1 << 2)
|
||||
|
||||
#define FISHING_SPOT_PRESET_BEACH "beach"
|
||||
#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_TOILET "toilet"
|
||||
|
||||
#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_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.
|
||||
#define FISHING_DEFAULT_HOOK_BONUS_ADDITIVE 0
|
||||
|
||||
@@ -190,6 +190,8 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list(
|
||||
|
||||
#define isitem(A) (istype(A, /obj/item))
|
||||
|
||||
#define isfish(A) (istype(A, /obj/item/fish))
|
||||
|
||||
#define isstack(A) (istype(A, /obj/item/stack))
|
||||
|
||||
#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 isaquarium(A) (istype(A, /obj/structure/aquarium))
|
||||
|
||||
#define ismachinery(A) (istype(A, /obj/machinery))
|
||||
|
||||
#define isvendor(A) (istype(A, /obj/machinery/vending))
|
||||
|
||||
@@ -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)
|
||||
|
||||
//Skill modifier types
|
||||
#define SKILL_SPEED_MODIFIER "skill_speed_modifier"//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_RANDS_MODIFIER "skill_randomness_modifier"//ideally added/subtracted where beneficial in rand(x,y) calls
|
||||
///ideally added/subtracted in speed calculations to make you do stuff faster
|
||||
#define SKILL_SPEED_MODIFIER "skill_speed_modifier"
|
||||
///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
|
||||
#define GetSkillRef(A) (SSskills.all_skills[A])
|
||||
@@ -34,3 +39,9 @@
|
||||
//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_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)
|
||||
|
||||
@@ -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
|
||||
#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
|
||||
#define TRAIT_GENERIC "generic"
|
||||
#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
|
||||
#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.
|
||||
#define BODYPART_TRAIT "bodypart"
|
||||
#define HEAD_TRAIT "head"
|
||||
|
||||
@@ -171,3 +171,31 @@ GLOBAL_LIST_EMPTY(gas_handbook)
|
||||
return object
|
||||
|
||||
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
|
||||
|
||||
@@ -301,7 +301,7 @@ GLOBAL_LIST_INIT(rarity_loot, list(//rare: really good items
|
||||
/obj/item/shield/buckler = 1,
|
||||
/obj/item/throwing_star = 1,
|
||||
/obj/item/weldingtool/hugetank = 1,
|
||||
/obj/item/fishing_rod/master = 1,
|
||||
/obj/item/fishing_rod/telescopic/master = 1,
|
||||
/obj/item/spess_knife = 1,
|
||||
) = 1,
|
||||
|
||||
|
||||
@@ -254,6 +254,15 @@ GLOBAL_LIST_INIT(traits_by_type, list(
|
||||
/obj/item/card/id = list(
|
||||
"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
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
/datum/radial_menu/persistent/element_chosen(choice_id, mob/user, 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)
|
||||
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
|
||||
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)
|
||||
return
|
||||
if(!uniqueid)
|
||||
@@ -71,9 +78,13 @@
|
||||
menu.radius = radius
|
||||
menu.radial_slice_icon = radial_slice_icon
|
||||
menu.select_proc_callback = select_proc
|
||||
if(istype(custom_check))
|
||||
menu.custom_check_callback = custom_check
|
||||
menu.anchor = anchor
|
||||
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.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
|
||||
|
||||
|
||||
@@ -7,3 +7,8 @@
|
||||
database_id = MEDAL_LEGENDARY_MINER
|
||||
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"
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
aquarium_vc_color = fish.aquarium_vc_color
|
||||
|
||||
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
|
||||
base_transform = matrix()
|
||||
else
|
||||
@@ -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
|
||||
/datum/component/chasm
|
||||
var/turf/target_turf
|
||||
@@ -35,6 +32,8 @@ GLOBAL_LIST_INIT(chasm_storage, list())
|
||||
))
|
||||
|
||||
/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_REMOVETRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_no_longer_stopped))
|
||||
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.
|
||||
if(!mapload)
|
||||
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()
|
||||
remove_storage()
|
||||
|
||||
/**
|
||||
* 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
|
||||
storage = null
|
||||
|
||||
/datum/component/chasm/proc/entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
||||
SIGNAL_HANDLER
|
||||
@@ -197,9 +182,7 @@ GLOBAL_LIST_INIT(chasm_storage, list())
|
||||
return
|
||||
|
||||
if(!storage)
|
||||
storage = new(get_turf(parent))
|
||||
RegisterSignal(storage, COMSIG_ATOM_EXITED, PROC_REF(left_chasm))
|
||||
GLOB.chasm_storage += WEAKREF(storage)
|
||||
storage = (locate() in parent) || new(parent)
|
||||
|
||||
if(storage.contains(dropped_thing))
|
||||
return
|
||||
@@ -209,14 +192,11 @@ GLOBAL_LIST_INIT(chasm_storage, list())
|
||||
dropped_thing.transform = oldtransform
|
||||
dropped_thing.pixel_y = oldoffset
|
||||
|
||||
if(dropped_thing.forceMove(storage))
|
||||
if (isliving(dropped_thing))
|
||||
RegisterSignal(dropped_thing, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
|
||||
else
|
||||
if(!dropped_thing.forceMove(storage))
|
||||
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))
|
||||
|
||||
if(isliving(dropped_thing))
|
||||
else if(isliving(dropped_thing))
|
||||
var/mob/living/fallen_mob = dropped_thing
|
||||
fallen_mob.notransform = FALSE
|
||||
if (fallen_mob.stat != DEAD)
|
||||
@@ -237,27 +217,8 @@ GLOBAL_LIST_INIT(chasm_storage, list())
|
||||
SIGNAL_HANDLER
|
||||
UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
|
||||
|
||||
#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.
|
||||
*/
|
||||
/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
|
||||
///Global list needed to let fishermen with a rescue hook fish fallen mobs from any place
|
||||
GLOBAL_LIST_EMPTY(chasm_fallen_mobs)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
. = ..()
|
||||
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
|
||||
|
||||
@@ -51,14 +51,6 @@
|
||||
/datum/component/fishing_spot/proc/start_fishing_challenge(obj/item/fishing_rod/rod, mob/user)
|
||||
/// Roll what we caught based on modified table
|
||||
var/result = fish_source.roll_reward(rod, user)
|
||||
var/datum/fishing_challenge/challenge = new(parent, result, rod, user)
|
||||
challenge.background = fish_source.background
|
||||
challenge.difficulty = fish_source.calculate_difficulty(result, rod, user)
|
||||
RegisterSignal(challenge, COMSIG_FISHING_CHALLENGE_COMPLETED, PROC_REF(fishing_completed))
|
||||
var/datum/fishing_challenge/challenge = new(src, result, rod, user)
|
||||
fish_source.pre_challenge_started(rod, 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)
|
||||
|
||||
48
code/datums/elements/skill_reward.dm
Normal 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()
|
||||
@@ -42,6 +42,21 @@
|
||||
icon_file = 'icons/obj/clothing/belt_overlays.dmi'
|
||||
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
|
||||
//
|
||||
|
||||
15
code/datums/greyscale/json_configs/fish_analyzer_worn.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -131,23 +131,13 @@
|
||||
return
|
||||
|
||||
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/trace_gases
|
||||
for(var/id in floor_gases)
|
||||
if(id in gases_to_check)
|
||||
continue
|
||||
trace_gases = TRUE
|
||||
break
|
||||
|
||||
// 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
|
||||
var/static/list/gases_to_check = list(
|
||||
/datum/gas/oxygen = list(16, 100),
|
||||
/datum/gas/nitrogen,
|
||||
/datum/gas/carbon_dioxide = list(0, 10)
|
||||
)
|
||||
if(!check_gases(floor_gases, gases_to_check))
|
||||
return FALSE
|
||||
|
||||
// Aim for goldilocks temperatures and pressure
|
||||
if((floor_gas_mixture.temperature <= 270) || (floor_gas_mixture.temperature >= 360))
|
||||
|
||||
@@ -6,8 +6,8 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill))
|
||||
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.
|
||||
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
|
||||
var/skill_cape_path
|
||||
///List Path pointing to the skill item reward that will appear when a user finishes leveling up a skill
|
||||
var/skill_item_path
|
||||
///List associating different messages that appear on level up with different levels
|
||||
var/list/levelUpMessages = list()
|
||||
///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)
|
||||
if (new_level != SKILL_LEVEL_LEGENDARY)
|
||||
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."))
|
||||
return
|
||||
if (LAZYFIND(mind.skills_rewarded, src.type))
|
||||
@@ -75,7 +75,7 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill))
|
||||
podspawn(list(
|
||||
"target" = get_turf(mind.current),
|
||||
"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)
|
||||
))
|
||||
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."))
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
title = "Cleaner"
|
||||
desc = "It’s 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
|
||||
skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/cleaning
|
||||
skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/cleaning
|
||||
|
||||
10
code/datums/skills/fishing.dm
Normal 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
|
||||
@@ -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."
|
||||
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_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()
|
||||
. = ..()
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
title = "Miner"
|
||||
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))
|
||||
skill_cape_path = /obj/item/clothing/neck/cloak/skill_reward/mining
|
||||
skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/mining
|
||||
|
||||
33
code/datums/storage/subtypes/fish_case.dm
Normal 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)
|
||||
@@ -641,6 +641,22 @@
|
||||
/obj/item/proc/on_found(mob/finder)
|
||||
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;
|
||||
* this is directly called instead of `equipped` on visual-only features like human dummies equipping outfits.
|
||||
@@ -652,7 +668,7 @@
|
||||
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.
|
||||
*
|
||||
@@ -663,7 +679,7 @@
|
||||
*/
|
||||
/obj/item/proc/equipped(mob/user, slot, initial = FALSE)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
SEND_SIGNAL(user, COMSIG_HUMAN_EQUIPPING_ITEM, src, slot)
|
||||
PROTECTED_PROC(TRUE)
|
||||
visual_equipped(user, slot, initial)
|
||||
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot)
|
||||
SEND_SIGNAL(user, COMSIG_MOB_EQUIPPED_ITEM, src, slot)
|
||||
|
||||
@@ -81,11 +81,11 @@
|
||||
icon_state = "armorfish_fillet"
|
||||
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
|
||||
name = "donkfillet"
|
||||
desc = "The dreaded donkfish fillet. No sane spaceman would eat this, and it does not get better when cooked."
|
||||
icon_state = "donkfillet"
|
||||
food_reagents = list(/datum/reagent/yuck = 3)
|
||||
|
||||
/obj/item/food/fishfingers
|
||||
name = "fish fingers"
|
||||
|
||||
@@ -165,3 +165,12 @@
|
||||
/obj/item/storage/wallet/random/PopulateContents()
|
||||
new /obj/item/holochip(src, rand(5, 30))
|
||||
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))
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
. = ..()
|
||||
open = round(rand(0, 1))
|
||||
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)
|
||||
. = ..()
|
||||
@@ -120,8 +121,9 @@
|
||||
return
|
||||
w_items += I.w_class
|
||||
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)
|
||||
return
|
||||
if(istype(I, /obj/item/food/monkeycube))
|
||||
|
||||
@@ -38,10 +38,13 @@
|
||||
var/mask_icon = 'icons/turf/floors.dmi'
|
||||
/// The icon state that covers the lava bits of our turf
|
||||
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)
|
||||
. = ..()
|
||||
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()
|
||||
if(!smoothing_flags)
|
||||
update_appearance()
|
||||
@@ -342,6 +345,7 @@
|
||||
icon_state = "liquidplasma"
|
||||
initial_gas_mix = BURNING_COLD
|
||||
baseturfs = /turf/open/lava/plasma
|
||||
fish_source_type = FISHING_SPOT_PRESET_ICEMOON_PLASMA
|
||||
|
||||
light_range = 3
|
||||
light_power = 0.75
|
||||
@@ -423,8 +427,10 @@
|
||||
initial_gas_mix = OPENTURF_DEFAULT_ATMOS
|
||||
baseturfs = /turf/open/lava/plasma/mafia
|
||||
slowdown = 0
|
||||
fish_source_type = null
|
||||
|
||||
//basketball specific lava (normal atmos, no slowdown)
|
||||
/turf/open/lava/smooth/basketball
|
||||
initial_gas_mix = OPENTURF_DEFAULT_ATMOS
|
||||
slowdown = 0
|
||||
fish_source_type = null
|
||||
|
||||
@@ -236,6 +236,12 @@
|
||||
cost = PAYCHECK_CREW
|
||||
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
|
||||
name = "Coffee Mug"
|
||||
desc = "A bog standard coffee mug, for drinking coffee."
|
||||
|
||||
@@ -95,6 +95,13 @@
|
||||
stock_max = 3
|
||||
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
|
||||
name = "Genuine V8 Engine (Perserved)"
|
||||
@@ -104,3 +111,13 @@
|
||||
price_max = CARGO_CRATE_VALUE * 6
|
||||
stock_max = 1
|
||||
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
|
||||
|
||||
@@ -68,6 +68,13 @@
|
||||
contains = list(/obj/item/storage/fish_case/tiziran = 2)
|
||||
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
|
||||
name = "Bicycle"
|
||||
desc = "Nanotrasen reminds all employees to never toy with powers outside their control."
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
icon_state = "cargosoft"
|
||||
inhand_icon_state = "greyscale_softcap" //todo wip
|
||||
var/soft_type = "cargo"
|
||||
var/soft_suffix = "soft"
|
||||
|
||||
dog_fashion = /datum/dog_fashion/head/cargo_tech
|
||||
|
||||
var/flipped = FALSE
|
||||
|
||||
/obj/item/clothing/head/soft/dropped()
|
||||
icon_state = "[soft_type]soft"
|
||||
icon_state = "[soft_type][soft_suffix]"
|
||||
flipped = FALSE
|
||||
..()
|
||||
|
||||
@@ -33,10 +34,10 @@
|
||||
if(!user.incapacitated())
|
||||
flipped = !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."))
|
||||
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."))
|
||||
usr.update_worn_head() //so our mob-overlays update
|
||||
|
||||
@@ -139,3 +140,24 @@
|
||||
icon_state = "paramedicsoft"
|
||||
soft_type = "paramedic"
|
||||
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)
|
||||
|
||||
@@ -55,31 +55,12 @@
|
||||
|
||||
/obj/item/clothing/neck/cloak/skill_reward
|
||||
var/associated_skill_path = /datum/skill
|
||||
var/element_type = /datum/element/skill_reward
|
||||
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.")
|
||||
|
||||
/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 ..()
|
||||
AddElement(element_type, associated_skill_path)
|
||||
|
||||
/obj/item/clothing/neck/cloak/skill_reward/gaming
|
||||
name = "legendary gamer's cloak"
|
||||
@@ -103,6 +84,5 @@
|
||||
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."
|
||||
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()
|
||||
|
||||
@@ -37,22 +37,39 @@
|
||||
///Current layers in use by aquarium contents
|
||||
var/list/used_layers = list()
|
||||
|
||||
/// /obj/item/fish in the aquarium - does not include things with aquarium visuals that are not fish
|
||||
var/list/tracked_fish = list()
|
||||
/// /obj/item/fish in the aquarium, sorted by type - does not include things with aquarium visuals that are not fish
|
||||
var/list/tracked_fish_by_type
|
||||
|
||||
/obj/structure/aquarium/Initialize(mapload)
|
||||
. = ..()
|
||||
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)
|
||||
. = ..()
|
||||
if(istype(arrived,/obj/item/fish))
|
||||
tracked_fish += arrived
|
||||
if(isfish(arrived))
|
||||
LAZYADDASSOCLIST(tracked_fish_by_type, arrived.type, arrived)
|
||||
|
||||
/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)
|
||||
/**
|
||||
@@ -111,9 +128,9 @@
|
||||
default_unfasten_wrench(user, tool)
|
||||
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)
|
||||
var/obj/item/stack/sheet/glass/glass = I
|
||||
var/obj/item/stack/sheet/glass/glass = item
|
||||
if(istype(glass))
|
||||
if(glass.get_amount() < 2)
|
||||
to_chat(user, span_warning("You need two glass sheets to fix the case!"))
|
||||
@@ -126,20 +143,27 @@
|
||||
update_appearance()
|
||||
return TRUE
|
||||
else
|
||||
var/datum/component/aquarium_content/content_component = I.GetComponent(/datum/component/aquarium_content)
|
||||
if(content_component && content_component.is_ready_to_insert(src))
|
||||
if(user.transferItemToLoc(I,src))
|
||||
update_appearance()
|
||||
return TRUE
|
||||
else
|
||||
return ..()
|
||||
var/datum/component/aquarium_content/content_component = item.GetComponent(/datum/component/aquarium_content)
|
||||
if(content_component && content_component.is_ready_to_insert(src) && user.transferItemToLoc(item, src))
|
||||
update_appearance()
|
||||
return TRUE
|
||||
|
||||
if(istype(item, /obj/item/fish_feed))
|
||||
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 ..()
|
||||
|
||||
/obj/structure/aquarium/proc/feed_feedback(datum/source, obj/item/thing, mob/user, params)
|
||||
SIGNAL_HANDLER
|
||||
if(istype(thing, /obj/item/fish_feed))
|
||||
to_chat(user,span_notice("You feed the fish."))
|
||||
return NONE
|
||||
/obj/structure/aquarium/proc/on_attacked(datum/source, mob/attacker, attack_flags)
|
||||
var/list/fishes = get_fishes()
|
||||
//I wish this were an aquarium signal, but the aquarium_content component got in the way.
|
||||
for(var/obj/item/fish/fish as anything in fishes)
|
||||
SEND_SIGNAL(fish, COMSIG_FISH_STIRRED)
|
||||
|
||||
/obj/structure/aquarium/interact(mob/user)
|
||||
if(!broken && user.pulling && isliving(user.pulling))
|
||||
@@ -176,6 +200,7 @@
|
||||
if(do_after(user, 5 SECONDS, target = src))
|
||||
var/alive_fish = 0
|
||||
var/dead_fish = 0
|
||||
var/list/tracked_fish = get_fishes()
|
||||
for(var/obj/item/fish/fish in tracked_fish)
|
||||
if(fish.status == FISH_ALIVE)
|
||||
alive_fish++
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
///Fish feed can
|
||||
/obj/item/fish_feed
|
||||
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_state = "fish_feed"
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
@@ -9,57 +9,85 @@
|
||||
/obj/item/fish_feed/Initialize(mapload)
|
||||
. = ..()
|
||||
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
|
||||
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_state = "fishbox"
|
||||
|
||||
inhand_icon_state = "syringe_kit"
|
||||
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
|
||||
storage_type = /datum/storage/fish_case
|
||||
|
||||
/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
|
||||
. = ..()
|
||||
return ..()
|
||||
|
||||
create_storage(max_slots = 1)
|
||||
atom_storage.can_hold_trait = TRAIT_FISH_CASE_COMPATIBILE
|
||||
atom_storage.can_hold_description = "fish and aquarium equipment"
|
||||
/obj/item/storage/fish_case/PopulateContents()
|
||||
var/fish_type = get_fish_type()
|
||||
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/random/PopulateContents()
|
||||
. = ..()
|
||||
var/fish_type = select_fish_type()
|
||||
new fish_type(src)
|
||||
/obj/item/storage/fish_case/proc/get_fish_type()
|
||||
return
|
||||
|
||||
/obj/item/storage/fish_case/random/proc/select_fish_type()
|
||||
return random_fish_type()
|
||||
/obj/item/storage/fish_case/random
|
||||
|
||||
/obj/item/storage/fish_case/random/freshwater/select_fish_type()
|
||||
return random_fish_type(required_fluid=AQUARIUM_FLUID_FRESHWATER)
|
||||
var/fluid_type
|
||||
|
||||
/obj/item/storage/fish_case/random/saltwater/select_fish_type()
|
||||
return random_fish_type(required_fluid=AQUARIUM_FLUID_SALTWATER)
|
||||
/obj/item/storage/fish_case/random/get_fish_type()
|
||||
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
|
||||
name = "ominous fish case"
|
||||
|
||||
/obj/item/storage/fish_case/syndicate/PopulateContents()
|
||||
. = ..()
|
||||
var/fish_type = pick(/obj/item/fish/donkfish, /obj/item/fish/emulsijack)
|
||||
new fish_type(src)
|
||||
/obj/item/storage/fish_case/syndicate/get_fish_type()
|
||||
return pick(/obj/item/fish/donkfish, /obj/item/fish/emulsijack)
|
||||
|
||||
/obj/item/storage/fish_case/tiziran
|
||||
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)
|
||||
new fish_type(src)
|
||||
for(var/obj/item/fish/fish as anything in contents)
|
||||
fish.set_status(FISH_DEAD)
|
||||
|
||||
/obj/item/aquarium_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."))
|
||||
|
||||
|
||||
/obj/item/aquarium_prop
|
||||
name = "generic aquarium prop"
|
||||
desc = "very boring"
|
||||
|
||||
241
code/modules/fishing/aquarium/fish_analyzer.dm
Normal 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)
|
||||
@@ -4,8 +4,16 @@
|
||||
desc = "very bland"
|
||||
icon = 'icons/obj/aquarium.dmi'
|
||||
icon_state = "bugfish"
|
||||
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
lefthand_file = 'icons/mob/inhands/fish_lefthand.dmi'
|
||||
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
|
||||
var/sprite_width = 3
|
||||
@@ -17,6 +25,11 @@
|
||||
/// Original height of aquarium visual icon - used to calculate scaledown factor
|
||||
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
|
||||
var/dedicated_in_aquarium_icon_state
|
||||
|
||||
@@ -31,7 +44,7 @@
|
||||
var/required_temperature_max = MAX_AQUARIUM_TEMP
|
||||
|
||||
/// 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
|
||||
var/feeding_frequency = 5 MINUTES
|
||||
/// Time of last feedeing
|
||||
@@ -39,9 +52,15 @@
|
||||
|
||||
/// Fish status
|
||||
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.
|
||||
var/health = 100
|
||||
/// The message shown when the fish dies.
|
||||
var/death_text = "%SRC dies."
|
||||
|
||||
/// Should this fish type show in fish catalog
|
||||
var/show_in_catalog = TRUE
|
||||
@@ -52,13 +71,21 @@
|
||||
|
||||
/// Fish autogenerated from this behaviour will be processable into this
|
||||
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.
|
||||
var/stable_population = 1
|
||||
/// Last time new fish was created
|
||||
var/last_breeding
|
||||
/// The time limit before new fish can be created
|
||||
var/breeding_wait
|
||||
/// How long it takes to produce new fish
|
||||
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
|
||||
|
||||
@@ -66,8 +93,11 @@
|
||||
|
||||
// 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
|
||||
var/fish_ai_type = FISH_AI_DUMB
|
||||
@@ -87,32 +117,44 @@
|
||||
*/
|
||||
var/list/disliked_bait = list()
|
||||
|
||||
/// Size in centimeters
|
||||
/// Size in centimeters. Item size class scales with it.
|
||||
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
|
||||
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
|
||||
/// Average weight for this fish type in grams
|
||||
var/average_weight = 1000
|
||||
|
||||
|
||||
|
||||
/obj/item/fish/Initialize(mapload)
|
||||
/obj/item/fish/Initialize(mapload, apply_qualities = TRUE)
|
||||
. = ..()
|
||||
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))
|
||||
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)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
size = average_size
|
||||
weight = average_weight
|
||||
//stops new fish from being able to reproduce right away.
|
||||
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)
|
||||
. = ..()
|
||||
@@ -120,18 +162,111 @@
|
||||
. += span_notice("It's [size] cm long.")
|
||||
. += span_notice("It weighs [weight] g.")
|
||||
|
||||
/obj/item/fish/proc/randomize_weight_and_size(modifier = 0)
|
||||
var/size_deviation = 0.2 * average_size
|
||||
var/size_mod = modifier * average_size
|
||||
size = max(1,gaussian(average_size + size_mod, size_deviation))
|
||||
///Randomizes weight and 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_deviation = 0.2 * avg_size
|
||||
var/new_size = round(max(1,gaussian(avg_size, size_deviation)), 1)
|
||||
|
||||
var/weight_deviation = 0.2 * average_weight
|
||||
var/weight_mod = modifier * average_weight
|
||||
weight = max(1,gaussian(average_weight + weight_mod, weight_deviation))
|
||||
var/weight_deviation = 0.2 * avg_weight
|
||||
var/new_weight = round(max(1,gaussian(avg_weight, weight_deviation)), 1)
|
||||
|
||||
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)
|
||||
. = ..()
|
||||
check_environment_after_movement()
|
||||
check_environment()
|
||||
|
||||
/obj/item/fish/proc/enter_stasis()
|
||||
in_stasis = TRUE
|
||||
@@ -144,49 +279,35 @@
|
||||
if(status != FISH_DEAD)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/fish/proc/on_aquarium_insertion(obj/structure/aquarium)
|
||||
if(isnull(last_feeding)) //Fish start fed.
|
||||
last_feeding = world.time
|
||||
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)
|
||||
///Feed the fishes with the contents of the fish feed
|
||||
/obj/item/fish/proc/feed(datum/reagents/fed_reagents)
|
||||
if(status != FISH_ALIVE)
|
||||
return
|
||||
UnregisterSignal(source,list(COMSIG_ATOM_EXITED,COMSIG_ATOM_ATTACKBY))
|
||||
|
||||
/// Our aquarium is hit with stuff
|
||||
/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))
|
||||
var/fed_reagent_type
|
||||
if(fed_reagents.remove_reagent(food, 0.1))
|
||||
fed_reagent_type = food
|
||||
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
|
||||
return
|
||||
// Apply/remove stasis as needed
|
||||
if(loc && HAS_TRAIT(loc, TRAIT_FISH_SAFE_STORAGE))
|
||||
enter_stasis()
|
||||
else if(in_stasis)
|
||||
exit_stasis()
|
||||
if(stasis_check)
|
||||
// Apply/remove stasis as needed
|
||||
if(loc && HAS_TRAIT(loc, TRAIT_FISH_SAFE_STORAGE))
|
||||
enter_stasis()
|
||||
else if(in_stasis)
|
||||
exit_stasis()
|
||||
|
||||
if(!do_flop_animation)
|
||||
return
|
||||
|
||||
// Do additional stuff
|
||||
var/in_aquarium = istype(loc,/obj/structure/aquarium)
|
||||
if(in_aquarium)
|
||||
on_aquarium_insertion(loc)
|
||||
|
||||
var/in_aquarium = isaquarium(loc)
|
||||
// Start flopping if outside of fish container
|
||||
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())
|
||||
try_to_reproduce()
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_FISH_LIFE, seconds_per_tick)
|
||||
|
||||
/obj/item/fish/proc/set_status(new_status)
|
||||
if(status == new_status)
|
||||
return
|
||||
switch(new_status)
|
||||
if(FISH_ALIVE)
|
||||
status = FISH_ALIVE
|
||||
health = initial(health) // since the fishe has been revived
|
||||
start_flopping()
|
||||
last_feeding = world.time //reset hunger
|
||||
check_environment(FALSE)
|
||||
START_PROCESSING(SSobj, src)
|
||||
if(FISH_DEAD)
|
||||
status = FISH_DEAD
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
stop_flopping()
|
||||
var/message = span_notice("\The [name] dies.")
|
||||
if(istype(loc,/obj/structure/aquarium))
|
||||
var/message = span_notice(replacetext(death_text, "%SRC", "[src]"))
|
||||
if(isaquarium(loc))
|
||||
loc.visible_message(message)
|
||||
else
|
||||
visible_message(message)
|
||||
update_appearance()
|
||||
SEND_SIGNAL(src, COMSIG_FISH_STATUS_CHANGED)
|
||||
|
||||
/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.
|
||||
/obj/item/fish/proc/proper_environment()
|
||||
var/obj/structure/aquarium/aquarium = loc
|
||||
if(!istype(aquarium))
|
||||
return FALSE
|
||||
if(istype(aquarium))
|
||||
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(aquarium.fluid_type != required_fluid_type)
|
||||
return FALSE
|
||||
else
|
||||
if(aquarium.fluid_type != AQUARIUM_FLUID_SALTWATER && aquarium.fluid_type != AQUARIUM_FLUID_FRESHWATER)
|
||||
return FALSE
|
||||
if(aquarium.fluid_temp < required_temperature_min || aquarium.fluid_temp > required_temperature_max)
|
||||
if(required_fluid_type != AQUARIUM_FLUID_AIR && !HAS_TRAIT(src, TRAIT_FISH_AMPHIBIOUS))
|
||||
return FALSE
|
||||
var/datum/gas_mixture/mixture = loc.return_air()
|
||||
if(!mixture)
|
||||
return FALSE
|
||||
var/static/list/gases_to_check = list(
|
||||
/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 TRUE
|
||||
|
||||
@@ -272,37 +414,109 @@
|
||||
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
|
||||
if(!istype(aquarium))
|
||||
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()
|
||||
var/obj/structure/aquarium/aquarium = loc
|
||||
if(!istype(aquarium))
|
||||
return
|
||||
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
|
||||
return FALSE
|
||||
|
||||
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
|
||||
break
|
||||
if(second_fish)
|
||||
new type(loc) //could use child_type var
|
||||
last_breeding = world.time
|
||||
second_fish.last_breeding = world.time
|
||||
#undef AQUARIUM_MAX_BREEDING_POPULATION
|
||||
|
||||
/**
|
||||
* Fishes with this trait cannot mate, but could still reproduce asexually, so don't early return.
|
||||
* Also mating takes priority over that.
|
||||
*/
|
||||
if(!HAS_TRAIT(src, TRAIT_FISH_NO_MATING))
|
||||
var/list/available_fishes = list()
|
||||
var/types_to_mate_with = aquarium.tracked_fish_by_type
|
||||
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_FLOPS 2
|
||||
@@ -345,9 +559,10 @@
|
||||
|
||||
/// Starts flopping animation
|
||||
/obj/item/fish/proc/start_flopping()
|
||||
if(!flopping) //Requires update_transform/animate_wrappers to be less restrictive.
|
||||
flopping = TRUE
|
||||
flop_animation(src)
|
||||
if(flopping) //Requires update_transform/animate_wrappers to be less restrictive.
|
||||
return
|
||||
flopping = TRUE
|
||||
flop_animation(src)
|
||||
|
||||
/// Stops flopping animation
|
||||
/obj/item/fish/proc/stop_flopping()
|
||||
@@ -374,11 +589,20 @@
|
||||
var/chance_table = list()
|
||||
for(var/_fish_type in subtypesof(/obj/item/fish))
|
||||
var/obj/item/fish/fish = _fish_type
|
||||
if(required_fluid && initial(fish.required_fluid_type) != required_fluid)
|
||||
continue
|
||||
if(required_fluid)
|
||||
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)
|
||||
chance_table[fish] = initial(fish.random_case_rarity)
|
||||
probability_table[argkey] = chance_table
|
||||
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
|
||||
|
||||
@@ -40,15 +40,7 @@
|
||||
if (prob(default_contents_chance))
|
||||
create_default_object()
|
||||
return
|
||||
|
||||
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)
|
||||
RegisterSignal(src, COMSIG_ATOM_FISHING_REWARD, PROC_REF(find_chasm_contents))
|
||||
|
||||
/// Returns the chosen detritus from the given list of things to choose from
|
||||
/obj/item/chasm_detritus/proc/determine_detritus(list/chasm_stuff)
|
||||
@@ -60,19 +52,24 @@
|
||||
new contents_type(get_turf(src))
|
||||
qdel(src)
|
||||
|
||||
/// Returns a list of every object which is currently inside of a chasm.
|
||||
/obj/item/chasm_detritus/proc/find_chasm_contents()
|
||||
var/list/chasm_contents = list()
|
||||
if (!GLOB.chasm_storage.len)
|
||||
return chasm_contents
|
||||
/// Returns an objected which is currently inside of a nearby chasm.
|
||||
/obj/item/chasm_detritus/proc/find_chasm_contents(datum/source, turf/fishing_spot)
|
||||
SIGNAL_HANDLER
|
||||
var/list/chasm_contents = get_chasm_contents(fishing_spot)
|
||||
|
||||
var/list/chasm_storage_resolved = recursive_list_resolve(GLOB.chasm_storage)
|
||||
for (var/obj/storage as anything in chasm_storage_resolved)
|
||||
if (length(chasm_contents))
|
||||
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)
|
||||
chasm_contents += thing
|
||||
|
||||
return chasm_contents
|
||||
|
||||
. += thing
|
||||
|
||||
/// Variant of the chasm detritus that allows for an easier time at fishing out
|
||||
/// bodies, and sometimes less desireable monsters too.
|
||||
@@ -81,21 +78,27 @@
|
||||
/// contained in the `GLOB.chasm_storage` global list in `find_chasm_contents()`.
|
||||
var/chasm_storage_restricted_type = /obj
|
||||
|
||||
|
||||
/obj/item/chasm_detritus/restricted/find_chasm_contents()
|
||||
var/list/chasm_contents = list()
|
||||
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)
|
||||
/obj/item/chasm_detritus/restricted/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)
|
||||
if(!istype(thing, chasm_storage_restricted_type))
|
||||
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
|
||||
/// The first sentient body found in the list of contents is returned, otherwise
|
||||
@@ -106,17 +109,6 @@
|
||||
return fallen_mob
|
||||
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 BODIES_ONLY
|
||||
#undef NO_CORPSES
|
||||
|
||||
113
code/modules/fishing/fish/fish_evolution.dm
Normal 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
|
||||
338
code/modules/fishing/fish/fish_traits.dm
Normal 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
|
||||
@@ -10,6 +10,8 @@
|
||||
average_size = 30
|
||||
average_weight = 500
|
||||
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
|
||||
name = "angelfish"
|
||||
@@ -21,6 +23,9 @@
|
||||
average_size = 30
|
||||
average_weight = 500
|
||||
stable_population = 3
|
||||
fish_traits = list(/datum/fish_trait/aggressive)
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+22
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+30
|
||||
|
||||
/obj/item/fish/guppy
|
||||
name = "guppy"
|
||||
@@ -33,6 +38,8 @@
|
||||
average_size = 30
|
||||
average_weight = 500
|
||||
stable_population = 6
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+20
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+28
|
||||
|
||||
/obj/item/fish/plasmatetra
|
||||
name = "plasma tetra"
|
||||
@@ -43,6 +50,8 @@
|
||||
average_size = 30
|
||||
average_weight = 500
|
||||
stable_population = 3
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+20
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+28
|
||||
|
||||
/obj/item/fish/catfish
|
||||
name = "cory catfish"
|
||||
@@ -59,6 +68,8 @@
|
||||
"Value" = JUNKFOOD
|
||||
)
|
||||
)
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+12
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+30
|
||||
|
||||
// Saltwater fish below
|
||||
|
||||
@@ -73,8 +84,23 @@
|
||||
average_size = 30
|
||||
average_weight = 500
|
||||
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
|
||||
name = "cardinalfish"
|
||||
@@ -85,7 +111,9 @@
|
||||
average_size = 30
|
||||
average_weight = 500
|
||||
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
|
||||
name = "green chromis"
|
||||
@@ -97,6 +125,8 @@
|
||||
average_size = 30
|
||||
average_weight = 500
|
||||
stable_population = 5
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+23
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+28
|
||||
|
||||
fishing_difficulty_modifier = 5 // Bit harder
|
||||
|
||||
@@ -112,10 +142,12 @@
|
||||
stable_population = 3
|
||||
disliked_bait = list(/obj/item/food/bait/worm, /obj/item/food/bait/doughball)
|
||||
fish_ai_type = FISH_AI_ZIPPY
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+23
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+28
|
||||
|
||||
/obj/item/fish/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"
|
||||
required_fluid_type = AQUARIUM_FLUID_SALTWATER
|
||||
sprite_width = 8
|
||||
@@ -123,8 +155,10 @@
|
||||
average_size = 60
|
||||
average_weight = 1000
|
||||
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
|
||||
name = "lanternfish"
|
||||
@@ -139,8 +173,9 @@
|
||||
average_size = 100
|
||||
average_weight = 1500
|
||||
stable_population = 3
|
||||
|
||||
fishing_traits = list(/datum/fishing_trait/nocturnal)
|
||||
fish_traits = list(/datum/fish_trait/nocturnal)
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+2 //My source is that the water at a depth 6600 feet is pretty darn cold.
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+18
|
||||
|
||||
//Tiziran Fish
|
||||
/obj/item/fish/dwarf_moonfish
|
||||
@@ -152,6 +187,8 @@
|
||||
fillet_type = /obj/item/food/fishmeat/moonfish
|
||||
average_size = 100
|
||||
average_weight = 2000
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+20
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+30
|
||||
|
||||
/obj/item/fish/gunner_jellyfish
|
||||
name = "gunner jellyfish"
|
||||
@@ -160,6 +197,8 @@
|
||||
required_fluid_type = AQUARIUM_FLUID_SALTWATER
|
||||
stable_population = 4
|
||||
fillet_type = /obj/item/food/fishmeat/gunner_jellyfish
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+24
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+32
|
||||
|
||||
/obj/item/fish/needlefish
|
||||
name = "needlefish"
|
||||
@@ -168,9 +207,11 @@
|
||||
required_fluid_type = AQUARIUM_FLUID_SALTWATER
|
||||
stable_population = 12
|
||||
fillet_type = null
|
||||
average_size = 30
|
||||
average_size = 20
|
||||
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
|
||||
name = "armorfish"
|
||||
@@ -180,6 +221,8 @@
|
||||
stable_population = 10
|
||||
fillet_type = /obj/item/food/fishmeat/armorfish
|
||||
fish_ai_type = FISH_AI_SLOW
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+10
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+32
|
||||
|
||||
//Chasm fish
|
||||
/obj/item/fish/chasm_crab
|
||||
@@ -195,6 +238,20 @@
|
||||
feeding_frequency = 15 MINUTES
|
||||
random_case_rarity = FISH_RARITY_RARE
|
||||
fillet_type = /obj/item/food/meat/slab/rawcrab
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+9
|
||||
required_temperature_max = 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
|
||||
name = "box full of fish"
|
||||
@@ -211,28 +268,20 @@
|
||||
required_fluid_type = AQUARIUM_FLUID_FRESHWATER
|
||||
stable_population = 4
|
||||
fillet_type = /obj/item/food/fishmeat/donkfish
|
||||
fish_traits = list(/datum/fish_trait/yucky)
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+15
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+28
|
||||
|
||||
/obj/item/fish/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"
|
||||
random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS
|
||||
required_fluid_type = AQUARIUM_FLUID_ANADROMOUS
|
||||
stable_population = 3
|
||||
|
||||
/obj/item/fish/emulsijack/process(seconds_per_tick)
|
||||
var/emulsified = FALSE
|
||||
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!
|
||||
..()
|
||||
fish_traits = list(/datum/fish_trait/emulsijack)
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+5
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+40
|
||||
|
||||
/obj/item/fish/ratfish
|
||||
name = "ratfish"
|
||||
@@ -242,6 +291,9 @@
|
||||
required_fluid_type = AQUARIUM_FLUID_FRESHWATER
|
||||
stable_population = 10 //set by New, but this is the default config value
|
||||
fillet_type = /obj/item/food/meat/slab/human/mutant/zombie //eww...
|
||||
fish_traits = list(/datum/fish_trait/necrophage)
|
||||
required_temperature_min = MIN_AQUARIUM_TEMP+15
|
||||
required_temperature_max = MIN_AQUARIUM_TEMP+35
|
||||
|
||||
fish_ai_type = FISH_AI_ZIPPY
|
||||
favorite_bait = list(
|
||||
@@ -255,3 +307,186 @@
|
||||
. = ..()
|
||||
//stable pop reflects the config for how many mice migrate. powerful...
|
||||
stable_population = CONFIG_GET(number/mice_roundstart)
|
||||
|
||||
/obj/item/fish/sludgefish
|
||||
name = "sludgefish"
|
||||
desc = "A misshapen, fragile, loosely fish-like living goop, the only thing that'd ever thrive in the acidic and claustrophobic cavities of the station's organic waste disposal system."
|
||||
icon_state = "sludgefish"
|
||||
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
|
||||
|
||||
@@ -91,10 +91,10 @@
|
||||
.["disliked_bait"] = english_list(bait_list, nothing_text = "None")
|
||||
// Fish traits description
|
||||
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)
|
||||
var/datum/fishing_trait/trait = fish_trait
|
||||
trait_descriptions += initial(trait.catalog_description)
|
||||
var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait]
|
||||
trait_descriptions += trait.catalog_description
|
||||
if(!length(trait_descriptions))
|
||||
trait_descriptions += "This fish exhibits no special behavior."
|
||||
.["traits"] = trait_descriptions
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
|
||||
/obj/item/fishing_hook/bone
|
||||
name = "bone hook"
|
||||
desc = "a simple hook carved from sharpened bone"
|
||||
desc = "A simple hook carved from sharpened bone"
|
||||
icon_state = "hook_bone"
|
||||
|
||||
/datum/crafting_recipe/bone_hook
|
||||
@@ -160,6 +160,24 @@
|
||||
time = 2 SECONDS
|
||||
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
|
||||
name = "fishing toolbox"
|
||||
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/cloaked(src)
|
||||
|
||||
|
||||
#undef MAGNET_HOOK_BONUS_MULTIPLIER
|
||||
#undef RESCUE_HOOK_FISH_MULTIPLIER
|
||||
|
||||
@@ -33,34 +33,51 @@
|
||||
/// Background image from /datum/asset/simple/fishing_minigame
|
||||
var/background = "default"
|
||||
|
||||
/// Max distance we can move from the spot
|
||||
var/max_distance = 5
|
||||
|
||||
/// Fishing line visual
|
||||
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.reward_path = reward_path
|
||||
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
|
||||
if(ispath(reward_path,/obj/item/fish))
|
||||
var/obj/item/fish/fish = reward_path
|
||||
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_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)
|
||||
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)
|
||||
/// Enable special parameters
|
||||
if(rod.line)
|
||||
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.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, ...)
|
||||
if(!completed)
|
||||
@@ -69,13 +86,29 @@
|
||||
QDEL_NULL(fishing_line)
|
||||
if(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)
|
||||
/// Create fishing line visuals
|
||||
fishing_line = used_rod.create_fishing_line(lure, target_py = 5)
|
||||
// If fishing line breaks los / rod gets dropped / deleted
|
||||
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))
|
||||
user.add_mood_event("fishing", /datum/mood_event/fishing)
|
||||
RegisterSignal(user, COMSIG_MOB_CLICKON, PROC_REF(handle_click))
|
||||
@@ -83,40 +116,53 @@
|
||||
to_chat(user, span_notice("You start fishing..."))
|
||||
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
|
||||
lure.balloon_alert(user, "miss!")
|
||||
send_alert("miss!")
|
||||
start_baiting_phase()
|
||||
else if(phase == BITING_PHASE)
|
||||
start_minigame_phase()
|
||||
INVOKE_ASYNC(src, PROC_REF(start_minigame_phase))
|
||||
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
|
||||
/datum/fishing_challenge/proc/interrupt()
|
||||
/datum/fishing_challenge/proc/interrupt(datum/source, balloon_alert = TRUE)
|
||||
SIGNAL_HANDLER
|
||||
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)
|
||||
|
||||
/datum/fishing_challenge/proc/complete(win = FALSE, perfect_win = FALSE)
|
||||
deltimer(next_phase_timer)
|
||||
completed = TRUE
|
||||
if(user)
|
||||
UnregisterSignal(user, list(COMSIG_MOB_CLICKON, COMSIG_MOVABLE_MOVED))
|
||||
REMOVE_TRAIT(user, TRAIT_GONE_FISHING, REF(src))
|
||||
if(used_rod)
|
||||
UnregisterSignal(used_rod, COMSIG_ITEM_DROPPED)
|
||||
if(phase == MINIGAME_PHASE)
|
||||
used_rod.consume_bait()
|
||||
if(start_time)
|
||||
var/seconds_spent = (world.time - start_time)/10
|
||||
if(!(FISHING_MINIGAME_RULE_NO_EXP in special_effects))
|
||||
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(reward_path != FISHING_DUD)
|
||||
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)
|
||||
qdel(src)
|
||||
|
||||
@@ -134,19 +180,41 @@
|
||||
phase = BITING_PHASE
|
||||
// Trashing animation
|
||||
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(pixel_y = -3, time = 5, flags = ANIMATION_RELATIVE)
|
||||
// Setup next phase
|
||||
var/wait_time = rand(3 SECONDS, 6 SECONDS)
|
||||
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()
|
||||
phase = MINIGAME_PHASE
|
||||
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
|
||||
experience_multiplier += difficulty * FISHING_SKILL_DIFFIULTY_EXP_MULT
|
||||
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)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
@@ -162,11 +230,12 @@
|
||||
/datum/fishing_challenge/ui_close(mob/user)
|
||||
. = ..()
|
||||
if(!completed)
|
||||
send_alert("stopped fishing")
|
||||
complete(FALSE)
|
||||
|
||||
/datum/fishing_challenge/ui_static_data(mob/user)
|
||||
. = ..()
|
||||
.["difficulty"] = max(1,min(difficulty,100))
|
||||
.["difficulty"] = clamp(difficulty, 1, 100)
|
||||
.["fish_ai"] = fish_ai
|
||||
.["special_effects"] = special_effects
|
||||
.["background_image"] = background
|
||||
@@ -176,7 +245,7 @@
|
||||
|
||||
/datum/fishing_challenge/ui_status(mob/user, datum/ui_state/state)
|
||||
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_is_abled(user, lure),
|
||||
)
|
||||
@@ -193,6 +262,7 @@
|
||||
if("win")
|
||||
complete(win = TRUE, perfect_win = params["perfect"])
|
||||
if("lose")
|
||||
send_alert("it got away")
|
||||
complete(win = FALSE)
|
||||
|
||||
/// The visual that appears over the fishing spot
|
||||
@@ -200,6 +270,15 @@
|
||||
icon = 'icons/obj/fishing.dmi'
|
||||
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 BITING_PHASE
|
||||
#undef MINIGAME_PHASE
|
||||
|
||||
@@ -38,12 +38,17 @@
|
||||
/// List of fishing line beams
|
||||
var/list/fishing_lines = list()
|
||||
|
||||
/// The default color for the reel overlay if no line is equipped.
|
||||
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)
|
||||
. = ..()
|
||||
register_context()
|
||||
register_item_context()
|
||||
update_appearance()
|
||||
|
||||
/obj/item/fishing_rod/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
||||
if(src == held_item)
|
||||
@@ -96,10 +101,7 @@
|
||||
* * 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)
|
||||
if(!hook)
|
||||
return null
|
||||
|
||||
return hook.reason_we_cant_fish(target_fish_source)
|
||||
return hook?.reason_we_cant_fish(target_fish_source)
|
||||
|
||||
|
||||
/obj/item/fishing_rod/proc/consume_bait()
|
||||
@@ -201,23 +203,27 @@
|
||||
reel(user)
|
||||
return .
|
||||
|
||||
/// If the line to whatever that is is clear and we're not already busy, try fishing in it
|
||||
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()
|
||||
cast_line(target, user, proximity_flag)
|
||||
|
||||
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
|
||||
/obj/item/fishing_rod/proc/hook_hit(atom/atom_hit_by_hook_projectile)
|
||||
var/mob/user = loc
|
||||
@@ -237,11 +243,15 @@
|
||||
|
||||
/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
|
||||
/// Line part by the rod, always visible
|
||||
var/mutable_appearance/reel_overlay = mutable_appearance(icon, "reel_overlay")
|
||||
reel_overlay.color = line_color;
|
||||
. += reel_overlay
|
||||
var/mutable_appearance/reel_appearance = mutable_appearance(icon, reel_overlay)
|
||||
reel_appearance.color = line_color;
|
||||
. += reel_appearance
|
||||
|
||||
// Line & hook is also visible when only bait is equipped but it uses default appearances then
|
||||
if(hook || bait)
|
||||
@@ -260,6 +270,10 @@
|
||||
|
||||
/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/mutable_appearance/reel_overlay = mutable_appearance(icon_file, "reel_overlay")
|
||||
reel_overlay.appearance_flags |= RESET_COLOR
|
||||
@@ -401,6 +415,8 @@
|
||||
name = "bone fishing rod"
|
||||
desc = "A humble rod, made with whatever happened to be on hand."
|
||||
icon_state = "fishing_rod_bone"
|
||||
reel_overlay = "reel_bone"
|
||||
default_line_color = "red"
|
||||
|
||||
/datum/crafting_recipe/bone_rod
|
||||
name = "Bone Fishing Rod"
|
||||
@@ -411,19 +427,84 @@
|
||||
/obj/item/stack/sheet/bone = 2)
|
||||
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"
|
||||
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
|
||||
ui_description = "This rods makes fishing easy even for an absolute beginner."
|
||||
icon_state = "fishing_rod_master"
|
||||
|
||||
reel_overlay = "reel_master"
|
||||
active_force = 13 //It's that sturdy
|
||||
|
||||
/obj/item/fishing_rod/tech
|
||||
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. "
|
||||
ui_description = "This rod has an infinite supply of synthetic bait."
|
||||
icon_state = "fishing_rod_science"
|
||||
reel_overlay = "reel_science"
|
||||
|
||||
/obj/item/fishing_rod/tech/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
@@ -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
|
||||
@@ -11,9 +11,15 @@ GLOBAL_LIST_INIT(preset_fish_sources,init_fishing_configurations())
|
||||
var/datum/fish_source/lavaland/lava_preset = new
|
||||
.[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
|
||||
.[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
|
||||
/datum/fish_source
|
||||
/// 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
|
||||
/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
|
||||
|
||||
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)
|
||||
if(is_matching_bait(bait, bait_identifer))
|
||||
. += FAV_BAIT_DIFFICULTY_MOD
|
||||
break
|
||||
//Disliked bait makes it harder
|
||||
var/list/disliked_bait = fish_list_properties[caught_fish][NAMEOF(caught_fish, disliked_bait)]
|
||||
for(var/bait_identifer in disliked_bait)
|
||||
if(is_matching_bait(bait, bait_identifer))
|
||||
. += 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
|
||||
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/multiplicative_mod = 1
|
||||
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)
|
||||
additive_mod += mod[ADDITIVE_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)
|
||||
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
|
||||
/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(fish_counts[reward_path] > 0)
|
||||
fish_counts[reward_path] -= 1
|
||||
else
|
||||
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))
|
||||
reward = new reward_path(get_turf(fisherman))
|
||||
if(ispath(reward_path,/obj/item))
|
||||
var/obj/item/reward = new reward_path(get_turf(fisherman))
|
||||
if(ispath(reward_path,/obj/item/fish))
|
||||
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
|
||||
//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]!")
|
||||
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!")
|
||||
new reward_path(get_turf(fisherman))
|
||||
else if (reward_path == FISHING_DUD)
|
||||
//baloon alert instead
|
||||
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
|
||||
GLOBAL_LIST(fishing_property_cache)
|
||||
@@ -113,11 +148,11 @@ GLOBAL_LIST(fishing_property_cache)
|
||||
if(GLOB.fishing_property_cache == null)
|
||||
var/list/fish_property_table = list()
|
||||
for(var/fish_type in subtypesof(/obj/item/fish))
|
||||
var/obj/item/fish/fish = new fish_type(null)
|
||||
var/obj/item/fish/fish = new fish_type(null, FALSE)
|
||||
fish_property_table[fish_type] = list()
|
||||
fish_property_table[fish_type][NAMEOF(fish, favorite_bait)] = fish.favorite_bait.Copy()
|
||||
fish_property_table[fish_type][NAMEOF(fish, disliked_bait)] = fish.disliked_bait.Copy()
|
||||
fish_property_table[fish_type][NAMEOF(fish, fishing_traits)] = fish.fishing_traits.Copy()
|
||||
fish_property_table[fish_type][NAMEOF(fish, fish_traits)] = fish.fish_traits.Copy()
|
||||
QDEL_NULL(fish)
|
||||
GLOB.fishing_property_cache = fish_property_table
|
||||
return GLOB.fishing_property_cache
|
||||
@@ -132,6 +167,8 @@ GLOBAL_LIST(fishing_property_cache)
|
||||
if("Foodtype")
|
||||
var/obj/item/food/food_bait = bait
|
||||
return istype(food_bait) && food_bait.foodtypes & special_identifier["Value"]
|
||||
if("Reagent")
|
||||
return bait.reagents?.has_reagent(special_identifier["Value"], special_identifier["Amount"], check_subtypes = TRUE)
|
||||
else
|
||||
CRASH("Unknown bait identifier in fish favourite/disliked list")
|
||||
else
|
||||
@@ -173,20 +210,18 @@ GLOBAL_LIST(fishing_property_cache)
|
||||
for(var/bait_identifer in fav_bait)
|
||||
if(is_matching_bait(bait, bait_identifer))
|
||||
final_table[result] *= 2
|
||||
break // could compound possibly
|
||||
//Bait matching dislikes
|
||||
var/list/disliked_bait = fish_list_properties[result][NAMEOF(caught_fish, disliked_bait)]
|
||||
for(var/bait_identifer in disliked_bait)
|
||||
if(is_matching_bait(bait, bait_identifer))
|
||||
final_table[result] *= 0.5
|
||||
break // same question as above
|
||||
|
||||
// Apply fishing trait modifiers
|
||||
var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fishing_traits)]
|
||||
// Apply fish trait modifiers
|
||||
var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fish_traits)]
|
||||
var/additive_mod = 0
|
||||
var/multiplicative_mod = 1
|
||||
for(var/fish_trait in fish_traits)
|
||||
var/datum/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)
|
||||
additive_mod += mod[ADDITIVE_FISHING_MOD]
|
||||
multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD]
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
/obj/item/fish/greenchromis = 15,
|
||||
/obj/item/fish/lanternfish = 5
|
||||
)
|
||||
fish_counts = list(
|
||||
/obj/item/fish/clownfish/lube = 2,
|
||||
)
|
||||
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5
|
||||
|
||||
/datum/fish_source/ocean/beach
|
||||
@@ -32,7 +35,6 @@
|
||||
|
||||
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5
|
||||
|
||||
|
||||
/datum/fish_source/chasm/roll_reward(obj/item/fishing_rod/rod, mob/fisherman)
|
||||
var/rolled_reward = ..()
|
||||
|
||||
@@ -41,7 +43,6 @@
|
||||
|
||||
return rod.hook.chasm_detritus_type
|
||||
|
||||
|
||||
/datum/fish_source/lavaland
|
||||
catalog_description = "Lava vents"
|
||||
background = "fishing_background_lavaland"
|
||||
@@ -65,11 +66,86 @@
|
||||
if(!rod.line || !(rod.line.fishing_line_traits & FISHING_LINE_REINFORCED))
|
||||
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
|
||||
catalog_description = "moisture trap basins"
|
||||
catalog_description = "Moisture trap basins"
|
||||
fish_table = list(
|
||||
FISHING_DUD = 20,
|
||||
/obj/item/fish/ratfish = 10
|
||||
/obj/item/fish/ratfish = 10,
|
||||
/obj/item/fish/slimefish = 4
|
||||
)
|
||||
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
|
||||
|
||||
@@ -269,42 +269,54 @@ GLOBAL_LIST_INIT(typecache_holodeck_linked_floorcheck_ok, typecacheof(list(/turf
|
||||
if(QDELETED(holo_atom))
|
||||
spawned -= holo_atom
|
||||
continue
|
||||
|
||||
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()
|
||||
|
||||
finalize_spawned(holo_atom)
|
||||
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
|
||||
/obj/machinery/computer/holodeck/proc/derez(atom/movable/holo_atom, silent = TRUE, forced = FALSE)
|
||||
spawned -= holo_atom
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
icon_state = "water"
|
||||
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
|
||||
gender = PLURAL
|
||||
name = "asteroid sand"
|
||||
|
||||
@@ -90,6 +90,9 @@
|
||||
.=..()
|
||||
create_reagents(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)
|
||||
flick("puddle-oil-splash",src)
|
||||
|
||||
@@ -163,15 +163,16 @@
|
||||
I.forceMove(src)
|
||||
held_items[hand_index] = I
|
||||
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)
|
||||
I.pulledby.stop_pulling()
|
||||
if(!I.on_equipped(src, ITEM_SLOT_HANDS))
|
||||
return FALSE
|
||||
update_held_items()
|
||||
I.pixel_x = I.base_pixel_x
|
||||
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
|
||||
|
||||
//Puts the item into the first available left hand if possible and calls all necessary triggers/updates. returns 1 on success.
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
|
||||
/// 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)
|
||||
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)
|
||||
. = ..() //Sets the default return value to what the parent returns.
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
item_module.screen_loc = inv3.screen_loc
|
||||
|
||||
held_items[module_num] = item_module
|
||||
item_module.equipped(src, ITEM_SLOT_HANDS)
|
||||
item_module.mouse_opacity = initial(item_module.mouse_opacity)
|
||||
SET_PLANE_EXPLICIT(item_module, ABOVE_HUD_PLANE, src)
|
||||
item_module.forceMove(src)
|
||||
@@ -78,6 +77,7 @@
|
||||
|
||||
if(storage_was_closed)
|
||||
hud_used.toggle_show_robot_modules()
|
||||
item_module.on_equipped(src, ITEM_SLOT_HANDS)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
return
|
||||
|
||||
//Call back for item being equipped to drone
|
||||
equipping.equipped(src, slot)
|
||||
equipping.on_equipped(src, slot)
|
||||
|
||||
/mob/living/simple_animal/drone/getBackSlot()
|
||||
return ITEM_SLOT_DEX_STORAGE
|
||||
|
||||
@@ -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.forceMove(src)
|
||||
equipping.equipped(src, slot)
|
||||
SET_PLANE_EXPLICIT(equipping, ABOVE_HUD_PLANE, src)
|
||||
equipping.on_equipped(src, slot)
|
||||
|
||||
/mob/living/simple_animal/hostile/guardian/proc/apply_overlay(cache_index)
|
||||
if((. = guardian_overlays[cache_index]))
|
||||
|
||||
@@ -449,20 +449,27 @@
|
||||
* Reagent takes a PATH to a reagent.
|
||||
* Amount checks for having a specific amount of that chemical.
|
||||
* 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
|
||||
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(needs_metabolizing && !holder_reagent.metabolizing)
|
||||
if(check_subtypes)
|
||||
continue
|
||||
return FALSE
|
||||
return holder_reagent
|
||||
else
|
||||
if(round(holder_reagent.volume, CHEMICAL_QUANTISATION_LEVEL) >= amount)
|
||||
if(needs_metabolizing && !holder_reagent.metabolizing)
|
||||
if(check_subtypes)
|
||||
continue
|
||||
return FALSE
|
||||
return holder_reagent
|
||||
else if(!check_subtypes)
|
||||
return FALSE
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
|
||||
@@ -1264,3 +1264,106 @@
|
||||
/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)
|
||||
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
|
||||
|
||||
@@ -979,6 +979,7 @@
|
||||
|
||||
/datum/design/fishing_rod_tech
|
||||
name = "Advanced Fishing Rod"
|
||||
desc = "A fishing rod with an embedded generator dispensing an infinite supply of fishing baits."
|
||||
id = "fishing_rod_tech"
|
||||
build_type = PROTOLATHE | AWAY_LATHE
|
||||
materials = list(/datum/material/uranium =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plastic =SHEET_MATERIAL_AMOUNT)
|
||||
@@ -988,6 +989,30 @@
|
||||
)
|
||||
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///////////////
|
||||
/////////////////////////////////////////
|
||||
|
||||
@@ -2328,7 +2328,9 @@
|
||||
description = "Cutting edge fishing advancements."
|
||||
prereq_ids = list("base")
|
||||
design_ids = list(
|
||||
"fishing_rod_tech"
|
||||
"fishing_rod_tech",
|
||||
"stabilized_hook",
|
||||
"fish_analyzer",
|
||||
)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
hidden = TRUE
|
||||
|
||||
@@ -26,6 +26,18 @@
|
||||
var/initial_inline_css
|
||||
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
|
||||
*
|
||||
@@ -381,11 +393,6 @@
|
||||
if(mouse_event_macro_set)
|
||||
return
|
||||
|
||||
var/list/byondToTguiEventMap = list(
|
||||
"MouseDown" = "byond/mousedown",
|
||||
"MouseUp" = "byond/mouseup"
|
||||
)
|
||||
|
||||
for(var/mouseMacro in byondToTguiEventMap)
|
||||
var/command_template = ".output CONTROL PAYLOAD"
|
||||
var/event_message = TGUI_CREATE_MESSAGE(byondToTguiEventMap[mouseMacro], null)
|
||||
@@ -406,10 +413,6 @@
|
||||
/datum/tgui_window/proc/remove_mouse_macro()
|
||||
if(!mouse_event_macro_set)
|
||||
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)
|
||||
winset(client, null, "[mouseMacro]Window[id]Macro.parent=null")
|
||||
mouse_event_macro_set = FALSE
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
#include "egg_glands.dm"
|
||||
#include "emoting.dm"
|
||||
#include "explosion_action.dm"
|
||||
#include "fish_unit_tests.dm"
|
||||
#include "focus_only_tests.dm"
|
||||
#include "font_awesome_icons.dm"
|
||||
#include "food_edibility_check.dm"
|
||||
|
||||
127
code/modules/unit_tests/fish_unit_tests.dm
Normal 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
|
||||
|
||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
BIN
icons/mob/inhands/fish_lefthand.dmi
Normal file
|
After Width: | Height: | Size: 879 B |
BIN
icons/mob/inhands/fish_righthand.dmi
Normal file
|
After Width: | Height: | Size: 880 B |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 26 KiB |
BIN
icons/obj/aquarium_wide.dmi
Normal file
|
After Width: | Height: | Size: 570 B |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 218 KiB |
@@ -880,7 +880,7 @@
|
||||
#include "code\datums\components\aggro_emote.dm"
|
||||
#include "code\datums\components\ai_retaliate_advanced.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_sound_manager.dm"
|
||||
#include "code\datums\components\areabound.dm"
|
||||
@@ -1283,6 +1283,7 @@
|
||||
#include "code\datums\elements\shatters_when_thrown.dm"
|
||||
#include "code\datums\elements\sideway_movement.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\snail_crawl.dm"
|
||||
#include "code\datums\elements\soft_landing.dm"
|
||||
@@ -1458,6 +1459,7 @@
|
||||
#include "code\datums\screentips\item_context.dm"
|
||||
#include "code\datums\skills\_skill.dm"
|
||||
#include "code\datums\skills\cleaning.dm"
|
||||
#include "code\datums\skills\fishing.dm"
|
||||
#include "code\datums\skills\gaming.dm"
|
||||
#include "code\datums\skills\mining.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\duffel_bag.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\organ_box.dm"
|
||||
#include "code\datums\storage\subtypes\pockets.dm"
|
||||
@@ -3503,11 +3506,13 @@
|
||||
#include "code\modules\fishing\fishing_minigame.dm"
|
||||
#include "code\modules\fishing\fishing_portal_machine.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_kit.dm"
|
||||
#include "code\modules\fishing\aquarium\fish_analyzer.dm"
|
||||
#include "code\modules\fishing\fish\_fish.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\sources\_fish_source.dm"
|
||||
#include "code\modules\fishing\sources\source_types.dm"
|
||||
|
||||
@@ -146,6 +146,14 @@ export const backendMiddleware = (store) => {
|
||||
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) {
|
||||
logger.log(`suspending (${Byond.windowId})`);
|
||||
// Keep sending suspend messages until it succeeds.
|
||||
|
||||
@@ -96,7 +96,7 @@ export const FishCatalog = (props, context) => {
|
||||
<LabeledList.Item label="Average weight">
|
||||
{currentFish.weight} g
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Fishing tips">
|
||||
<LabeledList.Item label="Fishing and Aquarium tips">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Fishing locations">
|
||||
{currentFish.fishing_tips.spots}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { clamp } from 'common/math';
|
||||
import { KEY_CTRL } from 'common/keycodes';
|
||||
import { randomInteger, randomNumber, randomPick, randomProb } from 'common/random';
|
||||
import { useDispatch } from 'common/redux';
|
||||
import { Component } from 'inferno';
|
||||
import { resolveAsset } from '../assets';
|
||||
import { backendSuspendStart, useBackend } from '../backend';
|
||||
import { Icon } from '../components';
|
||||
import { globalEvents } from '../events';
|
||||
import { Icon, KeyListener } from '../components';
|
||||
import { globalEvents, KeyEvent } from '../events';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type Bait = {
|
||||
@@ -26,6 +27,7 @@ type FishAI = 'dumb' | 'zippy' | 'slow';
|
||||
enum ReelingState {
|
||||
Idle,
|
||||
Reeling,
|
||||
ReelingDown,
|
||||
}
|
||||
|
||||
type FishingMinigameProps = {
|
||||
@@ -43,7 +45,13 @@ type FishingMinigameState = {
|
||||
fish: Fish;
|
||||
};
|
||||
|
||||
type SpecialRule = 'weighted' | 'limit_loss' | 'heavy';
|
||||
type SpecialRule =
|
||||
| 'weighted'
|
||||
| 'limit_loss'
|
||||
| 'heavy'
|
||||
| 'bidirectional'
|
||||
| 'no_escape'
|
||||
| 'lubed';
|
||||
|
||||
class FishingMinigame extends Component<
|
||||
FishingMinigameProps,
|
||||
@@ -63,6 +71,9 @@ class FishingMinigame extends Component<
|
||||
longJumpVelocityLimit: number = 200;
|
||||
shortJumpVelocityLimit: number = 400;
|
||||
idleVelocity: number = 0;
|
||||
accel_up_coeff: number = 1;
|
||||
bidirectional: boolean = false;
|
||||
no_escape: boolean = false;
|
||||
|
||||
baseLongJumpChancePerSecond: number = 0.0075;
|
||||
baseShortJumpChancePerSecond: number = 0.255;
|
||||
@@ -82,6 +93,9 @@ class FishingMinigame extends Component<
|
||||
: -6;
|
||||
this.baitBounceCoeff = props.special_rules.includes('weighted') ? 0.1 : 0.6;
|
||||
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) {
|
||||
case 'dumb':
|
||||
@@ -117,6 +131,10 @@ class FishingMinigame extends Component<
|
||||
|
||||
this.handle_mousedown = this.handle_mousedown.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.moveFish = this.moveFish.bind(this);
|
||||
this.moveBait = this.moveBait.bind(this);
|
||||
@@ -130,6 +148,8 @@ class FishingMinigame extends Component<
|
||||
this.animation_id = window.requestAnimationFrame(this.updateAnimation);
|
||||
globalEvents.on('byond/mousedown', this.handle_mousedown);
|
||||
globalEvents.on('byond/mouseup', this.handle_mouseup);
|
||||
globalEvents.on('byond/ctrldown', this.handle_ctrldown);
|
||||
globalEvents.on('byond/ctrlup', this.handle_ctrlup);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -138,6 +158,8 @@ class FishingMinigame extends Component<
|
||||
window.cancelAnimationFrame(this.animation_id);
|
||||
globalEvents.off('byond/mousedown', this.handle_mousedown);
|
||||
globalEvents.off('byond/mouseup', this.handle_mouseup);
|
||||
globalEvents.off('byond/ctrldown', this.handle_ctrldown);
|
||||
globalEvents.off('byond/ctrlup', this.handle_ctrlup);
|
||||
}
|
||||
|
||||
updateAnimation(timestamp: DOMHighResTimeStamp) {
|
||||
@@ -275,7 +297,7 @@ class FishingMinigame extends Component<
|
||||
const { fish, bait } = this.state;
|
||||
|
||||
// Speedup when reeling
|
||||
const acceleration_up = -1500;
|
||||
const acceleration_up = -1500 * this.accel_up_coeff;
|
||||
// Gravity
|
||||
const acceleration_down = 1000;
|
||||
// Velocity is multiplied by this when bouncing off the bottom/top
|
||||
@@ -298,17 +320,31 @@ class FishingMinigame extends Component<
|
||||
// Bottom bound
|
||||
if (newPosition + bait.height > this.area_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 =
|
||||
this.reeling === ReelingState.Reeling
|
||||
? acceleration_up
|
||||
: acceleration_down;
|
||||
const velocity_change = acceleration * seconds;
|
||||
: this.reeling === ReelingState.ReelingDown
|
||||
? -acceleration_up
|
||||
: acceleration_down;
|
||||
// Slowdown both ways when on fish
|
||||
if (this.fishOnBait(fish, bait)) {
|
||||
newVelocity += on_point_coeff * velocity_change;
|
||||
const 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 {
|
||||
newVelocity += velocity_change;
|
||||
}
|
||||
@@ -351,7 +387,7 @@ class FishingMinigame extends Component<
|
||||
|
||||
const dispatch = useDispatch(this.context);
|
||||
|
||||
if (newCompletion <= 0) {
|
||||
if (newCompletion <= 0 && !this.no_escape) {
|
||||
this.props.lose();
|
||||
dispatch(backendSuspendStart());
|
||||
} else if (newCompletion >= 100) {
|
||||
@@ -377,7 +413,33 @@ class FishingMinigame extends Component<
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -386,6 +448,10 @@ class FishingMinigame extends Component<
|
||||
const background_image = resolveAsset(this.props.background);
|
||||
return (
|
||||
<div class="fishing">
|
||||
<KeyListener
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
/>
|
||||
<div class="main">
|
||||
<div
|
||||
class="background"
|
||||
|
||||