From 17e8000c8e4d634e42f071919d2b72b945409f0b Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Sun, 9 Apr 2023 23:50:14 +0100 Subject: [PATCH] Reduce carp migration devastation (#74608) ## About The Pull Request I'm not totally satisfied with the amount of random destruction caused by space carp wandering around, they should certainly be dangerous and annoying but the random nature of their spawning and pathfinding means that they would trap themselves in random rooms and then smash all of the machinery in there. Because they could attack any dense object they perceived as being in their way that could result in venting random gas canisters, breaking terminals, or I even once saw them destroy the supermatter cooling loop by eating the thermomachines. While the latter is pretty funny, arbitrary destruction of machines simply caused because a fish teleported into a room without you knowing isn't really very engaging and doesn't create very interesting stories. This ultimately isn't meant to be a heavily destructive event and its probability to run isn't tuned as if it is. So, a couple of changes: I reduced both the range and cooldown of the carp teleporting ability. This means that AI carp can use it to pathfind past obstacles pretty reliably and don't spend so much time smashing things, and also reduces the chances of them getting the drop on you from a location you can't see. I also added a short click cooldown to carp travelling through other carp rifts so people being teleported _to_ have more of an advantage over people ambushing them (this was already true for the carp creating the rift). Additionally I added an optional whitelist to the "attack obstacles to your pathfinding" AI script, and heavily culled the kind of obstacles that carp will attack to be ones which are mostly replaceable. They will still cause a mess and might even vent a room, but they won't smash vital infrastructure. Finally I replaced a couple of instances of `get_ranged_target_turf` with `get_ranged_target_turf_direct` for better precision, and player carp using the ability can now just click anywhere on the screen and it will jaunt in that rough direction. With the reduced range, having to click within its radius was pretty annoying. With these changes I ran the event 10 times in a row on kilo and then watched JoJo's bizzarre adventure for 90 minutes and when I came back the level of destruction seemed pretty reasonable (aside from the big hole where one of them ran into the supermatter and delaminated it, but if there were players around that wouldn't happen). ## Why It's Good For The Game This event was still just a little bit _too_ annoying. If something destroys important machines it should have happened on purpose via an event which was supposed to do that, rather than through chance. Or preferably just be player-driven. ## Changelog :cl: balance: Carp can't teleport as far, but can do it more frequently. People who piggyback through their rifts will be blocked from attacking for a short duration (the same as the normal attack cooldown). balance: AI controlled carp will now be more selective about which objects they smash. Player controlled carp (or carp directly instructed to attack objects by people who have tamed them) can still attack whatever they like. /:cl: --- code/__DEFINES/ai.dm | 2 ++ .../basic_ai_behaviors/run_away_from_target.dm | 3 +-- .../basic_subtrees/attack_obstacle_in_path.dm | 4 ++++ .../mob/living/basic/space_fauna/carp/carp.dm | 16 ++++++++++++++++ .../basic/space_fauna/carp/carp_abilities.dm | 18 +++++++++++------- .../space_fauna/carp/carp_ai_rift_actions.dm | 9 ++++----- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 8a62039228e..58f83c91d2c 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -226,6 +226,8 @@ #define BB_TARGETTING_DATUM "targetting_datum" ///some behaviors that check current_target also set this on deep crit mobs #define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target" +///Blackboard key for a whitelist typecache of "things we can target while trying to move" +#define BB_OBSTACLE_TARGETTING_WHITELIST "BB_targetting_whitelist" ///Targetting keys for something to run away from, if you need to store this separately from current target #define BB_BASIC_MOB_FLEE_TARGET "BB_basic_flee_target" diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm index ccc58d9f0a9..7f152e43ce0 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm @@ -30,10 +30,9 @@ finish_action(controller, succeeded = FALSE, target_key = target_key, hiding_location_key = hiding_location_key) /datum/ai_behavior/run_away_from_target/proc/plot_path_away_from(datum/ai_controller/controller, atom/target) - var/run_direction = get_dir(controller.pawn, get_step_away(controller.pawn, target)) var/turf/target_destination = get_turf(controller.pawn) for (var/i in 1 to run_distance) - var/turf/test_destination = get_ranged_target_turf(controller.pawn, run_direction, i) + var/turf/test_destination = get_ranged_target_turf_direct(controller.pawn, target, range = i, offset = 180) if (test_destination.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn, ignore_atoms = GLOB.airlocks)) break target_destination = test_destination diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm index 32809ba652b..941063558e2 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm @@ -77,6 +77,10 @@ return FALSE if (basic_mob.see_invisible < object.invisibility) return FALSE + var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETTING_WHITELIST] + if(whitelist && !is_type_in_typecache(object, whitelist)) + return FALSE + return TRUE // It's in our way, let's get it out of our way /datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index b297a89e9de..d25a93b76a2 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -63,6 +63,21 @@ var/static/list/desired_food = list(/obj/item/food/meat/slab, /obj/item/food/meat/rawcutlet) /// Carp want to eat delicious six pack plastic rings var/static/list/desired_trash = list(/obj/item/storage/cans) + /// Structures that AI carp are willing to attack. This prevents them from deconstructing supermatter cooling equipment. + var/static/list/allowed_obstacle_targets = typecacheof(list( + /obj/structure/closet, + /obj/machinery/door, + /obj/structure/door_assembly, + /obj/structure/filingcabinet, + /obj/structure/frame, + /obj/structure/grille, + /obj/structure/plasticflaps, + /obj/structure/rack, + /obj/structure/reagent_dispensers, // Carp can have a little welding fuel, as a treat + /obj/structure/table, + /obj/machinery/vending, + /obj/structure/window, + )) /// Weighted list of colours a carp can be /// Weighted list of usual carp colors var/static/list/carp_colors = list( @@ -104,6 +119,7 @@ teleport = new(src) teleport.Grant(src) ai_controller.blackboard[BB_CARP_RIFT] = WEAKREF(teleport) + ai_controller.blackboard[BB_OBSTACLE_TARGETTING_WHITELIST] = allowed_obstacle_targets /mob/living/basic/carp/Destroy() QDEL_NULL(teleport) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm index 94fc89ca9da..9a6aea8eff1 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm @@ -37,12 +37,14 @@ button_icon = 'icons/effects/effects.dmi' button_icon_state = "rift" desc = "Open a rift through the carp stream, allowing passage to somewhere close by." - cooldown_time = 1 MINUTES - melee_cooldown_time = 2 SECONDS + cooldown_time = 15 SECONDS + melee_cooldown_time = 0 SECONDS // Handled by rift /// How far away can you place a rift? - var/max_range = 6 + var/max_range = 3 /datum/action/cooldown/mob_cooldown/lesser_carp_rift/Activate(atom/target_atom) + if (get_dist(get_turf(owner), target_atom) > max_range) + target_atom = get_ranged_target_turf_direct(owner, target_atom, range = max_range) if (!make_rift(target_atom)) return FALSE StartCooldown() @@ -58,10 +60,6 @@ if (!target_turf) return FALSE - if (get_dist(owner_turf, target_turf) > max_range) - owner.balloon_alert(owner, "too far!") - return FALSE - if (!target_turf) return FALSE @@ -107,6 +105,8 @@ /obj/effect/temp_visual/lesser_carp_rift/entrance /// Where you get teleported to var/list/exit_locs + /// Click CD to apply after teleporting + var/disorient_time = CLICK_CD_MELEE /obj/effect/temp_visual/lesser_carp_rift/entrance/Initialize(mapload) . = ..() @@ -129,6 +129,10 @@ if (isobserver(entered_atom)) return + if (isliving(entered_atom)) + var/mob/living/teleported_mob = entered_atom + teleported_mob.changeNext_move(disorient_time) + var/turf/destination = pick(exit_locs) do_teleport(entered_atom, destination, channel = TELEPORT_CHANNEL_MAGIC) playsound(src, 'sound/magic/wand_teleport.ogg', 50) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm index 71be65aadd7..9083467a8a0 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm @@ -99,8 +99,7 @@ /datum/ai_behavior/make_carp_rift/away /datum/ai_behavior/make_carp_rift/away/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) - var/run_direction = get_dir(controller.pawn, get_step_away(controller.pawn, target)) - return get_ranged_target_turf(controller.pawn, run_direction, ability.max_range) + return get_ranged_target_turf_direct(controller.pawn, target, range = ability.max_range, offset = 180) /** * # Make carp rift forwards @@ -161,7 +160,7 @@ * Make a rift towards your target if you are blocked from moving or if it is far away */ /datum/ai_behavior/make_carp_rift/towards/aggressive - teleport_buffer_distance = 2 // Don't aggressively drop carps directly on top of a target mob + teleport_buffer_distance = 1 // Don't aggressively drop carps directly on top of a target mob /** * # Make carp rift forwards (unvalidated) @@ -183,9 +182,9 @@ */ /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift /// How far away do we look for rifts? - var/search_distance = 2 + var/search_distance = 3 /// Minimum distance we should be from the target before we bother performing this action - var/minimum_distance = 3 + var/minimum_distance = 2 /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift/SelectBehaviors(datum/ai_controller/controller, delta_time) var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]