From c7e7cb494ffd5ebd6212d39f4866bfcc598bf5fa Mon Sep 17 00:00:00 2001 From: lessthanthree <83487515+lessthnthree@users.noreply.github.com> Date: Fri, 10 Feb 2023 12:22:23 -0800 Subject: [PATCH] [MANUAL MIRROR] Lazarus Injected mobs don't fight each other (#19072) * Dominate & Lazarus Inject basic mobs. Lazarus Injected mobs don't fight each other. (#72440) Fixes #72404 The Lazarus Injector doesn't currently work on basic mobs, but should. Problem: The EMPed state of the Lazarus Injector is intended to make a revived mob hostile to everyone except you, including other mobs you have revived wtih an EMPed Lazarus Injector. This is trivial to achieve for Simple Mobs which essentially all share the same AI, but I could not think of a single workable solution for Basic Mobs which don't, or at least any which didn't come with a tedious requirement to closely consider this niche item when programming any additional AI. Solution: Change the default behaviour of the Lazarus Injector so this is not a problem. If all it does it make the mob loyal to you _and_ friendly to other mobs which are loyal to you, then it's pretty easy because we can just use the existing faction flags. This is unambiguously a buff to using the item for nefarious purposes as now if you revive four ice drakes and fulton them onto the station they won't kill each other until only one is left, but is the only workable solution I could really think of. A lot of the very dangerous mining fauna can't be dragged so transporting your army to the station still poses a question. The alternate solution was just to replace the AI controller of any emp-revived basic mob with a "zombie" AI controller, but this has the problem that A- It would now make things like cows and dogs into hostile creatures when they previously weren't. B- It loses any interesting behaviour the mob previously had and for cases like Bileworms doesn't even make any sense (they'd try to walk and just get stuck in place). This ultimately leads to needing to make bespoke versions for various mobs, which doesn't seem desirable from a maintainability standpoint. As a side note it's still not a great idea to revive Bileworms _anyway_ as, their ability to move is tied to their ability to attack so once they don't have a target they will just kind of sit there and if they _do_ get a target their attempts to help you fight are difficult to distinguish from attempts to kill you... but at least being able to revive them makes it easier to make one sapient if you really want to trap a player's mind inside a body which is incapable of leaving lavaland. Additional edit: At Fikou's suggestion I've also added a sentience comparison proc to `mob/living` and removed some code duplication which dealt with this problem in the sentience/mind transfer potions, as well as added it to the Dominate spell. This device is meant to revive mobs and it shouldn't be required for players to memorise an arbitrary list of which mobs it does and doesn't work on. Especially as the goal is eventually that all simple mobs should be basic mobs. This way of working is more intuitive, even if it is also stronger. I was surprised when I used EMPed injectors and my "new minions" just killed each other. :cl: fix: You can now revive 'basic mobs' with a Lazarus Injector, such as dogs, cows, axolotls, or carp. fix: The same category of mobs can also now be effected by the Runic Golem Dominate spell. fix: Basic Mobs will switch target if they can no longer attack their current target; meaning that if you become a Bileworm's friend it will stop attacking you. balance: Mobs injected with the Lazarus Injector while it is EMPed will no longer attack other mobs revived by EMPed Lazarus Injectors. /:cl: * Update code/modules/research/xenobiology/xenobiology.dm * Update code/modules/research/xenobiology/xenobiology.dm --------- Co-authored-by: Jacquerel Co-authored-by: Tom <8881105+tf-4@users.noreply.github.com> --- .../basic_ai_behaviors/targetting.dm | 10 ++- .../basic_subtrees/simple_find_target.dm | 4 - .../mining/equipment/lazarus_injector.dm | 24 ++---- code/modules/mob/living/basic/basic.dm | 3 + code/modules/mob/living/living.dm | 15 ++++ .../living/simple_animal/hostile/hostile.dm | 7 ++ .../mob/living/simple_animal/simple_animal.dm | 3 + .../research/xenobiology/xenobiology.dm | 77 ++++++++----------- .../spells/spell_types/pointed/dominate.dm | 6 +- 9 files changed, 75 insertions(+), 74 deletions(-) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm index c52a52bb104..e23ba6ce54b 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targetting.dm @@ -7,14 +7,20 @@ /datum/ai_behavior/find_potential_targets/perform(delta_time, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key) . = ..() - var/list/potential_targets 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]") - potential_targets = hearers(vision_range, controller.pawn) - living_mob //Remove self, so we don't suicide + var/datum/weakref/weak_target = controller.blackboard[target_key] + var/atom/current_target = weak_target?.resolve() + if (targetting_datum.can_attack(living_mob, current_target)) + finish_action(controller, succeeded = TRUE) + return + + controller.blackboard[target_key] = null + var/list/potential_targets = hearers(vision_range, controller.pawn) - living_mob //Remove self, so we don't suicide for(var/HM in typecache_filter_list(range(vision_range, living_mob), hostile_machines)) //Can we see any hostile machines? if(can_see(living_mob, HM, vision_range)) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm index f6ad41d4e01..a4a6aea9b67 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm @@ -2,8 +2,4 @@ /datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, delta_time) . = ..() - var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/atom/target = weak_target?.resolve() - if(!QDELETED(target)) - return controller.queue_behavior(/datum/ai_behavior/find_potential_targets, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) diff --git a/code/modules/mining/equipment/lazarus_injector.dm b/code/modules/mining/equipment/lazarus_injector.dm index d6aa2abd765..2bcdab1032c 100644 --- a/code/modules/mining/equipment/lazarus_injector.dm +++ b/code/modules/mining/equipment/lazarus_injector.dm @@ -29,30 +29,16 @@ . = ..() if(!loaded || !(isliving(target) && proximity_flag) ) return - if(!isanimal(target)) - to_chat(user, span_info("[src] is only effective on lesser beings.")) - return - var/mob/living/simple_animal/target_animal = target - if(target_animal.sentience_type != revive_type) - to_chat(user, span_info("[src] does not work on this sort of creature.")) + var/mob/living/target_animal = target + if(!target_animal.compare_sentience_type(revive_type)) // Will also return false if not a basic or simple mob, which are the only two we want anyway + balloon_alert(user, "invalid creature!") return if(target_animal.stat != DEAD) - to_chat(user, span_info("[src] is only effective on the dead.")) + balloon_alert(user, "it's not dead!") return - target_animal.faction = list(FACTION_NEUTRAL) - target_animal.revive(HEAL_ALL) - if(ishostile(target)) - var/mob/living/simple_animal/hostile/target_hostile = target_animal - if(malfunctioning) - target_hostile.faction |= list("lazarus", "[REF(user)]") - target_hostile.robust_searching = TRUE - target_hostile.friends += user - target_hostile.attack_same = TRUE - user.log_message("has revived hostile mob [key_name(target)] with a malfunctioning lazarus injector.", LOG_GAME) - else - target_hostile.attack_same = FALSE + target_animal.lazarus_revive(user, malfunctioning) loaded = FALSE user.visible_message(span_notice("[user] injects [target_animal] with [src], reviving it.")) SSblackbox.record_feedback("tally", "lazarus_injector", 1, target_animal.type) diff --git a/code/modules/mob/living/basic/basic.dm b/code/modules/mob/living/basic/basic.dm index 9a2b1c210b7..f7a386e9ba9 100644 --- a/code/modules/mob/living/basic/basic.dm +++ b/code/modules/mob/living/basic/basic.dm @@ -202,3 +202,6 @@ . = ..() . += "Health: [round((health / maxHealth) * 100)]%" . += "Combat Mode: [combat_mode ? "On" : "Off"]" + +/mob/living/basic/compare_sentience_type(compare_type) + return sentience_type == compare_type diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 71b091e0bc4..25429252f96 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -2469,6 +2469,21 @@ GLOBAL_LIST_EMPTY(fire_appearances) . = ..() add_mood_event("gaming", /datum/mood_event/gaming) +/** + * Helper proc for basic and simple animals to return true if the passed sentience type matches theirs + * Living doesn't have a sentience type though so it always returns false if not a basic or simple mob + */ +/mob/living/proc/compare_sentience_type(compare_type) + return FALSE + +/// Proc called when targetted by a lazarus injector +/mob/living/proc/lazarus_revive(mob/living/reviver, malfunctioning) + revive(HEAL_ALL) + befriend(reviver) + faction = (malfunctioning) ? list("[REF(reviver)]") : list(FACTION_NEUTRAL) + if (malfunctioning) + reviver.log_message("has revived mob [key_name(src)] with a malfunctioning lazarus injector.", LOG_GAME) + /// Proc for giving a mob a new 'friend', generally used for AI control and targetting. Returns false if already friends. /mob/living/proc/befriend(mob/living/new_friend) SHOULD_CALL_PARENT(TRUE) diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index d19048c5b2d..262d2085752 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -672,3 +672,10 @@ return friends += new_friend faction = new_friend.faction.Copy() + +/mob/living/simple_animal/hostile/lazarus_revive(mob/living/reviver, malfunctioning) + . = ..() + if (malfunctioning) + robust_searching = TRUE // enables friends list check + return + robust_searching = initial(robust_searching) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 549ecb4a3e2..5eed9bbbce7 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -751,3 +751,6 @@ hunted = null COOLDOWN_START(src, emote_cooldown, 1 MINUTES) return + +/mob/living/simple_animal/compare_sentience_type(compare_type) + return sentience_type == compare_type diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index c12ab614751..33c8bd72027 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -689,46 +689,38 @@ var/sentience_type = SENTIENCE_ORGANIC /obj/item/slimepotion/slime/sentience/attack(mob/living/dumb_mob, mob/user) - if(being_used || !ismob(dumb_mob)) + if(being_used || !isliving(dumb_mob)) return - if((!isanimal(dumb_mob) && !isbasicmob(dumb_mob)) || dumb_mob.ckey) //only works on animals that aren't player controlled - to_chat(user, span_warning("[dumb_mob] is already too intelligent for this to work!")) + if(dumb_mob.ckey) //only works on animals that aren't player controlled + balloon_alert(user, "already sentient!") return if(dumb_mob.stat) - to_chat(user, span_warning("[dumb_mob] is dead!")) + balloon_alert(user, "it's dead!") + return + if(!dumb_mob.compare_sentience_type(sentience_type)) // Will also return false if not a basic or simple mob, which are the only two we want anyway + balloon_alert(user, "invalid creature!") return - if(isanimal(dumb_mob)) - var/mob/living/simple_animal/dumb_animal = dumb_mob - if(dumb_animal.sentience_type != sentience_type) - to_chat(user, span_warning("[src] won't work on [dumb_animal].")) - return - else if(isbasicmob(dumb_mob)) //duplicate shit code until all simple animasls are made into basic mobs. sentience_type is not on living, but it duplicated on basic and animal - var/mob/living/basic/basic_dumb_bitch = dumb_mob - if(basic_dumb_bitch.sentience_type != sentience_type) - to_chat(user, span_warning("[src] won't work on [basic_dumb_bitch].")) - return - to_chat(user, span_notice("You offer [src] to [dumb_mob]...")) + balloon_alert(user, "offering...") being_used = TRUE var/list/candidates = poll_candidates_for_mob("Do you want to play as [dumb_mob.name]?", ROLE_SENTIENCE, ROLE_SENTIENCE, 5 SECONDS, dumb_mob, POLL_IGNORE_SENTIENCE_POTION) // see poll_ignore.dm - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - dumb_mob.key = C.key - dumb_mob.mind.enslave_mind_to_creator(user) - SEND_SIGNAL(dumb_mob, COMSIG_SIMPLEMOB_SENTIENCEPOTION, user) - if(isanimal(dumb_mob)) - var/mob/living/simple_animal/smart_animal = dumb_mob - smart_animal.sentience_act() - dumb_mob.mind.add_antag_datum(/datum/antagonist/sentient_creature) - to_chat(user, span_notice("[dumb_mob] accepts [src] and suddenly becomes attentive and aware. It worked!")) - dumb_mob.copy_languages(user, LANGUAGE_MASTER) - after_success(user, dumb_mob) - qdel(src) - else - to_chat(user, span_notice("[dumb_mob] looks interested for a moment, but then looks back down. Maybe you should try again later.")) + if(!LAZYLEN(candidates)) + balloon_alert(user, "try again later!") being_used = FALSE - ..() + return ..() + + var/mob/dead/observer/C = pick(candidates) + dumb_mob.key = C.key + dumb_mob.mind.enslave_mind_to_creator(user) + SEND_SIGNAL(dumb_mob, COMSIG_SIMPLEMOB_SENTIENCEPOTION, user) + if(isanimal(dumb_mob)) + var/mob/living/simple_animal/smart_animal = dumb_mob + smart_animal.sentience_act() + dumb_mob.mind.add_antag_datum(/datum/antagonist/sentient_creature) + balloon_alert(user, "success") + after_success(user, dumb_mob) + qdel(src) /obj/item/slimepotion/slime/sentience/proc/after_success(mob/living/user, mob/living/smart_mob) return @@ -753,31 +745,24 @@ /obj/item/slimepotion/transference/afterattack(mob/living/switchy_mob, mob/living/user, proximity) if(!proximity) return - if(prompted || !ismob(switchy_mob)) + if(prompted || !isliving(switchy_mob)) return - if(!(isanimal(switchy_mob) || isbasicmob(switchy_mob)) || switchy_mob.ckey) //much like sentience, these will not work on something that is already player controlled - to_chat(user, span_warning("[switchy_mob] already has a higher consciousness!")) + if(switchy_mob.ckey) //much like sentience, these will not work on something that is already player controlled + balloon_alert(user, "already sentient!") return ..() if(switchy_mob.stat) - to_chat(user, span_warning("[switchy_mob] is dead!")) + balloon_alert(user, "it's dead!") + return ..() + if(!switchy_mob.compare_sentience_type(animal_type)) + balloon_alert(user, "invalid creature!") return ..() - if(isanimal(switchy_mob)) - var/mob/living/simple_animal/switchy_animal= switchy_mob - if(switchy_animal.sentience_type != animal_type) - to_chat(user, span_warning("You cannot transfer your consciousness to [switchy_animal].") ) - return ..() - else //ugly code duplication, but necccesary as sentience_type is implemented twice. - var/mob/living/basic/basic_mob = switchy_mob - if(basic_mob.sentience_type != animal_type) - to_chat(user, span_warning("You cannot transfer your consciousness to [basic_mob].") ) - return ..() var/job_banned = is_banned_from(user.ckey, ROLE_MIND_TRANSFER) if(QDELETED(src) || QDELETED(switchy_mob) || QDELETED(user)) return if(job_banned) - to_chat(user, span_warning("Your mind goes blank as you attempt to use the potion.")) + balloon_alert(user, "you're banned!") return prompted = 1 diff --git a/code/modules/spells/spell_types/pointed/dominate.dm b/code/modules/spells/spell_types/pointed/dominate.dm index 7d2c587a222..682d1559ff8 100644 --- a/code/modules/spells/spell_types/pointed/dominate.dm +++ b/code/modules/spells/spell_types/pointed/dominate.dm @@ -20,15 +20,15 @@ active_msg = "You prepare to dominate the mind of a target..." /datum/action/cooldown/spell/pointed/dominate/is_valid_target(atom/cast_on) - if(!isanimal(cast_on)) + if(!isliving(cast_on)) return FALSE - var/mob/living/simple_animal/animal = cast_on + var/mob/living/animal = cast_on if(animal.mind) return FALSE if(animal.stat == DEAD) return FALSE - if(animal.sentience_type != SENTIENCE_ORGANIC) + if(!animal.compare_sentience_type(SENTIENCE_ORGANIC)) // Will also return false if not a basic or simple mob, which are the only two we want anyway return FALSE if("cult" in animal.faction) return FALSE