The fishing portal generator expansion (plus skill-chip) (#78203)

## About The Pull Request
This is a PR I worked on last month, but had to put on hold while
dealing with some pressing issues with fishing feature, minigame and
other stuff, and because I had to atomize out some of the stuff
previously present here.

I've expanded on the fishing portal generator to do something other than
dispense guppies and goldfishes. It now has multiple settings,
unlockable by performing scanning experiments for fish types, available
from the get go, which also reward a meager amount of techweb points
upon completion. The generator can now be built too. No longer it has to
be ordered from cargo.
It can also be emagged for the syndicate setting, tho right now it only
dispenses donkfish and emulsijack, both otherwise impossible to get
outside of... exodrone adventures.

The advanced fishing rod now comes with an experiment handler component,
specific to the fish scanning experiment, that automatically scans
fished content. The node to get it now requires 2000 points and the
first fish scanning exp to be unock.

A new skillchip has been added, which adds a trait that changes the icon
of the fish shown in the minigame UI, giving some clues on what the
reward will be. The same trait is also gained by reaching the master
(penultimate) level of the fishing skill.

A new fish type has been added, with its own quirks. One of these quirks
included temporarily switching movement direction of the bait.
Currently, it can only be fished in the hyperspace and randomizer
setting of the fishing portal.

Screenshots:
![fuck
yea](https://github.com/tgstation/tgstation/assets/42542238/b4c75951-fa07-44ae-99ee-f602adf8a5a4)

![radial](https://github.com/tgstation/tgstation/assets/42542238/68ff21d8-69fd-4ba5-aa58-9976b6e3282f)

## Why It's Good For The Game
The fishing portal generator is but a stale and underdeveloped prototype
of the fishing feature right now, so much I was thinking of removing it
at first. However, we also have a lot of fishes which are pretty much
unfishable, so I came up with the idea of adding new portal settings
that allow people to actually get them.

As for the skillchip and trait, it's but an extra to both the vending
machine in the library and the fishing skill itself, which has an
overall humble impact on the minigame.

## Changelog

🆑
add: Expanded the fishing portal generator. It now comes with several
portal options that can be unlocked by performing fish scanning
experiments, which also award a modest amount of techweb points.
balance: The fishing portal generator is now buildable and no longer
orderable. The board can be printed from cargo, service and science
lathes.
balance: Advanced fishing tech is no longer a BEPIS design. It now
requires the base fish scanning experiment and 2000 points to be
unlocked.
add: The advanced fishing rod now comes with an incorporated
experiscanner specific for fish scanning.
add: Added a new skillchip that may change the icon of the "fish" shown
in the minigame UI to less generic ones. Reaching master level in
fishing also does that.
qol: The experiment handler UI no longer shows unselectable experiments.
/🆑
This commit is contained in:
Ghom
2023-10-01 04:11:55 +01:00
committed by GitHub
parent 59808249f1
commit db8eca7bf3
51 changed files with 960 additions and 298 deletions
-44
View File
@@ -1,44 +0,0 @@
#define AQUARIUM_ANIMATION_FISH_SWIM "fish"
#define AQUARIUM_ANIMATION_FISH_DEAD "dead"
#define AQUARIUM_PROPERTIES_PX_MIN "px_min"
#define AQUARIUM_PROPERTIES_PX_MAX "px_max"
#define AQUARIUM_PROPERTIES_PY_MIN "py_min"
#define AQUARIUM_PROPERTIES_PY_MAX "py_max"
#define AQUARIUM_LAYER_MODE_BOTTOM "bottom"
#define AQUARIUM_LAYER_MODE_TOP "top"
#define AQUARIUM_LAYER_MODE_AUTO "auto"
#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 (T0C + 24)
#define FISH_RARITY_BASIC 1000
#define FISH_RARITY_RARE 400
#define FISH_RARITY_VERY_RARE 200
#define FISH_RARITY_GOOD_LUCK_FINDING_THIS 1
#define FISH_RARITY_NOPE 0
#define AQUARIUM_FLUID_FRESHWATER "Freshwater"
#define AQUARIUM_FLUID_SALTWATER "Saltwater"
#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."
@@ -24,5 +24,10 @@
#define COMSIG_FISHING_ROD_CAST "fishing_rod_cast"
#define FISHING_ROD_CAST_HANDLED (1 << 0)
/// From /datum/fish_source/proc/dispense_reward(), not set if the reward is a dud: (reward, user)
#define COMSIG_FISHING_ROD_CAUGHT_FISH "fishing_rod_caught_fish"
/// From /obj/item/fishing_rod/proc/hook_item(): (reward, user)
#define COMSIG_FISHING_ROD_HOOKED_ITEM "fishing_rod_hooked_item"
/// Sent when the challenge is to be interrupted: (reason)
#define COMSIG_FISHING_SOURCE_INTERRUPT_CHALLENGE "fishing_spot_interrupt_challenge"
+6
View File
@@ -26,6 +26,8 @@
/// Destructive experiments which will destroy the sample
#define EXPERIMENT_TRAIT_DESTRUCTIVE (1 << 0)
/// Used by scanning experiments: instead of storing refs or be a number, the list for scanned atoms is used as typecache
#define EXPERIMENT_TRAIT_TYPECACHE (1 << 1)
/// Will always attempt to action every experiment eligible with a single input,
/// no experiment selection required
@@ -33,3 +35,7 @@
/// Experiment handlers with this flag will not automatically connect to the first techweb they find
/// on initialization
#define EXPERIMENT_CONFIG_NO_AUTOCONNECT (1 << 1)
/// Experiment handlers with this flag won't pester the user of objects not pertinent to the test or if no experiment is selected
#define EXPERIMENT_CONFIG_SILENT_FAIL (1 << 2)
/// Experiment handlers with this flag will bypass any delay when trying to scan something
#define EXPERIMENT_CONFIG_IMMEDIATE_ACTION (1 << 3)
+136
View File
@@ -0,0 +1,136 @@
/// Use in fish tables to denote miss chance.
#define FISHING_DUD "dud"
// Baseline fishing difficulty levels
#define FISHING_DEFAULT_DIFFICULTY 15
/// Difficulty modifier when bait is fish's favorite
#define FAV_BAIT_DIFFICULTY_MOD -5
/// Difficulty modifier when bait is fish's disliked
#define DISLIKED_BAIT_DIFFICULTY_MOD 15
/// Difficulty modifier when our fisherman has the trait TRAIT_SETTLER
#define SETTLER_DIFFICULTY_MOD -5
#define FISH_TRAIT_MINOR_DIFFICULTY_BOOST 5
// These define how the fish will behave in the minigame
#define FISH_AI_DUMB "dumb"
#define FISH_AI_ZIPPY "zippy"
#define FISH_AI_SLOW "slow"
#define ADDITIVE_FISHING_MOD "additive"
#define MULTIPLICATIVE_FISHING_MOD "multiplicative"
// These defines are intended for use to interact with fishing hooks when going
// through the fishing rod, and not the hook itself. They could probably be
// handled differently, but for now that's how they work. It's grounds for
// a future refactor, however.
/// Fishing hook trait that signifies that it's shiny. Useful for fishes
/// that care about shiner hooks more.
#define FISHING_HOOK_SHINY (1 << 0)
/// Fishing hook trait that lessens the bounce from hitting the edges of the minigame bar.
#define FISHING_HOOK_WEIGHTED (1 << 1)
///See FISHING_MINIGAME_RULE_BIDIRECTIONAL
#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)
///Automatically kills the fish after a while, at the cost of killing it.
#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 fishing line.
#define FISHING_LINE_BOUNCY (1 << 2)
///Keeps the bait from falling from gravity, instead allowing the player to move the bait down with right click.
#define FISHING_MINIGAME_RULE_BIDIRECTIONAL (1 << 2)
///Prevents the player from losing the minigame when the completion reaches 0
#define FISHING_MINIGAME_RULE_NO_ESCAPE (1 << 3)
///Automatically kills the fish after a while, at the cost of killing it
#define FISHING_MINIGAME_RULE_KILL (1 << 4)
///Prevents the fishing skill from having an effect on the minigame and experience from being awarded
#define FISHING_MINIGAME_RULE_NO_EXP (1 << 5)
///If enabled, the minigame will occasionally screw around and invert the velocity of the bait
#define FISHING_MINIGAME_RULE_ANTIGRAV (1 << 6)
///Will filp the minigame hud for the duration of the effect
#define FISHING_MINIGAME_RULE_FLIP (1 << 7)
///all the effects that are active and will last for a few seconds before triggering a cooldown
#define FISHING_MINIGAME_ACTIVE_EFFECTS (FISHING_MINIGAME_RULE_ANTIGRAV|FISHING_MINIGAME_RULE_FLIP)
/// The default additive value for fishing hook catch weight modifiers.
#define FISHING_DEFAULT_HOOK_BONUS_ADDITIVE 0
/// The default multiplicative value for fishing hook catch weight modifiers.
#define FISHING_DEFAULT_HOOK_BONUS_MULTIPLICATIVE 1
//Fish icon defines, used by fishing minigame
#define FISH_ICON_DEF "fish"
#define FISH_ICON_HOSTILE "hostile"
#define FISH_ICON_STAR "star"
#define FISH_ICON_CHUNKY "chunky"
#define FISH_ICON_SLIME "slime"
#define FISH_ICON_COIN "coin"
#define FISH_ICON_GEM "gem"
#define FISH_ICON_CRAB "crab"
#define FISH_ICON_JELLYFISH "jellyfish"
#define FISH_ICON_BONE "bone"
#define AQUARIUM_ANIMATION_FISH_SWIM "fish"
#define AQUARIUM_ANIMATION_FISH_DEAD "dead"
#define AQUARIUM_PROPERTIES_PX_MIN "px_min"
#define AQUARIUM_PROPERTIES_PX_MAX "px_max"
#define AQUARIUM_PROPERTIES_PY_MIN "py_min"
#define AQUARIUM_PROPERTIES_PY_MAX "py_max"
#define AQUARIUM_LAYER_MODE_BOTTOM "bottom"
#define AQUARIUM_LAYER_MODE_TOP "top"
#define AQUARIUM_LAYER_MODE_AUTO "auto"
#define FISH_ALIVE "alive"
#define FISH_DEAD "dead"
///Fish size thresholds for w_class.
#define FISH_SIZE_TINY_MAX 30
#define FISH_SIZE_SMALL_MAX 50
#define FISH_SIZE_NORMAL_MAX 90
#define FISH_SIZE_BULKY_MAX 130
///The coefficient for maximum weight/size divergence relative to the averages.
#define MAX_FISH_DEVIATION_COEFF 2.5
///The volume of the grind results is multiplied by the fish' weight and divided by this.
#define FISH_GRIND_RESULTS_WEIGHT_DIVISOR 500
///The number of fillets is multiplied by the fish' size and divided by this.
#define FISH_FILLET_NUMBER_SIZE_DIVISOR 30
///The breeding timeout for newly instantiated fish is multiplied by this.
#define NEW_FISH_BREEDING_TIMEOUT_MULT 2
///The last feeding timestamp of newly instantiated fish is multiplied by this: ergo, they spawn 50% hungry.
#define NEW_FISH_LAST_FEEDING_MULT 0.5
#define MIN_AQUARIUM_TEMP T0C
#define MAX_AQUARIUM_TEMP (T0C + 100)
#define DEFAULT_AQUARIUM_TEMP (T0C + 24)
///How likely one's to find a given fish from random fish cases.
#define FISH_RARITY_BASIC 1000
#define FISH_RARITY_RARE 400
#define FISH_RARITY_VERY_RARE 200
#define FISH_RARITY_GOOD_LUCK_FINDING_THIS 5
#define FISH_RARITY_NOPE 0
///Aquarium fluid variables. The fish' required fluid has to match this, or it'll slowly die.
#define AQUARIUM_FLUID_FRESHWATER "Freshwater"
#define AQUARIUM_FLUID_SALTWATER "Saltwater"
#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"
///Fluff. The name of the aquarium company shown in the fish catalog
#define AQUARIUM_COMPANY "Aquatech Ltd."
-62
View File
@@ -1,62 +0,0 @@
/// Use in fish tables to denote miss chance.
#define FISHING_DUD "dud"
// Baseline fishing difficulty levels
#define FISHING_DEFAULT_DIFFICULTY 15
/// Difficulty modifier when bait is fish's favorite
#define FAV_BAIT_DIFFICULTY_MOD -5
/// Difficulty modifier when bait is fish's disliked
#define DISLIKED_BAIT_DIFFICULTY_MOD 15
/// Difficulty modifier when our fisherman has the trait TRAIT_SETTLER
#define SETTLER_DIFFICULTY_MOD -5
#define FISH_TRAIT_MINOR_DIFFICULTY_BOOST 5
// These define how the fish will behave in the minigame
#define FISH_AI_DUMB "dumb"
#define FISH_AI_ZIPPY "zippy"
#define FISH_AI_SLOW "slow"
#define ADDITIVE_FISHING_MOD "additive"
#define MULTIPLICATIVE_FISHING_MOD "multiplicative"
// These defines are intended for use to interact with fishing hooks when going
// through the fishing rod, and not the hook itself. They could probably be
// handled differently, but for now that's how they work. It's grounds for
// a future refactor, however.
/// Fishing hook trait that signifies that it's shiny. Useful for fishes
/// that care about shiner hooks more.
#define FISHING_HOOK_SHINY (1 << 0)
/// 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_MINIGAME_RULE_BIDIRECTIONAL (1 << 2)
#define FISHING_MINIGAME_RULE_NO_ESCAPE (1 << 3)
#define FISHING_MINIGAME_RULE_KILL (1 << 4)
#define FISHING_MINIGAME_RULE_NO_EXP (1 << 5)
/// The default additive value for fishing hook catch weight modifiers.
#define FISHING_DEFAULT_HOOK_BONUS_ADDITIVE 0
/// The default multiplicative value for fishing hook catch weight modifiers.
#define FISHING_DEFAULT_HOOK_BONUS_MULTIPLICATIVE 1
+4
View File
@@ -319,6 +319,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_BLOOD_DEFICIENCY "blood_deficiency"
#define TRAIT_JOLLY "jolly"
#define TRAIT_NOCRITDAMAGE "no_crit"
///Added to mob or mind, changes the icons of the fish shown in the minigame UI depending on the possible reward.
#define TRAIT_REVEAL_FISH "reveal_fish"
/// Stops the mob from slipping on water, or banana peels, or pretty much anything that doesn't have [GALOSHES_DONT_HELP] set
#define TRAIT_NO_SLIP_WATER "noslip_water"
@@ -909,6 +911,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_FISH_AMPHIBIOUS "fish_amphibious"
///Trait needed for the lubefish evolution
#define TRAIT_FISH_FED_LUBE "fish_fed_lube"
#define TRAIT_FISH_NO_HUNGER "fish_no_hunger"
// common trait sources
#define TRAIT_GENERIC "generic"
@@ -1082,6 +1085,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// sticky nodrop sounds like a bad soundcloud rapper's name
#define STICKY_NODROP "sticky-nodrop"
#define SKILLCHIP_TRAIT "skillchip"
#define SKILL_TRAIT "skill"
#define BUSY_FLOORBOT_TRAIT "busy-floorbot"
#define PULLED_WHILE_SOFTCRIT_TRAIT "pulled-while-softcrit"
#define LOCKED_BORG_TRAIT "locked-borg"
+1
View File
@@ -269,6 +269,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_FISH_TOXIN_IMMUNE" = TRAIT_FISH_TOXIN_IMMUNE,
"TRAIT_FISH_CROSSBREEDER" = TRAIT_FISH_CROSSBREEDER,
"TRAIT_FISH_FED_LUBE" = TRAIT_FISH_FED_LUBE,
"TRAIT_FISH_NO_HUNGER" = TRAIT_FISH_NO_HUNGER,
),
))
+1
View File
@@ -12,6 +12,7 @@
fish_source = configuration
else
return COMPONENT_INCOMPATIBLE
fish_source.on_fishing_spot_init()
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(handle_attackby))
RegisterSignal(parent, COMSIG_FISHING_ROD_CAST, PROC_REF(handle_cast))
+2 -4
View File
@@ -18,12 +18,10 @@
experience_multiplier += experience_multiplier_reasons[key]
known_skills[skill][SKILL_EXP] = max(0, known_skills[skill][SKILL_EXP] + amt*experience_multiplier) //Update exp. Prevent going below 0
known_skills[skill][SKILL_LVL] = update_skill_level(skill)//Check what the current skill level is based on that skill's exp
if(silent)
return
if(known_skills[skill][SKILL_LVL] > old_level)
S.level_gained(src, known_skills[skill][SKILL_LVL], old_level)
S.level_gained(src, known_skills[skill][SKILL_LVL], old_level, silent)
else if(known_skills[skill][SKILL_LVL] < old_level)
S.level_lost(src, known_skills[skill][SKILL_LVL], old_level)
S.level_lost(src, known_skills[skill][SKILL_LVL], old_level, silent)
///Set experience of a specific skill to a number
/datum/mind/proc/set_experience(skill, amt, silent = FALSE)
+8 -4
View File
@@ -27,8 +27,7 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill))
span_nicegreen("I'm getting a little better at [name]!"),
span_nicegreen("I'm getting much better at [name]!"),
span_nicegreen("I feel like I've become quite proficient at [name]!"),
"<span class='nicegreen'>After lots of practice, I've begun to truly understand the intricacies \
and surprising depth behind [name]. I now consider myself a master [title].</span>",
span_nicegreen("After lots of practice, I've begun to truly understand the intricacies and surprising depth behind [name]. I now consider myself a master [title]."),
span_nicegreen("Through incredible determination and effort, I've reached the peak of my [name] abiltities. I'm finally able to consider myself a legendary [title]!") )
levelDownMessages = list(span_nicegreen("I have somehow completely lost all understanding of [name]. Please tell an admin if you see this."),
span_nicegreen("I'm starting to forget what [name] really even is. I need more practice..."),
@@ -46,13 +45,18 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill))
* * mind - The mind that you'll want to send messages
* * new_level - The newly gained level. Can check the actual level to give different messages at different levels, see defines in skills.dm
* * old_level - Similar to the above, but the level you had before levelling up.
* * silent - Silences the announcement if TRUE
*/
/datum/skill/proc/level_gained(datum/mind/mind, new_level, old_level)//just for announcements (doesn't go off if the xp gain is silent)
/datum/skill/proc/level_gained(datum/mind/mind, new_level, old_level, silent)
if(silent)
return
to_chat(mind.current, levelUpMessages[new_level]) //new_level will be a value from 1 to 6, so we get appropriate message from the 6-element levelUpMessages list
/**
* level_lost: See level_gained, same idea but fires on skill level-down
*/
/datum/skill/proc/level_lost(datum/mind/mind, new_level, old_level)
/datum/skill/proc/level_lost(datum/mind/mind, new_level, old_level, silent)
if(silent)
return
to_chat(mind.current, levelDownMessages[old_level]) //old_level will be a value from 1 to 6, so we get appropriate message from the 6-element levelUpMessages list
/**
+14
View File
@@ -8,3 +8,17 @@
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
/datum/skill/fishing/New()
. = ..()
levelUpMessages[SKILL_LEVEL_MASTER] = span_nicegreen("After lots of practice, I've begun to truly understand the surprising depth behind [name]. As a master [title], I can take an easier guess of what I'm trying to catch now.")
/datum/skill/fishing/level_gained(datum/mind/mind, new_level, old_level, silent)
. = ..()
if(new_level >= SKILL_LEVEL_MASTER && old_level < SKILL_LEVEL_MASTER)
ADD_TRAIT(mind, TRAIT_REVEAL_FISH, SKILL_TRAIT)
/datum/skill/fishing/level_lost(datum/mind/mind, new_level, old_level, silent)
. = ..()
if(old_level >= SKILL_LEVEL_MASTER && new_level < SKILL_LEVEL_MASTER)
REMOVE_TRAIT(mind, TRAIT_REVEAL_FISH, SKILL_TRAIT)
@@ -25,11 +25,15 @@
if(!CONFIG_GET(flag/no_default_techweb_link) && !linked_techweb)
CONNECT_TO_RND_SERVER_ROUNDSTART(linked_techweb, src)
experiment_handler = AddComponent( \
var/list/operating_signals = list(
COMSIG_OPERATING_COMPUTER_AUTOPSY_COMPLETE = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_autopsy_experiment),
)
experiment_handler = AddComponent(
/datum/component/experiment_handler, \
allowed_experiments = list(/datum/experiment/autopsy), \
config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE, \
config_mode = EXPERIMENT_CONFIG_ALTCLICK, \
experiment_signals = operating_signals, \
)
/obj/machinery/computer/operating/Destroy()
@@ -1303,6 +1303,15 @@
req_components = list(
/datum/stock_part/card_reader = 1)
/obj/item/circuitboard/machine/fishing_portal_generator
name = "Fishing Portal Generator"
greyscale_colors = CIRCUIT_COLOR_SERVICE
build_path = /obj/machinery/fishing_portal_generator
req_components = list(
/datum/stock_part/matter_bin = 2,
/datum/stock_part/capacitor = 1)
needs_anchored = FALSE
//Supply
/obj/item/circuitboard/machine/ore_redemption
name = "Ore Redemption"
-7
View File
@@ -292,13 +292,6 @@
crate_value = value
contents_uplink_type = uplink
/datum/supply_pack/misc/fishing_portal
name = "Fishing Portal Generator Crate"
desc = "Not enough fish near your location? Fishing portal has your back."
cost = CARGO_CRATE_VALUE * 4
contains = list(/obj/machinery/fishing_portal_generator)
crate_name = "fishing portal crate"
/datum/supply_pack/misc/papercutter
name = "Paper Cutters Crate"
desc = "Contains 3 office-grade paper cutters, equipped with sharp blades that can cut any paper into two thin slips.\
@@ -19,10 +19,17 @@
// Late load to ensure the component initialization occurs after the machines are initialized
/obj/machinery/destructive_scanner/LateInitialize()
. = ..()
var/static/list/destructive_signals = list(
COMSIG_MACHINERY_DESTRUCTIVE_SCAN = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_destructive_experiment),
)
AddComponent(/datum/component/experiment_handler, \
allowed_experiments = list(/datum/experiment/scanning),\
config_mode = EXPERIMENT_CONFIG_CLICK, \
start_experiment_callback = CALLBACK(src, PROC_REF(activate)))
start_experiment_callback = CALLBACK(src, PROC_REF(activate)), \
experiment_signals = destructive_signals, \
)
///Activates the machine; checks if it can actually scan, then starts.
/obj/machinery/destructive_scanner/proc/activate()
@@ -323,7 +323,7 @@
///Damage percent that each mech needs to be at for a scan to work.
var/damage_percent
/datum/experiment/scanning/random/mecha_damage_scan/New()
/datum/experiment/scanning/random/mecha_damage_scan/New(datum/techweb/techweb)
. = ..()
damage_percent = rand(15, 95)
//updating the description with the damage_percent var set
@@ -38,6 +38,7 @@
disallowed_traits = null,
config_flags = null,
datum/callback/start_experiment_callback = null,
list/experiment_signals
)
. = ..()
if(!ismovable(parent))
@@ -49,13 +50,8 @@
src.config_flags = config_flags
src.start_experiment_callback = start_experiment_callback
if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(try_run_handheld_experiment))
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, PROC_REF(ignored_handheld_experiment_attempt))
if(istype(parent, /obj/machinery/destructive_scanner))
RegisterSignal(parent, COMSIG_MACHINERY_DESTRUCTIVE_SCAN, PROC_REF(try_run_destructive_experiment))
if(istype(parent, /obj/machinery/computer/operating))
RegisterSignal(parent, COMSIG_OPERATING_COMPUTER_AUTOPSY_COMPLETE, PROC_REF(try_run_autopsy_experiment))
for(var/signal in experiment_signals)
RegisterSignal(parent, signal, experiment_signals[signal])
// Determine UI display mode
switch(config_mode)
@@ -85,9 +81,9 @@
*/
/datum/component/experiment_handler/proc/try_run_handheld_experiment(datum/source, atom/target, mob/user, params)
SIGNAL_HANDLER
if (!should_run_handheld_experiment(source, target, user, params))
if (!should_run_handheld_experiment(source, target, user))
return
INVOKE_ASYNC(src, PROC_REF(try_run_handheld_experiment_async), source, target, user, params)
INVOKE_ASYNC(src, PROC_REF(try_run_handheld_experiment_async), source, target, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
/**
@@ -98,7 +94,7 @@
if (!proximity_flag)
return
. |= COMPONENT_AFTERATTACK_PROCESSED_ITEM
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
if ((selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)) || config_flags & EXPERIMENT_CONFIG_SILENT_FAIL)
return .
playsound(user, 'sound/machines/buzz-sigh.ogg', 25)
to_chat(user, span_notice("[target] is not related to your currently selected experiment."))
@@ -107,7 +103,7 @@
/**
* Checks that an experiment can be run using the provided target, used for preventing the cancellation of the attack chain inappropriately
*/
/datum/component/experiment_handler/proc/should_run_handheld_experiment(datum/source, atom/target, mob/user, params)
/datum/component/experiment_handler/proc/should_run_handheld_experiment(datum/source, atom/target, mob/user)
// Check that there is actually an experiment selected
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
return
@@ -127,16 +123,17 @@
/**
* This proc exists because Jared Fogle really likes async
*/
/datum/component/experiment_handler/proc/try_run_handheld_experiment_async(datum/source, atom/target, mob/user, params)
/datum/component/experiment_handler/proc/try_run_handheld_experiment_async(datum/source, atom/target, mob/user)
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
to_chat(user, span_notice("You do not have an experiment selected!"))
if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
to_chat(user, span_notice("You do not have an experiment selected!"))
return
if(!do_after(user, 1 SECONDS, target = target))
if(!(config_flags & EXPERIMENT_CONFIG_IMMEDIATE_ACTION) && !do_after(user, 1 SECONDS, target = target))
return
if(action_experiment(source, target))
playsound(user, 'sound/machines/ping.ogg', 25)
to_chat(user, span_notice("You scan [target]."))
else
else if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
playsound(user, 'sound/machines/buzz-sigh.ogg', 25)
to_chat(user, span_notice("[target] is not related to your currently selected experiment."))
@@ -148,8 +145,9 @@
SIGNAL_HANDLER
var/atom/movable/our_scanner = parent
if (selected_experiment == null)
playsound(our_scanner, 'sound/machines/buzz-sigh.ogg', 25)
to_chat(our_scanner, span_notice("No experiment selected!"))
if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
playsound(our_scanner, 'sound/machines/buzz-sigh.ogg', 25)
to_chat(our_scanner, span_notice("No experiment selected!"))
return
var/successful_scan
for(var/scan_target in scanned_atoms)
@@ -159,7 +157,7 @@
if(successful_scan)
playsound(our_scanner, 'sound/machines/ping.ogg', 25)
to_chat(our_scanner, span_notice("The scan succeeds."))
else
else if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
our_scanner.say("The scan did not result in anything.")
@@ -261,6 +259,7 @@
/datum/component/experiment_handler/proc/link_techweb(datum/techweb/new_web)
if (new_web == linked_web)
return
selected_experiment?.on_unselected(src)
selected_experiment = null
linked_web = new_web
@@ -268,6 +267,7 @@
* Unlinks this handler from the selected techweb
*/
/datum/component/experiment_handler/proc/unlink_techweb()
selected_experiment?.on_unselected(src)
selected_experiment = null
linked_web = null
@@ -278,13 +278,15 @@
* * experiment - The experiment to attempt to link to
*/
/datum/component/experiment_handler/proc/link_experiment(datum/experiment/experiment)
if (experiment && can_select_experiment(experiment))
if (can_select_experiment(experiment))
selected_experiment = experiment
selected_experiment.on_selected(src)
/**
* Unlinks this handler from the selected experiment
*/
/datum/component/experiment_handler/proc/unlink_experiment()
selected_experiment?.on_unselected(src)
selected_experiment = null
/**
@@ -299,31 +301,19 @@
return FALSE
// Check against the list of allowed experimentors
if (experiment.allowed_experimentors && experiment.allowed_experimentors.len)
var/matched = FALSE
for (var/experimentor in experiment.allowed_experimentors)
if (istype(parent, experimentor))
matched = TRUE
break
if (!matched)
return FALSE
if (length(experiment.allowed_experimentors) && !is_type_in_list(parent, experiment.allowed_experimentors))
return FALSE
// Check that this experiment is visible currently
if (!linked_web || !(experiment in linked_web.available_experiments))
if (!(experiment in linked_web?.available_experiments))
return FALSE
// Check that this experiment type isn't blacklisted
for (var/badsci in blacklisted_experiments)
if (istype(experiment, badsci))
return FALSE
if(is_type_in_list(experiment, blacklisted_experiments))
return FALSE
// Check against the allowed experiment types
for (var/goodsci in allowed_experiments)
if (istype(experiment, goodsci))
return TRUE
// If we haven't returned yet then this shouldn't be allowed
return FALSE
// Finally, check against the allowed experiment types
return is_type_in_list(experiment, allowed_experiments)
/datum/component/experiment_handler/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -355,12 +345,13 @@
.["techwebs"] += list(data)
.["experiments"] = list()
if (linked_web)
for (var/datum/experiment/experiment in linked_web.available_experiments)
for (var/datum/experiment/experiment as anything in linked_web.available_experiments)
if(!can_select_experiment(experiment))
continue
var/list/data = list(
name = experiment.name,
description = experiment.description,
tag = experiment.exp_tag,
selectable = can_select_experiment(experiment),
selected = selected_experiment == experiment,
progress = experiment.check_progress(),
performance_hint = experiment.performance_hint,
@@ -22,11 +22,16 @@
/// A textual hint shown on the UI in a tooltip to help a user determine how to perform
/// the experiment
var/performance_hint
/**
* If set, these techweb points will be rewarded for completing the experiment.
* Useful for those loose ends not tied to any specific node discount or requirement.
*/
var/list/points_reward
/**
* Performs any necessary initialization of tags and other variables
*/
/datum/experiment/New()
/datum/experiment/New(datum/techweb/techweb)
if (traits & EXPERIMENT_TRAIT_DESTRUCTIVE)
exp_tag = "Destructive [exp_tag]"
@@ -60,6 +65,14 @@
/datum/experiment/proc/actionable(...)
return !is_complete()
///Called when the experiment is selected by an experiment handler, for specific signals and the such.
/datum/experiment/proc/on_selected(datum/component/experiment_handler/experiment_handler)
return
///Called when the opposite happens.
/datum/experiment/proc/on_unselected(datum/component/experiment_handler/experiment_handler)
return
/**
* Proc that tries to perform the experiment, and then checks if its completed.
*/
@@ -52,7 +52,7 @@
/// If not null the required_condition will be picked from this list
var/list/possible_random_site_types
/datum/experiment/exploration_scan/random/New()
/datum/experiment/exploration_scan/random/New(datum/techweb/techweb)
. = ..()
if(length(possible_random_site_types))
required_site_type = pick(possible_random_site_types)
@@ -8,7 +8,7 @@
/// Max amount of a requirement per type
var/max_requirement_per_type = 100
/datum/experiment/scanning/random/New()
/datum/experiment/scanning/random/New(datum/techweb/techweb)
// Generate random contents
if (possible_types.len)
var/picked = 0
@@ -17,16 +17,18 @@
var/list/required_atoms = list()
/// The list of atoms with sub-lists of atom references for scanned atoms contributing to the experiment (Or a count of atoms destoryed for destructive expiriments)
var/list/scanned = list()
/// If set, it'll be used in place of the generic "Scan samples of \a [initial(target.name)]" in serialize_progress_stage()
var/scan_message
/**
* Initializes the scanned atoms lists
*
* Initializes the internal scanned atoms list to keep track of which atoms have already been scanned
*/
/datum/experiment/scanning/New()
/datum/experiment/scanning/New(datum/techweb/techweb)
. = ..()
for (var/req_atom in required_atoms)
scanned[req_atom] = traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? 0 : list()
scanned[req_atom] = (traits & EXPERIMENT_TRAIT_DESTRUCTIVE && !(traits & EXPERIMENT_TRAIT_TYPECACHE)) ? 0 : list()
/**
* Checks if the scanning experiment is complete
@@ -37,8 +39,12 @@
/datum/experiment/scanning/is_complete()
. = TRUE
var/destructive = traits & EXPERIMENT_TRAIT_DESTRUCTIVE
var/typecache = traits & EXPERIMENT_TRAIT_TYPECACHE
for (var/req_atom in required_atoms)
var/list/seen = scanned[req_atom]
///typecache experiments work all the same whether it's destructive or not
if(typecache && length(seen) == required_atoms[req_atom])
continue
if (destructive && (!(req_atom in scanned) || scanned[req_atom] != required_atoms[req_atom]))
return FALSE
if (!destructive && (!seen || seen.len != required_atoms[req_atom]))
@@ -65,8 +71,9 @@
* * seen_instances - The number of instances seen of this atom
*/
/datum/experiment/scanning/proc/serialize_progress_stage(atom/target, list/seen_instances)
var/scanned_total = traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len
return EXPERIMENT_PROG_INT("Scan samples of \a [initial(target.name)]", scanned_total, required_atoms[target])
var/scanned_total = (traits & EXPERIMENT_TRAIT_DESTRUCTIVE && !(traits & EXPERIMENT_TRAIT_TYPECACHE)) ? scanned[target] : seen_instances.len
var/message = scan_message || "Scan samples of \a [initial(target.name)]"
return EXPERIMENT_PROG_INT(message, scanned_total, required_atoms[target])
/**
* Attempts to scan an atom towards the experiment's goal
@@ -79,7 +86,10 @@
/datum/experiment/scanning/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, atom/target)
var/contributing_index_value = get_contributing_index(target)
if (contributing_index_value)
scanned[contributing_index_value] += traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? 1 : WEAKREF(target)
if(traits & EXPERIMENT_TRAIT_TYPECACHE)
scanned[contributing_index_value][target.type] = TRUE
else
scanned[contributing_index_value] += traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? 1 : WEAKREF(target)
if(traits & EXPERIMENT_TRAIT_DESTRUCTIVE && !isliving(target))//only qdel things when destructive scanning and they're not living (living things get gibbed)
qdel(target)
do_after_experiment(target, contributing_index_value)
@@ -0,0 +1,116 @@
///a superlist containing typecaches shared between the several fish scanning experiments for each techweb.
GLOBAL_LIST_EMPTY(scanned_fish_by_techweb)
/**
* A special scanning experiment that unlocks further settings for the fishing portal generator.
* Mainly as an inventive solution to many a fish source being limited to maps that have it,
* and to make the fishing portal generator a bit than just gubby and goldfish.
*/
/datum/experiment/scanning/fish
name = "Fish Scanning Experiment 1"
description = "An experiment requiring different fish species to be scanned to unlock the 'Beach' setting for the fishing portal generator."
performance_hint = "Scan fish. Examine scanner to review progress. Unlock new fishing portals."
allowed_experimentors = list(/obj/item/experi_scanner, /obj/machinery/destructive_scanner, /obj/item/fishing_rod/tech)
traits = EXPERIMENT_TRAIT_TYPECACHE
points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 750)
required_atoms = list(/obj/item/fish = 4)
scan_message = "Scan different species of fish"
///Further experiments added to the techweb when this one is completed.
var/list/next_experiments = list(/datum/experiment/scanning/fish/second)
///Completing a experiment may also enable a fish source to be used for use for the portal generator.
var/fish_source_reward = /datum/fish_source/portal/beach
/**
* We make sure the scanned list is shared between all fish scanning experiments for this techweb,
* since this is about scanning each species, and having to redo it for each species is a hassle.
*/
/datum/experiment/scanning/fish/New(datum/techweb/techweb)
. = ..()
if(isnull(techweb))
return
var/techweb_ref = REF(techweb)
var/list/scanned_fish = GLOB.scanned_fish_by_techweb[techweb_ref]
if(isnull(scanned_fish))
scanned_fish = list()
GLOB.scanned_fish_by_techweb[techweb_ref] = scanned_fish
for(var/atom_type in required_atoms)
LAZYINITLIST(scanned_fish[atom_type])
scanned = scanned_fish
/**
* Registers a couple signals to review the fish scanned so far.
* It'd be an hassle not having any way (beside memory) to know which fish species have been scanned already otherwise.
*/
/datum/experiment/scanning/fish/on_selected(datum/component/experiment_handler/experiment_handler)
RegisterSignal(experiment_handler.parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_handler_examine))
RegisterSignal(experiment_handler.parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_handler_examine_more))
/datum/experiment/scanning/fish/on_unselected(datum/component/experiment_handler/experiment_handler)
UnregisterSignal(experiment_handler.parent, list(COMSIG_ATOM_EXAMINE, COMSIG_ATOM_EXAMINE_MORE))
/datum/experiment/scanning/fish/proc/on_handler_examine(datum/source, mob/user, list/examine_list)
SIGNAL_HANDLER
examine_list += span_notice("Examine again to review all the species of fish scanned so far.")
/datum/experiment/scanning/fish/proc/on_handler_examine_more(datum/source, mob/user, list/examine_list)
SIGNAL_HANDLER
var/message = span_notice("Fish species scanned hitherto, if any:")
message += "<span class='info ml-1'>"
for(var/atom_type in required_atoms)
for(var/obj/item/fish/fish_path as anything in scanned[atom_type])
message += "[initial(fish_path.name)]"
message += "</span>"
examine_list += message
///Only scannable fish will contribute towards the experiment.
/datum/experiment/scanning/fish/final_contributing_index_checks(obj/item/fish/target, typepath)
return target.experisci_scannable
/**
* After a fish scanning experiment is done, more may be unlocked. If so, add them to the techweb
* and automatically link the handler to the next experiment in the list as a bit of qol.
*/
/datum/experiment/scanning/fish/finish_experiment(datum/component/experiment_handler/experiment_handler, ...)
. = ..()
if(next_experiments)
experiment_handler.linked_web.add_experiments(next_experiments)
var/datum/experiment/next_in_line = locate(next_experiments[1]) in experiment_handler.linked_web.available_experiments
experiment_handler.link_experiment(next_in_line)
/datum/experiment/scanning/fish/second
name = "Fish Scanning Experiment 2"
description = "An experiment requiring more fish species to be scanned to unlock the 'Chasm' setting for the fishing portal."
points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 1500)
required_atoms = list(/obj/item/fish = 8)
next_experiments = list(/datum/experiment/scanning/fish/third)
fish_source_reward = /datum/fish_source/portal/chasm
/datum/experiment/scanning/fish/third
name = "Fish Scanning Experiment 3"
description = "An experiment requiring even more fish species to be scanned to unlock the 'Ocean' setting for the fishing portal."
points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
required_atoms = list(/obj/item/fish = 14)
next_experiments = list(/datum/experiment/scanning/fish/fourth, /datum/experiment/scanning/fish/holographic)
fish_source_reward = /datum/fish_source/portal/ocean
/datum/experiment/scanning/fish/holographic
name = "Holographic Fish Scanning Experiment"
description = "This one actually requires holographic fish to unlock the 'Randomizer' setting for the fishing portal."
performance_hint = "Load in the 'Beach' template at the Holodeck to fish some holo-fish."
points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 500)
required_atoms = list(/obj/item/fish/holo = 4)
scan_message = "Scan different species of holographic fish"
next_experiments = null
fish_source_reward = /datum/fish_source/portal/random
///holo fishes are normally unscannable, but this is an experiment for them, so we don't care for the experisci_scannable variable.
/datum/experiment/scanning/fish/holographic/final_contributing_index_checks(obj/item/fish/target, typepath)
return TRUE
/datum/experiment/scanning/fish/fourth
name = "Fish Scanning Experiment 4"
description = "An experiment requiring lotsa fish species to unlock the 'Hyperspace' setting for the fishing portal."
points_reward = list(TECHWEB_POINT_TYPE_GENERIC = 3250)
required_atoms = list(/obj/item/fish = 21)
next_experiments = null
fish_source_reward = /datum/fish_source/portal/hyperspace
@@ -9,7 +9,7 @@
///List of materials actually required, indexed by the atom that is required.
var/required_materials = list()
/datum/experiment/scanning/random/material/New()
/datum/experiment/scanning/random/material/New(datum/techweb/techweb)
. = ..()
for(var/req_atom in required_atoms)
var/chosen_material = pick(possible_material_types)
@@ -10,7 +10,7 @@
///List of plant genes actually required, indexed by the atom that is required.
var/list/required_genes = list()
/datum/experiment/scanning/random/plants/New()
/datum/experiment/scanning/random/plants/New(datum/techweb/techweb)
. = ..()
if(possible_plant_genes.len)
for(var/req_atom in required_atoms)
+8 -2
View File
@@ -19,9 +19,15 @@
// Late initialize to allow for the rnd servers to initialize first
/obj/item/experi_scanner/LateInitialize()
. = ..()
var/static/list/handheld_signals = list(
COMSIG_ITEM_PRE_ATTACK = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_handheld_experiment),
COMSIG_ITEM_AFTERATTACK = TYPE_PROC_REF(/datum/component/experiment_handler, ignored_handheld_experiment_attempt),
)
AddComponent(/datum/component/experiment_handler, \
allowed_experiments = list(/datum/experiment/scanning, /datum/experiment/physical),\
disallowed_traits = EXPERIMENT_TRAIT_DESTRUCTIVE)
allowed_experiments = list(/datum/experiment/scanning, /datum/experiment/physical), \
disallowed_traits = EXPERIMENT_TRAIT_DESTRUCTIVE, \
experiment_signals = handheld_signals, \
)
/obj/item/experi_scanner/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] is giving in to the Great Toilet Beyond! It looks like [user.p_theyre()] trying to commit suicide!"))
+11 -10
View File
@@ -190,16 +190,17 @@
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"
if(!HAS_TRAIT(fish, TRAIT_FISH_NO_HUNGER))
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>"
+21 -13
View File
@@ -136,6 +136,9 @@
var/min_pressure = WARNING_LOW_PRESSURE
var/max_pressure = HAZARD_HIGH_PRESSURE
/// If this fish type counts towards the Fish Species Scanning experiments
var/experisci_scannable = TRUE
/obj/item/fish/Initialize(mapload, apply_qualities = TRUE)
. = ..()
AddComponent(/datum/component/aquarium_content, PROC_REF(get_aquarium_animation), list(COMSIG_FISH_STATUS_CHANGED,COMSIG_FISH_STIRRED))
@@ -175,7 +178,7 @@
balloon_alert(user, "[src] is dead!")
return TRUE
feed(item.reagents)
balloon_alert(user, "you feed [src]")
balloon_alert(user, "fed [src]")
return TRUE
/obj/item/fish/examine(mob/user)
@@ -185,14 +188,14 @@
. += span_notice("It weighs [weight] g.")
///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)
/obj/item/fish/proc/randomize_size_and_weight(base_size = average_size, base_weight = average_weight, deviation = 0.2)
var/size_deviation = 0.2 * base_size
var/new_size = round(clamp(gaussian(base_size, size_deviation), average_size * 1/MAX_FISH_DEVIATION_COEFF, average_size * MAX_FISH_DEVIATION_COEFF))
var/weight_deviation = 0.2 * avg_weight
var/new_weight = round(max(1,gaussian(avg_weight, weight_deviation)), 1)
var/weight_deviation = 0.2 * base_weight
var/new_weight = round(clamp(gaussian(base_weight, weight_deviation), average_weight * 1/MAX_FISH_DEVIATION_COEFF, average_weight * MAX_FISH_DEVIATION_COEFF))
update_size_and_weight(new_size, new_weight, first_run)
update_size_and_weight(new_size, new_weight)
///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)
@@ -414,11 +417,14 @@
return FALSE
return TRUE
/obj/item/fish/proc/is_hungry()
return !HAS_TRAIT(src, TRAIT_FISH_NO_HUNGER) && world.time - last_feeding >= feeding_frequency
/obj/item/fish/proc/process_health(seconds_per_tick)
var/health_change_per_second = 0
if(!proper_environment())
health_change_per_second -= 3 //Dying here
if(world.time - last_feeding >= feeding_frequency)
if(is_hungry())
health_change_per_second -= 0.5 //Starving
else
health_change_per_second += 0.5 //Slowly healing
@@ -541,10 +547,11 @@
#define FLOP_SINGLE_MOVE_TIME 1.5
#define JUMP_X_DISTANCE 5
#define JUMP_Y_DISTANCE 6
/// This animation should be applied to actual parent atom instead of vc_object.
/proc/flop_animation(atom/movable/animation_target)
/// This flopping animation played while the fish is alive.
/obj/item/fish/proc/flop_animation()
var/pause_between = PAUSE_BETWEEN_PHASES + rand(1, 5) //randomized a bit so fish are not in sync
animate(animation_target, time = pause_between, loop = -1)
animate(src, time = pause_between, loop = -1)
//move nose down and up
for(var/_ in 1 to FLOP_COUNT)
var/matrix/up_matrix = matrix()
@@ -565,6 +572,7 @@
animate(time = up_time, pixel_y = JUMP_Y_DISTANCE , pixel_x=x_step, loop = -1, flags= ANIMATION_RELATIVE, easing = BOUNCE_EASING | EASE_IN)
animate(time = up_time, pixel_y = -JUMP_Y_DISTANCE, pixel_x=x_step, loop = -1, flags= ANIMATION_RELATIVE, easing = BOUNCE_EASING | EASE_OUT)
animate(time = PAUSE_BETWEEN_FLOPS, loop = -1)
#undef PAUSE_BETWEEN_PHASES
#undef PAUSE_BETWEEN_FLOPS
#undef FLOP_COUNT
@@ -578,7 +586,7 @@
if(flopping) //Requires update_transform/animate_wrappers to be less restrictive.
return
flopping = TRUE
flop_animation(src)
flop_animation()
/// Stops flopping animation
/obj/item/fish/proc/stop_flopping()
@@ -593,7 +601,7 @@
/obj/item/fish/proc/refresh_flopping()
if(flopping)
flop_animation(src)
flop_animation()
/// Returns random fish, using random_case_rarity probabilities.
/proc/random_fish_type(required_fluid)
+29 -3
View File
@@ -12,6 +12,8 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
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
/// Depending on the value, fish with trait will be reported as more or less difficult in the catalog.
var/added_difficulty = 0
/// 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)
@@ -161,7 +163,7 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
/datum/fish_trait/necrophage
name = "Necrophage"
catalog_description = "This fish will eat the carcasses of dead fishes when hungry."
catalog_description = "This fish will eat carcasses of dead fish when hungry."
incompatible_traits = list(/datum/fish_trait/vegan)
/datum/fish_trait/necrophage/apply_to_fish(obj/item/fish/fish)
@@ -169,7 +171,7 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
/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))
if(!source.is_hungry() || !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))
@@ -233,7 +235,7 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
/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))
if(!source.is_hungry() || !isaquarium(source.loc))
return
var/obj/structure/aquarium/aquarium = source.loc
for(var/obj/item/fish/victim in aquarium.get_fishes(TRUE, source))
@@ -334,6 +336,7 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
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."
added_difficulty = 5
/datum/fish_trait/lubed/apply_to_fish(obj/item/fish/fish)
fish.AddComponent(/datum/component/slippery, 8 SECONDS, SLIDE|GALOSHES_DONT_HELP)
@@ -352,3 +355,26 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
ADD_TRAIT(fish, TRAIT_FISH_AMPHIBIOUS, FISH_TRAIT_DATUM)
if(fish.required_fluid_type == AQUARIUM_FLUID_AIR)
fish.required_fluid_type = AQUARIUM_FLUID_FRESHWATER
/datum/fish_trait/mixotroph
name = "Mixotroph"
inheritability = 75
diff_traits_inheritability = 25
catalog_description = "This fish is capable of substaining itself by producing its own sources of energy (food)."
incompatible_traits = list(/datum/fish_trait/predator, /datum/fish_trait/necrophage)
/datum/fish_trait/antigrav/apply_to_fish(obj/item/fish/fish)
ADD_TRAIT(fish, TRAIT_FISH_NO_HUNGER, FISH_TRAIT_DATUM)
/datum/fish_trait/antigrav
name = "Anti-Gravity"
inheritability = 75
diff_traits_inheritability = 25
catalog_description = "This fish will invert the gravity of the bait at random. May fall upward outside after being caught."
added_difficulty = 15
/datum/fish_trait/antigrav/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
minigame.special_effects |= FISHING_MINIGAME_RULE_ANTIGRAV
/datum/fish_trait/antigrav/apply_to_fish(obj/item/fish/fish)
fish.AddElement(/datum/element/forced_gravity, NEGATIVE_GRAVITY)
+38 -3
View File
@@ -341,7 +341,6 @@
icon_state = "sludgefish_purple"
dedicated_in_aquarium_icon_state = "sludgefish_purple_small"
random_case_rarity = FISH_RARITY_NOPE
random_case_rarity = FISH_RARITY_VERY_RARE
fish_traits = list(/datum/fish_trait/parthenogenesis)
/obj/item/fish/slimefish
@@ -439,11 +438,13 @@
fillet_type = null
death_text = "%SRC gently disappears."
fish_traits = list(/datum/fish_trait/no_mating) //just to be sure, these shouldn't reproduce
experisci_scannable = FALSE
/obj/item/fish/holo/Initialize(mapload)
. = ..()
var/area/station/holodeck/holo_area = get_area(src)
if(!istype(holo_area))
addtimer(CALLBACK(src, PROC_REF(set_status), FISH_DEAD), 1 MINUTES)
return
holo_area.linked.add_to_spawned(src)
@@ -488,9 +489,9 @@
sprite_height = 5
/obj/item/fish/holo/checkered
name = "unrendered holographic fish" //it's a meta joke, buddy.
name = "unrendered holographic fish"
desc = "A checkered silhoutte of searing purple and pitch black presents itself before your eyes, like a tear in fabric of reality. It hurts to watch."
icon_state = "checkered"
icon_state = "checkered" //it's a meta joke, buddy.
dedicated_in_aquarium_icon_state = "checkered_small"
sprite_width = 4
@@ -502,3 +503,37 @@
sprite_height = 4
sprite_width = 10
average_size = 50
/obj/item/fish/starfish
name = "cosmostarfish"
desc = "A peculiar, gravity-defying, echinoderm-looking critter from hyperspace."
icon_state = "starfish"
dedicated_in_aquarium_icon_state = "starfish_small"
icon_state_dead = "starfish_dead"
sprite_width = 4
average_size = 30
average_weight = 300
stable_population = 3
required_fluid_type = AQUARIUM_FLUID_AIR
random_case_rarity = FISH_RARITY_NOPE
required_temperature_min = 0
required_temperature_max = INFINITY
safe_air_limits = null
min_pressure = 0
max_pressure = INFINITY
grind_results = list(/datum/reagent/bluespace = 10, /datum/reagent/consumable/liquidgibs = 5)
fillet_type = null
fish_traits = list(/datum/fish_trait/antigrav, /datum/fish_trait/mixotroph)
/obj/item/fish/starfish/Initialize(mapload)
. = ..()
update_appearance(UPDATE_OVERLAYS)
/obj/item/fish/starfish/update_overlays()
. = ..()
if(status == FISH_ALIVE)
. += emissive_appearance(icon, "starfish_emissive", src)
///It spins, and dimly glows in the dark.
/obj/item/fish/starfish/flop_animation()
DO_FLOATING_ANIM(src)
+10 -8
View File
@@ -68,14 +68,6 @@
if(source.catalog_description && (fish_type in source.fish_table))
spot_descriptions += source.catalog_description
.["spots"] = english_list(spot_descriptions, nothing_text = "Unknown")
///Difficulty descriptor
switch(initial(fishy.fishing_difficulty_modifier))
if(-INFINITY to 10)
.["difficulty"] = "Easy"
if(20 to 30)
.["difficulty"] = "Medium"
else
.["difficulty"] = "Hard"
var/list/fish_list_properties = collect_fish_properties()
var/list/fav_bait = fish_list_properties[fishy][NAMEOF(fishy, favorite_bait)]
var/list/disliked_bait = fish_list_properties[fishy][NAMEOF(fishy, disliked_bait)]
@@ -91,12 +83,22 @@
// Fish traits description
var/list/trait_descriptions = list()
var/list/fish_traits = fish_list_properties[fishy][NAMEOF(fishy, fish_traits)]
var/fish_difficulty = initial(fishy.fishing_difficulty_modifier)
for(var/fish_trait in fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait]
trait_descriptions += trait.catalog_description
fish_difficulty += trait.added_difficulty
if(!length(trait_descriptions))
trait_descriptions += "This fish exhibits no special behavior."
.["traits"] = trait_descriptions
///Difficulty descriptor
switch(fish_difficulty)
if(-INFINITY to 9)
.["difficulty"] = "Easy"
if(10 to 19)
.["difficulty"] = "Medium"
else
.["difficulty"] = "Hard"
return .
/obj/item/book/fish_catalog/ui_assets(mob/user)
+5 -3
View File
@@ -11,7 +11,8 @@
desc = "Simple fishing line."
icon = 'icons/obj/fishing.dmi'
icon_state = "reel_blue"
var/fishing_line_traits = NONE
///A list of traits that this fishing line has, checked by fish traits and the minigame.
var/list/fishing_line_traits
/// Color of the fishing line
var/line_color = "#808080"
@@ -59,7 +60,8 @@
icon_state = "hook"
w_class = WEIGHT_CLASS_TINY
var/fishing_hook_traits = NONE
/// A list of traits that this fishing hook has, checked by fish traits and the minigame
var/list/fishing_hook_traits
/// icon state added to main rod icon when this hook is equipped
var/rod_overlay_icon_state = "hook_overlay"
/// What subtype of `/obj/item/chasm_detritus` do we fish out of chasms? Defaults to `/obj/item/chasm_detritus`.
@@ -175,7 +177,7 @@
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 gets caught in its jaws."
icon_state = "jaws"
fishing_hook_traits = FISHING_HOOK_NO_ESCAPE|FISHING_HOOK_ENSNARE|FISHING_HOOK_KILL
fishing_hook_traits = FISHING_HOOK_NO_ESCAPE|FISHING_HOOK_NO_ESCAPE|FISHING_HOOK_KILL
rod_overlay_icon_state = "hook_jaws_overlay"
/obj/item/storage/toolbox/fishing
+82 -5
View File
@@ -43,8 +43,12 @@
var/fish_ai = FISH_AI_DUMB
/// Rule modifiers (eg weighted bait)
var/special_effects = NONE
/// Did the game get past the baiting phase, used to track if bait should be consumed afterwards
var/bait_taken = FALSE
/// A list of possible active minigame effects. If not empty, one will be picked from time to time.
var/list/active_effects
/// The cooldown between switching active effects
COOLDOWN_DECLARE(active_effect_cd)
/// The current active effect
var/current_active_effect
/// Result path
var/reward_path = FISHING_DUD
/// Minigame difficulty
@@ -61,6 +65,8 @@
var/obj/effect/fishing_lure/lure
/// Background icon state from fishing_hud.dmi
var/background = "background_default"
/// Fish icon state from fishing_hud.dmi
var/fish_icon = "fish"
/// Fishing line visual
var/datum/beam/fishing_line
@@ -168,7 +174,10 @@
RegisterSignal(user, COMSIG_MOB_FISHING_REWARD_DISPENSED, PROC_REF(hurt_fish))
difficulty += comp.fish_source.calculate_difficulty(reward_path, rod, user, src)
difficulty = round(difficulty)
difficulty = clamp(round(difficulty), 1, 100)
if(HAS_TRAIT(user, TRAIT_REVEAL_FISH))
fish_icon = GLOB.specific_fish_icons[reward_path] || "fish"
/**
* If the chances are higher than 1% (100% at maximum difficulty), they'll scale
@@ -219,6 +228,7 @@
/datum/fishing_challenge/proc/start(mob/living/user)
/// Create fishing line visuals
fishing_line = used_rod.create_fishing_line(lure, target_py = 5)
active_effects = bitfield_to_list(special_effects & FISHING_MINIGAME_ACTIVE_EFFECTS)
// If fishing line breaks los / rod gets dropped / deleted
RegisterSignal(fishing_line, COMSIG_QDELETING, PROC_REF(on_line_deleted))
RegisterSignal(used_rod, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_attack_self))
@@ -299,7 +309,30 @@
phase = BITING_PHASE
// Trashing animation
playsound(lure, 'sound/effects/fish_splash.ogg', 100)
send_alert("!!!")
if(HAS_TRAIT(user, TRAIT_REVEAL_FISH))
switch(fish_icon)
if(FISH_ICON_DEF)
send_alert("fish!!!")
if(FISH_ICON_HOSTILE)
send_alert("hostile!!!")
if(FISH_ICON_STAR)
send_alert("starfish!!!")
if(FISH_ICON_CHUNKY)
send_alert("round fish!!!")
if(FISH_ICON_JELLYFISH)
send_alert("jellyfish!!!")
if(FISH_ICON_SLIME)
send_alert("slime!!!")
if(FISH_ICON_COIN)
send_alert("valuable!!!")
if(FISH_ICON_GEM)
send_alert("ore!!!")
if(FISH_ICON_CRAB)
send_alert("crustacean!!!")
if(FISH_ICON_BONE)
send_alert("bones!!!")
else
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
@@ -350,6 +383,9 @@
RegisterSignal(user.client, COMSIG_CLIENT_MOUSEDOWN, PROC_REF(start_reeling))
RegisterSignal(user.client, COMSIG_CLIENT_MOUSEUP, PROC_REF(stop_reeling))
RegisterSignal(user, COMSIG_MOB_LOGOUT, PROC_REF(on_user_logout))
if(length(active_effects))
// Give the player a moment to prepare for active minigame effects
COOLDOWN_START(src, active_effect_cd, rand(5, 9) SECONDS)
START_PROCESSING(SSfishing, src)
///Stop processing and remove references to the minigame hud
@@ -374,11 +410,44 @@
///Update the state of the fish, the bait and the hud
/datum/fishing_challenge/process(seconds_per_tick)
if(length(active_effects) && COOLDOWN_FINISHED(src, active_effect_cd))
select_active_effect()
move_fish(seconds_per_tick)
move_bait(seconds_per_tick)
if(!QDELETED(fishing_hud))
update_visuals()
///The proc that handles fancy effects like flipping the hud or skewing movement
/datum/fishing_challenge/proc/select_active_effect()
///bring forth an active effect
if(isnull(current_active_effect))
current_active_effect = pick(active_effects)
switch(current_active_effect)
if(FISHING_MINIGAME_RULE_ANTIGRAV)
fishing_hud.icon_state = "background_antigrav"
SEND_SOUND(user, sound('sound/effects/arcade_jump.ogg', volume = 50))
COOLDOWN_START(src, active_effect_cd, rand(6, 9) SECONDS)
if(FISHING_MINIGAME_RULE_FLIP)
fishing_hud.icon_state = "background_flip"
fishing_hud.transform = fishing_hud.transform.Scale(1, -1)
SEND_SOUND(user, sound('sound/effects/boing.ogg'))
COOLDOWN_START(src, active_effect_cd, rand(5, 6) SECONDS)
return
///go back to normal
switch(current_active_effect)
if(FISHING_MINIGAME_RULE_ANTIGRAV)
var/sound/inverted_sound = sound('sound/effects/arcade_jump.ogg', volume = 50)
inverted_sound.frequency = -1
SEND_SOUND(user, inverted_sound)
COOLDOWN_START(src, active_effect_cd, rand(10, 13) SECONDS)
if(FISHING_MINIGAME_RULE_FLIP)
fishing_hud.transform = fishing_hud.transform.Scale(1, -1)
COOLDOWN_START(src, active_effect_cd, rand(8, 12) SECONDS)
fishing_hud.icon_state = background
current_active_effect = null
///The proc that moves the fish around, just like in the old TGUI, mostly.
/datum/fishing_challenge/proc/move_fish(seconds_per_tick)
var/long_chance = long_jump_chance * seconds_per_tick * 10
@@ -469,6 +538,9 @@
velocity_change = round(velocity_change)
if(current_active_effect == FISHING_MINIGAME_RULE_ANTIGRAV)
velocity_change = -velocity_change
/**
* Pull the brake on the velocity if the current velocity and the acceleration
* have different directions, making the bait less slippery, thus easier to control
@@ -529,7 +601,7 @@
icon_state = challenge.background
add_overlay("frame")
hud_bait = new(null, null, challenge)
hud_fish = new
hud_fish = new(null, null, challenge)
hud_completion = new(null, null, challenge)
vis_contents += list(hud_bait, hud_fish, hud_completion)
challenge.user.client.screen += src
@@ -562,6 +634,11 @@
icon_state = "fish"
vis_flags = VIS_INHERIT_ID
/atom/movable/screen/hud_fish/Initialize(mapload, datum/hud/hud_owner, datum/fishing_challenge/challenge)
. = ..()
if(challenge)
icon_state = challenge.fish_icon
/atom/movable/screen/hud_completion
icon = 'icons/hud/fishing_hud.dmi'
icon_state = "completion_0"
+68 -14
View File
@@ -1,40 +1,58 @@
/obj/machinery/fishing_portal_generator
name = "fish-porter 3000"
desc = "fishing anywhere, anytime, anyway what was i talking about"
desc = "Fishing anywhere, anytime... anyway what was I talking about?"
icon = 'icons/obj/fishing.dmi'
icon_state = "portal_off"
icon_state = "portal"
idle_power_usage = 0
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 2
anchored = FALSE
density = TRUE
circuit = /obj/item/circuitboard/machine/fishing_portal_generator
var/fishing_source = /datum/fish_source/portal
///The current fishing spot loaded in
var/datum/component/fishing_spot/active
/obj/machinery/fishing_portal_generator/on_set_panel_open()
update_appearance()
return ..()
/obj/machinery/fishing_portal_generator/wrench_act(mob/living/user, obj/item/tool)
. = ..()
default_unfasten_wrench(user, tool)
return TOOL_ACT_TOOLTYPE_SUCCESS
/obj/machinery/fishing_portal_generator/examine(mob/user)
. = ..()
. += span_notice("You can unlock further portal settings by completing fish scanning experiments.")
/obj/machinery/fishing_portal_generator/emag_act(mob/user, obj/item/card/emag/emag_card)
if(obj_flags & EMAGGED)
return FALSE
obj_flags |= EMAGGED
balloon_alert(user, "syndicate setting loaded")
playsound(src, SFX_SPARKS, 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
return TRUE
/obj/machinery/fishing_portal_generator/interact(mob/user, special_state)
. = ..()
if(active)
deactivate()
else
activate()
select_fish_source(user)
/obj/machinery/fishing_portal_generator/update_icon(updates)
/obj/machinery/fishing_portal_generator/update_overlays()
. = ..()
if(active)
icon_state = "portal_on"
else
icon_state = "portal_off"
if(panel_open)
. += "portal_open"
if(!active)
return
. += "portal_on"
var/datum/fish_source/portal/portal = active.fish_source
. += portal.overlay_state
. += emissive_appearance(icon, "portal_emissive", src)
/obj/machinery/fishing_portal_generator/proc/activate()
active = AddComponent(/datum/component/fishing_spot, fishing_source)
/obj/machinery/fishing_portal_generator/proc/activate(datum/fish_source/selected_source)
active = AddComponent(/datum/component/fishing_spot, selected_source)
use_power = ACTIVE_POWER_USE
update_icon()
@@ -46,3 +64,39 @@
/obj/machinery/fishing_portal_generator/on_set_is_operational(old_value)
if(old_value)
deactivate()
///Create a radial menu from a list of available fish sources. If only the default is available, activate it right away.
/obj/machinery/fishing_portal_generator/proc/select_fish_source(mob/user)
var/datum/fish_source/portal/default = GLOB.preset_fish_sources[/datum/fish_source/portal]
var/list/available_fish_sources = list(default.radial_name = default)
if(obj_flags & EMAGGED)
var/datum/fish_source/portal/syndicate = GLOB.preset_fish_sources[/datum/fish_source/portal/syndicate]
available_fish_sources[syndicate.radial_name] = syndicate
for (var/datum/techweb/techweb as anything in SSresearch.techwebs)
var/get_fish_sources = FALSE
for(var/obj/machinery/rnd/server/server as anything in techweb.techweb_servers)
if(!is_valid_z_level(get_turf(server), get_turf(src)))
continue
get_fish_sources = TRUE
break
if(!get_fish_sources)
continue
for(var/experiment_type in typesof(/datum/experiment/scanning/fish))
var/datum/experiment/scanning/fish/experiment = techweb.completed_experiments[experiment_type]
if(!experiment)
continue
var/datum/fish_source/portal/reward = GLOB.preset_fish_sources[experiment.fish_source_reward]
available_fish_sources[reward.radial_name] = reward
if(length(available_fish_sources) == 1)
activate(default)
return
var/list/choices = list()
for(var/radial_name in available_fish_sources)
var/datum/fish_source/portal/source = available_fish_sources[radial_name]
choices[radial_name] = image(icon = 'icons/hud/radial_fishing.dmi', icon_state = source.radial_state)
var/choice = show_radial_menu(user, src, choices, radius = 38, custom_check = CALLBACK(src, TYPE_PROC_REF(/atom, can_interact), user), tooltips = TRUE)
if(!choice || !can_interact(user))
return
activate(available_fish_sources[choice])
+21 -2
View File
@@ -67,7 +67,6 @@
/obj/item/fishing_rod/examine(mob/user)
. = ..()
. += "<b>Right-Click</b> in your active hand to access its slots UI"
var/list/equipped_stuff = list()
if(line)
equipped_stuff += "[icon2html(line, user)] <b>[line.name]</b>"
@@ -79,6 +78,7 @@
. += span_notice("\a [icon2html(bait, user)] <b>[bait]</b> is being used as bait.")
else
. += span_warning("It doesn't have any bait attached. Fishing will be more tedious!")
. += span_notice("<b>Right-Click</b> in your active hand to access its slots UI")
/**
* Catch weight modifier for the given fish_type (or FISHING_DUD)
@@ -185,6 +185,7 @@
return
currently_hooked_item = target_atom
create_fishing_line(target_atom)
SEND_SIGNAL(src, COMSIG_FISHING_ROD_HOOKED_ITEM, target_atom, user)
/// Checks what can be hooked
/obj/item/fishing_rod/proc/can_be_hooked(atom/movable/target)
@@ -508,16 +509,34 @@
/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."
ui_description = "This rod has an infinite supply of synth-bait. Also doubles as an Experi-Scanner for fish."
icon_state = "fishing_rod_science"
reel_overlay = "reel_science"
/obj/item/fishing_rod/tech/Initialize(mapload)
. = ..()
var/static/list/fishing_signals = list(
COMSIG_FISHING_ROD_HOOKED_ITEM = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_handheld_experiment),
COMSIG_FISHING_ROD_CAUGHT_FISH = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_handheld_experiment),
COMSIG_ITEM_PRE_ATTACK = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_handheld_experiment),
COMSIG_ITEM_AFTERATTACK = TYPE_PROC_REF(/datum/component/experiment_handler, ignored_handheld_experiment_attempt),
)
AddComponent(/datum/component/experiment_handler, \
config_mode = EXPERIMENT_CONFIG_ALTCLICK, \
allowed_experiments = list(/datum/experiment/scanning/fish), \
config_flags = EXPERIMENT_CONFIG_SILENT_FAIL|EXPERIMENT_CONFIG_IMMEDIATE_ACTION, \
experiment_signals = fishing_signals, \
)
var/obj/item/food/bait/doughball/synthetic/infinite_supply_of_bait = new(src)
bait = infinite_supply_of_bait
update_icon()
/obj/item/fishing_rod/tech/examine(mob/user)
. = ..()
. += span_notice("<b>Alt-Click</b> to access the Experiment Configuration UI")
/obj/item/fishing_rod/tech/consume_bait(atom/movable/reward)
return
+46 -4
View File
@@ -1,11 +1,47 @@
GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_source, list()))
/**
* When adding new fishable rewards to a table/counts, you can specify an icon to show in place of the
* generic fish icon in the minigame UI should the user have the TRAIT_REVEAL_FISH trait, by adding it to
* this list.
*
* A lot of the icons here may be a tad inaccurate, but since we're limited to the free font awesome icons we
* have access to, we got to make do.
*/
GLOBAL_LIST_INIT(specific_fish_icons, zebra_typecacheof(list(
/mob/living/basic/carp = FISH_ICON_DEF,
/mob/living/basic/mining = FISH_ICON_HOSTILE,
/obj/effect/decal/remains = FISH_ICON_BONE,
/obj/effect/mob_spawn/corpse = FISH_ICON_BONE,
/obj/item/coin = FISH_ICON_COIN,
/obj/item/fish = FISH_ICON_DEF,
/obj/item/fish/armorfish = FISH_ICON_CRAB,
/obj/item/fish/boned = FISH_ICON_BONE,
/obj/item/fish/chasm_crab = FISH_ICON_CRAB,
/obj/item/fish/gunner_jellyfish = FISH_ICON_JELLYFISH,
/obj/item/fish/holo/crab = FISH_ICON_CRAB,
/obj/item/fish/holo/puffer = FISH_ICON_CHUNKY,
/obj/item/fish/mastodon = FISH_ICON_BONE,
/obj/item/fish/pufferfish = FISH_ICON_CHUNKY,
/obj/item/fish/slimefish = FISH_ICON_SLIME,
/obj/item/fish/sludgefish = FISH_ICON_SLIME,
/obj/item/fish/starfish = FISH_ICON_STAR,
/obj/item/storage/wallet = FISH_ICON_COIN,
/obj/item/stack/sheet/bone = FISH_ICON_BONE,
/obj/item/stack/sheet/mineral = FISH_ICON_GEM,
/obj/item/stack/ore = FISH_ICON_GEM,
/obj/structure/closet/crate = FISH_ICON_COIN,
)))
/**
* 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
/**
* Fish catch weight table - these are relative weights
*
*/
var/list/fish_table = list()
/// If a key from fish_table is present here, that fish is availible in limited quantity and is reduced by one on successful fishing
var/list/fish_counts = list()
@@ -25,6 +61,10 @@ GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_sour
if(!(path in fish_table))
stack_trace("path [path] found in the 'fish_counts' list but not in the fish_table one of [type]")
///Called when src is set as the fish source of a fishing spot component
/datum/fish_source/proc/on_fishing_spot_init(/datum/component/fishing_spot/spot)
return
/// Can we fish in this spot at all. Returns DENIAL_REASON or null if we're good to go
/datum/fish_source/proc/reason_we_cant_fish(obj/item/fishing_rod/rod, mob/fisherman)
return rod.reason_we_cant_fish(src)
@@ -35,7 +75,7 @@ GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_sour
* This includes the source's fishing difficulty, that of the fish, the rod,
* favorite and disliked baits, fish traits and the fisherman skill.
*
* For non-fish, it's just the source's fishing difficulty minus the fisherman skill, rod and settler modifiers.
* For non-fish, it's just the source's fishing difficulty minus the fisherman skill.
*/
/datum/fish_source/proc/calculate_difficulty(result, obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/challenge)
. = fishing_difficulty
@@ -96,7 +136,7 @@ GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_sour
* 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)
/datum/fish_source/proc/pre_challenge_started(obj/item/fishing_rod/rod, mob/user, datum/fishing_challenge/challenge)
return
///Proc called when the challenge is interrupted within the fish source code.
@@ -116,7 +156,9 @@ GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_sour
user.add_mob_memory(/datum/memory/caught_fish, protagonist = user, deuteragonist = initial(caught.name))
var/turf/fishing_spot = get_turf(source.lure)
var/atom/movable/reward = dispense_reward(source.reward_path, user, fishing_spot)
source.used_rod?.consume_bait(reward)
if(source.used_rod)
SEND_SIGNAL(source.used_rod, COMSIG_FISHING_ROD_CAUGHT_FISH, reward, user)
source.used_rod.consume_bait(reward)
/// Gives out the reward if possible
/datum/fish_source/proc/dispense_reward(reward_path, mob/fisherman, turf/fishing_spot)
+161 -4
View File
@@ -19,11 +19,168 @@
/datum/fish_source/portal
fish_table = list(
FISHING_DUD = 5,
FISHING_DUD = 7,
/obj/item/fish/goldfish = 10,
/obj/item/fish/guppy = 10,
/obj/item/fish/angelfish = 10,
)
catalog_description = "Fish dimension (Fishing portal generator)"
catalog_description = "Aquarium dimension (Fishing portal generator)"
///The name of this option shown in the radial menu on the fishing portal generator
var/radial_name = "Aquarium"
///The icon state shown for this option in the radial menu
var/radial_state = "fish_tank"
///The icon state of the overlay shown on the machine when active.
var/overlay_state = "portal_aquarium"
/datum/fish_source/portal/beach
fish_table = list(
FISHING_DUD = 10,
/obj/item/fish/clownfish = 10,
/obj/item/fish/pufferfish = 10,
/obj/item/fish/cardinal = 10,
/obj/item/fish/greenchromis = 10,
)
catalog_description = "Beach dimension (Fishing portal generator)"
radial_name = "Beach"
radial_state = "palm_beach"
/datum/fish_source/portal/chasm
background = "background_lavaland"
fish_table = list(
FISHING_DUD = 5,
/obj/item/fish/chasm_crab = 10,
/obj/item/fish/boned = 5,
/obj/item/stack/sheet/bone = 5,
)
catalog_description = "Chasm dimension (Fishing portal generator)"
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 10
radial_name = "Chasm"
overlay_state = "portal_chasm"
radial_state = "ground_hole"
/datum/fish_source/portal/ocean
fish_table = list(
FISHING_DUD = 5,
/obj/item/fish/lanternfish = 5,
/obj/item/fish/firefish = 5,
/obj/item/fish/dwarf_moonfish = 5,
/obj/item/fish/gunner_jellyfish = 5,
/obj/item/fish/needlefish = 5,
/obj/item/fish/armorfish = 5,
)
catalog_description = "Ocean dimension (Fishing portal generator)"
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 10
radial_name = "Ocean"
overlay_state = "portal_ocean"
radial_state = "seaboat"
/datum/fish_source/portal/hyperspace
fish_table = list(
FISHING_DUD = 5,
/obj/item/fish/starfish = 6,
/obj/item/stack/ore/bluespace_crystal = 2,
/mob/living/basic/carp = 2,
)
fish_counts = list(
/obj/item/stack/ore/bluespace_crystal = 10,
)
catalog_description = "Hyperspace dimension (Fishing portal generator)"
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 10
radial_name = "Hyperspace"
overlay_state = "portal_hyperspace"
radial_state = "space_rocket"
///Unlocked by emagging the fishing portal generator with an emag.
/datum/fish_source/portal/syndicate
background = "background_lavaland"
fish_table = list(
FISHING_DUD = 5,
/obj/item/fish/donkfish = 5,
/obj/item/fish/emulsijack = 5,
)
catalog_description = "Syndicate dimension (Fishing portal generator)"
radial_name = "Syndicate"
fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 15
overlay_state = "portal_syndicate"
radial_state = "syndi_snake"
/**
* A special portal fish source which fish table is populated on init with the contents of all
* portal fish sources, except for FISHING_DUD, and a couple more caveats.
*/
/datum/fish_source/portal/random
fish_table = null //It's populated the first time the source is loaded on a fishing portal generator.
catalog_description = null // it'd make a bad entry in the catalog.
radial_name = "Randomizer"
overlay_state = "portal_randomizer"
var/static/list/all_portal_fish_sources_at_once
radial_state = "misaligned_question_mark"
///Generate the fish table if we don't have one already.
/datum/fish_source/portal/random/on_fishing_spot_init(datum/component/fishing_spot/spot)
if(fish_table)
return
///rewards not found in other fishing portals
fish_table = list(
/obj/item/fish/holo/checkered = 1,
)
for(var/portal_type in GLOB.preset_fish_sources)
if(portal_type == type || !ispath(portal_type, /datum/fish_source/portal))
continue
var/datum/fish_source/portal/preset_portal = GLOB.preset_fish_sources[portal_type]
fish_table |= preset_portal.fish_table
///We don't serve duds.
fish_table -= FISHING_DUD
for(var/reward_path in fish_table)
fish_table[reward_path] = rand(1, 4)
///Difficulty has to be calculated before the rest, because of how it influences jump chances
/datum/fish_source/portal/random/calculate_difficulty(result, obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/challenge)
. = ..()
. += rand(-10, 15)
///In the spirit of randomness, we skew a few values here and there
/datum/fish_source/portal/random/pre_challenge_started(obj/item/fishing_rod/rod, mob/user, datum/fishing_challenge/challenge)
challenge.bait_bounce_mult = clamp(challenge.bait_bounce_mult + (rand(-3, 3) * 0.1), 0.1, 1)
challenge.completion_loss = max(challenge.completion_loss + rand(-2, 2), 0)
challenge.completion_gain = max(challenge.completion_gain + rand(-1, 1), 2)
challenge.short_jump_velocity_limit += rand(-100, 100)
challenge.long_jump_velocity_limit += rand(-100, 100)
var/static/list/active_effects = bitfield_to_list(FISHING_MINIGAME_ACTIVE_EFFECTS)
for(var/effect in active_effects)
if(prob(30))
challenge.special_effects |= effect
///Cherry on top, fish caught from the randomizer portal also have (almost completely) random traits
/datum/fish_source/portal/random/spawn_reward(reward_path, mob/fisherman, turf/fishing_spot)
if(!ispath(reward_path, /obj/item/fish))
return ..()
var/static/list/weighted_traits
if(!weighted_traits)
weighted_traits = list()
for(var/trait_type as anything in GLOB.fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
weighted_traits[trait.type] = round(trait.inheritability**2/100)
var/obj/item/fish/caught_fish = new reward_path(get_turf(fisherman), FALSE)
var/list/fixed_traits = list()
for(var/trait_type in caught_fish.fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
if(caught_fish.type in trait.guaranteed_inheritance_types)
fixed_traits += trait_type
var/list/new_traits = list()
for(var/iteration in rand(1, 4))
new_traits |= pick_weight(weighted_traits)
caught_fish.inherit_traits(new_traits, fixed_traits = fixed_traits)
caught_fish.randomize_size_and_weight(deviation = 0.3)
caught_fish.progenitors = full_capitalize(caught_fish.name)
return caught_fish
/datum/fish_source/chasm
catalog_description = "Chasm depths"
@@ -99,7 +256,7 @@
fish_table = list(
FISHING_DUD = 18,
/obj/item/fish/sludgefish = 18,
/obj/item/fish/slimefish = 2,
/obj/item/fish/slimefish = 4,
/obj/item/storage/wallet/money = 2,
)
fish_counts = list(
@@ -125,7 +282,7 @@
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)
/datum/fish_source/holographic/pre_challenge_started(obj/item/fishing_rod/rod, mob/user, datum/fishing_challenge/challenge)
RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(check_area))
/datum/fish_source/holographic/proc/check_area(mob/user)
@@ -489,4 +489,13 @@
activate_message = span_notice("You recall learning from your grandmother how they baked their cookies with love.")
deactivate_message = span_notice("You forget all memories imparted upon you by your grandmother. Were they even your real grandma?")
/obj/item/skillchip/master_angler
name = "Mast-Angl-Er skillchip"
auto_traits = list(TRAIT_REVEAL_FISH)
skill_name = "Fisherman's Discernment"
skill_description = "While fishing, it'll make a smidge easier to guess whatever you're trying to catch."
skill_icon = "fish"
activate_message = span_notice("You feel the knowledge and passion of several sunbaked, seasoned fishermen burn within you.")
deactivate_message = span_notice("You no longer feel like casting a fishing rod by the sunny riverside.")
#undef SKILLCHIP_CATEGORY_GENERAL
@@ -1096,3 +1096,13 @@
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ROBOTICS
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
/datum/design/board/fishing_portal_generator
name = "Fishing Portal Generator Board"
desc = "The circuit board for the fishing portal generator"
id = "fishing_portal_generator"
build_path = /obj/item/circuitboard/machine/fishing_portal_generator
category = list(
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_SERVICE
)
departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE
+13 -5
View File
@@ -283,7 +283,7 @@
var/datum/experiment/experiment = completed_experiment
if (experiment == experiment_type)
return FALSE
available_experiments += new experiment_type()
available_experiments += new experiment_type(src)
/**
* Adds a list of experiments to this techweb by their types, ensures that no duplicates are added.
@@ -310,13 +310,21 @@
var/refund = skipped_experiment_types[completed_experiment.type] || 0
if(refund > 0)
add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = refund))
result_text += ", refunding [refund] points."
result_text += ", refunding [refund] points"
// Nothing more to gain here, but we keep it in the list to prevent double dipping
skipped_experiment_types[completed_experiment.type] = -1
else
result_text += "!"
var/points_rewarded
if(completed_experiment.points_reward)
add_point_list(completed_experiment.points_reward)
points_rewarded = ",[refund > 0 ? " and" : ""] rewarding "
var/list/english_list_keys = list()
for(var/points_type in completed_experiment.points_reward)
english_list_keys += "[completed_experiment.points_reward[points_type]] [points_type]"
points_rewarded += "[english_list(english_list_keys)] points"
result_text += points_rewarded
result_text += "!"
log_research("[completed_experiment.name] ([completed_experiment.type]) has been completed on techweb [id]/[organization][refund ? ", refunding [refund] points" : ""].")
log_research("[completed_experiment.name] ([completed_experiment.type]) has been completed on techweb [id]/[organization][refund ? ", refunding [refund] points" : ""][points_rewarded].")
return result_text
/datum/techweb/proc/printout_points()
+14 -14
View File
@@ -49,6 +49,7 @@
"extinguisher",
"fax",
"fishing_rod",
"fishing_portal_generator",
"flashlight",
"fluid_ducts",
"foam_dart",
@@ -1490,6 +1491,19 @@
required_experiments = list(/datum/experiment/scanning/random/plants/wild)
discount_experiments = list(/datum/experiment/scanning/random/plants/traits = 3000)
/datum/techweb_node/fishing
id = "fishing"
display_name = "Fishing Technology"
description = "Cutting edge fishing advancements."
prereq_ids = list("base")
design_ids = list(
"fishing_rod_tech",
"stabilized_hook",
"fish_analyzer",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000)
required_experiments = list(/datum/experiment/scanning/fish)
/datum/techweb_node/exp_tools
id = "exp_tools"
display_name = "Experimental Tools"
@@ -2354,20 +2368,6 @@
hidden = TRUE
experimental = TRUE
/datum/techweb_node/fishing
id = "fishing"
display_name = "Fishing Technology"
description = "Cutting edge fishing advancements."
prereq_ids = list("base")
design_ids = list(
"fishing_rod_tech",
"stabilized_hook",
"fish_analyzer",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
hidden = TRUE
experimental = TRUE
/datum/techweb_node/advanced_plastic_surgery
id = "plastic_surgery"
display_name = "Advanced Plastic Surgery"
+16 -3
View File
@@ -28,10 +28,10 @@
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(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(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.
@@ -41,11 +41,24 @@
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_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")
/datum/unit_test/fish_scanning
/datum/unit_test/fish_scanning/Run()
var/scannable_fishes = 0
for(var/obj/item/fish/fish_prototype as anything in subtypesof(/obj/item/fish))
if(initial(fish_prototype.experisci_scannable))
scannable_fishes++
for(var/datum/experiment/scanning/fish/fish_scan as anything in typesof(/datum/experiment/scanning/fish))
fish_scan = new fish_scan
var/scan_key = fish_scan.required_atoms[1]
if(fish_scan.required_atoms[scan_key] > scannable_fishes)
TEST_FAIL("[fish_scan.type] has requirements higher than the number of scannable fish types in the game: [scannable_fishes]")
///dummy fish item used for the tests, as well with related subtypes and datums.
/obj/item/fish/testdummy
grind_results = list()
+1
View File
@@ -56,6 +56,7 @@
/obj/item/skillchip/sabrage = 2,
/obj/item/skillchip/useless_adapter = 5,
/obj/item/skillchip/wine_taster = 2,
/obj/item/skillchip/master_angler = 2,
),
),
list(
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

+6
View File
@@ -99,3 +99,9 @@ https://freesound.org/people/FunWithSound/sounds/456965/
beeps_jingle.ogg is adapted from Eponn's "Achievement happy Beeps Jingle", which is public domain (CC 0):
https://freesound.org/people/Eponn/sounds/619838/
boing.ogg is adapted from reelworldstudio's "Cartoon Boing", which is public domain (CC 0):
https://freesound.org/people/reelworldstudio/sounds/161122/
arcade_jump.ogg is adapted from se2001's "8-Bit Jump 3", which is public domain (CC 0):
hhttps://freesound.org/people/se2001/sounds/528568/
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -39,7 +39,6 @@
#include "code\__DEFINES\antagonists.dm"
#include "code\__DEFINES\apc_defines.dm"
#include "code\__DEFINES\appearance.dm"
#include "code\__DEFINES\aquarium.dm"
#include "code\__DEFINES\area_editor.dm"
#include "code\__DEFINES\art.dm"
#include "code\__DEFINES\assemblies.dm"
@@ -93,7 +92,7 @@
#include "code\__DEFINES\external_organs.dm"
#include "code\__DEFINES\fantasy_affixes.dm"
#include "code\__DEFINES\firealarm.dm"
#include "code\__DEFINES\fishing.dm"
#include "code\__DEFINES\fish.dm"
#include "code\__DEFINES\flags.dm"
#include "code\__DEFINES\flora.dm"
#include "code\__DEFINES\font_awesome_icons.dm"
@@ -3759,6 +3758,7 @@
#include "code\modules\experisci\experiment\types\physical_experiment.dm"
#include "code\modules\experisci\experiment\types\random_scanning.dm"
#include "code\modules\experisci\experiment\types\scanning.dm"
#include "code\modules\experisci\experiment\types\scanning_fish.dm"
#include "code\modules\experisci\experiment\types\scanning_machinery.dm"
#include "code\modules\experisci\experiment\types\scanning_material.dm"
#include "code\modules\experisci\experiment\types\scanning_people.dm"
@@ -142,7 +142,7 @@ export const ExperimentConfigure = (props, context) => {
</Flex.Item>
<Flex.Item>
{experiments.map((exp, i) => {
return <Experiment key={i} exp={exp} controllable />;
return <Experiment key={i} exp={exp} />;
})}
</Flex.Item>
</Section>
@@ -168,44 +168,24 @@ export const ExperimentConfigure = (props, context) => {
export const Experiment = (props, context) => {
const { act, data } = useBackend(context);
const { exp, controllable } = props;
const {
name,
description,
tag,
selectable,
selected,
progress,
performance_hint,
ref,
} = exp;
const { exp } = props;
const { name, description, tag, selected, progress, performance_hint, ref } =
exp;
return (
<Box m={1} key={ref} className="ExperimentConfigure__ExperimentPanel">
<Button
fluid
onClick={() =>
controllable &&
(selected
selected
? act('clear_experiment')
: act('select_experiment', { 'ref': ref }))
: act('select_experiment', { 'ref': ref })
}
backgroundColor={selected ? 'good' : '#40628a'}
className="ExperimentConfigure__ExperimentName"
disabled={controllable && !selectable}>
className="ExperimentConfigure__ExperimentName">
<Flex align="center" justify="space-between">
<Flex.Item
color={
!controllable || selectable ? 'white' : 'rgba(0, 0, 0, 0.6)'
}>
{name}
</Flex.Item>
<Flex.Item
color={
!controllable || selectable
? 'rgba(255, 255, 255, 0.5)'
: 'rgba(0, 0, 0, 0.5)'
}>
<Flex.Item color={'white'}>{name}</Flex.Item>
<Flex.Item color={'rgba(255, 255, 255, 0.5)'}>
<Box className="ExperimentConfigure__TagContainer">
{tag}
<Tooltip content={performance_hint} position="bottom-start">