mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 18:22:14 +00:00
## About The Pull Request Since #90505 added another entry to it the Regal Rat Riot ability, which turns maintenance creatures into versions loyal to the rat, has become sort of unmanageable (and to be honest it was a bit gross to start with). Instead of having a big if/else list (which was making the same range check multiple times...) that sets stats on a bunch of mobs, I delegated it to the mobs themselves and instead of changing some stats of the existing mobs we just turn them into a new mob which can be spawned or placed separately by mappers or admins if they want. Other stuff I changed: Riot (the ability which transforms mobs into minions) no longer spawns a mouse if it fails to find anything. Instead you have a chance to fish mice out of disposals bins while digging out trash and items. Domain is now a toggle which activates itself every 6 seconds rather than a button you manually click every 6 seconds. Riot makes a visual effect when used. Rare Pepe randomisation is done via a random spawner instead of the mob modifying a bunch of its own properties in Initialise. A bunch of mobs now automatically follow you after being tamed. I wrote this assuming I was going to add it to the rioted mobs but then didn't end up doing that because you might want them to immediately attack someone. My rule of thumb is that if I think you'd want the mob to attack someone the moment it is befriended I didn't add this and if you wouldn't I did. I changed some of the regal rat minion names, and some of them can now spawn from gold slime which couldn't before. ## Why It's Good For The Game This proc sucked and now it's nicer. As for the other changes; - A tamed mob immediately following you is nice feedback and saves you a click as it's likely to be your first action. Also removes some admin panel shitcode I added. - I changed Domain to a toggle because you generally want to use it on cooldown and someone suggested it on this PR and it sounded like a good idea. - I saw someone in Discord complaining that the previous flow of recruiting rats by hitting Riot with nothing around to summon one, waiting, hitting it again to convert one rat, and waiting again was tedious and annoying which I agree with. This method improves the quality of life by separating these two actions but _also_ as a side effect reduces a regal rat's ability to secretly stockpile 50 rats in a hidden maintenance room because most disposal bins are in slightly more visible areas, they'll actually need to go and make a mess somewhere someone can see them. ## Changelog 🆑 balance: Regal Rats can now grab mice out of disposal bins, and no longer spawn them with the Riot ability. balance: The Riot ability no longer needs to be used once for each slightly different kind of mob in your radius. balance: The Regal Rat Domain ability is now toggled on and off. balance: Several kinds of mob will immediately start following you once tamed. balance: Rats, hostile frogs, and evil snails can be created via gold slime reaction. /🆑
352 lines
14 KiB
Plaintext
352 lines
14 KiB
Plaintext
// None of these are really complex enough to merit their own file
|
|
|
|
/**
|
|
* # Pet Command: Idle
|
|
* Tells a pet to resume its idle behaviour, usually staying put where you leave it
|
|
*/
|
|
/datum/pet_command/idle
|
|
command_name = "Stay"
|
|
command_desc = "Command your pet to stay idle in this location."
|
|
radial_icon_state = "halt"
|
|
speech_commands = list("sit", "stay", "stop")
|
|
command_feedback = "sits"
|
|
|
|
/datum/pet_command/idle/execute_action(datum/ai_controller/controller)
|
|
return SUBTREE_RETURN_FINISH_PLANNING // This cancels further AI planning
|
|
|
|
/datum/pet_command/idle/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to stay idle!"
|
|
|
|
/**
|
|
* # Pet Command: Stop
|
|
* Tells a pet to exit command mode and resume its normal behaviour, which includes regular target-seeking and what have you
|
|
*/
|
|
/datum/pet_command/free
|
|
command_name = "Loose"
|
|
command_desc = "Allow your pet to resume its natural behaviours."
|
|
radial_icon_state = "free"
|
|
speech_commands = list("free", "loose")
|
|
command_feedback = "relaxes"
|
|
|
|
/datum/pet_command/free/execute_action(datum/ai_controller/controller)
|
|
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
|
|
return // Just move on to the next planning subtree.
|
|
|
|
/datum/pet_command/free/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to go free!"
|
|
|
|
/**
|
|
* # Pet Command: Follow
|
|
* Tells a pet to follow you until you tell it to do something else
|
|
*/
|
|
/datum/pet_command/follow
|
|
command_name = "Follow"
|
|
command_desc = "Command your pet to accompany you."
|
|
radial_icon_state = "follow"
|
|
speech_commands = list("heel", "follow")
|
|
callout_type = /datum/callout_option/move
|
|
///the behavior we use to follow
|
|
var/follow_behavior = /datum/ai_behavior/pet_follow_friend
|
|
///should we activate immediately if we're doing nothing else and gain a friend?
|
|
var/activate_on_befriend = FALSE
|
|
|
|
/datum/pet_command/follow/set_command_active(mob/living/parent, mob/living/commander)
|
|
. = ..()
|
|
set_command_target(parent, commander)
|
|
|
|
/datum/pet_command/follow/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to follow!"
|
|
|
|
/datum/pet_command/follow/execute_action(datum/ai_controller/controller)
|
|
controller.queue_behavior(follow_behavior, BB_CURRENT_PET_TARGET)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/follow/add_new_friend(mob/living/tamer)
|
|
. = ..()
|
|
var/mob/living/parent = weak_parent.resolve()
|
|
if (!parent)
|
|
return
|
|
if (activate_on_befriend && !parent.ai_controller.blackboard_key_exists(BB_ACTIVE_PET_COMMAND))
|
|
try_activate_command(tamer)
|
|
|
|
/// Like follow but start active
|
|
/datum/pet_command/follow/start_active
|
|
activate_on_befriend = TRUE
|
|
|
|
/**
|
|
* # Pet Command: Play Dead
|
|
* Pretend to be dead for a random period of time
|
|
*/
|
|
/datum/pet_command/play_dead
|
|
command_name = "Play Dead"
|
|
command_desc = "Play a macabre trick."
|
|
radial_icon_state = "play_dead"
|
|
speech_commands = list("play dead") // Don't get too creative here, people talk about dying pretty often
|
|
|
|
/datum/pet_command/play_dead/execute_action(datum/ai_controller/controller)
|
|
controller.queue_behavior(/datum/ai_behavior/play_dead)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/play_dead/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to play dead!"
|
|
|
|
/**
|
|
* # Pet Command: Good Boy
|
|
* React if complimented
|
|
*/
|
|
/datum/pet_command/good_boy
|
|
command_name = "Good Boy"
|
|
command_desc = "Give your pet a compliment."
|
|
hidden = TRUE
|
|
|
|
/datum/pet_command/good_boy/New(mob/living/parent)
|
|
. = ..()
|
|
speech_commands += "good [parent.name]"
|
|
switch (parent.gender)
|
|
if (MALE)
|
|
speech_commands += "good boy"
|
|
return
|
|
if (FEMALE)
|
|
speech_commands += "good girl"
|
|
return
|
|
// If we get past this point someone has finally added a non-binary dog
|
|
|
|
/datum/pet_command/good_boy/execute_action(datum/ai_controller/controller)
|
|
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
|
|
var/mob/living/parent = weak_parent.resolve()
|
|
if (!parent)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
new /obj/effect/temp_visual/heart(parent.loc)
|
|
parent.emote("spin")
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/**
|
|
* # Pet Command: Use ability
|
|
* Use an an ability that does not require any targets
|
|
*/
|
|
/datum/pet_command/untargeted_ability
|
|
///untargeted ability we will use
|
|
var/ability_key
|
|
|
|
/datum/pet_command/untargeted_ability/execute_action(datum/ai_controller/controller)
|
|
var/datum/action/cooldown/ability = controller.blackboard[ability_key]
|
|
if(!ability?.IsAvailable())
|
|
return
|
|
controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key)
|
|
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/untargeted_ability/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to use an ability!"
|
|
|
|
/**
|
|
* # Pet Command: Attack
|
|
* Tells a pet to chase and bite the next thing you point at
|
|
*/
|
|
/datum/pet_command/attack
|
|
command_name = "Attack"
|
|
command_desc = "Command your pet to attack things that you point out to it."
|
|
radial_icon_state = "attack"
|
|
requires_pointing = TRUE
|
|
callout_type = /datum/callout_option/attack
|
|
speech_commands = list("attack", "sic", "kill")
|
|
command_feedback = "growl"
|
|
pointed_reaction = "and growls"
|
|
/// Balloon alert to display if providing an invalid target
|
|
var/refuse_reaction = "shakes head"
|
|
/// Attack behaviour to use
|
|
var/attack_behaviour = /datum/ai_behavior/basic_melee_attack
|
|
|
|
// Refuse to target things we can't target, chiefly other friends
|
|
/datum/pet_command/attack/set_command_target(mob/living/parent, atom/target)
|
|
if (!target)
|
|
return FALSE
|
|
var/mob/living/living_parent = parent
|
|
if (!living_parent.ai_controller)
|
|
return FALSE
|
|
var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(living_parent.ai_controller.blackboard[targeting_strategy_key])
|
|
if (!targeter)
|
|
return FALSE
|
|
if (!targeter.can_attack(living_parent, target))
|
|
refuse_target(parent, target)
|
|
return FALSE
|
|
return ..()
|
|
|
|
/datum/pet_command/attack/retrieve_command_text(atom/living_pet, atom/target)
|
|
return isnull(target) ? null : "signals [living_pet] to attack [target]!"
|
|
|
|
/// Display feedback about not targeting something
|
|
/datum/pet_command/attack/proc/refuse_target(mob/living/parent, atom/target)
|
|
var/mob/living/living_parent = parent
|
|
living_parent.balloon_alert_to_viewers("[refuse_reaction]")
|
|
living_parent.visible_message(span_notice("[living_parent] refuses to attack [target]."))
|
|
|
|
/datum/pet_command/attack/execute_action(datum/ai_controller/controller)
|
|
controller.queue_behavior(attack_behaviour, BB_CURRENT_PET_TARGET, targeting_strategy_key)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/**
|
|
* # Breed command. breed with a partner!
|
|
*/
|
|
/datum/pet_command/breed
|
|
command_name = "Breed"
|
|
command_desc = "Command your pet to attempt to breed with a partner."
|
|
requires_pointing = TRUE
|
|
radial_icon_state = "breed"
|
|
speech_commands = list("breed", "consummate")
|
|
///the behavior we use to make babies
|
|
var/datum/ai_behavior/reproduce_behavior = /datum/ai_behavior/make_babies
|
|
|
|
/datum/pet_command/breed/set_command_target(mob/living/parent, atom/target)
|
|
if(isnull(target) || !isliving(target))
|
|
return FALSE
|
|
if(!HAS_TRAIT(parent, TRAIT_MOB_BREEDER) || !HAS_TRAIT(target, TRAIT_MOB_BREEDER))
|
|
return FALSE
|
|
if(isnull(parent.ai_controller))
|
|
return FALSE
|
|
if(!parent.ai_controller.blackboard[BB_BREED_READY] || isnull(parent.ai_controller.blackboard[BB_BABIES_PARTNER_TYPES]))
|
|
return FALSE
|
|
var/mob/living/living_target = target
|
|
if(!living_target.ai_controller?.blackboard[BB_BREED_READY])
|
|
return FALSE
|
|
return ..()
|
|
|
|
/datum/pet_command/breed/execute_action(datum/ai_controller/controller)
|
|
if(is_type_in_list(controller.blackboard[BB_CURRENT_PET_TARGET], controller.blackboard[BB_BABIES_PARTNER_TYPES]))
|
|
controller.queue_behavior(reproduce_behavior, BB_CURRENT_PET_TARGET)
|
|
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/breed/retrieve_command_text(atom/living_pet, atom/target)
|
|
return isnull(target) ? null : "signals [living_pet] to breed with [target]!"
|
|
|
|
/**
|
|
* # Pet Command: Targetted Ability
|
|
* Tells a pet to use some kind of ability on the next thing you point at
|
|
*/
|
|
/datum/pet_command/use_ability
|
|
command_name = "Use ability"
|
|
command_desc = "Command your pet to use one of its special skills on something that you point out to it."
|
|
radial_icon = 'icons/mob/actions/actions_spells.dmi'
|
|
radial_icon_state = "projectile"
|
|
requires_pointing = TRUE
|
|
speech_commands = list("shoot", "blast", "cast")
|
|
command_feedback = "growl"
|
|
pointed_reaction = "and growls"
|
|
/// Blackboard key where a reference to some kind of mob ability is stored
|
|
var/pet_ability_key
|
|
/// The AI behavior to use for the ability
|
|
var/ability_behavior = /datum/ai_behavior/pet_use_ability
|
|
|
|
/datum/pet_command/use_ability/execute_action(datum/ai_controller/controller)
|
|
if (!pet_ability_key)
|
|
return
|
|
var/datum/action/cooldown/using_action = controller.blackboard[pet_ability_key]
|
|
if (QDELETED(using_action))
|
|
return
|
|
// We don't check if the target exists because we want to 'sit attentively' if we've been instructed to attack but not given one yet
|
|
// We also don't check if the cooldown is over because there's no way a pet owner can know that, the behaviour will handle it
|
|
controller.queue_behavior(ability_behavior, pet_ability_key, BB_CURRENT_PET_TARGET)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/use_ability/retrieve_command_text(atom/living_pet, atom/target)
|
|
return isnull(target) ? null : "signals [living_pet] to use an ability on [target]!"
|
|
|
|
/datum/pet_command/protect_owner
|
|
command_name = "Protect owner"
|
|
command_desc = "Your pet will run to your aid."
|
|
hidden = TRUE
|
|
callout_type = /datum/callout_option/guard
|
|
///the range our owner needs to be in for us to protect him
|
|
var/protect_range = 9
|
|
///the behavior we will use when he is attacked
|
|
var/protect_behavior = /datum/ai_behavior/basic_melee_attack
|
|
///message cooldown to prevent too many people from telling you not to commit suicide
|
|
COOLDOWN_DECLARE(self_harm_message_cooldown)
|
|
|
|
/datum/pet_command/protect_owner/add_new_friend(mob/living/tamer)
|
|
RegisterSignal(tamer, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(set_attacking_target))
|
|
if(!HAS_TRAIT(tamer, TRAIT_RELAYING_ATTACKER))
|
|
tamer.AddElement(/datum/element/relay_attackers)
|
|
|
|
/datum/pet_command/protect_owner/remove_friend(mob/living/unfriended)
|
|
UnregisterSignal(unfriended, COMSIG_ATOM_WAS_ATTACKED)
|
|
|
|
/datum/pet_command/protect_owner/execute_action(datum/ai_controller/controller)
|
|
var/mob/living/victim = controller.blackboard[BB_CURRENT_PET_TARGET]
|
|
if(QDELETED(victim))
|
|
return
|
|
// cancel the action if they're below our given crit stat, OR if we're trying to attack ourselves (this can happen on tamed mobs w/ protect subtree rarely)
|
|
if(victim.stat > controller.blackboard[BB_TARGET_MINIMUM_STAT] || victim == controller.pawn)
|
|
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
|
|
return
|
|
controller.queue_behavior(protect_behavior, BB_CURRENT_PET_TARGET, BB_PET_TARGETING_STRATEGY)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/protect_owner/set_command_active(mob/living/parent, mob/living/victim)
|
|
. = ..()
|
|
set_command_target(parent, victim)
|
|
|
|
/datum/pet_command/protect_owner/valid_callout_target(mob/living/speaker, datum/callout_option/callout, atom/target)
|
|
return target == speaker || get_dist(speaker, target) <= 1
|
|
|
|
/datum/pet_command/protect_owner/proc/set_attacking_target(atom/source, mob/living/attacker)
|
|
SIGNAL_HANDLER
|
|
|
|
var/mob/living/basic/owner = weak_parent.resolve()
|
|
if(isnull(owner))
|
|
return
|
|
if(source == attacker)
|
|
var/list/interventions = owner.ai_controller?.blackboard[BB_OWNER_SELF_HARM_RESPONSES] || list()
|
|
if (length(interventions) && COOLDOWN_FINISHED(src, self_harm_message_cooldown) && prob(30))
|
|
COOLDOWN_START(src, self_harm_message_cooldown, 5 SECONDS)
|
|
var/chosen_statement = pick(interventions)
|
|
INVOKE_ASYNC(owner, TYPE_PROC_REF(/atom/movable, say), chosen_statement)
|
|
return
|
|
var/mob/living/current_target = owner.ai_controller?.blackboard[BB_CURRENT_PET_TARGET]
|
|
if(attacker == current_target) //we are already dealing with this target
|
|
return
|
|
if(isliving(attacker) && can_see(owner, attacker, protect_range))
|
|
set_command_active(owner, attacker)
|
|
|
|
/**
|
|
* # Fish command: command the mob to fish at the next fishing spot you point at. Requires the profound fisher component
|
|
*/
|
|
/datum/pet_command/fish
|
|
command_name = "Fish"
|
|
command_desc = "Command your pet to try fishing at a nearby fishing spot."
|
|
requires_pointing = TRUE
|
|
radial_icon_state = "fish"
|
|
speech_commands = list("fish")
|
|
|
|
/datum/pet_command/fish/execute_action(datum/ai_controller/controller)
|
|
if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET))
|
|
controller.queue_behavior(/datum/ai_behavior/interact_with_target/fishing, BB_CURRENT_PET_TARGET)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/fish/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to go fish!"
|
|
|
|
/datum/pet_command/move
|
|
command_name = "Move"
|
|
command_desc = "Command your pet to move to a location!"
|
|
requires_pointing = TRUE
|
|
radial_icon_state = "move"
|
|
speech_commands = list("move", "walk")
|
|
///the behavior we use to walk towards targets
|
|
var/datum/ai_behavior/walk_behavior = /datum/ai_behavior/travel_towards
|
|
|
|
/datum/pet_command/move/set_command_target(mob/living/parent, atom/target)
|
|
if(isnull(target) || !can_see(parent, target, 9))
|
|
return FALSE
|
|
return ..()
|
|
|
|
/datum/pet_command/move/execute_action(datum/ai_controller/controller)
|
|
if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET))
|
|
controller.queue_behavior(walk_behavior, BB_CURRENT_PET_TARGET)
|
|
return SUBTREE_RETURN_FINISH_PLANNING
|
|
|
|
/datum/pet_command/move/retrieve_command_text(atom/living_pet, atom/target)
|
|
return "signals [living_pet] to move!"
|