[MIRROR] [READY] Fishing and aquarium expansion. [MDB IGNORE] (#22731)
* [READY] Fishing and aquarium expansion. * Update general.dm * Update production_skill.dm * Merge conflict * Merge branch 'upstream-merge-76531' of https://github.com/Skyrat-SS13/Skyrat-tg into upstream-merge-76531 --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
@@ -30,6 +30,9 @@
|
||||
/obj/item/toy/seashell,
|
||||
/turf/open/floor/holofloor/beach,
|
||||
/area/template_noop)
|
||||
"N" = (
|
||||
/turf/open/floor/holofloor/beach/water,
|
||||
/area/template_noop)
|
||||
"Q" = (
|
||||
/obj/effect/holodeck_effect/mobspawner/crab/jon,
|
||||
/turf/open/floor/holofloor/beach,
|
||||
@@ -64,8 +67,8 @@ R
|
||||
R
|
||||
R
|
||||
R
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
(2,1,1) = {"
|
||||
R
|
||||
@@ -76,8 +79,8 @@ R
|
||||
S
|
||||
R
|
||||
Q
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
(3,1,1) = {"
|
||||
X
|
||||
@@ -88,8 +91,8 @@ R
|
||||
t
|
||||
t
|
||||
R
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
(4,1,1) = {"
|
||||
R
|
||||
@@ -100,8 +103,8 @@ h
|
||||
R
|
||||
R
|
||||
R
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
(5,1,1) = {"
|
||||
R
|
||||
@@ -112,8 +115,8 @@ R
|
||||
t
|
||||
t
|
||||
R
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
(6,1,1) = {"
|
||||
R
|
||||
@@ -124,8 +127,8 @@ X
|
||||
g
|
||||
R
|
||||
q
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
(7,1,1) = {"
|
||||
R
|
||||
@@ -136,8 +139,8 @@ R
|
||||
R
|
||||
R
|
||||
f
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
(8,1,1) = {"
|
||||
W
|
||||
@@ -148,8 +151,8 @@ H
|
||||
R
|
||||
M
|
||||
R
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
(9,1,1) = {"
|
||||
R
|
||||
@@ -160,6 +163,6 @@ R
|
||||
R
|
||||
R
|
||||
R
|
||||
R
|
||||
a
|
||||
N
|
||||
"}
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -256,6 +256,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)
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -663,6 +663,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.
|
||||
@@ -674,7 +690,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.
|
||||
*
|
||||
@@ -685,7 +701,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"
|
||||
|
||||
@@ -168,3 +168,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
|
||||
|
||||
@@ -243,6 +243,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
|
||||
|
||||
@@ -67,6 +67,14 @@
|
||||
cost = CARGO_CRATE_VALUE * 2
|
||||
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"
|
||||
|
||||
/* SKYRAT REMOVAL
|
||||
/datum/supply_pack/misc/bicycle
|
||||
name = "Bicycle"
|
||||
@@ -76,6 +84,7 @@
|
||||
crate_name = "bicycle crate"
|
||||
crate_type = /obj/structure/closet/crate/large
|
||||
SKYRAT REMOVAL END */
|
||||
|
||||
/datum/supply_pack/misc/bigband
|
||||
name = "Big Band Instrument Collection"
|
||||
desc = "Get your sad station movin' and groovin' with this fine collection! \
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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///////////////
|
||||
/////////////////////////////////////////
|
||||
|
||||
@@ -2424,7 +2424,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
|
||||
|
||||
@@ -125,6 +125,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"
|
||||
|
||||
@@ -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: 47 KiB After Width: | Height: | Size: 49 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 |
|
After Width: | Height: | Size: 879 B |
|
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 |
|
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 |
@@ -6,7 +6,7 @@
|
||||
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/production
|
||||
skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/production
|
||||
|
||||
/obj/item/clothing/neck/cloak/skill_reward/production
|
||||
name = "legendary producer's cloak"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
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/smithing
|
||||
skill_item_path = /obj/item/clothing/neck/cloak/skill_reward/smithing
|
||||
|
||||
/obj/item/clothing/neck/cloak/skill_reward/smithing
|
||||
name = "legendary smithy's cloak"
|
||||
|
||||
@@ -964,7 +964,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"
|
||||
@@ -1367,6 +1367,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"
|
||||
@@ -1542,6 +1543,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"
|
||||
@@ -1589,6 +1591,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"
|
||||
@@ -3587,11 +3590,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"
|
||||
|
||||