GLOBAL_LIST_INIT_TYPED(preset_fish_sources, /datum/fish_source, 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, generate_specific_fish_icons()) /proc/generate_specific_fish_icons() var/list/return_list = zebra_typecacheof(list( /datum/data/vending_product = FISH_ICON_COIN, /mob/living/basic/axolotl = FISH_ICON_CRITTER, /obj/effect/spawner/random/frog = FISH_ICON_CRITTER, /mob/living/basic/carp = FISH_ICON_DEF, /mob/living/basic/mining = FISH_ICON_HOSTILE, /mob/living/basic/skeleton = FISH_ICON_BONE, /mob/living/basic/stickman = FISH_ICON_HOSTILE, /obj/effect/decal/remains = FISH_ICON_BONE, /obj/effect/mob_spawn/corpse = FISH_ICON_BONE, /obj/effect/spawner/message_in_a_bottle = FISH_ICON_BOTTLE, /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/chainsawfish = FISH_ICON_WEAPON, /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/jumpercable = FISH_ICON_ELECTRIC, /obj/item/fish/lavaloop = FISH_ICON_WEAPON, /obj/item/fish/mastodon = FISH_ICON_BONE, /obj/item/fish/pike/armored = FISH_ICON_WEAPON, /obj/item/fish/pufferfish = FISH_ICON_CHUNKY, /obj/item/fish/sand_crab = FISH_ICON_CRAB, /obj/item/fish/skin_crab = FISH_ICON_CRAB, /obj/item/fish/slimefish = FISH_ICON_SLIME, /obj/item/fish/sludgefish = FISH_ICON_SLIME, /obj/item/fish/starfish = FISH_ICON_STAR, /obj/item/fish/stingray = FISH_ICON_WEAPON, /obj/item/fish/swordfish = FISH_ICON_WEAPON, /obj/item/fish/zipzap = FISH_ICON_ELECTRIC, /obj/item/fishing_rod = FISH_ICON_COIN, /obj/item/instrument/trumpet/spectral = FISH_ICON_BONE, /obj/item/instrument/saxophone/spectral = FISH_ICON_BONE, /obj/item/instrument/trombone/spectral = FISH_ICON_BONE, /obj/item/knife/carp = FISH_ICON_WEAPON, /obj/item/seeds/grass = FISH_ICON_SEED, /obj/item/seeds/random = FISH_ICON_SEED, /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/item/survivalcapsule/fishing = FISH_ICON_COIN, /obj/structure/closet/crate = FISH_ICON_COIN, /obj/structure/mystery_box = FISH_ICON_COIN, )) return_list[FISHING_RANDOM_SEED] = FISH_ICON_SEED return_list[FISHING_RANDOM_ORGAN] = FISH_ICON_ORGAN return_list[FISHING_VENDING_CHUCK] = FISH_ICON_COIN return return_list /** * 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 * */ 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() /// Any limited quantity stuff in this list will be readded to the counts after a while var/list/fish_count_regen = list() /// A list of stuff that's currently waiting to be readded to fish_counts var/list/currently_on_regen /// Text shown as baloon alert when you roll a dud in the table var/duds = list("it was nothing", "the hook is empty") /// Baseline difficulty for fishing in this spot. THIS IS ADDED TO THE DEFAULT DIFFICULTY OF THE MINIGAME (15) var/fishing_difficulty = FISHING_DEFAULT_DIFFICULTY /// How the spot type is described in fish catalog section about fish sources, will be skipped if null var/catalog_description /// Background image name from /datum/asset/simple/fishing_minigame var/background = "background_default" var/fish_source_flags = NONE /// If FISH_SOURCE_FLAG_EXPLOSIVE_MALUS is set, this will track of how much we're "exhausting" the system by bombing it repeatedly. var/explosive_fishing_score = 0 ///When linked to a fishing portal, this will be the icon_state of this option in the radial menu var/radial_state = "default" ///When selected by the fishing portal, this will be the icon_state of the overlay shown on the machine. var/overlay_state = "portal_aquarium" ///If set, this overrides the upper and lower bounds of how long you should wait during the waiting phase of the minigame. var/list/wait_time_range /// Mindless mobs that can fish will never pull up items on this list var/static/list/profound_fisher_blacklist = typecacheof(list( /mob/living/basic/mining/lobstrosity, /mob/living/basic/mining/lobstrosity/juvenile, // BUBBER EDIT ADDITION /obj/structure/closet/crate/necropolis/tendril, )) ///List of multipliers used to make fishes more common compared to everything else depending on bait quality, indexed from best to worst. var/static/weight_result_multiplier = list( TRAIT_GREAT_QUALITY_BAIT = 9, TRAIT_GOOD_QUALITY_BAIT = 3.5, TRAIT_BASIC_QUALITY_BAIT = 2, ) ///List of exponents used to level out the table weight differences between fish depending on bait quality. var/static/weight_leveling_exponents = list( TRAIT_GREAT_QUALITY_BAIT = 0.7, TRAIT_GOOD_QUALITY_BAIT = 0.55, TRAIT_BASIC_QUALITY_BAIT = 0.4, ) //If set, fish types native to this source won't die if left on these turfs. var/list/associated_safe_turfs //list of subtypes of associated safe turfs that are NOT safe var/list/safe_turfs_blacklist /datum/fish_source/New() if(!SSfishing.initialized && associated_safe_turfs) //This is only needed during world init associated_safe_turfs = typecacheof(associated_safe_turfs) if(safe_turfs_blacklist) associated_safe_turfs -= typecacheof(safe_turfs_blacklist) if(!PERFORM_ALL_TESTS(focus_only/fish_sources_tables)) return for(var/path in fish_counts) if(!(path in fish_table)) stack_trace("path [path] found in the 'fish_counts' list but not in the 'fish_table'") if(wait_time_range && length(wait_time_range) != 2) stack_trace("wait_time_range for [type] is set but has length different than two") for(var/path in fish_counts) //we give anything unique an auto 30 min regen, that way if the round is extended you still get content. if (!(path in fish_count_regen)) fish_count_regen[path] = 30 MINUTES /datum/fish_source/Destroy() if(explosive_fishing_score) STOP_PROCESSING(SSprocessing, src) return ..() ///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 ///Called whenever a fishing spot with this fish source attached is deleted /datum/fish_source/proc/on_fishing_spot_del(datum/component/fishing_spot/spot) /// 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, atom/parent) return rod.reason_we_cant_fish(src) /// Called below above proc, in case the fishing source has anything to do that isn't denial /datum/fish_source/proc/on_start_fishing(obj/item/fishing_rod/rod, mob/fisherman, atom/parent) return /** * Calculates the difficulty of the minigame: * * This includes the source's fishing difficulty, that of the fish, the rod, * favorite and disliked baits, fish traits and the fisherman skill. * * This is the version to call while we are in the fishing minigame phase. */ /datum/fish_source/proc/calculate_difficulty_minigame(datum/fishing_challenge/challenge, reward_path, obj/item/fishing_rod/rod, mob/fisherman) SHOULD_NOT_OVERRIDE(TRUE) . = 0 // begin with a fresh calculation each time . += calculate_difficulty(reward_path, rod, fisherman) // Difficulty modifier added by the fisher's skill level if(!(challenge.special_effects & FISHING_MINIGAME_RULE_NO_EXP)) . += fisherman.mind?.get_skill_modifier(/datum/skill/fishing, SKILL_VALUE_MODIFIER) if(challenge.special_effects & FISHING_MINIGAME_RULE_KILL) challenge.RegisterSignal(src, COMSIG_FISH_SOURCE_REWARD_DISPENSED, TYPE_PROC_REF(/datum/fishing_challenge, hurt_fish)) /** * Calculates the difficulty of the minigame: * * 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. */ /datum/fish_source/proc/calculate_difficulty(result, obj/item/fishing_rod/rod, mob/fisherman) . = fishing_difficulty // Difficulty modifier added by having the Settler quirk if(HAS_TRAIT(fisherman, TRAIT_EXPERT_FISHER)) . += EXPERT_FISHER_DIFFICULTY_MOD // Difficulty modifier added by the rod . += rod.difficulty_modifier var/is_fish_instance = isfish(result) if(!ispath(result,/obj/item/fish) && !is_fish_instance) // In the future non-fish rewards can have variable difficulty calculated here return var/obj/item/fish/caught_fish = result //Just to clarify when we should use the path instead of the fish, which can be both a path and an instance. var/result_path = is_fish_instance ? caught_fish.type : result // Baseline fish difficulty . += initial(caught_fish.fishing_difficulty_modifier) var/list/fish_properties = SSfishing.fish_properties[result_path] if(rod.bait) var/obj/item/bait = rod.bait //Fav bait makes it easier var/list/fav_bait = fish_properties[FISH_PROPERTIES_FAV_BAIT] for(var/bait_identifer in fav_bait) if(is_matching_bait(bait, bait_identifer)) . += FAV_BAIT_DIFFICULTY_MOD //Disliked bait makes it harder var/list/disliked_bait = fish_properties[FISH_PROPERTIES_BAD_BAIT] for(var/bait_identifer in disliked_bait) if(is_matching_bait(bait, bait_identifer)) . += DISLIKED_BAIT_DIFFICULTY_MOD // Matching/not matching fish traits and equipment var/list/fish_traits if(is_fish_instance) fish_traits = caught_fish.fish_traits else fish_traits = fish_properties[FISH_PROPERTIES_TRAITS] var/additive_mod = 0 var/multiplicative_mod = 1 for(var/fish_trait in fish_traits) var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] var/list/mod = trait.difficulty_mod(rod, fisherman) additive_mod += mod[ADDITIVE_FISHING_MOD] multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD] . += additive_mod . *= multiplicative_mod ///Comsig proc from the fishing minigame for 'roll_reward' /datum/fish_source/proc/roll_reward_minigame(datum/source, obj/item/fishing_rod/rod, mob/fisherman, atom/location, list/rewards) SIGNAL_HANDLER SHOULD_NOT_OVERRIDE(TRUE) rewards += roll_reward(rod, fisherman, location) /// Returns a typepath, instance or another special value which we use for dispensing a reward later. /datum/fish_source/proc/roll_reward(obj/item/fishing_rod/rod, mob/fisherman, atom/location) return pick_weight(get_modified_fish_table(rod, fisherman, location)) || FISHING_DUD /// Version of roll_reward() that blacklists objects that shouldn't be caught by ai-controlled mobs. /datum/fish_source/proc/roll_mindless_reward(obj/item/fishing_rod/rod, mob/fisherman, atom/location) var/list/final_table = get_modified_fish_table(rod, fisherman, location) final_table -= profound_fisher_blacklist return pick_weight(final_table) || FISHING_DUD /** * 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/fishing_challenge/challenge) return ///Proc called when the challenge is interrupted within the fish source code. /datum/fish_source/proc/interrupt_challenge(reason) SEND_SIGNAL(src, COMSIG_FISHING_SOURCE_INTERRUPT_CHALLENGE, reason) /** * Proc called when the COMSIG_MOB_COMPLETE_FISHING signal is sent. * Check if we've succeeded. If so, write into memory and dispense the reward. */ /datum/fish_source/proc/on_challenge_completed(mob/user, datum/fishing_challenge/challenge, success) SIGNAL_HANDLER SHOULD_CALL_PARENT(TRUE) UnregisterSignal(user, COMSIG_MOB_COMPLETE_FISHING) if(!success) return var/atom/movable/reward = dispense_reward(challenge.reward_path, user, challenge.location, challenge.used_rod) SEND_SIGNAL(challenge.used_rod, COMSIG_FISHING_ROD_CAUGHT_FISH, reward, user) challenge.used_rod.on_reward_caught(reward, user) if(!reward) return user.add_mob_memory(/datum/memory/caught_fish, protagonist = user, deuteragonist = reward.name) REMOVE_TRAIT(reward, TRAIT_FISH_JUST_SPAWNED, TRAIT_GENERIC) /// Gives out the reward if possible /datum/fish_source/proc/dispense_reward(reward_path, mob/fisherman, atom/fishing_spot, obj/item/fishing_rod/rod) var/atom/movable/reward = simple_dispense_reward(reward_path, get_turf(fisherman), fishing_spot) if(!reward) //balloon alert instead fisherman.balloon_alert(fisherman, pick(duds)) return if(isitem(reward)) //Try to put it in hand INVOKE_ASYNC(fisherman, TYPE_PROC_REF(/mob, put_in_hands), reward) else if(istype(reward, /obj/effect/spawner)) // Do not attempt to forceMove() a spawner. It will break things, and the spawned item should already be at the mob's turf by now. fisherman.balloon_alert(fisherman, "caught something!") return fisherman.balloon_alert(fisherman, "caught [reward]!") var/list/fishing_data if (isfish(reward)) ADD_TRAIT(reward, TRAIT_NO_FISHING_ACHIEVEMENT, TRAIT_GENERIC) var/obj/item/fish/caught_fish = reward fishing_data = list(caught_fish.size, caught_fish.weight, caught_fish.custom_materials) log_fish("[fisherman] has caught a [reward] at [fishing_spot] using [rod].", fishing_data) return reward ///Simplified version of dispense_reward that doesn't need a fisherman. /datum/fish_source/proc/simple_dispense_reward(reward_path, atom/spawn_location, atom/fishing_spot) if(isnull(reward_path)) return null var/area/area = get_area(fishing_spot) if(!(area.area_flags & UNLIMITED_FISHING) && !isnull(fish_counts[reward_path])) // This is limited count result //Somehow, we're trying to spawn an expended reward. if(fish_counts[reward_path] <= 0) return null fish_counts[reward_path] -= 1 var/regen_time = fish_count_regen?[reward_path] if(regen_time) LAZYADDASSOC(currently_on_regen, reward_path, 1) if(currently_on_regen[reward_path] == 1) addtimer(CALLBACK(src, PROC_REF(regen_count), reward_path), regen_time) var/atom/movable/reward = spawn_reward(reward_path, spawn_location, fishing_spot) SEND_SIGNAL(src, COMSIG_FISH_SOURCE_REWARD_DISPENSED, reward) return reward /datum/fish_source/proc/regen_count(reward_path) if(!LAZYACCESS(currently_on_regen, reward_path)) return fish_counts[reward_path] += 1 currently_on_regen[reward_path] -= 1 if(currently_on_regen[reward_path] <= 0) LAZYREMOVE(currently_on_regen, reward_path) return var/regen_time = fish_count_regen[reward_path] addtimer(CALLBACK(src, PROC_REF(regen_count), reward_path), regen_time) /// Spawns a reward from a atom path right where the fisherman is. Part of the dispense_reward() logic. /datum/fish_source/proc/spawn_reward(reward_path, atom/spawn_location, atom/fishing_spot) if(reward_path == FISHING_DUD) return if(ismovable(reward_path)) var/atom/movable/reward = reward_path reward.forceMove(spawn_location) return reward if(ispath(reward_path, /datum/chasm_detritus)) return GLOB.chasm_detritus_types[reward_path].dispense_detritus(spawn_location, fishing_spot) if(!ispath(reward_path, /atom/movable)) CRASH("Unsupported /datum path [reward_path] passed to fish_source/proc/spawn_reward()") var/atom/movable/reward = new reward_path(spawn_location) if(isfish(reward)) var/obj/item/fish/caught_fish = reward ADD_TRAIT(caught_fish, TRAIT_FISH_JUST_SPAWNED, TRAIT_GENERIC) caught_fish.randomize_size_and_weight() return reward /// Returns the fish table, with with the unavailable items from fish_counts removed. /datum/fish_source/proc/get_fish_table(atom/location, from_explosion = FALSE) var/list/table = fish_table.Copy() //message bottles cannot spawn from explosions. They're meant to be one-time messages (rarely) and photos from past rounds //and it would suck if the pool of bottle messages were constantly being emptied by explosive fishing. if(from_explosion) table -= /obj/effect/spawner/message_in_a_bottle for(var/result in table) if(!isnull(fish_counts[result]) && fish_counts[result] <= 0) table -= result return table /// Builds a fish weights table modified by bait/rod/user properties /datum/fish_source/proc/get_modified_fish_table(obj/item/fishing_rod/rod, mob/fisherman, atom/location) var/obj/item/bait = rod.bait ///An exponent used to level out the table weight differences between fish depending on bait quality. var/leveling_exponent = 0 ///Multiplier used to make fishes more common compared to everything else. var/result_multiplier = 1 var/list/final_table = get_fish_table(location) if(bait) for(var/trait in weight_result_multiplier) if(HAS_TRAIT(bait, trait)) result_multiplier = weight_result_multiplier[trait] leveling_exponent = weight_leveling_exponents[trait] break if(HAS_TRAIT(rod, TRAIT_ROD_REMOVE_FISHING_DUD)) final_table -= FISHING_DUD if(!fisherman.client) final_table -= /obj/effect/spawner/message_in_a_bottle // avoids npc's to get messages in a bottle. Fish for them! for(var/result in final_table) final_table[result] *= rod.hook.get_hook_bonus_multiplicative(result) final_table[result] += rod.hook.get_hook_bonus_additive(result)//Decide on order here so it can be multiplicative if(ispath(result, /mob/living) && bait && (HAS_TRAIT(bait, TRAIT_GOOD_QUALITY_BAIT) || HAS_TRAIT(bait, TRAIT_GREAT_QUALITY_BAIT))) final_table[result] = round(final_table[result] * result_multiplier, 1) else if(ispath(result, /obj/item/fish) || isfish(result)) if(bait) final_table[result] = round(final_table[result] * result_multiplier, 1) var/mult = bait.check_bait(result) final_table[result] = round(final_table[result] * mult, 1) if(mult > 1 && HAS_TRAIT(bait, TRAIT_BAIT_ALLOW_FISHING_DUD)) final_table -= FISHING_DUD else final_table[result] = round(final_table[result] * FISH_WEIGHT_MULT_WITHOUT_BAIT, 1) //Fishing without bait is not going to be easy // Apply fish trait modifiers final_table[result] = get_fish_trait_catch_mods(final_table[result], result, rod, fisherman, location) if(final_table[result] <= 0) final_table -= result if(leveling_exponent) level_out_fish(final_table, leveling_exponent) return final_table ///A proc that levels out the weights of various fish, leading to rarer fishes being more common. /datum/fish_source/proc/level_out_fish(list/table, exponent) var/highest_fish_weight var/list/collected_fish_weights = list() for(var/fishable in table) if(ispath(fishable, /obj/item/fish) || isfish(fishable)) var/fish_weight = table[fishable] collected_fish_weights[fishable] = fish_weight if(fish_weight > highest_fish_weight) highest_fish_weight = fish_weight for(var/fish in collected_fish_weights) var/difference = highest_fish_weight - collected_fish_weights[fish] if(!difference) continue table[fish] += round(difference**exponent, 1) /datum/fish_source/proc/get_fish_trait_catch_mods(weight, obj/item/fish/fish, obj/item/fishing_rod/rod, mob/user, atom/location) var/is_fish_instance = isfish(fish) if(!ispath(fish, /obj/item/fish) && !is_fish_instance) return weight var/multiplier = 1 var/list/fish_traits if(is_fish_instance) fish_traits = fish.fish_traits else fish_traits = SSfishing.fish_properties[fish][FISH_PROPERTIES_TRAITS] for(var/fish_trait in fish_traits) var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] var/list/mod = trait.catch_weight_mod(rod, user, location, is_fish_instance ? fish.type : fish) weight += mod[ADDITIVE_FISHING_MOD] multiplier *= mod[MULTIPLICATIVE_FISHING_MOD] return round(weight * multiplier, 1) ///returns true if this fishing spot has fish that are shown in the catalog. /datum/fish_source/proc/has_known_fishes(atom/location) var/show_anyway = fish_source_flags & FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG for(var/reward in get_fish_table(location)) if(!ispath(reward, /obj/item/fish) && !isfish(reward)) continue var/obj/item/fish/prototype = reward if(!show_anyway && initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG) return TRUE return FALSE ///Add a string with the names of catchable fishes to the examine text. /datum/fish_source/proc/get_catchable_fish_names(mob/user, atom/location, list/examine_text) var/list/known_fishes = list() var/show_anyway = fish_source_flags & FISH_SOURCE_FLAG_IGNORE_HIDDEN_ON_CATALOG var/obj/item/fishing_rod/rod = user.get_active_held_item() var/list/final_table if(!istype(rod) || !rod.hook) rod = null else final_table = get_modified_fish_table(rod, user, location) var/total_weight = 0 var/list/rodless_weights = list() var/total_rod_weight = 0 var/list/rod_weights = list() var/list/table = get_fish_table(location) for(var/reward in table) var/weight = table[reward] var/final_weight if(rod) total_weight += weight final_weight = final_table[reward] total_rod_weight += final_weight if(!ispath(reward, /obj/item/fish) && !isfish(reward)) continue var/obj/item/fish/prototype = reward if(!show_anyway && !(initial(prototype.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG)) continue if(rod) rodless_weights[reward] = weight rod_weights[reward] = final_weight else known_fishes += initial(prototype.name) if(rod) for(var/reward in rodless_weights) var/percent_weight = rodless_weights[reward] / total_weight var/percent_rod_weight = rod_weights[reward] / total_rod_weight var/obj/item/fish/prototype = reward var/init_name = initial(prototype.name) var/ratio = percent_rod_weight ? percent_weight/percent_rod_weight : INFINITY if(ratio < 0.9) init_name = span_bold(init_name) if(ratio < 0.3) init_name = "[init_name]" else if(ratio > 1.1) init_name = span_small(init_name) known_fishes += init_name if(!length(known_fishes)) return var/info = "You can catch the following fish here" if(rod) info = span_tooltip("In bold are fish you're more likely to catch with the current setup. The opposite is true for the smaller font", info) examine_text += span_info("[info]: [english_list(known_fishes)].") ///How much the explosive_fishing_score impacts explosive fishing. The higher the value, the stronger the malus for repeated calls #define EXPLOSIVE_FISHING_MALUS_EXPONENT 0.55 ///How much the explosive_fishing_score is reduced each second. #define EXPLOSIVE_FISHING_RECOVERY_RATE 0.18 /datum/fish_source/proc/spawn_reward_from_explosion(atom/location, severity) SIGNAL_HANDLER if(fish_source_flags & FISH_SOURCE_FLAG_EXPLOSIVE_NONE) return var/multiplier = 1 if(fish_source_flags & FISH_SOURCE_FLAG_EXPLOSIVE_MALUS) if(explosive_fishing_score <= 0) explosive_fishing_score = 1 START_PROCESSING(SSprocessing, src) else explosive_fishing_score++ multiplier = explosive_fishing_score**-EXPLOSIVE_FISHING_MALUS_EXPONENT for(var/i in 1 to (severity + 2)) if(!prob((100 + 100 * severity)/i * multiplier)) continue var/reward_loot = pick_weight(get_fish_table(location, from_explosion = TRUE)) var/atom/spawn_location = isturf(location) ? location : location.drop_location() var/atom/movable/reward = simple_dispense_reward(reward_loot, spawn_location, location) if(isnull(reward)) continue if(isfish(reward)) var/obj/item/fish/fish = reward fish.set_status(FISH_DEAD, silent = TRUE) if(isitem(reward)) reward.pixel_x = rand(-9, 9) reward.pixel_y = rand(-9, 9) if(severity >= EXPLODE_DEVASTATE) reward.ex_act(EXPLODE_LIGHT) /datum/fish_source/process(seconds_per_tick) explosive_fishing_score -= EXPLOSIVE_FISHING_RECOVERY_RATE * seconds_per_tick if(explosive_fishing_score <= 0) STOP_PROCESSING(SSprocessing, src) explosive_fishing_score = 0 #undef EXPLOSIVE_FISHING_MALUS_EXPONENT #undef EXPLOSIVE_FISHING_RECOVERY_RATE ///Called when releasing a fish in a fishing spot with the TRAIT_CATCH_AND_RELEASE trait. /datum/fish_source/proc/readd_fish(atom/location, obj/item/fish/fish, mob/living/releaser) if(releaser) var/is_morbid = HAS_MIND_TRAIT(releaser, TRAIT_MORBID) var/is_naive = HAS_MIND_TRAIT(releaser, TRAIT_NAIVE) if(fish.status == FISH_DEAD) //ded fish won't repopulate the sea. if(is_naive || is_morbid) releaser.add_mood_event("fish_released", /datum/mood_event/fish_released, is_morbid && !is_naive, fish) if(((fish.type in fish_table) != is_morbid) || is_naive) releaser.add_mood_event("fish_released", /datum/mood_event/fish_released, is_morbid && !is_naive, fish) //don't do anything if the fish is dead, not native to this fish source or has no limited amount. if(fish.status == FISH_DEAD || isnull(fish_table[fish.type]) || isnull(fish_counts[fish.type])) return //ditto if no restrictions apply var/area/area = get_area(location) if(area.area_flags & UNLIMITED_FISHING) return //If this fish population isn't recovering from recent losses, we just increase it. if(!LAZYACCESS(currently_on_regen, fish.type)) fish_counts[fish.type] += 1 else regen_count(fish.type) /** * Called by /datum/autowiki/fish_sources unless the catalog entry for this fish source is null. * It should Return a list of entries with keys named "name", "icon", "weight" and "notes" * detailing the contents of this fish source. */ /datum/fish_source/proc/generate_wiki_contents(datum/autowiki/fish_sources/wiki) var/list/data = list() var/list/only_fish = list() var/total_weight = 0 var/total_weight_without_bait = 0 var/total_weight_no_fish = 0 var/list/tables_by_quality = list() var/list/total_weight_by_quality = list() var/list/total_weight_by_quality_no_fish = list() for(var/obj/item/fish/fish as anything in fish_table) var/weight = fish_table[fish] if(fish != FISHING_DUD) total_weight += weight if(!ispath(fish, /obj/item/fish)) total_weight_without_bait += weight total_weight_no_fish += weight continue if(initial(fish.fish_flags) & FISH_FLAG_SHOW_IN_CATALOG) only_fish += fish total_weight_without_bait += round(fish_table[fish] * FISH_WEIGHT_MULT_WITHOUT_BAIT, 1) for(var/trait in weight_result_multiplier) var/list/table_copy = fish_table.Copy() table_copy -= FISHING_DUD var/exponent = weight_leveling_exponents[trait] var/multiplier = weight_result_multiplier[trait] for(var/fish in table_copy) if(!ispath(fish, /obj/item/fish)) continue table_copy[fish] = round(table_copy[fish] * multiplier, 1) level_out_fish(table_copy, exponent) tables_by_quality[trait] = table_copy var/tot_weight = 0 var/tot_weight_no_fish = 0 for(var/result in table_copy) var/weight = table_copy[result] tot_weight += weight if(!ispath(result, /obj/item/fish)) tot_weight_no_fish += weight total_weight_by_quality[trait] = tot_weight total_weight_by_quality_no_fish[trait] = tot_weight_no_fish //show the improved weights in ascending orders for fish. tables_by_quality = reverseList(tables_by_quality) if(FISHING_DUD in fish_table) data += LIST_VALUE_WRAP_LISTS(list( FISH_SOURCE_AUTOWIKI_NAME = FISH_SOURCE_AUTOWIKI_DUD, FISH_SOURCE_AUTOWIKI_ICON = "", FISH_SOURCE_AUTOWIKI_WEIGHT = PERCENT(fish_table[FISHING_DUD]/total_weight_without_bait), FISH_SOURCE_AUTOWIKI_WEIGHT_SUFFIX = "WITHOUT A BAIT", FISH_SOURCE_AUTOWIKI_NOTES = "Unless you have a magnet or rescue hook or you know what you're doing, always use a bait", )) for(var/obj/item/fish/fish as anything in only_fish) var/weight = fish_table[fish] var/deets = "Can be caught indefinitely" if(fish in fish_counts) deets = "It's quite rare and can only be caught up to [fish_counts[fish]] times" if(fish in fish_count_regen) deets += " every [DisplayTimeText(fish::breeding_timeout)]" var/list/weight_deets = list() for(var/trait in tables_by_quality) weight_deets += "[round(PERCENT(tables_by_quality[trait][fish]/total_weight_by_quality[trait]), 0.1)]%" var/weight_suffix = "([english_list(weight_deets, and_text = ", ")])" data += LIST_VALUE_WRAP_LISTS(list( FISH_SOURCE_AUTOWIKI_NAME = wiki.escape_value(full_capitalize(initial(fish.name))), FISH_SOURCE_AUTOWIKI_ICON = FISH_AUTOWIKI_FILENAME(fish), FISH_SOURCE_AUTOWIKI_WEIGHT = PERCENT(weight/total_weight), FISH_SOURCE_AUTOWIKI_WEIGHT_SUFFIX = weight_suffix, FISH_SOURCE_AUTOWIKI_NOTES = deets, )) if(total_weight_no_fish) //There are things beside fish that we can catch. var/list/weight_deets = list() for(var/trait in tables_by_quality) weight_deets += "[round(PERCENT(total_weight_by_quality_no_fish[trait]/total_weight_by_quality[trait]), 0.1)]%" var/weight_suffix = "([english_list(weight_deets, and_text = ", ")])" data += LIST_VALUE_WRAP_LISTS(list( FISH_SOURCE_AUTOWIKI_NAME = FISH_SOURCE_AUTOWIKI_OTHER, FISH_SOURCE_AUTOWIKI_ICON = FISH_SOURCE_AUTOWIKI_QUESTIONMARK, FISH_SOURCE_AUTOWIKI_WEIGHT = PERCENT(total_weight_no_fish/total_weight), FISH_SOURCE_AUTOWIKI_WEIGHT_SUFFIX = weight_suffix, FISH_SOURCE_AUTOWIKI_NOTES = "Who knows what it may be. Try and find out", )) return data