diff --git a/code/__DEFINES/dcs/signals/signals_fish.dm b/code/__DEFINES/dcs/signals/signals_fish.dm index 2fbf99446ab..7980aa7ca1e 100644 --- a/code/__DEFINES/dcs/signals/signals_fish.dm +++ b/code/__DEFINES/dcs/signals/signals_fish.dm @@ -17,6 +17,10 @@ #define COMSIG_FISH_EATEN_BY_OTHER_FISH "fish_eaten_by_other_fish" ///From /obj/item/fish/feed: (fed_reagents, fed_reagent_type) #define COMSIG_FISH_FED "fish_on_fed" +///From /obj/item/fish/update_size_and_weight: (new_size, new_weight) +#define COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT "fish_update_size_and_weight" +///From /obj/item/fish/update_fish_force: (weight_rank, bonus_malus) +#define COMSIG_FISH_FORCE_UPDATED "fish_force_updated" /// Fishing challenge completed #define COMSIG_FISHING_CHALLENGE_COMPLETED "fishing_completed" diff --git a/code/__DEFINES/dcs/signals/signals_food.dm b/code/__DEFINES/dcs/signals/signals_food.dm index 36a8b7b3392..113826a4486 100644 --- a/code/__DEFINES/dcs/signals/signals_food.dm +++ b/code/__DEFINES/dcs/signals/signals_food.dm @@ -16,6 +16,9 @@ /// called when an edible ingredient is added: (datum/component/edible/ingredient) #define COMSIG_FOOD_INGREDIENT_ADDED "edible_ingredient_added" +/// from base of /datum/component/edible/get_recipe_complexity(): (list/extra_complexity) +#define COMSIG_FOOD_GET_EXTRA_COMPLEXITY "food_get_extra_complexity" + // Deep frying foods /// An item becomes fried - From /datum/element/fried_item/Attach: (fry_time) #define COMSIG_ITEM_FRIED "item_fried" @@ -31,6 +34,8 @@ #define COMPONENT_MICROWAVE_BAD_RECIPE (1<<1) ///called on item when created through microwaving (): (obj/machinery/microwave/M, cooking_efficiency) #define COMSIG_ITEM_MICROWAVE_COOKED "microwave_cooked" +///called on the ingredient through microwawing: (result) +#define COMSIG_ITEM_MICROWAVE_COOKED_FROM "item_microwave_cooked_from" // Grilling foods (griddle, grill, and bonfire) ///Called when an object is placed onto a griddle @@ -46,6 +51,9 @@ ///Called when an object is turned into another item through grilling ontop of a griddle #define COMSIG_ITEM_GRILLED "item_grill_completed" +///Called when the object is grilled by the grill (not to be confused by the griddle, but oh gee the two should be merged in one) +#define COMSIG_ITEM_BARBEQUE_GRILLED "item_barbeque_grilled" + // Baking foods (oven) //Called when an object is inserted into an oven (atom/oven, mob/baker) #define COMSIG_ITEM_OVEN_PLACED_IN "item_placed_in_oven" diff --git a/code/__DEFINES/dcs/signals/signals_reagent.dm b/code/__DEFINES/dcs/signals/signals_reagent.dm index 5bb2c89d4ef..367ec946361 100644 --- a/code/__DEFINES/dcs/signals/signals_reagent.dm +++ b/code/__DEFINES/dcs/signals/signals_reagent.dm @@ -53,8 +53,6 @@ #define COMSIG_REAGENTS_EXPOSE_MOB "reagents_expose_mob" ///from base of [/turf/proc/expose_reagents]: (/turf, /list, methods, volume_modifier, show_message) #define COMSIG_REAGENTS_EXPOSE_TURF "reagents_expose_turf" -///from base of [/datum/component/personal_crafting/proc/del_reqs]: () -#define COMSIG_REAGENTS_CRAFTING_PING "reagents_crafting_ping" /// sent when reagents are transfered from a cup, to something refillable (atom/transfer_to) #define COMSIG_REAGENTS_CUP_TRANSFER_TO "reagents_cup_transfer_to" /// sent when reagents are transfered from some reagent container, to a cup (atom/transfer_from) diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm index 3671ff3d8cd..ec991b713ec 100644 --- a/code/__DEFINES/fish.dm +++ b/code/__DEFINES/fish.dm @@ -1,5 +1,7 @@ /// Use in fish tables to denote miss chance. #define FISHING_DUD "dud" +///Used in the the hydro tray fishing spot to define a random seed reward +#define FISHING_RANDOM_SEED "Random seed" // Baseline fishing difficulty levels #define FISHING_DEFAULT_DIFFICULTY 15 @@ -14,11 +16,6 @@ #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" - ///Slot defines for the fishing rod and its equipment #define ROD_SLOT_BAIT "bait" #define ROD_SLOT_LINE "line" @@ -90,6 +87,10 @@ #define FISH_ICON_CRAB "crab" #define FISH_ICON_JELLYFISH "jellyfish" #define FISH_ICON_BONE "bone" +#define FISH_ICON_ELECTRIC "electric" +#define FISH_ICON_WEAPON "weapon" +#define FISH_ICON_CRITTER "critter" +#define FISH_ICON_SEED "seed" #define AQUARIUM_ANIMATION_FISH_SWIM "fish" #define AQUARIUM_ANIMATION_FISH_DEAD "dead" @@ -110,8 +111,11 @@ ///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 +#define FISH_SIZE_NORMAL_MAX 80 +#define FISH_SIZE_BULKY_MAX 120 +///size threshold for requiring two-handed carry +#define FISH_SIZE_TWO_HANDS_REQUIRED 135 +#define FISH_SIZE_HUGE_MAX 165 ///The coefficient for maximum weight/size divergence relative to the averages. #define MAX_FISH_DEVIATION_COEFF 2.5 @@ -121,6 +125,15 @@ ///The number of fillets is multiplied by the fish' size and divided by this. #define FISH_FILLET_NUMBER_SIZE_DIVISOR 30 +///The slowdown of the fish when carried begins at this value +#define FISH_WEIGHT_SLOWDOWN 2100 +///The value of the slowdown equals to the weight divided by this (and then at the power of a sub-1 exponent) +#define FISH_WEIGHT_SLOWDOWN_DIVISOR 500 +///The sub-one exponent that results in the final slowdown of the fish item +#define FISH_WEIGHT_SLOWDOWN_EXPONENT 0.54 +///Used to calculate the force of the fish by comparing (1 + log(weight/this_define)) and the w_class of the item. +#define FISH_WEIGHT_FORCE_DIVISOR 250 + ///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. @@ -164,3 +177,7 @@ //Fish breeding stops if fish count exceeds this. #define AQUARIUM_MAX_BREEDING_POPULATION 20 + +//Minigame defines +/// The height of the minigame slider. Not in pixels, but minigame units. +#define FISHING_MINIGAME_AREA 1000 diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 30fdd765d88..79d4c988dd9 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -746,6 +746,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_FOOD_SILVER "food_silver" /// If this item's been made by a chef instead of being map-spawned or admin-spawned or such #define TRAIT_FOOD_CHEF_MADE "food_made_by_chef" +/// This atom has a quality_food_ingredient element attached +#define TRAIT_QUALITY_FOOD_INGREDIENT "quality_food_ingredient" /// The items needs two hands to be carried #define TRAIT_NEEDS_TWO_HANDS "needstwohands" /// Can't be catched when thrown @@ -754,6 +756,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_FISH_CASE_COMPATIBILE "fish_case_compatibile" /// If the item can be used as a bit. #define TRAIT_FISHING_BAIT "fishing_bait" +/// This bait will kill any fish that doesn't have it on its favorite_bait list +#define TRAIT_POISONOUS_BAIT "poisonous_bait" /// The quality of the bait. It influences odds of catching fish #define TRAIT_BASIC_QUALITY_BAIT "baic_quality_bait" #define TRAIT_GOOD_QUALITY_BAIT "good_quality_bait" @@ -959,6 +963,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_FISH_FROM_CASE "fish_from_case" ///Fish will also occasionally fire weak tesla zaps #define TRAIT_FISH_ELECTROGENESIS "fish_electrogenesis" +///Offsprings from this fish will never be of its same type (unless it's self-reproducing). +#define TRAIT_FISH_RECESSIVE "fish_recessive" +///This fish comes equipped with a stinger (increased damage and potentially venomous if also toxic) +#define TRAIT_FISH_STINGER "fish_stinger" /// Trait given to angelic constructs to let them purge cult runes #define TRAIT_ANGELIC "angelic" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 3e1f08464ba..f872dde9b34 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -19,6 +19,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_DRYABLE" = TRAIT_DRYABLE, "TRAIT_FOOD_CHEF_MADE" = TRAIT_FOOD_CHEF_MADE, "TRAIT_FOOD_FRIED" = TRAIT_FOOD_FRIED, + "TRAIT_QUALITY_FOOD_INGREDIENT" = TRAIT_QUALITY_FOOD_INGREDIENT, "TRAIT_FOOD_SILVER" = TRAIT_FOOD_SILVER, "TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER, "TRAIT_LIGHTING_DEBUGGED" = TRAIT_LIGHTING_DEBUGGED, @@ -569,6 +570,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NODROP" = TRAIT_NODROP, "TRAIT_OMNI_BAIT" = TRAIT_OMNI_BAIT, "TRAIT_PLANT_WILDMUTATE" = TRAIT_PLANT_WILDMUTATE, + "TRAIT_POISONOUS_BAIT" = TRAIT_POISONOUS_BAIT, "TRAIT_T_RAY_VISIBLE" = TRAIT_T_RAY_VISIBLE, "TRAIT_TRANSFORM_ACTIVE" = TRAIT_TRANSFORM_ACTIVE, "TRAIT_UNCATCHABLE" = TRAIT_UNCATCHABLE, @@ -599,15 +601,17 @@ GLOBAL_LIST_INIT(traits_by_type, list( /obj/item/fish = list( "TRAIT_FISH_AMPHIBIOUS" = TRAIT_FISH_AMPHIBIOUS, "TRAIT_FISH_CROSSBREEDER" = TRAIT_FISH_CROSSBREEDER, + "TRAIT_FISH_ELECTROGENESIS" = TRAIT_FISH_ELECTROGENESIS, "TRAIT_FISH_FED_LUBE" = TRAIT_FISH_FED_LUBE, "TRAIT_FISH_FLOPPING" = TRAIT_FISH_FLOPPING, "TRAIT_FISH_FROM_CASE" = TRAIT_FISH_FROM_CASE, "TRAIT_FISH_NO_HUNGER" = TRAIT_FISH_NO_HUNGER, "TRAIT_FISH_NO_MATING" = TRAIT_FISH_NO_MATING, + "TRAIT_FISH_RECESSIVE" = TRAIT_FISH_RECESSIVE, "TRAIT_FISH_SELF_REPRODUCE" = TRAIT_FISH_SELF_REPRODUCE, "TRAIT_FISH_STASIS" = TRAIT_FISH_STASIS, + "TRAIT_FISH_STINGER" = TRAIT_FISH_STINGER, "TRAIT_FISH_TOXIN_IMMUNE" = TRAIT_FISH_TOXIN_IMMUNE, - "TRAIT_FISH_ELECTROGENESIS" = TRAIT_FISH_ELECTROGENESIS, "TRAIT_RESIST_EMULSIFY" = TRAIT_RESIST_EMULSIFY, "TRAIT_YUCKY_FISH" = TRAIT_YUCKY_FISH, ), diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index bad14b7b5e5..dd4456c0ccd 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -335,14 +335,19 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_MAGNETIC_ID_CARD" = TRAIT_MAGNETIC_ID_CARD, ), /obj/item/fish = list( + "TRAIT_FISH_AMPHIBIOUS" = TRAIT_FISH_AMPHIBIOUS, "TRAIT_FISH_CROSSBREEDER" = TRAIT_FISH_CROSSBREEDER, + "TRAIT_FISH_ELECTROGENESIS" = TRAIT_FISH_ELECTROGENESIS, "TRAIT_FISH_FED_LUBE" = TRAIT_FISH_FED_LUBE, + "TRAIT_FISH_FROM_CASE" = TRAIT_FISH_FROM_CASE, "TRAIT_FISH_NO_HUNGER" = TRAIT_FISH_NO_HUNGER, "TRAIT_FISH_NO_MATING" = TRAIT_FISH_NO_MATING, + "TRAIT_FISH_RECESSIVE" = TRAIT_FISH_RECESSIVE, "TRAIT_FISH_SELF_REPRODUCE" = TRAIT_FISH_SELF_REPRODUCE, "TRAIT_FISH_STASIS" = TRAIT_FISH_STASIS, + "TRAIT_FISH_STINGER" = TRAIT_FISH_STINGER, "TRAIT_FISH_TOXIN_IMMUNE" = TRAIT_FISH_TOXIN_IMMUNE, - "TRAIT_RESIST_EMULSIFY" = TRAIT_RESIST_EMULSIFY, + "TRAIT_RESIST_EMULSIFY" = TRAIT_RESIST_EMULSIFY, "TRAIT_YUCKY_FISH" = TRAIT_YUCKY_FISH, ), /obj/item/organ/internal/liver = list( diff --git a/code/controllers/subsystem/processing/fishing.dm b/code/controllers/subsystem/processing/fishing.dm index da10d3d631a..ca54141de82 100644 --- a/code/controllers/subsystem/processing/fishing.dm +++ b/code/controllers/subsystem/processing/fishing.dm @@ -1,7 +1,4 @@ -/** - * So far, only used by the fishing minigame. Feel free to rename it to something like veryfastprocess - * if you need one that fires 10 times a second - */ +/// subsystem for the fishing minigame processing. PROCESSING_SUBSYSTEM_DEF(fishing) name = "Fishing" - wait = 0.1 SECONDS + wait = 0.05 SECONDS // If you raise it to 0.1 SECONDS, you better also modify [datum/fish_movement/move_fish()] diff --git a/code/datums/components/bakeable.dm b/code/datums/components/bakeable.dm index afc71936f1b..93e96f65d58 100644 --- a/code/datums/components/bakeable.dm +++ b/code/datums/components/bakeable.dm @@ -93,11 +93,11 @@ var/list/asomnia_hadders = list() for(var/mob/smeller in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, used_oven)) if(HAS_TRAIT(smeller, TRAIT_ANOSMIA)) - asomnia_hadders += smeller + asomnia_hadders += smeller if(positive_result) used_oven.visible_message( - span_notice("You smell something great coming from [used_oven]."), + span_notice("You smell something great coming from [used_oven]."), blind_message = span_notice("You smell something great..."), ignored_mobs = asomnia_hadders, ) diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index fa47d86a310..3854c1ad013 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -345,7 +345,6 @@ RC.reagents.trans_to(holder, reagent_volume, target_id = path_key, no_react = TRUE) surroundings -= RC amt -= reagent_volume - SEND_SIGNAL(RC.reagents, COMSIG_REAGENTS_CRAFTING_PING) // - [] TODO: Make this entire thing less spaghetti else surroundings -= RC RC.update_appearance(UPDATE_ICON) diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm index 056f1e5791e..cff5c2fb4fe 100644 --- a/code/datums/components/food/edible.dm +++ b/code/datums/components/food/edible.dm @@ -523,13 +523,13 @@ Behavior that's still missing from this component that original food items had t /datum/component/edible/proc/apply_buff(mob/eater) var/buff var/recipe_complexity = get_recipe_complexity() - if(recipe_complexity == 0) + if(recipe_complexity <= 0) return var/obj/item/food/food = parent if(!isnull(food.crafted_food_buff)) buff = food.crafted_food_buff else - buff = pick_weight(GLOB.food_buffs[recipe_complexity]) + buff = pick_weight(GLOB.food_buffs[min(recipe_complexity, FOOD_COMPLEXITY_5)]) if(!isnull(buff)) var/mob/living/living_eater = eater var/atom/owner = parent @@ -590,10 +590,13 @@ Behavior that's still missing from this component that original food items had t /// Get the complexity of the crafted food /datum/component/edible/proc/get_recipe_complexity() + var/list/extra_complexity = list(0) + SEND_SIGNAL(parent, COMSIG_FOOD_GET_EXTRA_COMPLEXITY, extra_complexity) + var/complexity_to_add = extra_complexity[1] if(!HAS_TRAIT(parent, TRAIT_FOOD_CHEF_MADE) || !istype(parent, /obj/item/food)) - return 0 // It is factory made. Soulless. + return complexity_to_add // It is factory made. Soulless. var/obj/item/food/food = parent - return food.crafting_complexity + return food.crafting_complexity + complexity_to_add /// Get food quality adjusted according to eater's preferences /datum/component/edible/proc/get_perceived_food_quality(mob/living/carbon/human/eater) diff --git a/code/datums/elements/food/grilled_item.dm b/code/datums/elements/food/grilled_item.dm index de6c2ef41c1..74e772eb73c 100644 --- a/code/datums/elements/food/grilled_item.dm +++ b/code/datums/elements/food/grilled_item.dm @@ -28,6 +28,8 @@ if(grill_time > 30 SECONDS && isnull(this_food.GetComponent(/datum/component/edible))) this_food.AddComponent(/datum/component/edible, foodtypes = FRIED) + SEND_SIGNAL(this_food, COMSIG_ITEM_BARBEQUE_GRILLED) + /datum/element/grilled_item/Detach(atom/source, ...) source.name = initial(source.name) source.desc = initial(source.desc) diff --git a/code/datums/elements/food/microwavable.dm b/code/datums/elements/food/microwavable.dm index 8e7305545c0..5fdd4c084ad 100644 --- a/code/datums/elements/food/microwavable.dm +++ b/code/datums/elements/food/microwavable.dm @@ -44,6 +44,7 @@ var/efficiency = istype(used_microwave) ? used_microwave.efficiency : 1 SEND_SIGNAL(result, COMSIG_ITEM_MICROWAVE_COOKED, source, efficiency) + SEND_SIGNAL(source, COMSIG_ITEM_MICROWAVE_COOKED_FROM, result, efficiency) if(IS_EDIBLE(result) && (result_typepath != default_typepath)) BLACKBOX_LOG_FOOD_MADE(result.type) diff --git a/code/datums/elements/quality_food_ingredient.dm b/code/datums/elements/quality_food_ingredient.dm new file mode 100644 index 00000000000..e9bfec246c5 --- /dev/null +++ b/code/datums/elements/quality_food_ingredient.dm @@ -0,0 +1,71 @@ +///An element that adds extra food quality to any edible that was made from an atom with this attached. +/datum/element/quality_food_ingredient + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + ///The increase of recipe complexity (basically hardcoded food quality) of edibles made with this. + var/complexity_increase = 0 + +/datum/element/quality_food_ingredient/Attach(datum/target, complexity_increase) + . = ..() + if(!isatom(target)) + return ELEMENT_INCOMPATIBLE + if(HAS_TRAIT_FROM(target, TRAIT_QUALITY_FOOD_INGREDIENT, REF(src))) //It already has this element attached. + return + + src.complexity_increase = complexity_increase + + RegisterSignal(target, COMSIG_ATOM_USED_IN_CRAFT, PROC_REF(used_in_craft)) + RegisterSignal(target, COMSIG_ITEM_BAKED, PROC_REF(item_baked)) + RegisterSignal(target, COMSIG_ITEM_MICROWAVE_COOKED_FROM, PROC_REF(microwaved_from)) + RegisterSignal(target, COMSIG_ITEM_GRILLED, PROC_REF(item_grilled)) + RegisterSignals(target, list(COMSIG_ITEM_BARBEQUE_GRILLED, COMSIG_ITEM_FRIED), PROC_REF(simply_cooked)) + RegisterSignal(target, COMSIG_ITEM_USED_AS_INGREDIENT, PROC_REF(used_as_ingredient)) + +/datum/element/quality_food_ingredient/Detach(datum/source) + UnregisterSignal(source, list( + COMSIG_ATOM_USED_IN_CRAFT, + COMSIG_ITEM_BAKED, + COMSIG_ITEM_MICROWAVE_COOKED_FROM, + COMSIG_ITEM_GRILLED, + COMSIG_ITEM_BARBEQUE_GRILLED, + COMSIG_ITEM_FRIED, + COMSIG_ITEM_USED_AS_INGREDIENT, + COMSIG_FOOD_GET_EXTRA_COMPLEXITY, + )) + REMOVE_TRAIT(source, TRAIT_QUALITY_FOOD_INGREDIENT, REF(src)) + return ..() + +/datum/element/quality_food_ingredient/proc/used_in_craft(datum/source, atom/result) + SIGNAL_HANDLER + add_quality(result) + +/datum/element/quality_food_ingredient/proc/item_baked(datum/source, atom/baked_result) + SIGNAL_HANDLER + add_quality(baked_result) + +/datum/element/quality_food_ingredient/proc/microwaved_from(datum/source, atom/result) + SIGNAL_HANDLER + add_quality(result) + +/datum/element/quality_food_ingredient/proc/item_grilled(datum/source, atom/grill_result) + SIGNAL_HANDLER + add_quality(grill_result) + +/datum/element/quality_food_ingredient/proc/simply_cooked(datum/source) + SIGNAL_HANDLER + //The target of the food quality and the source are the same, there's no need to re-add the whole element. + RegisterSignal(source, COMSIG_FOOD_GET_EXTRA_COMPLEXITY, PROC_REF(add_complexity), TRUE) + ADD_TRAIT(source, TRAIT_QUALITY_FOOD_INGREDIENT, REF(src)) + +/datum/element/quality_food_ingredient/proc/used_as_ingredient(datum/source, atom/container) + SIGNAL_HANDLER + add_quality(container) + +/datum/element/quality_food_ingredient/proc/add_quality(atom/target) + target.AddElement(/datum/element/quality_food_ingredient, complexity_increase) + RegisterSignal(target, COMSIG_FOOD_GET_EXTRA_COMPLEXITY, PROC_REF(add_complexity), TRUE) + ADD_TRAIT(target, TRAIT_QUALITY_FOOD_INGREDIENT, REF(src)) + +/datum/element/quality_food_ingredient/proc/add_complexity(datum/source, list/extra_complexity) + SIGNAL_HANDLER + extra_complexity[1] += complexity_increase diff --git a/code/game/objects/items/food/meatdish.dm b/code/game/objects/items/food/meatdish.dm index 73cd1c9336c..bd21d32d609 100644 --- a/code/game/objects/items/food/meatdish.dm +++ b/code/game/objects/items/food/meatdish.dm @@ -36,6 +36,36 @@ w_class = WEIGHT_CLASS_SMALL starting_reagent_purity = 1.0 +/obj/item/food/fishmeat/quality + name = "quality fish fillet" + desc = "A fillet of some precious fish meat." + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/vitamin = 3, + ) + bite_consumption = 7 + crafting_complexity = FOOD_COMPLEXITY_1 + +/obj/item/food/fishmeat/quality/Initialize(mapload) + . = ..() + AddElement(/datum/element/quality_food_ingredient, FOOD_COMPLEXITY_1) + +/obj/item/food/fishmeat/salmon + name = "salmon fillet" + desc = "a chunky, fatty fillet of salmon meat." + icon_state = "salmon" + food_reagents = list( + /datum/reagent/consumable/nutriment/protein = 4, + /datum/reagent/consumable/nutriment/vitamin = 3, + /datum/reagent/consumable/nutriment/fat/oil = 2, + ) + bite_consumption = 4.5 + crafting_complexity = FOOD_COMPLEXITY_1 + +/obj/item/food/fishmeat/salmon/Initialize(mapload) + . = ..() + AddElement(/datum/element/quality_food_ingredient, FOOD_COMPLEXITY_1) + /obj/item/food/fishmeat/carp name = "carp fillet" desc = "A fillet of spess carp meat." diff --git a/code/game/turfs/open/ice.dm b/code/game/turfs/open/ice.dm index 481dcb6b847..dbfff2efc8f 100644 --- a/code/game/turfs/open/ice.dm +++ b/code/game/turfs/open/ice.dm @@ -15,10 +15,21 @@ clawfootstep = FOOTSTEP_HARD_CLAW heavyfootstep = FOOTSTEP_GENERIC_HEAVY rust_resistance = RUST_RESISTANCE_ORGANIC + var/can_make_hole = TRUE + var/static/list/tool_screentips = list( + TOOL_SHOVEL = list( + SCREENTIP_CONTEXT_LMB = "Dig fishing hole", + ), + TOOL_MINING = list( + SCREENTIP_CONTEXT_LMB = "Dig fishing hole", + ), + ) /turf/open/misc/ice/Initialize(mapload) . = ..() MakeSlippery(TURF_WET_PERMAFROST, INFINITY, 0, INFINITY, TRUE, FALSE) + if(can_make_hole) + AddElement(/datum/element/contextual_screentip_tools, tool_screentips) /turf/open/misc/ice/break_tile() return @@ -26,6 +37,28 @@ /turf/open/misc/ice/burn_tile() return +/turf/open/misc/ice/examine(mob/user) + . = ..() + if(can_make_hole) + . += span_info("You could use a [EXAMINE_HINT("shovel")] or a [EXAMINE_HINT("pick")] to dig a fishing hole here.") + +/turf/open/misc/ice/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!can_make_hole) + return NONE + if(tool.tool_behaviour != TOOL_SHOVEL && tool.tool_behaviour != TOOL_MINING) + return NONE + balloon_alert(user, "digging...") + playsound(src, 'sound/effects/shovel_dig.ogg', 50, TRUE) + if(!do_after(user, 5 SECONDS, src)) + return NONE + balloon_alert(user, "dug hole") + AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[/datum/fish_source/ice_fishing]) + add_overlay(mutable_appearance('icons/turf/overlays.dmi', "ice_hole")) + can_make_hole = FALSE + RemoveElement(/datum/element/contextual_screentip_tools, tool_screentips) + flags_1 &= ~HAS_CONTEXTUAL_SCREENTIPS_1 + return ITEM_INTERACT_SUCCESS + /turf/open/misc/ice/smooth icon_state = "ice_turf-255" base_icon_state = "ice_turf" @@ -40,11 +73,13 @@ /turf/open/misc/ice/icemoon/no_planet_atmos planetary_atmos = FALSE + can_make_hole = FALSE /turf/open/misc/ice/temperate baseturfs = /turf/open/misc/ice/temperate desc = "Somehow, it is not melting under these conditions. Must be some very thick ice. Just as slippery too." initial_gas_mix = COLD_ATMOS //it works with /turf/open/misc/asteroid/snow/temperatre + can_make_hole = FALSE //For when you want real, genuine ice in your kitchen's cold room. /turf/open/misc/ice/coldroom diff --git a/code/game/turfs/open/sand.dm b/code/game/turfs/open/sand.dm index c863e28231d..fb10d297e18 100644 --- a/code/game/turfs/open/sand.dm +++ b/code/game/turfs/open/sand.dm @@ -10,6 +10,10 @@ heavyfootstep = FOOTSTEP_GENERIC_HEAVY rust_resistance = RUST_RESISTANCE_ORGANIC +/turf/open/misc/beach/Initialize(mapload) + . = ..() + AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/sand) + /turf/open/misc/beach/ex_act(severity, target) return FALSE diff --git a/code/game/turfs/open/water.dm b/code/game/turfs/open/water.dm index 83987ee7b54..20c6215209a 100644 --- a/code/game/turfs/open/water.dm +++ b/code/game/turfs/open/water.dm @@ -21,7 +21,7 @@ var/immerse_overlay_color = "#5AAA88" /// Fishing element for this specific water tile - var/datum/fish_source/fishing_datum = /datum/fish_source/portal + var/datum/fish_source/fishing_datum = /datum/fish_source/river /turf/open/water/Initialize(mapload) . = ..() diff --git a/code/modules/fishing/aquarium/aquarium.dm b/code/modules/fishing/aquarium/aquarium.dm index bc3df83a797..f7698a80b43 100644 --- a/code/modules/fishing/aquarium/aquarium.dm +++ b/code/modules/fishing/aquarium/aquarium.dm @@ -399,9 +399,13 @@ new /obj/item/aquarium_prop/sand(src) new /obj/item/aquarium_prop/seaweed(src) - new /obj/item/fish/goldfish/gill(src) + if(prob(85)) + new /obj/item/fish/goldfish/gill(src) + reagents.add_reagent(/datum/reagent/consumable/nutriment, 2) + else + new /obj/item/fish/three_eyes/gill(src) + reagents.add_reagent(/datum/reagent/toxin/mutagen, 2) //three eyes goldfish feed on mutagen. - reagents.add_reagent(/datum/reagent/consumable/nutriment, 2) /obj/structure/aquarium/prefilled anchored = TRUE diff --git a/code/modules/fishing/aquarium/aquarium_kit.dm b/code/modules/fishing/aquarium/aquarium_kit.dm index a9dff21d83e..e8bf5e91040 100644 --- a/code/modules/fishing/aquarium/aquarium_kit.dm +++ b/code/modules/fishing/aquarium/aquarium_kit.dm @@ -58,7 +58,7 @@ name = "ominous fish case" /obj/item/storage/fish_case/syndicate/get_fish_type() - return pick(/obj/item/fish/donkfish, /obj/item/fish/emulsijack, /obj/item/fish/jumpercable) + return pick(/obj/item/fish/donkfish, /obj/item/fish/emulsijack, /obj/item/fish/jumpercable, /obj/item/fish/chainsawfish) /obj/item/storage/fish_case/tiziran name = "imported fish case" diff --git a/code/modules/fishing/bait.dm b/code/modules/fishing/bait.dm index 78d18aa539e..8eb8911a864 100644 --- a/code/modules/fishing/bait.dm +++ b/code/modules/fishing/bait.dm @@ -61,3 +61,21 @@ bait_type = /obj/item/food/bait/doughball/synthetic/super uses_left = 12 + +/// Helper proc that checks if a bait matches identifier from fav/disliked bait list +/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"] + if("Reagent") + return bait.reagents?.has_reagent(special_identifier["Value"], special_identifier["Amount"], check_subtypes = TRUE) + else + CRASH("Unknown bait identifier in fish favourite/disliked list") + else + return HAS_TRAIT(bait, identifier) + diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index e5e522b4302..6c662163464 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -6,17 +6,19 @@ icon_state = "bugfish" lefthand_file = 'icons/mob/inhands/fish_lefthand.dmi' righthand_file = 'icons/mob/inhands/fish_righthand.dmi' - inhand_icon_state = "fish_normal" force = 6 + throwforce = 6 + throw_range = 8 attack_verb_continuous = list("slaps", "whacks") attack_verb_simple = list("slap", "whack") hitsound = 'sound/weapons/slap.ogg' ///The grind results of the fish. They scale with the weight of the fish. grind_results = list(/datum/reagent/blood = 5, /datum/reagent/consumable/liquidgibs = 5) obj_flags = UNIQUE_RENAME + item_flags = IMMUTABLE_SLOW|SLOWS_WHILE_IN_HAND /// Resulting width of aquarium visual icon - default size of "fish_greyscale" state - var/sprite_width = 3 + var/sprite_width = 5 /// Resulting height of aquarium visual icon - default size of "fish_greyscale" state var/sprite_height = 3 @@ -93,8 +95,8 @@ */ var/list/fish_traits = list() - /// Fishing behaviour - var/fish_ai_type = FISH_AI_DUMB + /// path to datums that dictate how the fish moves during the fishing minigame + var/fish_movement_type = /datum/fish_movement /// Base additive modifier to fishing difficulty var/fishing_difficulty_modifier = 0 @@ -121,6 +123,9 @@ /// Average weight for this fish type in grams var/average_weight = 1000 + ///The general deviation from the average weight and size this fish has in the wild + var/weight_size_deviation = 0.2 + /// When outside of an aquarium, these gases that are checked (as well as pressure and temp) to assert if the environment is safe or not. var/list/safe_air_limits = list( /datum/gas/oxygen = list(12, 100), @@ -203,7 +208,7 @@ . += span_notice("It weighs [weight] g.") ///Randomizes weight and size. -/obj/item/fish/proc/randomize_size_and_weight(base_size = average_size, base_weight = average_weight, deviation = 0.2) +/obj/item/fish/proc/randomize_size_and_weight(base_size = average_size, base_weight = average_weight, deviation = weight_size_deviation) 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)) @@ -214,38 +219,125 @@ ///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) - if(size && fillet_type) - RemoveElement(/datum/element/processable, TOOL_KNIFE, fillet_type, num_fillets, 0.5 SECONDS, screentip_verb = "Cut") + SEND_SIGNAL(src, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, new_size, new_weight) + if(size) + if(fillet_type) + RemoveElement(/datum/element/processable, TOOL_KNIFE, fillet_type, num_fillets, 0.5 SECONDS * num_fillets, screentip_verb = "Cut") + if(size > FISH_SIZE_TWO_HANDS_REQUIRED) + qdel(GetComponent(/datum/component/two_handed)) size = new_size + var/init_icon_state = initial(inhand_icon_state) switch(size) if(0 to FISH_SIZE_TINY_MAX) update_weight_class(WEIGHT_CLASS_TINY) - inhand_icon_state = "fish_small" + if(!init_icon_state) + inhand_icon_state = "fish_small" if(FISH_SIZE_TINY_MAX to FISH_SIZE_SMALL_MAX) - inhand_icon_state = "fish_small" + if(!init_icon_state) + inhand_icon_state = "fish_small" update_weight_class(WEIGHT_CLASS_SMALL) if(FISH_SIZE_SMALL_MAX to FISH_SIZE_NORMAL_MAX) - inhand_icon_state = "fish_normal" + if(!init_icon_state) + inhand_icon_state = "fish_normal" update_weight_class(WEIGHT_CLASS_NORMAL) if(FISH_SIZE_NORMAL_MAX to FISH_SIZE_BULKY_MAX) - inhand_icon_state = "fish_bulky" + if(!init_icon_state) + inhand_icon_state = "fish_bulky" update_weight_class(WEIGHT_CLASS_BULKY) - if(FISH_SIZE_BULKY_MAX to INFINITY) - inhand_icon_state = "fish_huge" + if(FISH_SIZE_BULKY_MAX to FISH_SIZE_HUGE_MAX) + if(!init_icon_state) + inhand_icon_state = "fish_huge" update_weight_class(WEIGHT_CLASS_HUGE) + if(FISH_SIZE_HUGE_MAX to INFINITY) + if(!init_icon_state) + inhand_icon_state = "fish_huge" + update_weight_class(WEIGHT_CLASS_GIGANTIC) + + if(size > FISH_SIZE_TWO_HANDS_REQUIRED) + inhand_icon_state = "[inhand_icon_state]_wielded" + AddComponent(/datum/component/two_handed, require_twohands = TRUE) + if(fillet_type) var/init_fillets = initial(num_fillets) var/amount = max(round(init_fillets * size / FISH_FILLET_NUMBER_SIZE_DIVISOR, 1), 1) num_fillets = amount - AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, num_fillets, 0.5 SECONDS, screentip_verb = "Cut") + AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, num_fillets, 0.5 SECONDS * num_fillets, screentip_verb = "Cut") if(weight) for(var/reagent_type in grind_results) grind_results[reagent_type] /= FLOOR(weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR, 0.1) weight = new_weight + + if(weight >= FISH_WEIGHT_SLOWDOWN) + slowdown = round(((weight/FISH_WEIGHT_SLOWDOWN_DIVISOR)**FISH_WEIGHT_SLOWDOWN_EXPONENT)-1.3, 0.1) + drag_slowdown = round(slowdown * 0.5, 1) + else + slowdown = 0 + drag_slowdown = 0 + if(ismob(loc)) + var/mob/mob = loc + mob.update_equipment_speed_mods() + for(var/reagent_type in grind_results) grind_results[reagent_type] *= FLOOR(weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR, 0.1) + update_fish_force() + +///Reset weapon-related variables of this items and recalculates those values based on the fish weight and size. +/obj/item/fish/proc/update_fish_force() + force = initial(force) + throwforce = initial(throwforce) + throw_range = initial(throw_range) + demolition_mod = initial(demolition_mod) + attack_verb_continuous = initial(attack_verb_continuous) + attack_verb_simple = initial(attack_verb_simple) + hitsound = initial(hitsound) + damtype = initial(damtype) + attack_speed = initial(attack_speed) + block_chance = initial(block_chance) + armour_penetration = initial(armour_penetration) + wound_bonus = initial(wound_bonus) + bare_wound_bonus = initial(bare_wound_bonus) + toolspeed = initial(toolspeed) + + var/weight_rank = max(round(1 + log(2, weight/FISH_WEIGHT_FORCE_DIVISOR), 1), 1) + + throw_range -= weight_rank + get_force_rank() + + var/bonus_malus = weight_rank - w_class + if(bonus_malus) + calculate_fish_force_bonus(bonus_malus) + + throwforce = force + + SEND_SIGNAL(src, COMSIG_FISH_FORCE_UPDATED, weight_rank, bonus_malus) + +///A proc that makes the fish slightly stronger or weaker if there's a noticeable discrepancy between size and weight. +/obj/item/fish/proc/calculate_fish_force_bonus(bonus_malus) + demolition_mod += bonus_malus * 0.1 + attack_speed += bonus_malus * 0.1 + force = round(force * (1 + bonus_malus * 0.1), 0.1) + +/obj/item/fish/proc/get_force_rank() + switch(w_class) + if(WEIGHT_CLASS_TINY) + force -= 3 + attack_speed -= 0.1 SECONDS + if(WEIGHT_CLASS_NORMAL) + force += 2 + if(WEIGHT_CLASS_BULKY) + force += 5 + attack_speed += 0.1 SECONDS + if(WEIGHT_CLASS_HUGE) + force += 9 + attack_speed += 0.2 SECONDS + demolition_mod += 0.2 + if(WEIGHT_CLASS_GIGANTIC) + force += 13 + attack_speed += 0.4 SECONDS + demolition_mod += 0.4 + /** * This proc has fish_traits list populated with fish_traits paths from three different lists: * traits from x_traits and y_traits are compared, and inserted if conditions are met; @@ -370,7 +462,7 @@ SEND_SIGNAL(src, COMSIG_FISH_LIFE, seconds_per_tick) -/obj/item/fish/proc/set_status(new_status) +/obj/item/fish/proc/set_status(new_status, silent = FALSE) if(status == new_status) return switch(new_status) @@ -384,12 +476,14 @@ status = FISH_DEAD STOP_PROCESSING(SSobj, src) stop_flopping() - var/message = span_notice(replacetext(death_text, "%SRC", "[src]")) - if(isaquarium(loc)) - loc.visible_message(message) - else - visible_message(message) + if(!silent) + var/message = span_notice(replacetext(death_text, "%SRC", "[src]")) + if(isaquarium(loc)) + loc.visible_message(message) + else + visible_message(message) update_appearance() + update_fish_force() SEND_SIGNAL(src, COMSIG_FISH_STATUS_CHANGED) /obj/item/fish/expose_reagents(list/reagents, datum/reagents/source, methods = TOUCH, volume_modifier = 1, show_message = TRUE) @@ -476,7 +570,7 @@ return FALSE if(!being_targeted && length(aquarium.get_fishes()) >= AQUARIUM_MAX_BREEDING_POPULATION) return FALSE - return aquarium.allow_breeding && health >= initial(health) * 0.8 && stable_population > 1 && world.time >= breeding_wait + return aquarium.allow_breeding && health >= initial(health) * 0.8 && stable_population >= 1 && world.time >= breeding_wait /obj/item/fish/proc/try_to_reproduce() var/obj/structure/aquarium/aquarium = loc @@ -511,36 +605,48 @@ second_fish = other_fish break - if(!second_fish && !HAS_TRAIT(src, TRAIT_FISH_SELF_REPRODUCE)) - return FALSE + if(!second_fish) + if(!HAS_TRAIT(src, TRAIT_FISH_SELF_REPRODUCE)) + return FALSE + if(length(aquarium.tracked_fish_by_type[type]) >= stable_population) + return FALSE + + if(PERFORM_ALL_TESTS(fish_breeding) && second_fish && !length(evolution_types)) + return create_offspring(second_fish.type, second_fish) var/chosen_type var/datum/fish_evolution/chosen_evolution - if(PERFORM_ALL_TESTS(fish_breeding) && second_fish && !length(evolution_types)) - chosen_type = second_fish.type - else - var/list/possible_evolutions = list() - for(var/evolution_type in evolution_types) + var/list/possible_evolutions = list() + for(var/evolution_type in evolution_types) + var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type] + if(evolution.check_conditions(src, second_fish, aquarium)) + possible_evolutions += evolution + if(second_fish?.evolution_types) + var/secondary_evolutions = (second_fish.evolution_types - evolution_types) + for(var/evolution_type in secondary_evolutions) var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type] - if(evolution.check_conditions(src, second_fish, aquarium)) + if(evolution.check_conditions(second_fish, src, aquarium)) possible_evolutions += evolution - if(second_fish?.evolution_types) - var/secondary_evolutions = (second_fish.evolution_types - evolution_types) - for(var/evolution_type in secondary_evolutions) - var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type] - if(evolution.check_conditions(second_fish, src, aquarium)) - possible_evolutions += evolution - if(length(possible_evolutions)) - chosen_evolution = pick(possible_evolutions) - chosen_type = chosen_evolution.new_fish_type - else if(second_fish) - if(length(aquarium.tracked_fish_by_type[type]) >= stable_population) + if(length(possible_evolutions)) + chosen_evolution = pick(possible_evolutions) + chosen_type = chosen_evolution.new_fish_type + else if(second_fish) + var/recessive = HAS_TRAIT(src, TRAIT_FISH_RECESSIVE) + var/recessive_partner = HAS_TRAIT(second_fish, TRAIT_FISH_RECESSIVE) + if(length(aquarium.tracked_fish_by_type[type]) >= stable_population) + if(recessive_partner && !recessive) + return FALSE + chosen_type = second_fish.type + else + if(recessive && !recessive_partner) chosen_type = second_fish.type + else if(recessive_partner && !recessive) + chosen_type = type else chosen_type = pick(second_fish.type, type) - else - chosen_type = type + else + chosen_type = type return create_offspring(chosen_type, second_fish, chosen_evolution) diff --git a/code/modules/fishing/fish/fish_evolution.dm b/code/modules/fishing/fish/fish_evolution.dm index e6e2f1d9570..4fcc35109a2 100644 --- a/code/modules/fishing/fish/fish_evolution.dm +++ b/code/modules/fishing/fish/fish_evolution.dm @@ -78,6 +78,7 @@ GLOBAL_LIST_INIT(fish_evolutions, init_subtypes_w_path_keys(/datum/fish_evolutio /datum/fish_evolution/purple_sludgefish probability = 5 new_fish_type = /obj/item/fish/sludgefish/purple + new_traits = list(/datum/fish_trait/recessive) removed_traits = list(/datum/fish_trait/no_mating) /datum/fish_evolution/mastodon @@ -85,7 +86,7 @@ GLOBAL_LIST_INIT(fish_evolutions, init_subtypes_w_path_keys(/datum/fish_evolutio probability = 40 new_fish_type = /obj/item/fish/mastodon new_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/amphibious, /datum/fish_trait/predator, /datum/fish_trait/aggressive) - conditions_note = "The fish (and its mate) need to be unusually big both in size and weight." + conditions_note = "The fish (and its mate) needs to be unusually big both in size and weight." /datum/fish_evolution/mastodon/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) if((source.size < 120 || source.weight < 3000) || (mate && (mate.size < 120 || mate.weight < 3000))) @@ -103,3 +104,21 @@ GLOBAL_LIST_INIT(fish_evolutions, init_subtypes_w_path_keys(/datum/fish_evolutio new_fish_type = /obj/item/fish/chasm_crab/ice required_temperature_min = MIN_AQUARIUM_TEMP+9 required_temperature_max = MIN_AQUARIUM_TEMP+10 + +/datum/fish_evolution/three_eyes + name = "Three-eyed Goldfish" + probability = 3 + new_fish_type = /obj/item/fish/three_eyes + new_traits = list(/datum/fish_trait/recessive) + +/datum/fish_evolution/chainsawfish + name = "Chainsawfish" + probability = 30 + new_fish_type = /obj/item/fish/chainsawfish + new_traits = list(/datum/fish_trait/predator, /datum/fish_trait/aggressive) + conditions_note = "The fish needs to be unusually big and aggressive" + +/datum/fish_evolution/chainsawfish/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) + if(source.size >= 60 && source.size >= 1000 && (/datum/fish_trait/aggressive in source.fish_traits)) + return ..() + return FALSE diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm index 76b0cc8691c..3667a038bff 100644 --- a/code/modules/fishing/fish/fish_traits.dm +++ b/code/modules/fishing/fish/fish_traits.dm @@ -37,6 +37,8 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) var/list/fish_whitelist /// Depending on the value, fish with trait will be reported as more or less difficult in the catalog. var/added_difficulty = 0 + /// Reagents added to the fish when gained + var/list/reagents_to_add /// 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) @@ -54,7 +56,11 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /// Applies some special qualities to the fish that has been spawned /datum/fish_trait/proc/apply_to_fish(obj/item/fish/fish) - return + SHOULD_CALL_PARENT(TRUE) + if(reagents_to_add) + for(var/reagent in reagents_to_add) + add_to_reagents(fish, reagent, reagents_to_add[reagent]) + RegisterSignal(fish, COMSIG_ATOM_PROCESSED, PROC_REF(process_reagents)) /// Applies some special qualities to basic mobs generated by fish (i.e. chasm chrab --> young lobstrosity --> lobstrosity). /datum/fish_trait/proc/apply_to_mob(mob/living/basic/mob) @@ -79,6 +85,37 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) fish.grind_results.Insert(1, reagent_type) fish.grind_results[reagent_type] = amount +/// Proc that handles adding reagents from the trait to the fillets from butchered fish. +/datum/fish_trait/proc/process_reagents(obj/item/fish/source, mob/living/user, obj/item/process_item, list/results) + SIGNAL_HANDLER + var/results_with_reagents = 0 + for(var/atom/result as anything in results) + if(result.reagents) + results_with_reagents++ + if(!results_with_reagents) + return + for(var/reagent in reagents_to_add) + var/amount = round(source.grind_results[reagent] / results_with_reagents, 0.1) + for(var/atom/result as anything in results) + result.reagents?.add_reagent(reagent, amount) + +/// Proc that adds or changes the venomous when the fish size and/or weight are updated +/datum/fish_trait/proc/add_venom(obj/item/fish/source, venom_path, new_weight, mult = 0.25) + if(source.size) + var/old_amount = max(round((source.weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * mult, 0.1), mult) + source.RemoveElement(/datum/element/venomous, venom_path, old_amount) + + var/new_amount = max(round((new_weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * mult, 0.1), mult) + source.AddElement(/datum/element/venomous, venom_path, new_amount) + +/// Proc that changes the venomous element based on if the fish is alive or dead (basically dead fish are weaker). +/datum/fish_trait/proc/change_venom_on_death(obj/item/fish/source, venom_path, live_mult, dead_mult) + var/live_amount = max(round((source.weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * live_mult, 0.1), live_mult) + var/dead_amount = max(round((source.weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * dead_mult, 0.1), dead_mult) + var/is_dead = source.status == FISH_DEAD + source.RemoveElement(/datum/element/venomous, venom_path, is_dead ? live_amount : dead_amount) + source.AddElement(/datum/element/venomous, venom_path, is_dead ? dead_amount : live_amount) + /datum/fish_trait/wary name = "Wary" catalog_description = "This fish will avoid visible fish lines, cloaked line recommended." @@ -110,7 +147,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) return if(HAS_TRAIT(rod.bait, TRAIT_OMNI_BAIT)) return - if(HAS_TRAIT(rod.bait, TRAIT_GOOD_QUALITY_BAIT) || HAS_TRAIT(rod.bait, TRAIT_GREAT_QUALITY_BAIT)) + if(!HAS_TRAIT(rod.bait, TRAIT_GOOD_QUALITY_BAIT) && !HAS_TRAIT(rod.bait, TRAIT_GREAT_QUALITY_BAIT)) .[MULTIPLICATIVE_FISHING_MOD] = 0 @@ -126,6 +163,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) .[MULTIPLICATIVE_FISHING_MOD] = 0 /datum/fish_trait/nocturnal/apply_to_fish(obj/item/fish/fish) + . = ..() RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(check_light)) /datum/fish_trait/nocturnal/proc/check_light(obj/item/fish/source, seconds_per_tick) @@ -170,7 +208,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) mob.obj_damage *= 1.3 /datum/fish_trait/heavy/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame) - minigame.fish_idle_velocity -= 10 + minigame.mover.fish_idle_velocity -= 10 /datum/fish_trait/carnivore name = "Carnivore" @@ -211,6 +249,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish emits an invisible toxin that emulsifies other fish for it to feed on." /datum/fish_trait/emulsijack/apply_to_fish(obj/item/fish/fish) + . = ..() RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(emulsify)) ADD_TRAIT(fish, TRAIT_RESIST_EMULSIFY, FISH_TRAIT_DATUM) @@ -250,6 +289,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) incompatible_traits = list(/datum/fish_trait/vegan) /datum/fish_trait/necrophage/apply_to_fish(obj/item/fish/fish) + . = ..() RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(eat_dead_fishes)) /datum/fish_trait/necrophage/proc/eat_dead_fishes(obj/item/fish/source, seconds_per_tick) @@ -269,6 +309,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) diff_traits_inheritability = 25 /datum/fish_trait/parthenogenesis/apply_to_fish(obj/item/fish/fish) + . = ..() ADD_TRAIT(fish, TRAIT_FISH_SELF_REPRODUCE, FISH_TRAIT_DATUM) /** @@ -282,8 +323,19 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) incompatible_traits = list(/datum/fish_trait/crossbreeder) /datum/fish_trait/no_mating/apply_to_fish(obj/item/fish/fish) + . = ..() ADD_TRAIT(fish, TRAIT_FISH_NO_MATING, FISH_TRAIT_DATUM) +///Prevent offsprings of fish with this trait from being of the same type (unless self-mating or the partner also has the trait) +/datum/fish_trait/recessive + name = "Recessive" + catalog_description = "If crossbred, offsprings will always be of the mate species, unless it also possess the trait." + diff_traits_inheritability = 0 + +/datum/fish_trait/no_mating/apply_to_fish(obj/item/fish/fish) + . = ..() + ADD_TRAIT(fish, TRAIT_FISH_RECESSIVE, FISH_TRAIT_DATUM) + /datum/fish_trait/revival diff_traits_inheritability = 15 name = "Self-Revival" @@ -291,6 +343,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) spontaneous_manifest_types = list(/obj/item/fish/boned = 100, /obj/item/fish/mastodon = 100) /datum/fish_trait/revival/apply_to_fish(obj/item/fish/fish) + . = ..() RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(check_status)) /datum/fish_trait/revival/proc/check_status(obj/item/fish/source) @@ -319,6 +372,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) incompatible_traits = list(/datum/fish_trait/vegan) /datum/fish_trait/predator/apply_to_fish(obj/item/fish/fish) + . = ..() RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(eat_fishes)) /datum/fish_trait/predator/proc/eat_fishes(obj/item/fish/source, seconds_per_tick) @@ -327,7 +381,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) return var/obj/structure/aquarium/aquarium = source.loc for(var/obj/item/fish/victim in aquarium.get_fishes(TRUE, source)) - if(victim.size < source.size * 0.75) // It's a big fish eat small fish world + if(victim.size < source.size * 0.7) // It's a big fish eat small fish world continue if(victim.status != FISH_ALIVE || victim == source || HAS_TRAIT(victim, TRAIT_YUCKY_FISH) || SPT_PROB(80, seconds_per_tick)) continue @@ -337,31 +391,35 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/yucky name = "Yucky" catalog_description = "This fish tastes so repulsive, other fishes won't try to eat it." + reagents_to_add = list(/datum/reagent/yuck = 3) /datum/fish_trait/yucky/apply_to_fish(obj/item/fish/fish) - RegisterSignal(fish, COMSIG_ATOM_PROCESSED, PROC_REF(add_yuck)) + . = ..() ADD_TRAIT(fish, TRAIT_YUCKY_FISH, FISH_TRAIT_DATUM) - add_to_reagents(fish, /datum/reagent/yuck, 3) - -/datum/fish_trait/yucky/proc/add_yuck(obj/item/fish/source, mob/living/user, obj/item/process_item, list/results) - var/amount = source.grind_results[/datum/reagent/yuck] / length(results) - for(var/atom/result as anything in results) - result.reagents?.add_reagent(/datum/reagent/yuck, amount) /datum/fish_trait/toxic name = "Toxic" - catalog_description = "This fish contains toxins in its liver. Feeding it to predatory fishes or people is not reccomended." + catalog_description = "This fish contains toxins. Feeding it to predatory fishes or people is not reccomended." diff_traits_inheritability = 25 + reagents_to_add = list(/datum/reagent/toxin/tetrodotoxin = 2.5) /datum/fish_trait/toxic/apply_to_fish(obj/item/fish/fish) - RegisterSignal(fish, COMSIG_ATOM_PROCESSED, PROC_REF(add_toxin)) + . = ..() + RegisterSignal(fish, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, PROC_REF(make_venomous)) + RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_status_change)) RegisterSignal(fish, COMSIG_FISH_EATEN_BY_OTHER_FISH, PROC_REF(on_eaten)) - add_to_reagents(fish, /datum/reagent/toxin/tetrodotoxin, 2.5) -/datum/fish_trait/toxic/proc/add_toxin(obj/item/fish/source, mob/living/user, obj/item/process_item, list/results) - var/amount = source.grind_results[ /datum/reagent/toxin/tetrodotoxin] / length(results) - for(var/atom/result as anything in results) - result.reagents?.add_reagent(/datum/reagent/toxin/tetrodotoxin, amount) +/datum/fish_trait/toxic/proc/make_venomous(obj/item/fish/source, new_size, new_weight) + SIGNAL_HANDLER + if(!HAS_TRAIT(source, TRAIT_FISH_STINGER)) + return + add_venom(source, /datum/reagent/toxin/tetrodotoxin, new_weight, mult = source.status == FISH_DEAD ? 0.1 : 0.25) + +/datum/fish_trait/toxic/proc/on_status_change(obj/item/fish/source) + SIGNAL_HANDLER + if(!HAS_TRAIT(source, TRAIT_FISH_STINGER)) + return + change_venom_on_death(source, /datum/reagent/toxin/tetrodotoxin, 0.25, 0.1) /datum/fish_trait/toxic/proc/on_eaten(obj/item/fish/source, obj/item/fish/predator) if(HAS_TRAIT(predator, TRAIT_FISH_TOXIN_IMMUNE)) @@ -388,6 +446,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) diff_traits_inheritability = 40 /datum/fish_trait/toxin_immunity/apply_to_fish(obj/item/fish/fish) + . = ..() ADD_TRAIT(fish, TRAIT_FISH_TOXIN_IMMUNE, FISH_TRAIT_DATUM) /datum/fish_trait/crossbreeder @@ -398,6 +457,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) incompatible_traits = list(/datum/fish_trait/no_mating) /datum/fish_trait/crossbreeder/apply_to_fish(obj/item/fish/fish) + . = ..() ADD_TRAIT(fish, TRAIT_FISH_CROSSBREEDER, FISH_TRAIT_DATUM) /datum/fish_trait/aggressive @@ -407,6 +467,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish is aggressively territorial, and may attack fish that come close to it." /datum/fish_trait/aggressive/apply_to_fish(obj/item/fish/fish) + . = ..() RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(try_attack_fish)) /datum/fish_trait/aggressive/proc/try_attack_fish(obj/item/fish/source, seconds_per_tick) @@ -431,6 +492,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) 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) /datum/fish_trait/lubed/apply_to_mob(mob/living/basic/mob) @@ -448,6 +510,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish has developed a primitive adaptation to life on both land and water." /datum/fish_trait/amphibious/apply_to_fish(obj/item/fish/fish) + . = ..() ADD_TRAIT(fish, TRAIT_FISH_AMPHIBIOUS, FISH_TRAIT_DATUM) if(fish.required_fluid_type == AQUARIUM_FLUID_AIR) fish.required_fluid_type = AQUARIUM_FLUID_FRESHWATER @@ -460,6 +523,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) incompatible_traits = list(/datum/fish_trait/predator, /datum/fish_trait/necrophage) /datum/fish_trait/mixotroph/apply_to_fish(obj/item/fish/fish) + . = ..() ADD_TRAIT(fish, TRAIT_FISH_NO_HUNGER, FISH_TRAIT_DATUM) /datum/fish_trait/antigrav @@ -467,12 +531,13 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) 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 + added_difficulty = 20 /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) /datum/fish_trait/antigrav/apply_to_mob(mob/living/basic/mob) @@ -489,6 +554,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish tends to die of stress when forced to be around too many other fish." /datum/fish_trait/anxiety/apply_to_fish(obj/item/fish/fish) + . = ..() RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(on_fish_life)) ///signal sent when the anxiety fish is fed, killing it if sharing contents with too many fish. @@ -511,24 +577,18 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) catalog_description = "This fish is electroreceptive, and will generate electric fields. Can be harnessed inside a bioelectric generator." /datum/fish_trait/electrogenesis/apply_to_fish(obj/item/fish/fish) + . = ..() ADD_TRAIT(fish, TRAIT_FISH_ELECTROGENESIS, FISH_TRAIT_DATUM) - RegisterSignal(fish, COMSIG_ITEM_ATTACK, PROC_REF(on_item_attack)) + RegisterSignal(fish, COMSIG_FISH_FORCE_UPDATED, PROC_REF(on_force_updated)) -/datum/fish_trait/electrogenesis/proc/on_item_attack(obj/item/fish/fish, mob/living/target, mob/living/user) +/datum/fish_trait/electrogenesis/proc/on_force_updated(obj/item/fish/fish, weight_rank, bonus_or_malus) SIGNAL_HANDLER - if(fish.status == FISH_ALIVE) - fish.force = 16 + fish.force += 10 - fish.w_class fish.damtype = BURN fish.attack_verb_continuous = list("shocks", "zaps") fish.attack_verb_simple = list("shock", "zap") fish.hitsound = 'sound/effects/sparks4.ogg' - else - fish.force = fish::force - fish.damtype = fish::damtype - fish.attack_verb_continuous = fish::attack_verb_continuous - fish.attack_verb_simple = fish::attack_verb_simple - fish.hitsound = fish::hitsound /datum/fish_trait/electrogenesis/apply_to_mob(mob/living/basic/mob) . = ..() @@ -546,3 +606,44 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/stunted/apply_to_mob(mob/living/basic/mob) . = ..() qdel(mob.GetComponent(/datum/component/growth_and_differentiation)) + +/datum/fish_trait/stinger + name = "Stinger" + inheritability = 80 + diff_traits_inheritability = 35 + catalog_description = "This fish is equipped with a sharp stringer or bill capable of delivering damage and toxins." + spontaneous_manifest_types = list(/obj/item/fish/stingray = 100, /obj/item/fish/swordfish = 100, /obj/item/fish/chainsawfish = 100) + +/datum/fish_trait/stinger/apply_to_fish(obj/item/fish/fish) + . = ..() + ADD_TRAIT(fish, TRAIT_FISH_STINGER, FISH_TRAIT_DATUM) + RegisterSignal(fish, COMSIG_FISH_FORCE_UPDATED, PROC_REF(on_force_updated)) + +/datum/fish_trait/stinger/proc/on_force_updated(obj/item/fish/fish, weight_rank, bonus_or_malus) + SIGNAL_HANDLER + fish.force += 1 + fish.w_class + bonus_or_malus + +/datum/fish_trait/toxic_barbs + name = "Toxic Barbs" + catalog_description = "This fish' stinger, bill or otherwise, is coated with simple, yet effetive venom." + spontaneous_manifest_types = list(/obj/item/fish/stingray = 35) + +/datum/fish_trait/toxic_barbs/apply_to_fish(obj/item/fish/fish) + . = ..() + RegisterSignal(fish, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, PROC_REF(make_venomous)) + RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_status_change)) + +/datum/fish_trait/toxic_barbs/proc/make_venomous(obj/item/fish/source, new_size, new_weight) + SIGNAL_HANDLER + if(!HAS_TRAIT(source, TRAIT_FISH_STINGER)) + ///Remove the trait from the fish so it doesn't show on the analyzer as it doesn't do anything on stingerless ones. + source.fish_traits -= type + UnregisterSignal(source, list(COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, COMSIG_FISH_STATUS_CHANGED)) + return + add_venom(source, /datum/reagent/toxin/venom, new_weight, mult = source.status == FISH_DEAD ? 0.3 : 0.7) + +/datum/fish_trait/toxic_barbs/proc/on_status_change(obj/item/fish/source) + SIGNAL_HANDLER + if(!HAS_TRAIT(source, TRAIT_FISH_STINGER)) + return + change_venom_on_death(source, /datum/reagent/toxin/venom, 0.7, 0.3) diff --git a/code/modules/fishing/fish/fish_types.dm b/code/modules/fishing/fish/fish_types.dm index aa36c80696e..95027b75ea6 100644 --- a/code/modules/fishing/fish/fish_types.dm +++ b/code/modules/fishing/fish/fish_types.dm @@ -10,9 +10,12 @@ stable_population = 3 average_size = 30 average_weight = 500 + weight_size_deviation = 0.35 favorite_bait = list(/obj/item/food/bait/worm) required_temperature_min = MIN_AQUARIUM_TEMP+18 required_temperature_max = MIN_AQUARIUM_TEMP+26 + evolution_types = list(/datum/fish_evolution/three_eyes, /datum/fish_evolution/chainsawfish) + compatible_types = list(/obj/item/fish/goldfish/gill, /obj/item/fish/three_eyes, /obj/item/fish/three_eyes/gill) /obj/item/fish/goldfish/gill name = "McGill" @@ -21,6 +24,8 @@ random_case_rarity = FISH_RARITY_NOPE show_in_catalog = FALSE beauty = FISH_BEAUTY_GOOD + compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/three_eyes) + fish_traits = list(/datum/fish_trait/recessive) /obj/item/fish/angelfish name = "angelfish" @@ -63,13 +68,14 @@ required_temperature_max = MIN_AQUARIUM_TEMP+28 /obj/item/fish/catfish - name = "cory catfish" + name = "catfish" desc = "A catfish has about 100,000 taste buds, and their bodies are covered with them to help detect chemicals present in the water and also to respond to touch." icon_state = "catfish" dedicated_in_aquarium_icon_state = "fish_greyscale" aquarium_vc_color = "#907420" - average_size = 100 - average_weight = 2000 + average_size = 80 + average_weight = 1600 + weight_size_deviation = 0.35 stable_population = 3 favorite_bait = list( list( @@ -107,7 +113,7 @@ RegisterSignal(src, COMSIG_FISH_BEFORE_GROWING, PROC_REF(growth_checks)) RegisterSignal(src, COMSIG_FISH_FINISH_GROWING, PROC_REF(on_growth)) -/obj/item/fish/tadpole/set_status(new_status) +/obj/item/fish/tadpole/set_status(new_status, silent = FALSE) . = ..() if(status == FISH_DEAD) del_timerid = QDEL_IN_STOPPABLE(src, 12 SECONDS) @@ -159,6 +165,7 @@ evolution_types = null compatible_types = list(/obj/item/fish/clownfish) food = /datum/reagent/lube + fishing_difficulty_modifier = 5 beauty = FISH_BEAUTY_GREAT /obj/item/fish/cardinal @@ -200,7 +207,7 @@ average_weight = 500 stable_population = 3 disliked_bait = list(/obj/item/food/bait/worm, /obj/item/food/bait/doughball) - fish_ai_type = FISH_AI_ZIPPY + fish_movement_type = /datum/fish_movement/zippy required_temperature_min = MIN_AQUARIUM_TEMP+23 required_temperature_max = MIN_AQUARIUM_TEMP+28 @@ -216,10 +223,10 @@ stable_population = 3 required_temperature_min = MIN_AQUARIUM_TEMP+23 required_temperature_max = MIN_AQUARIUM_TEMP+28 + fillet_type = /obj/item/food/fishmeat/quality //Too bad they're poisonous fish_traits = list(/datum/fish_trait/heavy, /datum/fish_trait/toxic) beauty = FISH_BEAUTY_GOOD - /obj/item/fish/lanternfish name = "lanternfish" desc = "Typically found in areas below 6600 feet below the surface of the ocean, they live in complete darkness." @@ -230,8 +237,8 @@ source_height = 21 sprite_width = 8 sprite_height = 8 - average_size = 100 - average_weight = 1500 + average_size = 50 + average_weight = 1000 stable_population = 3 fish_traits = list(/datum/fish_trait/nocturnal) required_temperature_min = MIN_AQUARIUM_TEMP+2 //My source is that the water at a depth 6600 feet is pretty darn cold. @@ -246,8 +253,8 @@ required_fluid_type = AQUARIUM_FLUID_SALTWATER stable_population = 2 fillet_type = /obj/item/food/fishmeat/moonfish - average_size = 100 - average_weight = 2000 + average_size = 60 + average_weight = 1000 required_temperature_min = MIN_AQUARIUM_TEMP+20 required_temperature_max = MIN_AQUARIUM_TEMP+30 beauty = FISH_BEAUTY_GOOD @@ -271,6 +278,7 @@ sprite_width = 7 required_fluid_type = AQUARIUM_FLUID_SALTWATER stable_population = 12 + breeding_timeout = 1 MINUTES fillet_type = null average_size = 20 average_weight = 300 @@ -285,10 +293,13 @@ dedicated_in_aquarium_icon_state = "armorfish_small" sprite_height = 5 sprite_width = 6 + average_size = 25 + average_weight = 350 required_fluid_type = AQUARIUM_FLUID_SALTWATER stable_population = 10 + breeding_timeout = 1.25 MINUTES fillet_type = /obj/item/food/fishmeat/armorfish - fish_ai_type = FISH_AI_SLOW + fish_movement_type = /datum/fish_movement/slow required_temperature_min = MIN_AQUARIUM_TEMP+10 required_temperature_max = MIN_AQUARIUM_TEMP+32 @@ -456,7 +467,7 @@ fish_traits = list(/datum/fish_trait/necrophage) required_temperature_min = MIN_AQUARIUM_TEMP+15 required_temperature_max = MIN_AQUARIUM_TEMP+35 - fish_ai_type = FISH_AI_ZIPPY + fish_movement_type = /datum/fish_movement/zippy favorite_bait = list( list( "Type" = "Foodtype", @@ -534,7 +545,7 @@ dedicated_in_aquarium_icon_state = "bonemass_small" sprite_width = 10 sprite_height = 7 - fish_ai_type = FISH_AI_ZIPPY + fish_movement_type = /datum/fish_movement/zippy random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS required_fluid_type = AQUARIUM_FLUID_ANY_WATER min_pressure = HAZARD_LOW_PRESSURE @@ -563,16 +574,16 @@ sprite_height = 7 show_in_catalog = FALSE random_case_rarity = FISH_RARITY_NOPE - fishing_difficulty_modifier = 5 + fishing_difficulty_modifier = 30 required_fluid_type = AQUARIUM_FLUID_ANY_WATER min_pressure = HAZARD_LOW_PRESSURE health = 300 - stable_population = 2 //This means they can only crossbreed. + stable_population = 1 //This means they can only crossbreed. grind_results = list(/datum/reagent/bone_dust = 5, /datum/reagent/consumable/liquidgibs = 5) fillet_type = /obj/item/stack/sheet/bone num_fillets = 2 feeding_frequency = 2 MINUTES - breeding_timeout = 10 MINUTES + breeding_timeout = 5 MINUTES average_size = 180 average_weight = 5000 death_text = "%SRC stops moving." @@ -605,7 +616,7 @@ return holo_area.linked.add_to_spawned(src) -/obj/item/fish/holo/set_status(new_status) +/obj/item/fish/holo/set_status(new_status, silent = FALSE) . = ..() if(status == FISH_DEAD) animate(src, alpha = 0, 3 SECONDS, easing = SINE_EASING) @@ -709,7 +720,7 @@ average_weight = 500 resistance_flags = FIRE_PROOF | LAVA_PROOF required_fluid_type = AQUARIUM_FLUID_ANY_WATER //if we can survive hot lava and freezing plasrivers, we can survive anything - fish_ai_type = FISH_AI_ZIPPY + fish_movement_type = /datum/fish_movement/zippy min_pressure = HAZARD_LOW_PRESSURE required_temperature_min = MIN_AQUARIUM_TEMP+30 required_temperature_max = MIN_AQUARIUM_TEMP+35 @@ -795,3 +806,404 @@ //anxiety naturally limits the amount of zipzaps per tank, so they are stronger alone electrogenesis_power = 20 MEGA JOULES beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/sockeye_salmon + name = "sockeye salmon" + desc = "A fairly common and iconic salmon endemic of the Pacific Ocean. At some point imported into outer space, where we're now." + icon_state = "sockeye" + dedicated_in_aquarium_icon_state = "sockeye_small" + sprite_width = 6 + sprite_height = 4 + stable_population = 6 + required_temperature_min = MIN_AQUARIUM_TEMP+3 + required_temperature_max = MIN_AQUARIUM_TEMP+19 + required_fluid_type = AQUARIUM_FLUID_ANADROMOUS + fillet_type = /obj/item/food/fishmeat/salmon + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/arctic_char + name = "arctic char" + desc = "A cold-water anadromous fish widespread around the Northern Hemisphere of Earth, yet it has somehow found a way here." + icon_state = "arctic_char" + dedicated_in_aquarium_icon_state = "arctic_char" + sprite_width = 7 + sprite_height = 4 + stable_population = 6 + average_size = 60 + average_weight = 1200 + weight_size_deviation = 0.5 // known for their size dismophism + required_temperature_min = MIN_AQUARIUM_TEMP+3 + required_temperature_max = MIN_AQUARIUM_TEMP+19 + required_fluid_type = AQUARIUM_FLUID_ANADROMOUS + +/obj/item/fish/stingray + name = "stingray" + desc = "A type of ray, most known for its venomous stinger. Despite that, They're normally docile, if not a bit easily frightened." + icon_state = "stingray" + dedicated_in_aquarium_icon_state = "stingray_small" + stable_population = 4 + sprite_height = 7 + sprite_width = 8 + average_size = 60 + average_weight = 700 + beauty = FISH_BEAUTY_GREAT + random_case_rarity = FISH_RARITY_RARE + required_fluid_type = AQUARIUM_FLUID_SALTWATER //Someone ought to add river rays later I guess. + fish_traits = list(/datum/fish_trait/stinger, /datum/fish_trait/toxic_barbs, /datum/fish_trait/wary, /datum/fish_trait/carnivore, /datum/fish_trait/predator) + +/obj/item/fish/sand_surfer + name = "sand surfer" + desc = "A bronze alien \"fish\" living and swimming underneath faraway sandy places." + icon_state = "sand_surfer" + dedicated_in_aquarium_icon_state = "sand_surfer_small" + sprite_height = 6 + sprite_width = 6 + stable_population = 5 + average_size = 65 + average_weight = 1100 + weight_size_deviation = 0.35 + random_case_rarity = FISH_RARITY_RARE + required_fluid_type = AQUARIUM_FLUID_AIR + required_temperature_min = MIN_AQUARIUM_TEMP+25 + required_temperature_max = MIN_AQUARIUM_TEMP+60 + fish_movement_type = /datum/fish_movement/plunger + fishing_difficulty_modifier = 5 + fish_traits = list(/datum/fish_trait/shiny_lover) + beauty = FISH_BEAUTY_GOOD + +/obj/item/fish/sand_crab + name = "burrower crab" + desc = "A sand-dwelling crustacean. It looks like a crab and tastes like a crab, but waddles like a fish." + icon_state = "crab" + dedicated_in_aquarium_icon_state = "crab_small" + sprite_height = 6 + sprite_width = 10 + average_size = 60 + average_weight = 1000 + weight_size_deviation = 0.1 + required_fluid_type = AQUARIUM_FLUID_SALTWATER + required_temperature_min = MIN_AQUARIUM_TEMP+20 + required_temperature_max = MIN_AQUARIUM_TEMP+40 + fillet_type = /obj/item/food/meat/slab/rawcrab + fish_traits = list(/datum/fish_trait/amphibious, /datum/fish_trait/shiny_lover, /datum/fish_trait/carnivore) + fish_movement_type = /datum/fish_movement/slow + favorite_bait = list( + list( + "Type" = "Foodtype", + "Value" = SEAFOOD, + ), + ) + +/obj/item/fish/bumpy + name = "bump-fish" + desc = "An misshapen fish-thing all covered in stubby little tendrils" + icon_state = "bumpy" + dedicated_in_aquarium_icon_state = "bumpy_small" + sprite_height = 4 + sprite_width = 5 + stable_population = 4 + required_fluid_type = AQUARIUM_FLUID_ANY_WATER + required_temperature_min = MIN_AQUARIUM_TEMP+15 + required_temperature_max = MIN_AQUARIUM_TEMP+40 + beauty = FISH_BEAUTY_BAD + fish_traits = list(/datum/fish_trait/amphibious, /datum/fish_trait/vegan) + favorite_bait = list( + list( + "Type" = "Foodtype", + "Value" = VEGETABLES, + ), + ) + +/obj/item/fish/three_eyes + name = "three-eyed goldfish" + desc = "A goldfish with an extra half a pair of eyes. You wonder what it's been feeding on lately..." + icon_state = "three_eyes" + sprite_width = 8 + sprite_height = 8 + average_size = 30 + average_weight = 500 + stable_population = 4 + fish_traits = list(/datum/fish_trait/recessive, /datum/fish_trait/shiny_lover) + compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/goldfish/gill, /obj/item/fish/three_eyes/gill) + beauty = FISH_BEAUTY_GOOD + fishing_difficulty_modifier = 10 + random_case_rarity = FISH_RARITY_VERY_RARE + food = /datum/reagent/toxin/mutagen + favorite_bait = list( + list( + "Type" = "Reagent", + "Value" = /datum/reagent/toxin/mutagen, + "Amount" = 3, + ), + ) + +/obj/item/fish/three_eyes/gill + name = "McGill" + desc = "A great rubber duck tool for Lawyers who can't get a grasp over their case. It looks kinda different today..." + compatible_types = list(/obj/item/fish/goldfish, /obj/item/fish/three_eyes) + beauty = FISH_BEAUTY_GREAT + show_in_catalog = FALSE + stable_population = 1 + random_case_rarity = FISH_RARITY_NOPE + +/obj/item/fish/swordfish + name = "swordfish" + desc = "A large billfish, most famous for its elongated bill, while also fairly popular for cooking, and as a fearsome weapon in the hands of a veteran spess-fisherman." + icon = 'icons/obj/structures/aquarium/wide.dmi' + icon_state = "swordfish" + inhand_icon_state = "swordfish" + dedicated_in_aquarium_icon = 'icons/obj/structures/aquarium/fish.dmi' + dedicated_in_aquarium_icon_state = "swordfish_small" + force = 18 + sharpness = SHARP_EDGED + attack_verb_continuous = list("slashes", "cuts", "pierces") + attack_verb_simple = list("slash", "cut", "pierce") + block_sound = 'sound/weapons/parry.ogg' + hitsound = 'sound/weapons/rapierhit.ogg' + demolition_mod = 0.75 + attack_speed = 1 SECONDS + block_chance = 50 + wound_bonus = 10 + bare_wound_bonus = 20 + armour_penetration = 75 + base_pixel_x = -18 + pixel_x = -18 + sprite_width = 13 + sprite_height = 6 + stable_population = 3 + average_size = 140 + average_weight = 4000 + breeding_timeout = 4.5 MINUTES + feeding_frequency = 4 MINUTES + health = 180 + beauty = FISH_BEAUTY_EXCELLENT + random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS + required_fluid_type = AQUARIUM_FLUID_SALTWATER + fish_movement_type = /datum/fish_movement/plunger + fishing_difficulty_modifier = 25 + fillet_type = /obj/item/food/fishmeat/quality + favorite_bait = list( + list( + "Type" = "Foodtype", + "Value" = SEAFOOD, + ), + ) + fish_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/stinger) + +/obj/item/fish/swordfish/get_force_rank() + switch(w_class) + if(WEIGHT_CLASS_TINY) + force -= 11 + attack_speed -= 0.4 SECONDS + block_chance -= 45 + armour_penetration -= 20 + wound_bonus -= 15 + bare_wound_bonus -= 20 + if(WEIGHT_CLASS_SMALL) + force -= 8 + attack_speed -= 0.3 SECONDS + block_chance -= 30 + armour_penetration -= 15 + wound_bonus -= 10 + bare_wound_bonus -= 20 + if(WEIGHT_CLASS_NORMAL) + force -= 5 + attack_speed -= 0.2 SECONDS + block_chance -= 20 + armour_penetration -= 10 + wound_bonus -= 10 + bare_wound_bonus -= 15 + if(WEIGHT_CLASS_BULKY) + force -= 3 + attack_speed -= 0.1 SECONDS + block_chance -= 10 + armour_penetration -= 5 + wound_bonus -= 5 + bare_wound_bonus -= 10 + if(WEIGHT_CLASS_GIGANTIC) + force += 5 + attack_speed += 0.2 SECONDS + demolition_mod += 0.15 + block_chance += 10 + armour_penetration += 5 + wound_bonus += 5 + bare_wound_bonus += 10 + + if(status == FISH_DEAD) + force -= 4 + w_class + block_chance -= 25 + armour_penetration -= 30 + wound_bonus -= 10 + bare_wound_bonus -= 10 + +/obj/item/fish/swordfish/calculate_fish_force_bonus(bonus_malus) + . = ..() + armour_penetration += bonus_malus * 5 + wound_bonus += bonus_malus * 3 + bare_wound_bonus += bonus_malus * 5 + block_chance += bonus_malus * 7 + +/obj/item/fish/chainsawfish + name = "chainsawfish" + desc = "A very, very angry bioweapon, whose sole purpose is to rip and tear." + icon = 'icons/obj/structures/aquarium/wide.dmi' + icon_state = "chainsawfish" + inhand_icon_state = "chainsawfish" + icon_state_dead = "chainsawfish_dead" + dedicated_in_aquarium_icon = 'icons/obj/structures/aquarium/fish.dmi' + dedicated_in_aquarium_icon_state = "chainsaw_small" + force = 22 + demolition_mod = 1.5 + block_chance = 15 + attack_verb_continuous = list("saws", "tears", "lacerates", "cuts", "chops", "dices") + attack_verb_simple = list("saw", "tear", "lacerate", "cut", "chop", "dice") + hitsound = 'sound/weapons/chainsawhit.ogg' + sharpness = SHARP_EDGED + tool_behaviour = TOOL_SAW + toolspeed = 0.5 + base_pixel_x = -16 + pixel_x = -16 + sprite_width = 8 + sprite_height = 5 + stable_population = 3 + average_size = 85 + average_weight = 2500 + breeding_timeout = 4.25 MINUTES + feeding_frequency = 3 MINUTES + health = 180 + beauty = FISH_BEAUTY_GREAT + random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS + required_fluid_type = AQUARIUM_FLUID_FRESHWATER + fish_movement_type = /datum/fish_movement/accelerando + fishing_difficulty_modifier = 30 + favorite_bait = list( + list( + "Type" = "Foodtype", + "Value" = GORE + ), + ) + fish_traits = list(/datum/fish_trait/aggressive, /datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/stinger) + required_temperature_min = MIN_AQUARIUM_TEMP+18 + required_temperature_max = MIN_AQUARIUM_TEMP+26 + +/obj/item/fish/chainsawfish/Initialize(mapload) + . = ..() + AddElement(/datum/element/update_icon_updates_onmob) + +/obj/item/fish/chainsawfish/update_icon_state() + if(status == FISH_DEAD) + inhand_icon_state = "chainsawfish_dead" + else + inhand_icon_state = "chainsawfish" + if(HAS_TRAIT(src, TRAIT_WIELDED)) + inhand_icon_state = "[inhand_icon_state]_wielded" + return ..() + +/obj/item/fish/chainsawfish/get_force_rank() + switch(w_class) + if(WEIGHT_CLASS_TINY) + force -= 10 + attack_speed -= 0.2 SECONDS + demolition_mod -= 0.4 + block_chance -= 15 + armour_penetration -= 10 + wound_bonus -= 10 + bare_wound_bonus -= 10 + toolspeed += 0.6 + if(WEIGHT_CLASS_SMALL) + force -= 8 + attack_speed -= 0.1 SECONDS + demolition_mod -= 0.3 + block_chance -= 10 + armour_penetration -= 10 + wound_bonus -= 10 + bare_wound_bonus -= 10 + toolspeed += 0.4 + if(WEIGHT_CLASS_NORMAL) + force -= 5 + demolition_mod -= 0.15 + block_chance -= 5 + armour_penetration -= 5 + wound_bonus -= 5 + bare_wound_bonus -= 5 + toolspeed += 0.2 + if(WEIGHT_CLASS_HUGE) + force += 2 + attack_speed += 0.2 SECONDS + demolition_mod += 0.15 + armour_penetration += 10 + block_chance += 10 + wound_bonus += 10 + bare_wound_bonus += 5 + if(WEIGHT_CLASS_GIGANTIC) + force += 4 + attack_speed += 0.4 SECONDS + demolition_mod += 0.3 + block_chance += 20 + armour_penetration += 20 + wound_bonus += 15 + bare_wound_bonus += 10 + toolspeed -= 0.1 + + if(status == FISH_DEAD) + force -= 8 + w_class + hitsound = SFX_SWING_HIT + block_chance -= 25 + demolition_mod -= 0.3 + armour_penetration -= 15 + wound_bonus -= 5 + bare_wound_bonus -= 5 + toolspeed += 1 + +/obj/item/fish/chainsawfish/calculate_fish_force_bonus(bonus_malus) + . = ..() + armour_penetration += bonus_malus * 3 + wound_bonus += bonus_malus * 2 + bare_wound_bonus += bonus_malus * 3 + block_chance += bonus_malus * 2 + toolspeed -= bonus_malus * 0.1 + +/obj/item/fish/soul + name = "soulfish" + desc = "A distant yet vaguely close critter, like a long lost relative. You feel your soul rejuvenated just from looking at it... Also, what the fuck is this shit?!" + icon_state = "soulfish" + dedicated_in_aquarium_icon_state = "soul_small" + sprite_width = 7 + sprite_height = 6 + average_size = 60 + average_weight = 1200 + stable_population = 4 + show_in_catalog = FALSE + beauty = FISH_BEAUTY_EXCELLENT + fish_movement_type = /datum/fish_movement/choppy //Glideless legacy movement? in my fishing minigame? + favorite_bait = list( + list( + "Type" = "Foodtype", + "Value" = FRIED + ), + ) + fillet_type = /obj/item/food/meat/cutlet/plain/human + required_temperature_min = MIN_AQUARIUM_TEMP+3 + required_temperature_max = MIN_AQUARIUM_TEMP+38 + random_case_rarity = FISH_RARITY_NOPE + +/obj/item/fish/skin_crab + name = "skin crab" + desc = "\"And on the eighth day, a demential mockery of both humanity and crabity was made.\" Fascinating." + icon_state = "skin_crab" + dedicated_in_aquarium_icon_state = "skin_crab_small" + sprite_width = 7 + sprite_height = 6 + average_size = 40 + average_weight = 750 + stable_population = 5 + show_in_catalog = FALSE + beauty = FISH_BEAUTY_GREAT + favorite_bait = list( + list( + "Type" = "Foodtype", + "Value" = FRIED + ), + ) + fillet_type = /obj/item/food/meat/slab/rawcrab + random_case_rarity = FISH_RARITY_NOPE diff --git a/code/modules/fishing/fish_movement.dm b/code/modules/fishing/fish_movement.dm new file mode 100644 index 00000000000..a328903617f --- /dev/null +++ b/code/modules/fishing/fish_movement.dm @@ -0,0 +1,214 @@ +/// Any lower than this, and the target position of the fish is considered null +#define FISH_TARGET_MIN_DISTANCE 6 +/// The friction applied to fish jumps, so that it decelerates over time +#define FISH_FRICTION_MULT 0.9 +/// Used to decide whether the fish can jump in a certain direction +#define FISH_SHORT_JUMP_MIN_DISTANCE 100 +/// The maximum distance for a short jump +#define FISH_SHORT_JUMP_MAX_DISTANCE 200 + +///Fish movements are simple datums, generated by the fishing minigame, that represent how the fish moves suring the minigame. +/datum/fish_movement + /// The minigame that spawned us + var/datum/fishing_challenge/master + /// How many times move_fish() has been called + var/times_fired = 0 + /// How likely the fish is to perform a standard jump, then multiplied by difficulty + var/short_jump_chance = 2.25 + /// How likely the fish is to perform a long jump, then multiplied by difficulty + var/long_jump_chance = 0.0625 + /// The speed limit for the short jump + var/short_jump_velocity_limit = 400 + /// The speed limit for the long jump + var/long_jump_velocity_limit = 200 + /// The current speed limit used + var/current_velocity_limit + /// The base velocity of the fish, which may affect jump distances and falling speed. + var/fish_idle_velocity = 0 + /// A position on the slider the fish wants to get to + var/target_position + /// If true, the fish can jump while a target position is set, thus overriding it + var/can_interrupt_move = TRUE + /// The current speed the fish is moving at + var/fish_velocity = 0 + +/datum/fish_movement/New(datum/fishing_challenge/master) + src.master = master + +/** + * Proc that adjusts movement values to the difficulty of the minigame. + * The operations can be a tad complex, but basically it ensures that jump + * chances with a probability higher than 1% increase in a smooth curve so that + * they still reach 100% prob when the difficulty peakes. + */ +/datum/fish_movement/proc/adjust_to_difficulty() + var/square_angle_rad = TORADIANS(90) + var/zero_one_difficulty = master.difficulty/100 + if(short_jump_chance > 1) + short_jump_chance = (zero_one_difficulty**(square_angle_rad-TORADIANS(arctan(short_jump_chance * 1/square_angle_rad))))*100 + else + short_jump_chance *= master.difficulty + if(long_jump_chance > 1) + long_jump_chance = (zero_one_difficulty**(square_angle_rad-TORADIANS(arctan(long_jump_chance * 1/square_angle_rad))))*100 + else + long_jump_chance *= master.difficulty + +///The main proc, called by minigame every SSfishing tick while it's in the 'active' phase. +/datum/fish_movement/proc/move_fish(seconds_per_tick) + times_fired++ + /** + * The jump chances are meant to run every odd tick (each every decisecond) + * We cannot do it every tick because the fish would be jumpier than intended + * and we cannot cut the chances in half to fit on each tick, because the maximum probability + * would go from 100% to 75%. + */ + var/can_roll = times_fired % 2 + + var/long_chance = long_jump_chance * seconds_per_tick * (1/seconds_per_tick) + var/short_chance = short_jump_chance * seconds_per_tick * (1/seconds_per_tick) + + // If we have the target but we're close enough, mark as target reached + if(abs(target_position - master.fish_position) < FISH_TARGET_MIN_DISTANCE) + target_position = null + + // Switching to new long jump target can interrupt any other + if(can_roll && (can_interrupt_move || isnull(target_position)) && prob(long_chance)) + /** + * Move at least 0.75 to full of the availible bar in given direction, + * and more likely to move in the direction where there's more space + */ + var/distance_from_top = FISHING_MINIGAME_AREA - master.fish_position - master.fish_height + var/distance_from_bottom = master.fish_position + var/top_chance + if(distance_from_top < FISH_SHORT_JUMP_MIN_DISTANCE) + top_chance = 0 + else + top_chance = (distance_from_top/max(distance_from_bottom, 1)) * 100 + var/new_target = master.fish_position + if(prob(top_chance)) + new_target += distance_from_top * rand(75, 100)/100 + else + new_target -= distance_from_bottom * rand(75, 100)/100 + target_position = round(new_target) + current_velocity_limit = long_jump_velocity_limit + + // Move towards target + if(!isnull(target_position)) + var/distance = target_position - master.fish_position + // about 5 at diff 15 , 10 at diff 30, 30 at diff 100 + var/acceleration_mult = get_acceleration(seconds_per_tick) + var/target_acceleration = distance * acceleration_mult * seconds_per_tick + + fish_velocity = fish_velocity * FISH_FRICTION_MULT + target_acceleration + else if(can_roll && prob(short_chance)) + var/distance_from_top = FISHING_MINIGAME_AREA - master.fish_position - master.fish_height + var/distance_from_bottom = master.fish_position + var/jump_length + if(distance_from_top >= FISH_SHORT_JUMP_MIN_DISTANCE) + jump_length = rand(FISH_SHORT_JUMP_MIN_DISTANCE, FISH_SHORT_JUMP_MAX_DISTANCE) + if(distance_from_bottom >= FISH_SHORT_JUMP_MIN_DISTANCE && (!jump_length || prob(50))) + jump_length = -rand(FISH_SHORT_JUMP_MIN_DISTANCE, FISH_SHORT_JUMP_MAX_DISTANCE) + target_position = clamp(master.fish_position + jump_length, 0, FISHING_MINIGAME_AREA - master.fish_height) + current_velocity_limit = short_jump_velocity_limit + + fish_velocity = clamp(fish_velocity + fish_idle_velocity, -current_velocity_limit, current_velocity_limit) + set_fish_position(seconds_per_tick) + +///Proc that returns the acceleration of the fish during the minigame. +/datum/fish_movement/proc/get_acceleration(seconds_per_tick) + return 0.3 * master.difficulty + 0.5 + +///Called at the end of move_fish(), for updating the position of the fish in the fishing minigame. +/datum/fish_movement/proc/set_fish_position(seconds_per_tick) + master.fish_position = clamp(master.fish_position + fish_velocity * seconds_per_tick, 0, FISHING_MINIGAME_AREA - master.fish_height) + +///Generic fish movement datum that only performs slow, uninterrupted long jumps +/datum/fish_movement/slow + short_jump_chance = 0 + long_jump_chance = 1.5 + long_jump_velocity_limit = 150 + can_interrupt_move = FALSE + +///Generic fish movement datum with triple the short jump chance. +/datum/fish_movement/zippy + short_jump_chance = parent_type::short_jump_chance * 3 + +///fish movement datum that progressively gets faster until acceleration and velocity are double the starting ones. +/datum/fish_movement/accelerando + ///The jump velocity to add each tick + var/short_jump_vel_add + ///The long jump velocity to add each tick + var/long_jump_vel_add + ///Time to reach full speed, in seconds. + var/accel_time_cap = 30 + +/datum/fish_movement/accelerando/move_fish(seconds_per_tick) + var/seconds_elapsed = (times_fired * seconds_per_tick) + if(seconds_elapsed >= accel_time_cap) + return ..() + if(!times_fired) //First tick, cache the initial jump velocities + short_jump_vel_add = short_jump_velocity_limit/accel_time_cap + long_jump_vel_add = long_jump_velocity_limit/accel_time_cap + return ..() + + if(current_velocity_limit) + var/vel_add = current_velocity_limit == short_jump_velocity_limit ? short_jump_vel_add : long_jump_vel_add + current_velocity_limit += round(vel_add * seconds_per_tick, 0.01) + + short_jump_velocity_limit += round(short_jump_vel_add * seconds_per_tick, 0.01) + long_jump_velocity_limit += round(long_jump_vel_add * seconds_per_tick, 0.01) + return ..() + +/datum/fish_movement/accelerando/get_acceleration(seconds_per_tick) + var/acceleration = ..() + return acceleration + min(acceleration, acceleration * times_fired * seconds_per_tick / accel_time_cap) + +/datum/fish_movement/accelerando/set_fish_position(seconds_per_tick) + fish_velocity = round(fish_velocity) + return ..() + +///Fish movement datum that updates the fish position twice per second. +/datum/fish_movement/choppy + ///We keep of the theorical fish position to eventually use + var/faux_position = 0 + +/datum/fish_movement/choppy/set_fish_position(seconds_per_tick) + faux_position = clamp(faux_position + fish_velocity * seconds_per_tick, 0, FISHING_MINIGAME_AREA - master.fish_height) + if(!((times_fired * SSfishing.wait) % (0.5 SECONDS))) + master.fish_position = faux_position + +///Fish movement datum that weakly pushes the fish up and then down with greater force once it reaches the top of the minigame. +/datum/fish_movement/plunger + ///Is the fish plunging to the bottom of the minigame area, or should it swim up? + var/is_plunging = TRUE + ///The added idle velocity when plunging + var/plunging_speed = -22 + +/datum/fish_movement/plunger/adjust_to_difficulty() + . = ..() + //Adjust the fleeing velocity, up to five times the initial value. + plunging_speed += round(plunging_speed * master.difficulty * 0.03) + fish_idle_velocity += plunging_speed //so it can be safely subtracted if the fish starts at the bottom. + +/datum/fish_movement/plunger/move_fish(seconds_per_tick) + var/fish_area = FISHING_MINIGAME_AREA - master.fish_height + if(is_plunging) + if(target_position > master.fish_position) //nothing should stop us from plunging. + target_position = null + var/dist_bot_percent = master.fish_position/fish_area + if(dist_bot_percent <= 0.04) + fish_idle_velocity -= plunging_speed + is_plunging = FALSE + else + var/dist_top_percent = (fish_area - master.fish_position)/fish_area + if(dist_top_percent <= 0.04) + fish_idle_velocity += plunging_speed + is_plunging = TRUE + + return ..() + + +#undef FISH_TARGET_MIN_DISTANCE +#undef FISH_FRICTION_MULT +#undef FISH_SHORT_JUMP_MIN_DISTANCE +#undef FISH_SHORT_JUMP_MAX_DISTANCE diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index 9c70d474a62..db01410d0e2 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -5,16 +5,6 @@ // UI minigame phase #define MINIGAME_PHASE 3 -/// The height of the minigame slider. Not in pixels, but minigame units. -#define FISHING_MINIGAME_AREA 1000 -/// Any lower than this, and the target position of the fish is considered null -#define FISH_TARGET_MIN_DISTANCE 6 -/// The friction applied to fish jumps, so that it decelerates over time -#define FISH_FRICTION_MULT 0.9 -/// Used to decide whether the fish can jump in a certain direction -#define FISH_SHORT_JUMP_MIN_DISTANCE 100 -/// The maximum distance for a short jump -#define FISH_SHORT_JUMP_MAX_DISTANCE 200 // Acceleration mod when bait is over fish #define FISH_ON_BAIT_ACCELERATION_MULT 0.6 /// The minimum velocity required for the bait to bounce @@ -47,8 +37,6 @@ var/start_time /// Is it finished (either by win/lose or window closing) var/completed = FALSE - /// Fish AI type to use - var/fish_ai = FISH_AI_DUMB /// Rule modifiers (eg weighted bait) var/special_effects = NONE /// A list of possible active minigame effects. If not empty, one will be picked from time to time. @@ -93,8 +81,6 @@ var/fish_position = 0 /// The position of the bait on the minigame slider var/bait_position = 0 - /// The current speed the fish is moving at - var/fish_velocity = 0 /// The current speed the bait is moving at var/bait_velocity = 0 @@ -105,22 +91,7 @@ /// How much completion is gained per second when the bait area is intersecting with the fish's var/completion_gain = 5 - /// How likely the fish is to perform a standard jump, then multiplied by difficulty - var/short_jump_chance = 2.25 - /// How likely the fish is to perform a long jump, then multiplied by difficulty - var/long_jump_chance = 0.0625 - /// The speed limit for the short jump - var/short_jump_velocity_limit = 400 - /// The speed limit for the long jump - var/long_jump_velocity_limit = 200 - /// The current speed limit used - var/current_velocity_limit = 200 - /// The base velocity of the fish, which may affect jump distances and falling speed. - var/fish_idle_velocity = 0 - /// A position on the slider the fish wants to get to - var/target_position - /// If true, the fish can jump while a target position is set, thus overriding it - var/can_interrupt_move = TRUE + var/datum/fish_movement/mover /// Whether the bait is idle or reeling up or down (left and right click) var/reeling_state = REELING_STATE_IDLE @@ -144,24 +115,21 @@ RegisterSignal(comp.fish_source, COMSIG_FISHING_SOURCE_INTERRUPT_CHALLENGE, PROC_REF(interrupt_challenge)) comp.fish_source.RegisterSignal(src, COMSIG_FISHING_CHALLENGE_COMPLETED, TYPE_PROC_REF(/datum/fish_source, on_challenge_completed)) background = comp.fish_source.background + /// Fish minigame properties if(ispath(reward_path,/obj/item/fish)) var/obj/item/fish/fish = reward_path - fish_ai = initial(fish.fish_ai_type) - switch(fish_ai) - if(FISH_AI_ZIPPY) // Keeps on jumping - short_jump_chance *= 3 - if(FISH_AI_SLOW) // Only does long jump, and doesn't change direction until it gets there - short_jump_chance = 0 - long_jump_chance = 1.5 - long_jump_velocity_limit = 150 - long_jump_velocity_limit = FALSE + var/movement_path = initial(fish.fish_movement_type) + mover = new movement_path(src) // Apply fish trait modifiers var/list/fish_list_properties = collect_fish_properties() var/list/fish_traits = fish_list_properties[fish][NAMEOF(fish, fish_traits)] for(var/fish_trait in fish_traits) var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait] trait.minigame_mod(rod, user, src) + else + mover = new /datum/fish_movement(src) + /// Enable special parameters if(rod.line) completion_gain += 1 // Any fishing line will provide a small boost by default @@ -198,22 +166,7 @@ if(HAS_MIND_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 - * less than proportionally (exponent less than 1) instead. - * This way we ensure fish with high jump chances won't get TOO jumpy until - * they near the maximum difficulty, at which they hit 100% - */ - var/square_angle_rad = TORADIANS(90) - var/zero_one_difficulty = difficulty/100 - if(short_jump_chance > 1) - short_jump_chance = (zero_one_difficulty**(square_angle_rad-TORADIANS(arctan(short_jump_chance * 1/square_angle_rad))))*100 - else - short_jump_chance *= difficulty - if(long_jump_chance > 1) - long_jump_chance = (zero_one_difficulty**(square_angle_rad-TORADIANS(arctan(long_jump_chance * 1/square_angle_rad))))*100 - else - long_jump_chance *= difficulty + mover.adjust_to_difficulty() bait_height -= round(difficulty * BAIT_HEIGHT_DIFFICULTY_MALUS) bait_pixel_height = round(MINIGAME_BAIT_HEIGHT * (bait_height/initial(bait_height)), 1) @@ -230,6 +183,7 @@ SStgui.close_uis(src) user = null used_rod = null + QDEL_NULL(mover) return ..() /datum/fishing_challenge/proc/send_alert(message) @@ -369,6 +323,14 @@ send_alert("crustacean!!!") if(FISH_ICON_BONE) send_alert("bones!!!") + if(FISH_ICON_ELECTRIC) + send_alert("zappy!!!") + if(FISH_ICON_WEAPON) + send_alert("weapon!!!") + if(FISH_ICON_CRITTER) + send_alert("critter!!!") + if(FISH_ICON_SEED) + send_alert("seed!!!") else send_alert("!!!") animate(lure, pixel_y = 3, time = 5, loop = -1, flags = ANIMATION_RELATIVE) @@ -413,6 +375,10 @@ completion *= 1.2 if(BITING_TIME_WINDOW - 0.5 SECONDS to BITING_TIME_WINDOW) completion *= 1.4 + //randomize the position of the fish a little + fish_position = rand(0, (FISHING_MINIGAME_AREA - fish_height) * 0.8) + var/diff_dist = 100 + difficulty + bait_position = clamp(round(fish_position + rand(-diff_dist, diff_dist) - bait_height * 0.5), 0, FISHING_MINIGAME_AREA - bait_height) if(!prepare_minigame_hud()) return phase = MINIGAME_PHASE @@ -465,7 +431,7 @@ /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) + mover.move_fish(seconds_per_tick) move_bait(seconds_per_tick) if(!QDELETED(fishing_hud)) update_visuals() @@ -501,58 +467,6 @@ 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 - var/short_chance = short_jump_chance * seconds_per_tick * 10 - - // If we have the target but we're close enough, mark as target reached - if(abs(target_position - fish_position) < FISH_TARGET_MIN_DISTANCE) - target_position = null - - // Switching to new long jump target can interrupt any other - if((can_interrupt_move || isnull(target_position)) && prob(long_chance)) - /** - * Move at least 0.75 to full of the availible bar in given direction, - * and more likely to move in the direction where there's more space - */ - var/distance_from_top = FISHING_MINIGAME_AREA - fish_position - fish_height - var/distance_from_bottom = fish_position - var/top_chance - if(distance_from_top < FISH_SHORT_JUMP_MIN_DISTANCE) - top_chance = 0 - else - top_chance = (distance_from_top/max(distance_from_bottom, 1)) * 100 - var/new_target = fish_position - if(prob(top_chance)) - new_target += distance_from_top * rand(75, 100)/100 - else - new_target -= distance_from_bottom * rand(75, 100)/100 - target_position = round(new_target) - current_velocity_limit = long_jump_velocity_limit - - // Move towards target - if(!isnull(target_position)) - var/distance = target_position - fish_position - // about 5 at diff 15 , 10 at diff 30, 30 at diff 100 - var/acceleration_mult = 0.3 * difficulty + 0.5 - var/target_acceleration = distance * acceleration_mult * seconds_per_tick - - fish_velocity = fish_velocity * FISH_FRICTION_MULT + target_acceleration - else if(prob(short_chance)) - var/distance_from_top = FISHING_MINIGAME_AREA - fish_position - fish_height - var/distance_from_bottom = fish_position - var/jump_length - if(distance_from_top >= FISH_SHORT_JUMP_MIN_DISTANCE) - jump_length = rand(FISH_SHORT_JUMP_MIN_DISTANCE, FISH_SHORT_JUMP_MAX_DISTANCE) - if(distance_from_bottom >= FISH_SHORT_JUMP_MIN_DISTANCE && (!jump_length || prob(50))) - jump_length = -rand(FISH_SHORT_JUMP_MIN_DISTANCE, FISH_SHORT_JUMP_MAX_DISTANCE) - target_position = clamp(fish_position + jump_length, 0, FISHING_MINIGAME_AREA - fish_height) - current_velocity_limit = short_jump_velocity_limit - - fish_velocity = clamp(fish_velocity + fish_idle_velocity, -current_velocity_limit, current_velocity_limit) - fish_position = clamp(fish_position + fish_velocity * seconds_per_tick, 0, FISHING_MINIGAME_AREA - fish_height) - ///The proc that moves the bait around, just like in the old TGUI, mostly. /datum/fishing_challenge/proc/move_bait(seconds_per_tick) var/should_bounce = abs(bait_velocity) > BAIT_MIN_VELOCITY_BOUNCE @@ -613,9 +527,7 @@ bait_velocity += velocity_change //check that the fish area is still intersecting the bait now that it has moved - fish_on_bait = (fish_position + fish_height >= bait_position) && (bait_position + bait_height >= fish_position) - - if(fish_on_bait) + if(is_fish_on_bait()) completion += completion_gain * seconds_per_tick if(completion >= 100) complete(TRUE) @@ -627,6 +539,10 @@ completion = clamp(completion, 0, 100) +///Returns TRUE if the fish and the bait are intersecting +/datum/fishing_challenge/proc/is_fish_on_bait() + return (fish_position + fish_height >= bait_position) && (bait_position + bait_height >= fish_position) + ///update the vertical pixel position of both fish and bait, and the icon state of the completion bar /datum/fishing_challenge/proc/update_visuals() var/bait_offset_mult = bait_position/FISHING_MINIGAME_AREA @@ -726,15 +642,6 @@ #undef BITING_PHASE #undef MINIGAME_PHASE -#undef FISHING_MINIGAME_AREA -#undef FISH_TARGET_MIN_DISTANCE -#undef FISH_FRICTION_MULT -#undef FISH_SHORT_JUMP_MIN_DISTANCE -#undef FISH_SHORT_JUMP_MAX_DISTANCE -#undef FISH_ON_BAIT_ACCELERATION_MULT -#undef BAIT_MIN_VELOCITY_BOUNCE -#undef BAIT_DECELERATION_MULT - #undef MINIGAME_SLIDER_HEIGHT #undef MINIGAME_BAIT_HEIGHT #undef MINIGAME_FISH_HEIGHT @@ -745,5 +652,9 @@ #undef REELING_STATE_UP #undef REELING_STATE_DOWN +#undef FISH_ON_BAIT_ACCELERATION_MULT +#undef BAIT_MIN_VELOCITY_BOUNCE +#undef BAIT_DECELERATION_MULT + #undef MAX_FISH_COMPLETION_MALUS #undef BITING_TIME_WINDOW diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index 33c8a0e6ff8..ece2f9d84af 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -107,8 +107,19 @@ var/mob/living/caught_mob = reward if(caught_mob.stat == DEAD) return - else if(!isfish(reward)) - return + else + if(!isfish(reward)) + return + var/obj/item/fish/fish = reward + if(HAS_TRAIT(bait, TRAIT_POISONOUS_BAIT) && !HAS_TRAIT(fish, TRAIT_FISH_TOXIN_IMMUNE)) + var/kill_fish = TRUE + for(var/bait_identifer in fish.favorite_bait) + if(is_matching_bait(bait, bait_identifer)) + kill_fish = FALSE + break + if(kill_fish) + fish.set_status(FISH_DEAD, silent = TRUE) + QDEL_NULL(bait) update_icon() diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index 4a0419f98f5..dc9b910ad8f 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -8,30 +8,48 @@ GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_sour * 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, -))) +GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) + +/proc/generate_specific_fish_icons() + var/list/return_list = zebra_typecacheof(list( + /mob/living/basic/axolotl = FISH_ICON_CRITTER, + /mob/living/basic/frog = FISH_ICON_CRITTER, + /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/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/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/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/structure/closet/crate = FISH_ICON_COIN, + )) + + return_list[FISHING_RANDOM_SEED] = FISH_ICON_SEED + return return_list /** * Where the fish actually come from - every fishing spot has one assigned but multiple fishing holes @@ -45,6 +63,10 @@ GLOBAL_LIST_INIT(specific_fish_icons, zebra_typecacheof(list( 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 + /// 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 @@ -198,16 +220,26 @@ GLOBAL_LIST_INIT(specific_fish_icons, zebra_typecacheof(list( /datum/fish_source/proc/simple_dispense_reward(reward_path, atom/spawn_location, turf/fishing_spot) if(isnull(reward_path)) return null - if((reward_path in fish_counts)) // This is limited count result + if(reward_path in fish_counts) // This is limited count result fish_counts[reward_path] -= 1 - if(!fish_counts[reward_path]) - fish_counts -= reward_path //Ran out of these since rolling (multiple fishermen on same source most likely) - fish_table -= reward_path + 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, regen_time) + fish_counts[reward_path] += 1 + currently_on_regen[reward_path] -= 1 + if(!currently_on_regen[reward_path]) + LAZYREMOVE(currently_on_regen, reward_path) + else + 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, turf/fishing_spot) if(reward_path == FISHING_DUD) @@ -239,22 +271,13 @@ GLOBAL_LIST(fishing_property_cache) 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"] - if("Reagent") - return bait.reagents?.has_reagent(special_identifier["Value"], special_identifier["Amount"], check_subtypes = TRUE) - else - CRASH("Unknown bait identifier in fish favourite/disliked list") - else - return HAS_TRAIT(bait, identifier) +/// Returns the fish table, with with the unavailable items from fish_counts removed. +/datum/fish_source/proc/get_fish_table() + var/list/table = fish_table.Copy() + for(var/result in table) + if(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) @@ -364,13 +387,13 @@ GLOBAL_LIST(fishing_property_cache) for(var/i in 1 to (severity + 2)) if(!prob((100 + 100 * severity)/i * multiplier)) continue - var/reward_loot = pick_weight(fish_table) + var/reward_loot = pick_weight(get_fish_table()) var/atom/movable/reward = simple_dispense_reward(reward_loot, location, location) if(isnull(reward)) continue if(isfish(reward)) var/obj/item/fish/fish = reward - fish.set_status(FISH_DEAD) + fish.set_status(FISH_DEAD, silent = TRUE) if(isitem(reward)) reward.pixel_x = rand(-9, 9) reward.pixel_y = rand(-9, 9) diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index e7192bbd09c..5b4b6fe3624 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -1,32 +1,90 @@ /datum/fish_source/ocean fish_table = list( FISHING_DUD = 15, - /obj/item/coin/gold = 5, + /obj/item/coin/gold = 7, /obj/item/fish/clownfish = 15, /obj/item/fish/pufferfish = 15, /obj/item/fish/cardinal = 15, /obj/item/fish/greenchromis = 15, - /obj/item/fish/lanternfish = 5, - /obj/item/fish/zipzap = 5, - /obj/item/fish/clownfish/lube = 3, + /obj/item/fish/stingray = 10, + /obj/item/fish/lanternfish = 7, + /obj/item/fish/zipzap = 7, + /obj/item/fish/clownfish/lube = 5, + /obj/item/fish/swordfish = 5, /obj/structure/mystery_box/fishing = 1, ) fish_counts = list( /obj/item/fish/clownfish/lube = 2, + /obj/item/fish/swordfish = 2, /obj/structure/mystery_box/fishing = 1, ) + fish_count_regen = list( + /obj/item/fish/clownfish/lube = 3 MINUTES, + /obj/item/fish/swordfish = 5 MINUTES, + ) fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5 explosive_malus = TRUE /datum/fish_source/ocean/beach catalog_description = "Beach shore water" +/datum/fish_source/ice_fishing + catalog_description = "Ice-covered water" + fish_table = list( + FISHING_DUD = 4, + /obj/item/fish/arctic_char = 5, + /obj/item/fish/sockeye_salmon = 5, + /obj/item/fish/chasm_crab/ice = 2, + /obj/item/fish/boned = 1, + ) + fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 20 + +/datum/fish_source/river + catalog_description = "River water" + fish_table = list( + FISHING_DUD = 4, + /obj/item/fish/goldfish = 5, + /obj/item/fish/guppy = 5, + /obj/item/fish/angelfish = 4, + /obj/item/fish/catfish = 4, + /obj/item/fish/slimefish = 2, + /obj/item/fish/sockeye_salmon = 1, + /obj/item/fish/arctic_char = 1, + /obj/item/fish/three_eyes = 1, + ) + fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 5 + +/datum/fish_source/sand + catalog_description = "Sand" + fish_table = list( + FISHING_DUD = 8, + /obj/item/fish/sand_crab = 10, + /obj/item/fish/sand_surfer = 10, + /obj/item/fish/bumpy = 10, + /obj/item/coin/gold = 3, + ) + fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 20 + +/datum/fish_source/cursed_spring + catalog_description = null //it's a secret (sorta, I know you're reading this) + fish_table = list( + FISHING_DUD = 2, + /obj/item/fish/soul = 3, + /obj/item/fish/skin_crab = 3, + /obj/item/fishing_rod/telescopic/master = 1, + ) + fish_counts = list( + /obj/item/fishing_rod/telescopic/master = 1, + ) + fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 25 + /datum/fish_source/portal fish_table = list( FISHING_DUD = 7, /obj/item/fish/goldfish = 10, /obj/item/fish/guppy = 10, /obj/item/fish/angelfish = 10, + /obj/item/fish/three_eyes = 3, ) catalog_description = "Aquarium dimension (Fishing portal generator)" ///The name of this option shown in the radial menu on the fishing portal generator @@ -73,6 +131,14 @@ /obj/item/fish/needlefish = 5, /obj/item/fish/armorfish = 5, /obj/item/fish/zipzap = 5, + /obj/item/fish/stingray = 4, + /obj/item/fish/swordfish = 3, + ) + fish_counts = list( + /obj/item/fish/swordfish = 2, + ) + fish_count_regen = list( + /obj/item/fish/swordfish = 5 MINUTES, ) catalog_description = "Ocean dimension (Fishing portal generator)" fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 10 @@ -104,6 +170,13 @@ /obj/item/fish/donkfish = 5, /obj/item/fish/emulsijack = 5, /obj/item/fish/jumpercable = 5, + /obj/item/fish/chainsawfish = 3, + ) + fish_counts = list( + /obj/item/fish/chainsawfish = 1, + ) + fish_count_regen = list( + /obj/item/fish/chainsawfish = 7 MINUTES, ) catalog_description = "Syndicate dimension (Fishing portal generator)" radial_name = "Syndicate" @@ -130,6 +203,7 @@ ///rewards not found in other fishing portals fish_table = list( + /obj/item/fish/three_eyes = 3, /obj/item/fish/holo/checkered = 1, ) @@ -155,8 +229,8 @@ 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) + challenge.mover.short_jump_velocity_limit += rand(-100, 100) + challenge.mover.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)) @@ -319,16 +393,19 @@ /obj/item/clothing/gloves/bracer = 2, /obj/effect/decal/remains/human = 2, /obj/item/fish/mastodon = 1, + /obj/item/fishing_rod/telescopic/master = 1, ) fish_counts = list( /obj/item/clothing/gloves/bracer = 1, /obj/effect/decal/remains/human = 1, /obj/item/fish/mastodon = 1, + /obj/item/fishing_rod/telescopic/master = 1, + ) + fish_count_regen = list( + /obj/item/fish/mastodon = 8 MINUTES, ) fishing_difficulty = FISHING_DEFAULT_DIFFICULTY + 15 -#define RANDOM_SEED "Random seed" - /datum/fish_source/hydro_tray catalog_description = "Hydroponics trays" fish_table = list( @@ -372,7 +449,7 @@ return ..() /datum/fish_source/hydro_tray/spawn_reward(reward_path, mob/fisherman, turf/fishing_spot) - if(reward_path != RANDOM_SEED) + if(reward_path != FISHING_RANDOM_SEED) var/mob/living/created_reward = ..() if(istype(created_reward)) created_reward.name = "small [created_reward.name]" @@ -393,5 +470,3 @@ var/picked_path = pick(seeds_to_draw_from) return new picked_path(get_turf(fishing_spot)) - -#undef RANDOM_SEED diff --git a/code/modules/mapfluff/ruins/icemoonruin_code/hotsprings.dm b/code/modules/mapfluff/ruins/icemoonruin_code/hotsprings.dm index 9e188d45856..7c674e98e24 100644 --- a/code/modules/mapfluff/ruins/icemoonruin_code/hotsprings.dm +++ b/code/modules/mapfluff/ruins/icemoonruin_code/hotsprings.dm @@ -14,6 +14,7 @@ baseturfs = /turf/open/water/cursed_spring planetary_atmos = TRUE initial_gas_mix = ICEMOON_DEFAULT_ATMOS + fishing_datum = /datum/fish_source/cursed_spring /turf/open/water/cursed_spring/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) . = ..() diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index a168150c7da..addb8cdb6b8 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -20,6 +20,11 @@ custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*7, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5) grind_results = list(/datum/reagent/lithium = 15, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) +/obj/item/stock_parts/power_store/cell/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_FISHING_BAIT, INNATE_TRAIT) + ADD_TRAIT(src, TRAIT_POISONOUS_BAIT, INNATE_TRAIT) //bro is fishing using lithium... + /* Cell variants*/ /obj/item/stock_parts/power_store/cell/empty empty = TRUE diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm index 5cb582ca969..1c5415596e6 100644 --- a/code/modules/unit_tests/fish_unit_tests.dm +++ b/code/modules/unit_tests/fish_unit_tests.dm @@ -76,10 +76,11 @@ incompatible_traits = list(/datum/fish_trait/dummy/two) inheritability = 100 diff_traits_inheritability = 100 + reagents_to_add = list(/datum/reagent = 10) /datum/fish_trait/dummy/apply_to_fish(obj/item/fish/fish) + . = ..() ADD_TRAIT(fish, TRAIT_FISH_TESTING, FISH_TRAIT_DATUM) - fish.grind_results[/datum/reagent] = 10 /datum/fish_trait/dummy/two incompatible_traits = list(/datum/fish_trait/dummy) @@ -259,15 +260,17 @@ /datum/unit_test/explosive_fishing/Run() var/datum/fish_source/source = GLOB.preset_fish_sources[/datum/fish_source/unit_test] source.spawn_reward_from_explosion(run_loc_floor_bottom_left, 1) - if(length(source.fish_table)) + if(source.fish_counts[/obj/item/wrench]) TEST_FAIL("The unit test item wasn't removed/spawned from fish_table during 'spawn_reward_from_explosion'.") /datum/fish_source/unit_test fish_table = list( /obj/item/wrench = 1, + /obj/item/screwdriver = INFINITY, ) fish_counts = list( /obj/item/wrench = 1, + /obj/item/screwdriver = 0, ) #undef TRAIT_FISH_TESTING diff --git a/icons/hud/fishing_hud.dmi b/icons/hud/fishing_hud.dmi index f18ed7c6cfc..0a952615d58 100644 Binary files a/icons/hud/fishing_hud.dmi and b/icons/hud/fishing_hud.dmi differ diff --git a/icons/mob/inhands/fish_lefthand.dmi b/icons/mob/inhands/fish_lefthand.dmi index f5aeef62027..b61231c6b9d 100644 Binary files a/icons/mob/inhands/fish_lefthand.dmi and b/icons/mob/inhands/fish_lefthand.dmi differ diff --git a/icons/mob/inhands/fish_righthand.dmi b/icons/mob/inhands/fish_righthand.dmi index d9c23c3d1b1..48ede802899 100644 Binary files a/icons/mob/inhands/fish_righthand.dmi and b/icons/mob/inhands/fish_righthand.dmi differ diff --git a/icons/obj/food/meat.dmi b/icons/obj/food/meat.dmi index f321e087fb9..925675f7303 100644 Binary files a/icons/obj/food/meat.dmi and b/icons/obj/food/meat.dmi differ diff --git a/icons/obj/structures/aquarium/fish.dmi b/icons/obj/structures/aquarium/fish.dmi index 638c8300f30..73304ea6af1 100644 Binary files a/icons/obj/structures/aquarium/fish.dmi and b/icons/obj/structures/aquarium/fish.dmi differ diff --git a/icons/obj/structures/aquarium/wide.dmi b/icons/obj/structures/aquarium/wide.dmi index 7c5f941a1db..61dfbe12718 100644 Binary files a/icons/obj/structures/aquarium/wide.dmi and b/icons/obj/structures/aquarium/wide.dmi differ diff --git a/icons/turf/overlays.dmi b/icons/turf/overlays.dmi index 91ad71baada..aa7b6b67a18 100644 Binary files a/icons/turf/overlays.dmi and b/icons/turf/overlays.dmi differ diff --git a/tgstation.dme b/tgstation.dme index afff6b31400..72a4ae195be 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1536,6 +1536,7 @@ #include "code\datums\elements\proficient_miner.dm" #include "code\datums\elements\projectile_drop.dm" #include "code\datums\elements\projectile_shield.dm" +#include "code\datums\elements\quality_food_ingredient.dm" #include "code\datums\elements\radiation_protected_clothing.dm" #include "code\datums\elements\radioactive.dm" #include "code\datums\elements\ranged_armour.dm" @@ -4160,6 +4161,7 @@ #include "code\modules\fishing\admin.dm" #include "code\modules\fishing\bait.dm" #include "code\modules\fishing\fish_catalog.dm" +#include "code\modules\fishing\fish_movement.dm" #include "code\modules\fishing\fishing_equipment.dm" #include "code\modules\fishing\fishing_minigame.dm" #include "code\modules\fishing\fishing_portal_machine.dm"