mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 10:11:09 +00:00
681 lines
28 KiB
Plaintext
681 lines
28 KiB
Plaintext
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,
|
|
/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
|
|
|
|
///Comsig proc from the fishing minigame for 'calculate_difficulty'
|
|
/datum/fish_source/proc/calculate_difficulty_minigame(datum/fishing_challenge/challenge, reward_path, obj/item/fishing_rod/rod, mob/fisherman, list/difficulty_holder)
|
|
SIGNAL_HANDLER
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
difficulty_holder[1] += calculate_difficulty(reward_path, rod, fisherman)
|
|
|
|
// Difficulty modifier added by the fisher's skill level
|
|
if(!(challenge.special_effects & FISHING_MINIGAME_RULE_NO_EXP))
|
|
difficulty_holder[1] += 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)
|
|
if(reward)
|
|
user.add_mob_memory(/datum/memory/caught_fish, protagonist = user, deuteragonist = reward.name)
|
|
SEND_SIGNAL(challenge.used_rod, COMSIG_FISHING_ROD_CAUGHT_FISH, reward, user)
|
|
challenge.used_rod.on_reward_caught(reward, user)
|
|
|
|
/// 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]!")
|
|
if (isfish(reward))
|
|
ADD_TRAIT(reward, TRAIT_NO_FISHING_ACHIEVEMENT, TRAIT_GENERIC)
|
|
|
|
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
|
|
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 = "<u>[init_name]</u>"
|
|
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
|