diff --git a/code/__DEFINES/aquarium.dm b/code/__DEFINES/aquarium.dm deleted file mode 100644 index 6e0a39f5040..00000000000 --- a/code/__DEFINES/aquarium.dm +++ /dev/null @@ -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." diff --git a/code/__DEFINES/dcs/signals/signals_fish.dm b/code/__DEFINES/dcs/signals/signals_fish.dm index f65b8c99805..90e580e9166 100644 --- a/code/__DEFINES/dcs/signals/signals_fish.dm +++ b/code/__DEFINES/dcs/signals/signals_fish.dm @@ -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" diff --git a/code/__DEFINES/experisci.dm b/code/__DEFINES/experisci.dm index 9130e0ae565..c74290ff5f7 100644 --- a/code/__DEFINES/experisci.dm +++ b/code/__DEFINES/experisci.dm @@ -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) diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm new file mode 100644 index 00000000000..89b7963d91d --- /dev/null +++ b/code/__DEFINES/fish.dm @@ -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." diff --git a/code/__DEFINES/fishing.dm b/code/__DEFINES/fishing.dm deleted file mode 100644 index dc73623f27c..00000000000 --- a/code/__DEFINES/fishing.dm +++ /dev/null @@ -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 diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index cd110f2464d..73fbe6723e6 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -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" diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index 54fe44a609b..512b528f86f 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -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, ), )) diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index 3f230b8754d..f88c27a7135 100644 --- a/code/datums/components/fishing_spot.dm +++ b/code/datums/components/fishing_spot.dm @@ -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)) diff --git a/code/datums/mind/skills.dm b/code/datums/mind/skills.dm index 5847236435b..474291d5ae0 100644 --- a/code/datums/mind/skills.dm +++ b/code/datums/mind/skills.dm @@ -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) diff --git a/code/datums/skills/_skill.dm b/code/datums/skills/_skill.dm index ec52ebd9e96..b8438c67927 100644 --- a/code/datums/skills/_skill.dm +++ b/code/datums/skills/_skill.dm @@ -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]!"), - "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("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 /** diff --git a/code/datums/skills/fishing.dm b/code/datums/skills/fishing.dm index d5ecff3c8f5..ddf90e1a0a3 100644 --- a/code/datums/skills/fishing.dm +++ b/code/datums/skills/fishing.dm @@ -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) diff --git a/code/game/machinery/computer/operating_computer.dm b/code/game/machinery/computer/operating_computer.dm index f7b0ef24ffd..3b639a9ce57 100644 --- a/code/game/machinery/computer/operating_computer.dm +++ b/code/game/machinery/computer/operating_computer.dm @@ -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() diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index faa48559100..4bea5b1335c 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -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" diff --git a/code/modules/cargo/packs/general.dm b/code/modules/cargo/packs/general.dm index fa7a3bfa374..5bfcf01eb00 100644 --- a/code/modules/cargo/packs/general.dm +++ b/code/modules/cargo/packs/general.dm @@ -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.\ diff --git a/code/modules/experisci/destructive_scanner.dm b/code/modules/experisci/destructive_scanner.dm index 3591a31cd7b..ef89dc9b94a 100644 --- a/code/modules/experisci/destructive_scanner.dm +++ b/code/modules/experisci/destructive_scanner.dm @@ -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() diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index 8eee5bcc592..577ae59a900 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -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 diff --git a/code/modules/experisci/experiment/handlers/experiment_handler.dm b/code/modules/experisci/experiment/handlers/experiment_handler.dm index abc5d4ad1dd..29e7da95391 100644 --- a/code/modules/experisci/experiment/handlers/experiment_handler.dm +++ b/code/modules/experisci/experiment/handlers/experiment_handler.dm @@ -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, diff --git a/code/modules/experisci/experiment/types/experiment.dm b/code/modules/experisci/experiment/types/experiment.dm index f760723f8db..add015622f6 100644 --- a/code/modules/experisci/experiment/types/experiment.dm +++ b/code/modules/experisci/experiment/types/experiment.dm @@ -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. */ diff --git a/code/modules/experisci/experiment/types/exploration.dm b/code/modules/experisci/experiment/types/exploration.dm index a6a5d2cd4cf..821e69a103a 100644 --- a/code/modules/experisci/experiment/types/exploration.dm +++ b/code/modules/experisci/experiment/types/exploration.dm @@ -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) diff --git a/code/modules/experisci/experiment/types/random_scanning.dm b/code/modules/experisci/experiment/types/random_scanning.dm index e80a80b5a0e..c9d39bd47b4 100644 --- a/code/modules/experisci/experiment/types/random_scanning.dm +++ b/code/modules/experisci/experiment/types/random_scanning.dm @@ -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 diff --git a/code/modules/experisci/experiment/types/scanning.dm b/code/modules/experisci/experiment/types/scanning.dm index d9bbed88c8f..54bd2ad637e 100644 --- a/code/modules/experisci/experiment/types/scanning.dm +++ b/code/modules/experisci/experiment/types/scanning.dm @@ -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) diff --git a/code/modules/experisci/experiment/types/scanning_fish.dm b/code/modules/experisci/experiment/types/scanning_fish.dm new file mode 100644 index 00000000000..83978010869 --- /dev/null +++ b/code/modules/experisci/experiment/types/scanning_fish.dm @@ -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 += "" + 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 += "" + 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 diff --git a/code/modules/experisci/experiment/types/scanning_material.dm b/code/modules/experisci/experiment/types/scanning_material.dm index 714205289de..fb8a7ff354b 100644 --- a/code/modules/experisci/experiment/types/scanning_material.dm +++ b/code/modules/experisci/experiment/types/scanning_material.dm @@ -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) diff --git a/code/modules/experisci/experiment/types/scanning_plants.dm b/code/modules/experisci/experiment/types/scanning_plants.dm index c34822d6e7e..b92a4cc20b4 100644 --- a/code/modules/experisci/experiment/types/scanning_plants.dm +++ b/code/modules/experisci/experiment/types/scanning_plants.dm @@ -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) diff --git a/code/modules/experisci/handheld_scanner.dm b/code/modules/experisci/handheld_scanner.dm index e0fd4d480d5..97aa034afa1 100644 --- a/code/modules/experisci/handheld_scanner.dm +++ b/code/modules/experisci/handheld_scanner.dm @@ -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!")) diff --git a/code/modules/fishing/aquarium/fish_analyzer.dm b/code/modules/fishing/aquarium/fish_analyzer.dm index c7feab860ca..8afe053a4d5 100644 --- a/code/modules/fishing/aquarium/fish_analyzer.dm +++ b/code/modules/fishing/aquarium/fish_analyzer.dm @@ -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 += "Hunger: [hunger_string]\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 += "Hunger: [hunger_string]\n" var/time_left = round(max(fish.breeding_wait - world.time, 0)/10) render_list += "Time until it can breed: [time_left] seconds" diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index 21585a07546..1275886141b 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -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) diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm index bec868ad24e..63de4cfdf1d 100644 --- a/code/modules/fishing/fish/fish_traits.dm +++ b/code/modules/fishing/fish/fish_traits.dm @@ -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) diff --git a/code/modules/fishing/fish/fish_types.dm b/code/modules/fishing/fish/fish_types.dm index 652b0aba8aa..5ff62266ad9 100644 --- a/code/modules/fishing/fish/fish_types.dm +++ b/code/modules/fishing/fish/fish_types.dm @@ -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) diff --git a/code/modules/fishing/fish_catalog.dm b/code/modules/fishing/fish_catalog.dm index d41b4657e50..a0f66c2227d 100644 --- a/code/modules/fishing/fish_catalog.dm +++ b/code/modules/fishing/fish_catalog.dm @@ -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) diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm index 6169b41fd88..3f34222ad87 100644 --- a/code/modules/fishing/fishing_equipment.dm +++ b/code/modules/fishing/fishing_equipment.dm @@ -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 diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index c0a8c53e0b0..06ac03d64ac 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -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" diff --git a/code/modules/fishing/fishing_portal_machine.dm b/code/modules/fishing/fishing_portal_machine.dm index 3fc6b0eb938..b156a37ba05 100644 --- a/code/modules/fishing/fishing_portal_machine.dm +++ b/code/modules/fishing/fishing_portal_machine.dm @@ -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]) diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index ed9e164644e..1abba8e414c 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -67,7 +67,6 @@ /obj/item/fishing_rod/examine(mob/user) . = ..() - . += "Right-Click in your active hand to access its slots UI" var/list/equipped_stuff = list() if(line) equipped_stuff += "[icon2html(line, user)] [line.name]" @@ -79,6 +78,7 @@ . += span_notice("\a [icon2html(bait, user)] [bait] is being used as bait.") else . += span_warning("It doesn't have any bait attached. Fishing will be more tedious!") + . += span_notice("Right-Click 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("Alt-Click to access the Experiment Configuration UI") + /obj/item/fishing_rod/tech/consume_bait(atom/movable/reward) return diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index 5a34db9ce5f..657b2f30968 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -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) diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index ffb37753881..e2e5491dd1d 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -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) diff --git a/code/modules/library/skill_learning/skillchip.dm b/code/modules/library/skill_learning/skillchip.dm index f9fd629c1ed..762e8e0162c 100644 --- a/code/modules/library/skill_learning/skillchip.dm +++ b/code/modules/library/skill_learning/skillchip.dm @@ -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 diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 72bdaa02a95..0a2c96dddd5 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -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 diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm index 521504da351..8a06607ec59 100644 --- a/code/modules/research/techweb/_techweb.dm +++ b/code/modules/research/techweb/_techweb.dm @@ -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() diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index d12ebfaca1e..507199164b8 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -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" diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm index 1ef15f8d0f5..d0d39227f43 100644 --- a/code/modules/unit_tests/fish_unit_tests.dm +++ b/code/modules/unit_tests/fish_unit_tests.dm @@ -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() diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm index 80fb1350841..e51205c00e4 100644 --- a/code/modules/vending/games.dm +++ b/code/modules/vending/games.dm @@ -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( diff --git a/icons/hud/fishing_hud.dmi b/icons/hud/fishing_hud.dmi index b68acee09b7..58c478d0710 100644 Binary files a/icons/hud/fishing_hud.dmi and b/icons/hud/fishing_hud.dmi differ diff --git a/icons/hud/radial_fishing.dmi b/icons/hud/radial_fishing.dmi new file mode 100644 index 00000000000..65fd55176b7 Binary files /dev/null and b/icons/hud/radial_fishing.dmi differ diff --git a/icons/obj/aquarium.dmi b/icons/obj/aquarium.dmi index 3a27c83c906..19e2e68c4f8 100644 Binary files a/icons/obj/aquarium.dmi and b/icons/obj/aquarium.dmi differ diff --git a/icons/obj/fishing.dmi b/icons/obj/fishing.dmi index 39bcc853442..f7ab9fc1ad9 100644 Binary files a/icons/obj/fishing.dmi and b/icons/obj/fishing.dmi differ diff --git a/sound/attributions.txt b/sound/attributions.txt index 09ac2bd5642..3ae6c797dd3 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -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/ diff --git a/sound/effects/arcade_jump.ogg b/sound/effects/arcade_jump.ogg new file mode 100644 index 00000000000..65f0cc448b5 Binary files /dev/null and b/sound/effects/arcade_jump.ogg differ diff --git a/sound/effects/boing.ogg b/sound/effects/boing.ogg new file mode 100644 index 00000000000..8328cc33926 Binary files /dev/null and b/sound/effects/boing.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 972ed35951e..2fe8275a014 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -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" diff --git a/tgui/packages/tgui/interfaces/ExperimentConfigure.js b/tgui/packages/tgui/interfaces/ExperimentConfigure.js index e515c2bfb94..02cc759dd7f 100644 --- a/tgui/packages/tgui/interfaces/ExperimentConfigure.js +++ b/tgui/packages/tgui/interfaces/ExperimentConfigure.js @@ -142,7 +142,7 @@ export const ExperimentConfigure = (props, context) => { {experiments.map((exp, i) => { - return ; + return ; })} @@ -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 (