Files
Bubberstation/code/modules/fishing/sources/_fish_source.dm
SkyratBot 3f95ebbd5e [MIRROR] Fishing, Version 1 [MDB IGNORE] (#14370)
* Fishing, Version 1 (#67691)

Adds fishing and fishing minigame.
You use fishing rod to fish.
Equipping specific bait/hook/reels will affect your success chances.
You can fish out fish,items and other things.

Fishing Equipment
Fishing rods have three slots: Bait, Reel and Hook.
Any food can be used as bait but dedicated bait makes fishing easier.
You can buy hook and line sets
New bait types:

Worms : Buy can of them at cargo (alternative acquirement method pending)
Doughballs : Use knife on flat piece of dough to get five of them.
Fishing rod types:

Basic : Print these at the lathe, nothing fancy here.
Tech: Experimental tech. Provides infinite bait
Fishing rods can also hook and reel normal items.

Equipment screen and reeling video
Fishing spots
Keep in mind this PR is meant to add the basic systems and i intend to fill these with more fish in future PR's so wait with suggestions until then.

Lavaland lava (no fish here right now, just other stuff), requires reinforced line to fish in.
Maintenance moisture traps.
Beach away mission water.
Fishing portal available for purchase from cargo - This is stopgap until we fill more spots.
Difficulty depends on fishing spot, fish type, and the fish traits and rod setup combinations.
All fish types can have specific traits, most common ones being favourite and disliked bait types/categories.

Other
Fishing catalog now lists fishing related info
New admin debug verb, fishing calculator that show probabilities with different setups so it's easier to balance this.
Fish now have average weight and size. Make sure to boast if you catch a big one.
Adds tgui mouse passthrough
Screens
Sprites:

Fishing portal sprite by @ ArcaneMusic
Other sprites by @ Mey-Ha-Zah
Bad ones by me. (Could still use better fishing minigame backgrounds)
Sounds:

https://freesound.org/people/soundscalpel.com/sounds/110393/
https://freesound.org/people/soundslikewillem/sounds/343748/

* Fishing, Version 1

Co-authored-by: AnturK <AnturK@users.noreply.github.com>
2022-06-17 00:30:40 +01:00

199 lines
8.2 KiB
Plaintext

/// Keyed list of preset sources to configuration instance
GLOBAL_LIST_INIT(preset_fish_sources,init_fishing_configurations())
/// These are shared between their spots
/proc/init_fishing_configurations()
. = list()
var/datum/fish_source/ocean/beach/beach_preset = new
.[FISHING_SPOT_PRESET_BEACH] = beach_preset
var/datum/fish_source/lavaland/lava_preset = new
.[FISHING_SPOT_PRESET_LAVALAND_LAVA] = lava_preset
/// Where the fish actually come from - every fishing spot has one assigned but multiple fishing holes can share single source, ie single shared one for ocean/lavaland river
/datum/fish_source
/// Fish catch weight table - these are relative weights
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()
/// 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
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 = "fishing_background_default"
/// Can we fish in this spot at all. Returns DENIAL_REASON or null if we're good to go
/datum/fish_source/proc/can_fish(obj/item/fishing_rod/rod, mob/fisherman)
return
/// DIFFICULTY = (SPOT_BASE_VALUE + FISH_MODIFIER + ROD_MODIFIER + FAV/DISLIKED_BAIT_MODIFIER + TRAITS_ADDITIVE) * TRAITS_MULTIPLICATIVE , For non-fish it's just SPOT_BASE_VALUE
/datum/fish_source/proc/calculate_difficulty(result, obj/item/fishing_rod/rod, mob/fisherman)
. = fishing_difficulty
if(!ispath(result,/obj/item/fish))
// In the future non-fish rewards can have variable difficulty calculated here
return
var/list/fish_list_properties = collect_fish_properties()
var/obj/item/fish/caught_fish = result
// Baseline fish difficulty
. += initial(caught_fish.fishing_difficulty_modifier)
. += rod.difficulty_modifier
if(rod.bait)
var/obj/item/bait = rod.bait
//Fav bait makes it easier
var/list/fav_bait = fish_list_properties[caught_fish][NAMEOF(caught_fish, favorite_bait)]
for(var/bait_identifer in fav_bait)
if(is_matching_bait(bait, bait_identifer))
. += FAV_BAIT_DIFFICULTY_MOD
break
//Disliked bait makes it harder
var/list/disliked_bait = fish_list_properties[caught_fish][NAMEOF(caught_fish, disliked_bait)]
for(var/bait_identifer in disliked_bait)
if(is_matching_bait(bait, bait_identifer))
. += DISLIKED_BAIT_DIFFICULTY_MOD
break
// Matching/not matching fish traits and equipment
var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fishing_traits)]
var/additive_mod = 0
var/multiplicative_mod = 1
for(var/fish_trait in fish_traits)
var/datum/fishing_trait/trait = new fish_trait
var/list/mod = trait.difficulty_mod(rod, fisherman)
additive_mod += mod[ADDITIVE_FISHING_MOD]
multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD]
. += additive_mod
. *= multiplicative_mod
/// In case you want more complex rules for specific spots
/datum/fish_source/proc/roll_reward(obj/item/fishing_rod/rod, mob/fisherman)
return pick_weight(get_modified_fish_table(rod,fisherman))
/// Gives out the reward if possible
/datum/fish_source/proc/dispense_reward(reward_path, mob/fisherman)
if((reward_path in fish_counts)) // This is limited count result
if(fish_counts[reward_path] > 0)
fish_counts[reward_path] -= 1
else
reward_path = FISHING_DUD //Ran out of these since rolling (multiple fishermen on same source most likely)
if(ispath(reward_path))
if(ispath(reward_path,/obj/item))
var/obj/item/reward = new reward_path
if(ispath(reward_path,/obj/item/fish))
var/obj/item/fish/caught_fish = reward
caught_fish.randomize_weight_and_size()
//fish caught signal if needed goes here and/or fishing achievements
//Try to put it in hand
fisherman.put_in_hands(reward)
fisherman.balloon_alert(fisherman, "caught [reward]!")
else //If someone adds fishing out carp/chests/singularities or whatever just plop it down on the fisher's turf
fisherman.balloon_alert(fisherman, "caught something!")
new reward_path(get_turf(fisherman))
else if (reward_path == FISHING_DUD)
//baloon alert instead
fisherman.balloon_alert(fisherman,pick(duds))
/// Cached fish list properties so we don't have to initalize fish every time, init deffered
GLOBAL_LIST(fishing_property_cache)
/// Awful workaround around initial(x.list_variable) not being a thing while trying to keep some semblance of being structured
/proc/collect_fish_properties()
if(GLOB.fishing_property_cache == null)
var/list/fish_property_table = list()
for(var/fish_type in subtypesof(/obj/item/fish))
var/obj/item/fish/fish = new fish_type(null)
fish_property_table[fish_type] = list()
fish_property_table[fish_type][NAMEOF(fish, favorite_bait)] = fish.favorite_bait.Copy()
fish_property_table[fish_type][NAMEOF(fish, disliked_bait)] = fish.disliked_bait.Copy()
fish_property_table[fish_type][NAMEOF(fish, fishing_traits)] = fish.fishing_traits.Copy()
QDEL_NULL(fish)
GLOB.fishing_property_cache = fish_property_table
return GLOB.fishing_property_cache
/// Checks if bait matches identifier from fav/disliked bait list
/datum/fish_source/proc/is_matching_bait(obj/item/bait, identifier)
if(ispath(identifier)) //Just a path
return istype(bait, identifier)
if(islist(identifier))
var/list/special_identifier = identifier
switch(special_identifier["Type"])
if("Foodtype")
var/obj/item/food/food_bait = bait
return istype(food_bait) && food_bait.foodtypes & special_identifier["Value"]
else
CRASH("Unknown bait identifier in fish favourite/disliked list")
else
return HAS_TRAIT(bait, identifier)
/// 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)
var/obj/item/bait = rod.bait
var/list/fish_list_properties = collect_fish_properties()
var/list/final_table = fish_table.Copy()
for(var/result in final_table)
if((result in fish_counts) && fish_counts[result] <= 0) //ran out of these, ignore
final_table -= result
continue
final_table[result] += rod.fish_bonus(result) //Decide on order here so it can be multiplicative
if(result == FISHING_DUD)
//Modify dud result
//Bait quality reduces dud chance heavily.
if(bait)
if(HAS_TRAIT(bait, GREAT_QUALITY_BAIT_TRAIT))
final_table[result] *= 0.1
else if(HAS_TRAIT(bait, GOOD_QUALITY_BAIT_TRAIT))
final_table[result] *= 0.3
else if(HAS_TRAIT(bait, BASIC_QUALITY_BAIT_TRAIT))
final_table[result] *= 0.5
else
final_table[result] *= 10 //Fishing without bait is not going to be easy
else if(ispath(result, /obj/item/fish))
//Modify fish roll chance
var/obj/item/fish/caught_fish = result
if(bait)
//Bait matching likes doubles the chance
var/list/fav_bait = fish_list_properties[result][NAMEOF(caught_fish, favorite_bait)]
for(var/bait_identifer in fav_bait)
if(is_matching_bait(bait, bait_identifer))
final_table[result] *= 2
break // could compound possibly
//Bait matching dislikes
var/list/disliked_bait = fish_list_properties[result][NAMEOF(caught_fish, disliked_bait)]
for(var/bait_identifer in disliked_bait)
if(is_matching_bait(bait, bait_identifer))
final_table[result] *= 0.5
break // same question as above
// Apply fishing trait modifiers
var/list/fish_traits = fish_list_properties[caught_fish][NAMEOF(caught_fish, fishing_traits)]
var/additive_mod = 0
var/multiplicative_mod = 1
for(var/fish_trait in fish_traits)
var/datum/fishing_trait/trait = new fish_trait
var/list/mod = trait.catch_weight_mod(rod, fisherman)
additive_mod += mod[ADDITIVE_FISHING_MOD]
multiplicative_mod *= mod[MULTIPLICATIVE_FISHING_MOD]
final_table[result] += additive_mod
final_table[result] *= multiplicative_mod
else
//Modify other paths chance
if(rod.hook && rod.hook.fishing_hook_traits & FISHING_HOOK_MAGNETIC)
final_table[result] *= 5
if(final_table[result] <= 0)
final_table -= result
return final_table