From 58b61a17a78e90ea9da91351572abee9a4f93ccb Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Fri, 9 Dec 2022 18:53:58 +0000 Subject: [PATCH] Basic Mob Carp: Retaliate Element (#71593) ## About The Pull Request Adds an Element and AI behaviour intended to replicate the "retaliate" behaviour which made up an entire widely-populated subtype of simple mobs. The behaviour is pretty simply "If you fuck with me I fuck with you". Mobs with the component will "remember" being attacked and will try to attack people who attacked them, until they lose sight of those people. They don't have very long memories so breaking line of sight is enough to remove you from their grudge list. The implementation unfortunately requires registering to 600 different "I have been attacked by X" signals but c'est la vie. It will still be cleaner than `/mob/living/simple_animal/hostile/retaliate/clown/clownhulk/honcmunculus` and `mob/living/simple_animal/hostile/retaliate/bat/sgt_araneus`. I attached it to the pig for testing and left it there because out of all the farm animals we have right now, a pig would probably get pissed off if you tried to kill it. Unfortunately it's got a sausage's chance in hell of ever killing anyone. ## Why It's Good For The Game It doesn't have much purpose yet but as we make more basic mobs this is going to see a **lot** of use. ## Changelog :cl: add: Basic mobs have the capability of being upset that you kicked and punched them. add: Pigs destined for slaughter will now ineffectually attempt to resist their fate, at least until they lose sight of you. balance: Bar bots are better at noticing that you're trying to kill them. /:cl: --- code/__DEFINES/ai.dm | 3 + .../signals_atom/signals_atom_attack.dm | 4 + .../basic_subtrees/target_retaliate.dm | 69 ++++++++++++++++ .../basic_targetting_datum.dm | 18 ++++- code/datums/ai/monkey/monkey_controller.dm | 67 ++-------------- .../robot_customer_controller.dm | 7 +- code/datums/elements/ai_retaliate.dm | 29 +++++++ code/datums/elements/relay_attackers.dm | 79 +++++++++++++++++++ .../mob/living/basic/farm_animals/pig.dm | 19 ++++- .../vehicles/mecha/mech_melee_attack.dm | 1 + tgstation.dme | 3 + 11 files changed, 232 insertions(+), 67 deletions(-) create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm create mode 100644 code/datums/elements/ai_retaliate.dm create mode 100644 code/datums/elements/relay_attackers.dm diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 1f879efb01e..b837799f963 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -220,6 +220,9 @@ ///some behaviors that check current_target also set this on deep crit mobs #define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target" +///List of mobs who have damaged us +#define BB_BASIC_MOB_RETALIATE_LIST "BB_basic_mob_shitlist" + ///list of foods this mob likes #define BB_BASIC_FOODS "BB_basic_foods" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm index 04755aca997..0e9b7161a99 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm @@ -40,3 +40,7 @@ #define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand" ///from base of atom/attack_paw(): (mob/user) #define COMSIG_ATOM_ATTACK_PAW "atom_attack_paw" +///from base of atom/mech_melee_attack(): (obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user) +#define COMSIG_ATOM_ATTACK_MECH "atom_attack_mech" +///from relay_attackers element: (atom/attacker) +#define COMSIG_ATOM_WAS_ATTACKED "atom_was_attacked" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm new file mode 100644 index 00000000000..ff92435765e --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm @@ -0,0 +1,69 @@ +/// Sets the BB target to a mob which you can see and who has recently attacked you +/datum/ai_planning_subtree/target_retaliate + +/datum/ai_planning_subtree/target_retaliate/SelectBehaviors(datum/ai_controller/controller, delta_time) + . = ..() + controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list, BB_BASIC_MOB_RETALIATE_LIST, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + +/** + * Picks a target from a provided list of atoms who have been pissing you off + * You will probably need /datum/element/ai_retaliate to take advantage of this unless you're populating the blackboard yourself + */ +/datum/ai_behavior/target_from_retaliate_list + action_cooldown = 2 SECONDS + /// How far can we see stuff? + var/vision_range = 9 + +/datum/ai_behavior/target_from_retaliate_list/perform(delta_time, datum/ai_controller/controller, shitlist_key, target_key, targetting_datum_key, hiding_location_key) + . = ..() + var/mob/living/living_mob = controller.pawn + var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key] + if(!targetting_datum) + CRASH("No target datum was supplied in the blackboard for [controller.pawn]") + + var/list/enemy_refs = controller.blackboard[shitlist_key] + if (!length(enemy_refs)) + finish_action(controller, succeeded = FALSE) + return + + var/list/enemies_list = list() + for (var/datum/weakref/enemy_ref as anything in enemy_refs) + var/atom/enemy = enemy_ref.resolve() + if (!can_attack_target(living_mob, enemy, targetting_datum)) + controller.blackboard[shitlist_key] -= enemy_ref + continue + enemies_list += enemy + + if (!length(enemies_list)) + finish_action(controller, succeeded = FALSE) + return + + var/datum/weakref/weak_target = controller.blackboard[target_key] + var/atom/target = weak_target?.resolve() + if (target && (locate(target) in enemies_list)) // Don't bother changing + finish_action(controller, succeeded = FALSE) + return + + var/atom/new_target = pick_final_target(controller, enemies_list) + controller.blackboard[target_key] = WEAKREF(new_target) + + var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, new_target) + + if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. + controller.blackboard[hiding_location_key] = WEAKREF(potential_hiding_location) + + finish_action(controller, succeeded = TRUE) + +/// Returns true if this target is valid for attacking based on current conditions +/datum/ai_behavior/target_from_retaliate_list/proc/can_attack_target(mob/living/living_mob, atom/target, datum/targetting_datum/targetting_datum) + if (!target) + return FALSE + if (target == living_mob) + return FALSE + if (!can_see(living_mob, target, vision_range)) + return FALSE + return targetting_datum.can_attack(living_mob, target) + +/// Returns the desired final target from the filtered list of enemies +/datum/ai_behavior/target_from_retaliate_list/proc/pick_final_target(datum/ai_controller/controller, list/enemies_list) + return pick(enemies_list) diff --git a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm index 5b1ffcd0e4e..86fa48b377a 100644 --- a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm +++ b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm @@ -25,16 +25,15 @@ if(M.status_flags & GODMODE) return FALSE - if(living_mob.see_invisible < the_target.invisibility)//Target's invisible to us, forget it + if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it return FALSE - if(living_mob.z != the_target.z) + if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech return FALSE if(isliving(the_target)) //Targeting vs living mobs var/mob/living/L = the_target - var/faction_check = living_mob.faction_check_mob(L, exact_match = check_factions_exactly) - if(faction_check || L.stat) + if(faction_check(living_mob, L) || L.stat) return FALSE return TRUE @@ -56,6 +55,10 @@ return FALSE +/// Returns true if the mob and target share factions +/datum/targetting_datum/basic/proc/faction_check(mob/living/living_mob, mob/living/the_target) + return living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly) + /// Subtype more forgiving for items. /// Careful, this can go wrong and keep a mob hyper-focused on an item it can't lose aggro on /datum/targetting_datum/basic/allow_items @@ -65,3 +68,10 @@ if(isitem(the_target)) // trust fall exercise return TRUE + +/// Subtype which doesn't care about faction +/// Mobs which retaliate but don't otherwise target seek should just attack anything which annoys them +/datum/targetting_datum/basic/ignore_faction + +/datum/targetting_datum/basic/ignore_faction/faction_check(mob/living/living_mob, mob/living/the_target) + return FALSE diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 7e1ab026bfc..b2bcb7bdd93 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -53,16 +53,10 @@ have ways of interacting with a specific mob and control it. return AI_CONTROLLER_INCOMPATIBLE var/mob/living/living_pawn = new_pawn - RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby)) - RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand)) - RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_PAW, PROC_REF(on_attack_paw)) - RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_ANIMAL, PROC_REF(on_attack_animal)) - RegisterSignal(new_pawn, COMSIG_MOB_ATTACK_ALIEN, PROC_REF(on_attack_alien)) - RegisterSignal(new_pawn, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act)) - RegisterSignal(new_pawn, COMSIG_ATOM_HITBY, PROC_REF(on_hitby)) + living_pawn.AddElement(/datum/element/relay_attackers) + RegisterSignal(new_pawn, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) RegisterSignal(new_pawn, COMSIG_LIVING_START_PULL, PROC_REF(on_startpulling)) RegisterSignal(new_pawn, COMSIG_LIVING_TRY_SYRINGE, PROC_REF(on_try_syringe)) - RegisterSignal(new_pawn, COMSIG_ATOM_HULK_ATTACK, PROC_REF(on_attack_hulk)) RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, PROC_REF(on_attempt_cuff)) RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, PROC_REF(update_movespeed)) @@ -72,18 +66,11 @@ have ways of interacting with a specific mob and control it. /datum/ai_controller/monkey/UnpossessPawn(destroy) UnregisterSignal(pawn, list( - COMSIG_PARENT_ATTACKBY, - COMSIG_ATOM_ATTACK_HAND, - COMSIG_ATOM_ATTACK_PAW, - COMSIG_ATOM_BULLET_ACT, - COMSIG_ATOM_HITBY, + COMSIG_ATOM_WAS_ATTACKED, COMSIG_LIVING_START_PULL, COMSIG_LIVING_TRY_SYRINGE, - COMSIG_ATOM_HULK_ATTACK, COMSIG_CARBON_CUFF_ATTEMPTED, COMSIG_MOB_MOVESPEED_UPDATED, - COMSIG_ATOM_ATTACK_ANIMAL, - COMSIG_MOB_ATTACK_ALIEN, )) return ..() //Run parent at end @@ -159,48 +146,10 @@ have ways of interacting with a specific mob and control it. var/list/enemies = blackboard[BB_MONKEY_ENEMIES] enemies[WEAKREF(L)] += MONKEY_HATRED_AMOUNT -/datum/ai_controller/monkey/proc/on_attackby(datum/source, obj/item/I, mob/user) +/datum/ai_controller/monkey/proc/on_attacked(datum/source, mob/attacker) SIGNAL_HANDLER - if(I.force && I.damtype != STAMINA) - retaliate(user) - -/datum/ai_controller/monkey/proc/on_attack_hand(datum/source, mob/living/user, list/modifiers) - SIGNAL_HANDLER - if((user.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK)) && prob(MONKEY_RETALIATE_PROB)) - retaliate(user) - -/datum/ai_controller/monkey/proc/on_attack_paw(datum/source, mob/living/user, list/modifiers) - SIGNAL_HANDLER - if((user.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK)) && prob(MONKEY_RETALIATE_PROB)) - retaliate(user) - -/datum/ai_controller/monkey/proc/on_attack_animal(datum/source, mob/living/user) - SIGNAL_HANDLER - if(user.melee_damage_upper > 0 && prob(MONKEY_RETALIATE_PROB)) - retaliate(user) - -/datum/ai_controller/monkey/proc/on_attack_alien(datum/source, mob/living/user, list/modifiers) - SIGNAL_HANDLER - if((user.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK)) && prob(MONKEY_RETALIATE_PROB)) - retaliate(user) - -/datum/ai_controller/monkey/proc/on_bullet_act(datum/source, obj/projectile/Proj) - SIGNAL_HANDLER - var/mob/living/living_pawn = pawn - if(istype(Proj, /obj/projectile/beam) || istype(Proj, /obj/projectile/bullet)) - if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) - if(!Proj.nodamage && Proj.damage < living_pawn.health && isliving(Proj.firer)) - retaliate(Proj.firer) - -/datum/ai_controller/monkey/proc/on_hitby(datum/source, atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) - SIGNAL_HANDLER - if(isitem(AM)) - var/mob/living/living_pawn = pawn - var/obj/item/I = AM - var/mob/thrown_by = I.thrownby?.resolve() - if(I.throwforce && I.throwforce < living_pawn.health && ishuman(thrown_by)) - var/mob/living/carbon/human/H = thrown_by - retaliate(H) + if(prob(MONKEY_RETALIATE_PROB)) + retaliate(attacker) /datum/ai_controller/monkey/proc/on_startpulling(datum/source, atom/movable/puller, state, force) SIGNAL_HANDLER @@ -215,10 +164,6 @@ have ways of interacting with a specific mob and control it. if(prob(MONKEY_SYRINGE_RETALIATION_PROB)) retaliate(user) -/datum/ai_controller/monkey/proc/on_attack_hulk(datum/source, mob/user) - SIGNAL_HANDLER - retaliate(user) - /datum/ai_controller/monkey/proc/on_attempt_cuff(datum/source, mob/user) SIGNAL_HANDLER // chance of monkey retaliation diff --git a/code/datums/ai/robot_customer/robot_customer_controller.dm b/code/datums/ai/robot_customer/robot_customer_controller.dm index 901c0f2f69c..cf53c521b3a 100644 --- a/code/datums/ai/robot_customer/robot_customer_controller.dm +++ b/code/datums/ai/robot_customer/robot_customer_controller.dm @@ -20,13 +20,15 @@ /datum/ai_controller/robot_customer/TryPossessPawn(atom/new_pawn) if(!istype(new_pawn, /mob/living/simple_animal/robot_customer)) return AI_CONTROLLER_INCOMPATIBLE + new_pawn.AddElement(/datum/element/relay_attackers) RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby)) + RegisterSignal(new_pawn, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) RegisterSignal(new_pawn, COMSIG_LIVING_GET_PULLED, PROC_REF(on_get_pulled)) RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_get_punched)) return ..() //Run parent at end /datum/ai_controller/robot_customer/UnpossessPawn(destroy) - UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_ATTACK_HAND)) + UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_WAS_ATTACKED, COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_ATTACK_HAND)) return ..() //Run parent at end /datum/ai_controller/robot_customer/proc/on_attackby(datum/source, obj/item/I, mob/living/user) @@ -39,6 +41,9 @@ else INVOKE_ASYNC(src, PROC_REF(warn_greytider), user) +/datum/ai_controller/robot_customer/proc/on_attacked(datum/source, mob/living/attacker) + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(warn_greytider), attacker) /datum/ai_controller/robot_customer/proc/eat_order(obj/item/order_item, datum/venue/attending_venue) if(!blackboard[BB_CUSTOMER_EATING]) diff --git a/code/datums/elements/ai_retaliate.dm b/code/datums/elements/ai_retaliate.dm new file mode 100644 index 00000000000..d4e2144f558 --- /dev/null +++ b/code/datums/elements/ai_retaliate.dm @@ -0,0 +1,29 @@ +/** + * Attached to a mob with an AI controller, passes things which have damaged it to a blackboard. + * The AI controller is responsible for doing anything with that information. + */ +/datum/element/ai_retaliate + +/datum/element/ai_retaliate/Attach(datum/target) + . = ..() + if(!ismob(target)) + return ELEMENT_INCOMPATIBLE + + target.AddElement(/datum/element/relay_attackers) + RegisterSignal(target, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) + +/datum/element/ai_retaliate/Detach(datum/source, ...) + . = ..() + UnregisterSignal(source, COMSIG_ATOM_WAS_ATTACKED) + +/// Add an attacking atom to a blackboard list of things which attacked us +/datum/element/ai_retaliate/proc/on_attacked(mob/victim, atom/attacker) + SIGNAL_HANDLER + + if (!victim.ai_controller) + return + var/list/enemy_refs = victim.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST] + if (!enemy_refs) + enemy_refs = list() + enemy_refs |= WEAKREF(attacker) + victim.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST] = enemy_refs diff --git a/code/datums/elements/relay_attackers.dm b/code/datums/elements/relay_attackers.dm new file mode 100644 index 00000000000..27eb25a537d --- /dev/null +++ b/code/datums/elements/relay_attackers.dm @@ -0,0 +1,79 @@ +/** + * This element registers to a shitload of signals which can signify "someone attacked me". + * If anyone does it sends a single "someone attacked me" signal containing details about who done it. + * This prevents other components and elements from having to register to the same list of a million signals, should be more maintainable in one place. + */ +/datum/element/relay_attackers + +/datum/element/relay_attackers/Attach(datum/target) + . = ..() + // Boy this sure is a lot of ways to tell us that someone tried to attack us + RegisterSignal(target, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby)) + RegisterSignals(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW, COMSIG_MOB_ATTACK_ALIEN), PROC_REF(on_attack_generic)) + RegisterSignals(target, list(COMSIG_ATOM_ATTACK_BASIC_MOB, COMSIG_ATOM_ATTACK_ANIMAL), PROC_REF(on_attack_npc)) + RegisterSignal(target, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act)) + RegisterSignal(target, COMSIG_ATOM_HITBY, PROC_REF(on_hitby)) + RegisterSignal(target, COMSIG_ATOM_HULK_ATTACK, PROC_REF(on_attack_hulk)) + RegisterSignal(target, COMSIG_ATOM_ATTACK_MECH, PROC_REF(on_attack_mech)) + +/datum/element/relay_attackers/Detach(datum/source, ...) + . = ..() + UnregisterSignal(source, list( + COMSIG_PARENT_ATTACKBY, + COMSIG_ATOM_ATTACK_HAND, + COMSIG_ATOM_ATTACK_PAW, + COMSIG_ATOM_ATTACK_BASIC_MOB, + COMSIG_ATOM_ATTACK_ANIMAL, + COMSIG_MOB_ATTACK_ALIEN, + COMSIG_ATOM_BULLET_ACT, + COMSIG_ATOM_HITBY, + COMSIG_ATOM_HULK_ATTACK, + COMSIG_ATOM_ATTACK_MECH, + )) + +/datum/element/relay_attackers/proc/on_attackby(atom/target, obj/item/weapon, mob/attacker) + SIGNAL_HANDLER + if(weapon.force) + relay_attacker(target, attacker) + +/datum/element/relay_attackers/proc/on_attack_generic(atom/target, mob/living/attacker, list/modifiers) + SIGNAL_HANDLER + if(attacker.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK)) + relay_attacker(target, attacker) + +/datum/element/relay_attackers/proc/on_attack_npc(atom/target, mob/living/attacker) + SIGNAL_HANDLER + if(attacker.melee_damage_upper > 0) + relay_attacker(target, attacker) + +/datum/element/relay_attackers/proc/on_bullet_act(atom/target, obj/projectile/hit_projectile) + SIGNAL_HANDLER + if(hit_projectile.nodamage) + return + if(!ismob(hit_projectile.firer)) + return + relay_attacker(target, hit_projectile.firer) + +/datum/element/relay_attackers/proc/on_hitby(atom/target, atom/movable/hit_atom, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + SIGNAL_HANDLER + if(!isitem(hit_atom)) + return + var/obj/item/hit_item = hit_atom + if(!hit_item.throwforce) + return + var/mob/thrown_by = hit_item.thrownby?.resolve() + if(!ismob(thrown_by)) + return + relay_attacker(target, thrown_by) + +/datum/element/relay_attackers/proc/on_attack_hulk(atom/target, mob/attacker) + SIGNAL_HANDLER + relay_attacker(target, attacker) + +/datum/element/relay_attackers/proc/on_attack_mech(atom/target, obj/vehicle/sealed/mecha/mecha_attacker, mob/living/pilot) + SIGNAL_HANDLER + relay_attacker(target, mecha_attacker) + +/// Send out a signal identifying whoever just attacked us (usually a mob but sometimes a mech or turret) +/datum/element/relay_attackers/proc/relay_attacker(atom/victim, atom/attacker) + SEND_SIGNAL(victim, COMSIG_ATOM_WAS_ATTACKED, attacker) diff --git a/code/modules/mob/living/basic/farm_animals/pig.dm b/code/modules/mob/living/basic/farm_animals/pig.dm index 42bf1d546e8..8d1c4b323c4 100644 --- a/code/modules/mob/living/basic/farm_animals/pig.dm +++ b/code/modules/mob/living/basic/farm_animals/pig.dm @@ -21,6 +21,8 @@ attack_verb_simple = "kick" attack_sound = 'sound/weapons/punch1.ogg' attack_vis_effect = ATTACK_EFFECT_KICK + melee_damage_lower = 1 + melee_damage_upper = 2 health = 50 maxHealth = 50 gold_core_spawnable = FRIENDLY_SPAWN @@ -29,6 +31,7 @@ /mob/living/basic/pig/Initialize() AddElement(/datum/element/pet_bonus, "oinks!") + AddElement(/datum/element/ai_retaliate) make_tameable() . = ..() @@ -43,7 +46,21 @@ visible_message(span_notice("[src] snorts respectfully.")) /datum/ai_controller/basic_controller/pig + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction() + ) + ai_traits = STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance idle_behavior = /datum/idle_behavior/idle_random_walk - + + planning_subtrees = list( + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/basic_melee_attack_subtree/pig, + ) + +/datum/ai_planning_subtree/basic_melee_attack_subtree/pig + melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/pig + +/datum/ai_behavior/basic_melee_attack/pig + action_cooldown = 2 SECONDS diff --git a/code/modules/vehicles/mecha/mech_melee_attack.dm b/code/modules/vehicles/mecha/mech_melee_attack.dm index 7b1261b6f4a..08669ebdd0c 100644 --- a/code/modules/vehicles/mecha/mech_melee_attack.dm +++ b/code/modules/vehicles/mecha/mech_melee_attack.dm @@ -9,6 +9,7 @@ */ /atom/proc/mech_melee_attack(obj/vehicle/sealed/mecha/mecha_attacker, mob/living/user) SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_MECH, mecha_attacker, user) log_combat(user, src, "attacked", mecha_attacker, "(COMBAT MODE: [uppertext(user.combat_mode)] (DAMTYPE: [uppertext(mecha_attacker.damtype)])") return 0 diff --git a/tgstation.dme b/tgstation.dme index 0a6f0bfc715..43da1764229 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -721,6 +721,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm" #include "code\datums\ai\basic_mobs\targetting_datums\basic_targetting_datum.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" @@ -1025,6 +1026,7 @@ #include "code\datums\diseases\advance\symptoms\weight.dm" #include "code\datums\diseases\advance\symptoms\youth.dm" #include "code\datums\elements\_element.dm" +#include "code\datums\elements\ai_retaliate.dm" #include "code\datums\elements\animal_variety.dm" #include "code\datums\elements\art.dm" #include "code\datums\elements\atmos_requirements.dm" @@ -1096,6 +1098,7 @@ #include "code\datums\elements\radiation_protected_clothing.dm" #include "code\datums\elements\radioactive.dm" #include "code\datums\elements\ranged_attacks.dm" +#include "code\datums\elements\relay_attackers.dm" #include "code\datums\elements\ridable.dm" #include "code\datums\elements\rust.dm" #include "code\datums\elements\selfknockback.dm"