mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
## About The Pull Request It's just a partial cleanup of anti-[STYLE](https://github.com/tgstation/tgstation/blob/master/.github/guides/STYLE.md) code from /tg/'s ancient history. I compiled & tested with my helpful assistant and damage is still working. <img width="1920" height="1040" alt="image" src="https://github.com/user-attachments/assets/26dabc17-088f-4008-b299-3ff4c27142c3" /> I'll upload the .cs script I used to do it shortly. ## Why It's Good For The Game Just minor code cleanup. Script used is located at https://metek.tech/camelTo-Snake.7z EDIT 11/23/25: Updated the script to use multithreading and sequential scan so it works a hell of a lot faster ``` /* // Copyright 2025 Joshua 'Joan Metekillot' Kidder This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. // */ using System.Text.RegularExpressions; class Program { static async Task Main(string[] args) { var readFile = new FileStreamOptions { Access = FileAccess.Read, Share = FileShare.ReadWrite, Options = FileOptions.Asynchronous | FileOptions.SequentialScan }; FileStreamOptions writeFile = new FileStreamOptions { Share = FileShare.ReadWrite, Access = FileAccess.ReadWrite, Mode = FileMode.Truncate, Options = FileOptions.Asynchronous }; RegexOptions regexOptions = RegexOptions.Multiline | RegexOptions.Compiled; Dictionary<string, int> changedProcs = new(); string regexPattern = @"(?<=\P{L})([a-z]+)([A-Z]{1,2}[a-z]+)*(Brute|Burn|Fire|Tox|Oxy|Organ|Stamina)(Loss)([A-Z]{1,2}[a-z]+)*"; Regex camelCaseProcRegex = new(regexPattern, regexOptions); string snakeify(Match matchingRegex) { var vals = matchingRegex.Groups.Cast<Group>().SelectMany(_ => _.Captures).Select(_ => _.Value).ToArray(); var newVal = string.Join("_", vals.Skip(1).ToArray()).ToLower(); string logString = $"{vals[0]} => {newVal}"; if (changedProcs.TryGetValue(logString, out int value)) { changedProcs[logString] = value + 1; } else { changedProcs.Add(logString, 1); } return newVal; } var dmFiles = Directory.EnumerateFiles(".", "*.dm", SearchOption.AllDirectories).ToAsyncEnumerable<string>(); // uses default ParallelOptions // https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.paralleloptions?view=net-10.0#main await Parallel.ForEachAsync(dmFiles, async (filePath, UnusedCancellationToken) => { var reader = new StreamReader(filePath, readFile); string oldContent = await reader.ReadToEndAsync(); string newContent = camelCaseProcRegex.Replace(oldContent, new MatchEvaluator((Func<Match, string>)snakeify)); if (oldContent != newContent) { var writer = new StreamWriter(filePath, writeFile); await writer.WriteAsync(newContent); await writer.DisposeAsync(); } reader.Dispose(); }); var logToList = changedProcs.Cast<KeyValuePair<string, int>>().ToList(); foreach (var pair in logToList) { Console.WriteLine($"{pair.Key}: {pair.Value} locations"); } } } ``` ## Changelog 🆑 Bisar code: All (Brute|Burn|Fire|Tox|Oxy|Organ|Stamina)(Loss) procs now use snake_case, in-line with the STYLE guide. Underscores rule! /🆑
459 lines
16 KiB
Plaintext
459 lines
16 KiB
Plaintext
#define FORCED_SPEECH_COOLDOWN_DURATION 5 SECONDS
|
|
|
|
GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
|
|
/datum/strippable_item/parrot_headset,
|
|
)))
|
|
|
|
|
|
/// Parrots! Klepto bastards that imitate your speech and hoard your shit.
|
|
/mob/living/basic/parrot
|
|
name = "parrot"
|
|
desc = "The parrot squawks, \"They're a Parrot! BAWWK!\""
|
|
icon = 'icons/mob/simple/animal.dmi'
|
|
icon_state = "parrot_fly"
|
|
icon_living = "parrot_fly"
|
|
icon_dead = "parrot_dead"
|
|
density = FALSE
|
|
health = 80
|
|
maxHealth = 80
|
|
pass_flags = PASSTABLE | PASSMOB
|
|
|
|
guaranteed_butcher_results = list(/obj/item/food/cracker = 1)
|
|
melee_damage_upper = 10
|
|
melee_damage_lower = 5
|
|
|
|
response_help_continuous = "pets"
|
|
response_help_simple = "pet"
|
|
response_disarm_continuous = "gently moves aside"
|
|
response_disarm_simple = "gently move aside"
|
|
response_harm_continuous = "swats"
|
|
response_harm_simple = "swat"
|
|
combat_mode = TRUE //parrots now start "aggressive" since only player parrots will nuzzle.
|
|
attack_verb_continuous = "chomps"
|
|
attack_verb_simple = "chomp"
|
|
attack_vis_effect = ATTACK_EFFECT_BITE
|
|
friendly_verb_continuous = "grooms"
|
|
friendly_verb_simple = "groom"
|
|
mob_size = MOB_SIZE_SMALL
|
|
gold_core_spawnable = FRIENDLY_SPAWN
|
|
|
|
ai_controller = /datum/ai_controller/basic_controller/parrot
|
|
|
|
/// Icon we use while sitting
|
|
var/icon_sit = "parrot_sit"
|
|
|
|
///Headset for Poly to yell at engineers :)
|
|
var/obj/item/radio/headset/ears = null
|
|
|
|
///Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding.
|
|
var/obj/item/held_item = null
|
|
|
|
/// The blackboard key we use to store the string we're repeating
|
|
var/speech_blackboard_key = BB_PARROT_REPEAT_STRING
|
|
/// The generic probability odds we have to do a speech-related action
|
|
var/speech_probability_rate = 5
|
|
/// The generic probability odds we have to switch out our speech string
|
|
var/speech_shuffle_rate = 30
|
|
|
|
/// Contains all of the perches that parrots will generally sit on until something catches their eye.
|
|
var/static/list/desired_perches = typecacheof(list(
|
|
/obj/machinery/computer,
|
|
/obj/machinery/dna_scannernew,
|
|
/obj/machinery/nuclearbomb,
|
|
/obj/machinery/recharge_station,
|
|
/obj/machinery/smartfridge,
|
|
/obj/machinery/suit_storage_unit,
|
|
/obj/machinery/telecomms,
|
|
/obj/machinery/teleport,
|
|
/obj/structure/displaycase,
|
|
/obj/structure/filingcabinet,
|
|
/obj/structure/frame/computer,
|
|
))
|
|
///items we wont pick up
|
|
var/static/list/ignore_items = typecacheof(list(/obj/item/radio))
|
|
|
|
/// Food that Poly loves to eat (spoiler alert it's just crackers)
|
|
var/static/list/edibles = list(
|
|
/obj/item/food/cracker,
|
|
)
|
|
///list of commands we follow
|
|
var/static/list/pet_commands = list(
|
|
/datum/pet_command/idle,
|
|
/datum/pet_command/free,
|
|
/datum/pet_command/follow,
|
|
/datum/pet_command/perform_trick_sequence,
|
|
)
|
|
|
|
/// Tracks the times when we send a phrase through either being pet or attack to ensure it's not abused to flood
|
|
COOLDOWN_DECLARE(forced_speech_cooldown)
|
|
|
|
/mob/living/basic/parrot/Initialize(mapload)
|
|
. = ..()
|
|
setup_headset()
|
|
update_speech_blackboards()
|
|
ai_controller.set_blackboard_key(BB_PARROT_PERCH_TYPES, desired_perches)
|
|
ai_controller.set_blackboard_key(BB_IGNORE_ITEMS, ignore_items)
|
|
AddElement(/datum/element/pet_bonus) // parrots will listen for the signal this element sends in order to say something in response to the pat
|
|
AddElement(/datum/element/ai_retaliate)
|
|
AddElement(/datum/element/strippable, GLOB.strippable_parrot_items)
|
|
AddElement(/datum/element/simple_flying)
|
|
AddComponent(/datum/component/listen_and_repeat, desired_phrases = get_static_list_of_phrases(), blackboard_key = BB_PARROT_REPEAT_STRING)
|
|
AddComponent(/datum/component/tameable, food_types = edibles, tame_chance = 100, bonus_tame_chance = 0)
|
|
AddComponent(/datum/component/obeys_commands, pet_commands)
|
|
RegisterSignal(src, COMSIG_ATOM_ATTACKBY_SECONDARY, PROC_REF(on_attacked)) // this means we could have a peaceful interaction, like getting a cracker
|
|
RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_injured)) // this means we got hurt and it's go time
|
|
RegisterSignal(src, COMSIG_ANIMAL_PET, PROC_REF(on_pet))
|
|
RegisterSignal(src, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(drop_item_on_signal))
|
|
ADD_TRAIT(src, TRAIT_CAN_MOUNT_HUMANS, INNATE_TRAIT)
|
|
|
|
/mob/living/basic/parrot/Destroy()
|
|
// should have cleaned these up on death, but let's be super safe in case that didn't happen
|
|
if(!QDELETED(ears))
|
|
QDEL_NULL(ears)
|
|
if(!QDELETED(held_item))
|
|
QDEL_NULL(held_item)
|
|
return ..()
|
|
|
|
/mob/living/basic/parrot/death(gibbed)
|
|
if(held_item)
|
|
held_item.forceMove(drop_location())
|
|
held_item = null
|
|
|
|
if(ears)
|
|
ears.forceMove(drop_location())
|
|
ears = null
|
|
|
|
if(!isnull(buckled))
|
|
buckled.unbuckle_mob(src, force = TRUE)
|
|
buckled = null
|
|
pixel_x = base_pixel_x
|
|
pixel_y = base_pixel_y
|
|
|
|
return ..()
|
|
|
|
/mob/living/basic/parrot/examine(mob/user)
|
|
. = ..()
|
|
. += "It appears to [isnull(held_item) ? "not be holding anything." : "be holding \a [held_item]."]"
|
|
|
|
if(stat != DEAD)
|
|
return
|
|
|
|
if(HAS_MIND_TRAIT(user, TRAIT_NAIVE))
|
|
. += pick(
|
|
"It seems tired and shagged out after a long squawk.",
|
|
"It seems to be pining for the fjords.",
|
|
"It's resting. It's a beautiful bird. Lovely plumage.",
|
|
)
|
|
else
|
|
. += pick(
|
|
"This is a late parrot.",
|
|
"This is an ex-parrot.",
|
|
"This parrot is no more.",
|
|
)
|
|
|
|
/mob/living/basic/parrot/say_dead(message)
|
|
return // this is so flarped
|
|
|
|
/mob/living/basic/parrot/get_status_tab_items()
|
|
. = ..()
|
|
. += "Held Item: [held_item]"
|
|
|
|
/mob/living/basic/parrot/radio(message, list/message_mods = list(), list/spans, language) //literally copied from human/radio(), but there's no other way to do this. at least it's better than it used to be.
|
|
. = ..()
|
|
if(. != NONE)
|
|
return
|
|
|
|
if(message_mods[MODE_HEADSET])
|
|
if(ears)
|
|
ears.talk_into(src, message, , spans, language, message_mods)
|
|
return ITALICS | REDUCE_RANGE
|
|
else if(message_mods[RADIO_EXTENSION] == MODE_DEPARTMENT)
|
|
if(ears)
|
|
ears.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
|
|
return ITALICS | REDUCE_RANGE
|
|
else if(message_mods[RADIO_EXTENSION] in GLOB.default_radio_channels)
|
|
if(ears)
|
|
ears.talk_into(src, message, message_mods[RADIO_EXTENSION], spans, language, message_mods)
|
|
return ITALICS | REDUCE_RANGE
|
|
|
|
return NONE
|
|
|
|
/mob/living/basic/parrot/update_icon_state()
|
|
. = ..()
|
|
if(stat == DEAD)
|
|
return
|
|
icon_state = HAS_TRAIT(src, TRAIT_PARROT_PERCHED) ? icon_sit : icon_living
|
|
|
|
/// Proc that we just use to see if we're rightclicking something for perch behavior or dropping the item we currently ahve
|
|
/mob/living/basic/parrot/resolve_right_click_attack(atom/target, list/modifiers)
|
|
if(!start_perching(target))
|
|
return SECONDARY_ATTACK_CALL_NORMAL
|
|
if(!isnull(held_item))
|
|
drop_held_item(gently = TRUE)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/// Proc that handles sending the signal and returning a valid phrase to say. Will not do anything if we don't have a stat or if we're cliented.
|
|
/// Will return either a string or null.
|
|
/mob/living/basic/parrot/proc/get_phrase()
|
|
if(!isnull(client) || stat != CONSCIOUS)
|
|
return null
|
|
|
|
if(!COOLDOWN_FINISHED(src, forced_speech_cooldown))
|
|
return null
|
|
|
|
var/return_value = SEND_SIGNAL(src, COMSIG_NEEDS_NEW_PHRASE)
|
|
if(return_value & NO_NEW_PHRASE_AVAILABLE)
|
|
return null
|
|
|
|
COOLDOWN_START(src, forced_speech_cooldown, FORCED_SPEECH_COOLDOWN_DURATION)
|
|
return ai_controller.blackboard[BB_PARROT_REPEAT_STRING]
|
|
|
|
/// Proc that listens for when a parrot is pet so we can dispatch a voice line.
|
|
/mob/living/basic/parrot/proc/on_pet(mob/living/basic/source, mob/living/petter, modifiers)
|
|
SIGNAL_HANDLER
|
|
var/return_value = get_phrase()
|
|
if(isnull(return_value))
|
|
return
|
|
|
|
INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable, say), message = return_value, forced = "parrot oneliner on pet")
|
|
|
|
/// Proc that ascertains the type of perch we're dealing with and starts the perching process.
|
|
/// Returns TRUE if we started perching, FALSE otherwise.
|
|
/mob/living/basic/parrot/proc/start_perching(atom/target)
|
|
if(HAS_TRAIT(src, TRAIT_PARROT_PERCHED))
|
|
balloon_alert(src, "already perched!")
|
|
return FALSE
|
|
|
|
if(ishuman(target))
|
|
return perch_on_human(target)
|
|
|
|
if(!is_type_in_typecache(target, desired_perches))
|
|
return FALSE
|
|
|
|
forceMove(get_turf(target))
|
|
drop_held_item(gently = TRUE) // comfy :)
|
|
toggle_perched(perched = TRUE)
|
|
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(after_move))
|
|
return TRUE
|
|
|
|
/mob/living/basic/parrot/proc/after_move(atom/source)
|
|
SIGNAL_HANDLER
|
|
|
|
UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
|
|
toggle_perched(perched = FALSE)
|
|
|
|
/// Proc that will perch us on a human. Returns TRUE if we perched, FALSE otherwise.
|
|
/mob/living/basic/parrot/proc/perch_on_human(mob/living/carbon/human/target)
|
|
if(LAZYLEN(target.buckled_mobs) >= target.max_buckled_mobs)
|
|
balloon_alert(src, "can't perch on them!")
|
|
return FALSE
|
|
|
|
forceMove(get_turf(target))
|
|
if(!target.buckle_mob(src, TRUE))
|
|
return FALSE
|
|
|
|
to_chat(src, span_notice("You sit on [target]'s shoulder."))
|
|
toggle_perched(perched = TRUE)
|
|
RegisterSignal(src, COMSIG_LIVING_SET_BUCKLED, PROC_REF(on_unbuckle))
|
|
return TRUE
|
|
|
|
/mob/living/basic/parrot/proc/on_unbuckle(mob/living/source, atom/movable/new_buckled)
|
|
SIGNAL_HANDLER
|
|
|
|
if(new_buckled)
|
|
return
|
|
UnregisterSignal(src, COMSIG_LIVING_SET_BUCKLED)
|
|
toggle_perched(perched = FALSE)
|
|
|
|
#define PERCH_SOURCE "perched"
|
|
|
|
/mob/living/basic/parrot/proc/toggle_perched(perched)
|
|
if(!perched)
|
|
REMOVE_TRAIT(src, TRAIT_PARROT_PERCHED, PERCH_SOURCE)
|
|
remove_offsets(PERCH_SOURCE)
|
|
else
|
|
ADD_TRAIT(src, TRAIT_PARROT_PERCHED, PERCH_SOURCE)
|
|
add_offsets(PERCH_SOURCE, y_add = 9)
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
|
|
#undef PERCH_SOURCE
|
|
|
|
/// Master proc which will determine the intent of OUR attacks on an object and summon the relevant procs accordingly.
|
|
/// This is pretty much meant for players, AI will use the task-specific procs instead.
|
|
/mob/living/basic/parrot/early_melee_attack(atom/target, list/modifiers, ignore_cooldown)
|
|
. = ..()
|
|
if(!.)
|
|
return FALSE
|
|
|
|
if(isitem(target) && steal_from_ground(target))
|
|
return FALSE
|
|
|
|
if(iscarbon(target) && steal_from_mob(target))
|
|
return FALSE
|
|
|
|
/// Picks up an item from the ground and puts it in our claws. Returns TRUE if we picked it up, FALSE otherwise.
|
|
/mob/living/basic/parrot/proc/steal_from_ground(obj/item/target)
|
|
if(!isnull(held_item))
|
|
balloon_alert(src, "already holding something!")
|
|
return FALSE
|
|
|
|
if(target.w_class > WEIGHT_CLASS_SMALL)
|
|
balloon_alert(src, "too big to pick up!")
|
|
return FALSE
|
|
|
|
pick_up_item(target)
|
|
visible_message(
|
|
span_notice("[src] grabs [held_item]!"),
|
|
span_notice("You grab [held_item]!"),
|
|
span_hear("You hear the sounds of wings flapping furiously."),
|
|
)
|
|
return TRUE
|
|
|
|
/// Looks for an item that we can snatch and puts it in our claws. Returns TRUE if we picked it up, FALSE otherwise.
|
|
/mob/living/basic/parrot/proc/steal_from_mob(mob/living/carbon/victim)
|
|
if(!isnull(held_item))
|
|
balloon_alert(src, "already holding something!")
|
|
return FALSE
|
|
|
|
for(var/obj/item/stealable in victim.held_items)
|
|
if(stealable.w_class > WEIGHT_CLASS_SMALL)
|
|
continue
|
|
|
|
if(!victim.temporarilyRemoveItemFromInventory(stealable))
|
|
continue
|
|
|
|
visible_message(
|
|
span_notice("[src] grabs [held_item] out of [victim]'s hand!"),
|
|
span_notice("You snag [held_item] out of [victim]'s hand!"),
|
|
span_hear("You hear the sounds of wings flapping furiously."),
|
|
)
|
|
pick_up_item(stealable)
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
/// If we're right-clicked on with a cracker, we eat the cracker.
|
|
/mob/living/basic/parrot/proc/on_attacked(mob/living/basic/source, obj/item/thing, mob/living/attacker, list/modifiers)
|
|
SIGNAL_HANDLER
|
|
if(!istype(thing, /obj/item/food/cracker)) // Poly wants a cracker
|
|
return
|
|
|
|
consume_cracker(thing) // potential clash with the tameable element so we'll leave it to that to handle qdeling the cracker.
|
|
return COMPONENT_NO_AFTERATTACK
|
|
|
|
/// Eats a cracker (or anything i guess). This would be nice to eventually fold into the basic_eating element but we do too much snowflake inventory code stuff for this to be reliable presently.
|
|
/// We don't qdel the item here, we assume the invoking proc will have handled that somehow.
|
|
/// Returns TRUE if we ate the thing.
|
|
/mob/living/basic/parrot/proc/consume_cracker(obj/item/thing)
|
|
to_chat(src, span_notice("[src] eagerly devours \the [thing]."))
|
|
if(!istype(thing, /obj/item/food/cracker))
|
|
return TRUE // we still ate it
|
|
|
|
if(health < maxHealth)
|
|
adjust_brute_loss(-10)
|
|
speech_probability_rate *= 1.27
|
|
speech_shuffle_rate += 10
|
|
update_speech_blackboards()
|
|
return TRUE
|
|
|
|
/// Handles special behavior whenever we are injured.
|
|
/mob/living/basic/parrot/proc/on_injured(mob/living/basic/source, mob/living/attacker, attack_flags)
|
|
SIGNAL_HANDLER
|
|
if(!isnull(client) || stat == CONSCIOUS)
|
|
return
|
|
|
|
drop_held_item(gently = FALSE)
|
|
|
|
var/return_value = get_phrase()
|
|
if(isnull(return_value))
|
|
return
|
|
|
|
INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable, say), message = return_value, forced = "parrot oneliner on attack")
|
|
|
|
/// Handles picking up the item we're holding, done in its own proc because of a snowflake edge case we need to account for. No additional logic beyond that.
|
|
/// Returns TRUE if we picked it up, FALSE otherwise.
|
|
/mob/living/basic/parrot/proc/pick_up_item(obj/item/target)
|
|
if(istype(target, /obj/item/food/cracker))
|
|
consume_cracker(target)
|
|
qdel(target)
|
|
return
|
|
|
|
target.forceMove(src)
|
|
held_item = target
|
|
|
|
/// Handles dropping items we're holding. Gently is a special modifier we can use for special interactions.
|
|
/mob/living/basic/parrot/proc/drop_held_item(gently = TRUE)
|
|
if(isnull(held_item))
|
|
balloon_alert(src, "nothing to drop!")
|
|
return
|
|
|
|
if(stat != CONSCIOUS) // don't gotta do shit
|
|
return
|
|
|
|
if(!gently && isgrenade(held_item))
|
|
var/obj/item/grenade/bomb = held_item
|
|
balloon_alert(src, "bombs away!") // you'll likely die too so we can get away with the `!` here
|
|
bomb.forceMove(drop_location())
|
|
bomb.detonate()
|
|
return
|
|
|
|
balloon_alert(src, "dropped item")
|
|
held_item.forceMove(drop_location())
|
|
|
|
/mob/living/basic/parrot/Exited(atom/movable/gone, direction)
|
|
. = ..()
|
|
if(gone != held_item)
|
|
return
|
|
held_item = null
|
|
|
|
/mob/living/basic/parrot/vv_edit_var(var_name, vval)
|
|
. = ..() // give admins an easier time when it comes to fucking with poly
|
|
switch(var_name)
|
|
if(NAMEOF(src, speech_probability_rate))
|
|
update_speech_blackboards()
|
|
if(NAMEOF(src, speech_shuffle_rate))
|
|
update_speech_blackboards()
|
|
|
|
/// Updates our speech blackboards mob-side to reflect the current speech on the controller to ensure everything is synchronized.
|
|
/mob/living/basic/parrot/proc/update_speech_blackboards()
|
|
ai_controller.set_blackboard_key(BB_PARROT_REPEAT_PROBABILITY, speech_probability_rate)
|
|
ai_controller.set_blackboard_key(BB_PARROT_PHRASE_CHANGE_PROBABILITY, speech_shuffle_rate)
|
|
|
|
/// Will simply set up the headset for the parrot to use. Stub, implemented on subtypes.
|
|
/mob/living/basic/parrot/proc/setup_headset()
|
|
return
|
|
|
|
/// Gets a static list of phrases we wish to pass to the element.
|
|
/mob/living/basic/parrot/proc/get_static_list_of_phrases()
|
|
var/static/list/default_phrases = list(
|
|
"BAWWWWK george mellons griffing me!",
|
|
"Cracker?",
|
|
"Hello!",
|
|
"Hi!",
|
|
)
|
|
|
|
return default_phrases
|
|
|
|
/// Gets the available channels that this parrot has access to. Returns a list of the channels we can use.
|
|
/mob/living/basic/parrot/proc/get_available_channels()
|
|
var/list/returnable_list = list()
|
|
if(isnull(ears))
|
|
return returnable_list
|
|
|
|
var/list/headset_channels = ears.channels
|
|
for(var/channel in headset_channels)
|
|
returnable_list += GLOB.channel_tokens[channel] // will return something like ":e" or ":c" y'know
|
|
|
|
return returnable_list
|
|
|
|
/mob/living/basic/parrot/tamed(mob/living/tamer, atom/food)
|
|
new /obj/effect/temp_visual/heart(drop_location())
|
|
|
|
/mob/living/basic/parrot/proc/drop_item_on_signal(mob/living/user)
|
|
SIGNAL_HANDLER
|
|
|
|
drop_held_item()
|
|
return COMSIG_KB_ACTIVATED
|
|
|
|
#undef FORCED_SPEECH_COOLDOWN_DURATION
|