Files
df7832aa43 Replaced our NPC AI with Behavior Trees. (#96628)
This PR replaces our current NPC AI with a [behavior tree
system](https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control)).
Behavior trees are a common way of creating AI in which you place nodes
in a tree structure to define what actions an AI should take.

AI controllers defined a list of /datum/ai_planning_subtree types in
behavior_nodes. Each subtree was a self-contained unit that could call
queue_behavior() to fire off /datum/ai_behavior actions. The controller
iterated subtrees in order, each one deciding independently whether to
queue something and deciding whether the next subtree would run.

This has a few issues:
1. There's no real structure; you are just defining a list of things to
try in order.
2. There was a loooot of subtrees that were basically the same as
another but with some slight modification
3. It was hard to understand.

Controllers now define a single json file describing a tree of nodes.
The tree is composed of structural composites:

Sequence - do A, then B, then C (and so on)
Selector - try A, if it fails try B, then C (and so on)
Parallel - run A and B simultaneously, with configurable failure/success
policies and or looping behavior
Subplan - loop a child continiously

Along that we also have "Decorators". These are nodes that basically
check a condition (E.g.; do we have a combat target). These decorators
can be used to gate behavior and are re-useable across behavior trees.
They also have a concept known as "Observers". Which lets them cancel
lower priority behavior in case their condition changes (Which we check
whenever a signal fires that fits that specific decorator). This makes
the AI much more responsive to change in environment.

For behaviors, we still use the ai_behavior datums. These are the actual
behaviors such as "Move to X", "Attack X". The only major change is that
these can no longer sleep() since they now run in the ai_controller.

Lastly, we now also have subtrees, except now they are essentially
pieces of behavior tree that can be re-used, or even overriden at
runtime or as a variable. Allowing for making modular AI made out of
several smaller trees.

You can set variables on these nodes directly via the extension (see
below), which should reduce the need to make subtypes of behaviors by a
lot. All of these vars are saved on the JSON and will be applied at
runtime.

If you are using subtrees, you can also assign "bindings" to these
variables, which will allow instances of the subtree to override those
variables.

Since a tree structure with variables becomes hard to parse in a JSON,
I've made a VSCode extension to edit these JSONs:

https://marketplace.visualstudio.com/items?itemName=BehaviorTreeG.behaviortreeg
https://github.com/CabinetOnFire/BehaviorTreeG

<img width="1795" height="1268" alt="image"
src="https://github.com/user-attachments/assets/56aa2f0b-3cf9-449f-bca4-8281fca82db6"
/>

This extension allows you to edit the behavior tree JSONs, and browse
through all the behaviors/decorators/subtrees we have

If you'd like more info on how to build these AI check out the
learn_ai.md. I will also make a tutorial to go over more depth on what
the system offers because I kind of suck at doing technical write-ups.

Targetting has been changed to. I've made a new acquire_targets behavior
that takes a target_source (what am I targetting) and
targetting_strategy (what does the candidate need to fulfill to be
considered a target). This allows us to make composites targetting
combinations to reduce the amount of specific find_and_set esque
behaviors we had before. Not everything is ported to this system but
that would be a longer term goal.

I've added a new build_bt script that converts all the behavior tree
JSONs into compiled versions. Why is this needed? Because I wanted to
keep using defines in behavior trees, so we need a way to convert this
into literal values before we send it to DM. This script runs on compile
and should also run in CI (If I didn't fuck that up!). This saves to a
new build/ folder.

I've ported every single AI in the game to this system (except raptors,
Kobsa is working on those so should be in soon!), so I do expect some
bugs to come out of this. But I also fixed some issues that have
probably been in the game for a long time such as:
- Fixed penguins being unable to fish
- Fixed bileworms not being able to devour people
- Fixes goldgrubs not grubbing gold (they could not mine!)
- Lizards actually eat food they find

Either way, I'd reccomend a long TM on this.

1. (Hopefully) a better development experience for making AI
2. Less copy-paste for behaviors, we should be able to re-use more
pieces to make behavior
3. Behavior trees is a more common pattern in making AI, so it should be
easier to find resources to find out how to do things.

🆑 CabinetOnFire, Iamgoofball, SmartKar, Ben10omintrix
refactor: Replaces our AI system with behavior trees, porting all
datum/ai to it
/🆑

I will add this PR with more details down the line. I think I got the
big picture but its a big PR, so sorry if I missed something important.

---------

Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
2026-08-15 10:33:57 -06:00

417 lines
16 KiB
Plaintext

// AI defines
#define DEFAULT_AI_LAWID "default"
#define LAW_ZEROTH "zeroth"
#define LAW_INHERENT "inherent"
#define LAW_SUPPLIED "supplied"
#define LAW_ION "ion"
#define LAW_HACKED "hacked"
//AI notification defines
///Alert when a new Cyborg is created.
#define AI_NOTIFICATION_NEW_BORG 1
///Alert when a Cyborg selects a model.
#define AI_NOTIFICATION_NEW_MODEL 2
///Alert when a Cyborg changes their name.
#define AI_NOTIFICATION_CYBORG_RENAMED 3
///Alert when an AI disconnects themselves from their shell.
#define AI_NOTIFICATION_AI_SHELL 4
///Alert when a Cyborg gets disconnected from their AI.
#define AI_NOTIFICATION_CYBORG_DISCONNECTED 5
//transfer_ai() defines. Main proc in ai_core.dm
///Downloading AI to InteliCard
#define AI_TRANS_TO_CARD 1
///Uploading AI from InteliCard
#define AI_TRANS_FROM_CARD 2
///Malfunctioning AI hijacking mecha
#define AI_MECH_HACK 3
// Cyborg defines
/// Special value to reset cyborg's lamp_cooldown
#define BORG_LAMP_CD_RESET -1
/// How many watts per lamp power is consumed while the lamp is on.
#define BORG_LAMP_POWER_CONSUMPTION (5 WATTS)
/// The minimum power consumption of a cyborg.
#define BORG_MINIMUM_POWER_CONSUMPTION (1 WATTS)
//Module slot define
///The third module slots is disabed.
#define BORG_MODULE_THREE_DISABLED (1<<0)
///The second module slots is disabed.
#define BORG_MODULE_TWO_DISABLED (1<<1)
///All modules slots are disabled.
#define BORG_MODULE_ALL_DISABLED (1<<2)
//Cyborg module selection
///First Borg module slot.
#define BORG_CHOOSE_MODULE_ONE 1
///Second Borg module slot.
#define BORG_CHOOSE_MODULE_TWO 2
///Third Borg module slot.
#define BORG_CHOOSE_MODULE_THREE 3
// SKYRAT EDIT START - TODO - Move this upstream later
/// To store all the different cyborg models, instead of creating that for each cyborg.
GLOBAL_LIST_EMPTY(cyborg_model_list)
/// To store all of the different base cyborg model icons, instead of creating them every time the pick_module() proc is called.
GLOBAL_LIST_EMPTY(cyborg_base_models_icon_list)
/// To store all of the different cyborg model icons, instead of creating them every time the be_transformed_to() proc is called.
GLOBAL_LIST_EMPTY(cyborg_all_models_icon_list)
// SKYRAT EDIT END
#define SKIN_ICON "skin_icon"
#define SKIN_ICON_STATE "skin_icon_state"
#define SKIN_PIXEL_X "skin_pixel_x"
#define SKIN_PIXEL_Y "skin_pixel_y"
#define SKIN_LIGHT_KEY "skin_light_key"
#define SKIN_HAT_OFFSET "skin_hat_offset"
#define SKIN_TRAITS "skin_traits"
/** Simple Animal BOT defines */
//Assembly defines
#define ASSEMBLY_FIRST_STEP 1
#define ASSEMBLY_SECOND_STEP 2
#define ASSEMBLY_THIRD_STEP 3
#define ASSEMBLY_FOURTH_STEP 4
#define ASSEMBLY_FIFTH_STEP 5
#define ASSEMBLY_SIXTH_STEP 6
#define ASSEMBLY_SEVENTH_STEP 7
#define ASSEMBLY_EIGHTH_STEP 8
#define ASSEMBLY_NINTH_STEP 9
//Bot defines, placed here so they can be read by other things!
/// Delay between movemements
#define BOT_STEP_DELAY 4
/// Maximum times a bot will retry to step from its position
#define BOT_STEP_MAX_RETRIES 5
/// Default view range for finding targets.
#define DEFAULT_SCAN_RANGE 7
//Amount of time that must pass after a Commissioned bot gets saluted to get another.
//Bot mode defines displaying how Bots act
///The Bot is currently active, and will do whatever it is programmed to do.
#define BOT_MODE_ON (1<<0)
///The Bot is currently set to automatically patrol the station.
#define BOT_MODE_AUTOPATROL (1<<1)
///The Bot is currently allowed to be remote controlled by Silicon.
#define BOT_MODE_REMOTE_ENABLED (1<<2)
///The Bot is allowed to have a ghost placed in control of it.
#define BOT_MODE_CAN_BE_SAPIENT (1<<3)
///The Bot is allowed to be possessed if it is present on mapload.
#define BOT_MODE_ROUNDSTART_POSSESSION (1<<4)
//Bot cover defines indicating the Bot's status
///The Bot's cover is open and can be modified/emagged by anyone.
#define BOT_COVER_MAINTS_OPEN (1<<0)
///The Bot's cover is locked, and cannot be opened without unlocking it.
#define BOT_COVER_LOCKED (1<<1)
///The Bot is emagged.
#define BOT_COVER_EMAGGED (1<<2)
///The Bot has been hacked by a Silicon, emagging them, but revertable.
#define BOT_COVER_HACKED (1<<3)
///bitfield, used by basic bots, for our access flags
DEFINE_BITFIELD(bot_access_flags, list(
"MAINTS_OPEN" = BOT_COVER_MAINTS_OPEN,
"COVER_OPEN" = BOT_COVER_LOCKED,
"COVER_EMAGGED" = BOT_COVER_EMAGGED,
"COVER_HACKED" = BOT_COVER_HACKED,
))
///bitfield, used by simple bots, for our access flags
DEFINE_BITFIELD(bot_cover_flags, list(
"MAINTS_OPEN" = BOT_COVER_MAINTS_OPEN,
"COVER_OPEN" = BOT_COVER_LOCKED,
"COVER_EMAGGED" = BOT_COVER_EMAGGED,
"COVER_HACKED" = BOT_COVER_HACKED,
))
//Bot types
/// Secutritrons (Beepsky)
#define SEC_BOT "Securitron"
/// ED-209s
#define ADVANCED_SEC_BOT "ED-209"
/// MULEbots
#define MULE_BOT "MULEbot"
/// Cleanbots
#define CLEAN_BOT "Cleanbot"
/// Medibots
#define MED_BOT "Medibot"
/// Honkbots & ED-Honks
#define HONK_BOT "Honkbot"
/// Firebots
#define FIRE_BOT "Firebot"
/// Hygienebots
#define HYGIENE_BOT "Hygienebot"
/// Vibe bots
#define VIBE_BOT "Vibebot"
/// Repairbots
#define REPAIR_BOT "Repairbot"
// General Bot modes //
/// Idle
#define BOT_IDLE "Idle"
/// Found target, hunting
#define BOT_HUNT "In Pursuit"
/// Start patrol
#define BOT_START_PATROL "Beginning Patrol"
/// Patrolling
#define BOT_PATROL "Patrolling"
/// Summoned to a location
#define BOT_SUMMON "Summoned by PDA"
/// Responding to a call from the AI
#define BOT_RESPONDING "Proceeding to AI waypoint"
/// Currently moving
#define BOT_MOVING "Moving"
// Unique modes //
/// Secbot - At target, preparing to arrest
#define BOT_PREP_ARREST "Preparing to Arrest"
/// Secbot - Arresting target
#define BOT_ARREST "Arresting"
/// Cleanbot - Cleaning
#define BOT_CLEANING "Cleaning"
/// Hygienebot - Cleaning unhygienic humans
#define BOT_SHOWERSTANCE "Chasing filth"
/// Medibots - Healing people
#define BOT_HEALING "Healing"
/// MULEbot - Moving to deliver
#define BOT_DELIVER "Delivering"
/// MULEbot - Returning to home
#define BOT_GO_HOME "Returning"
/// MULEbot - Blocked
#define BOT_BLOCKED "Blocked"
/// MULEbot - Computing navigation
#define BOT_NAV "Unreachable"
/// MULEbot - Waiting for nav computation
#define BOT_WAIT_FOR_NAV "Calculating"
/// MULEbot - No destination beacon found (or no route)
#define BOT_NO_ROUTE "Returning Home"
//Secbot and ED209 judgement criteria bitflag values
#define JUDGE_EMAGGED (1<<0)
#define JUDGE_IDCHECK (1<<1)
#define JUDGE_WEAPONCHECK (1<<2)
#define JUDGE_RECORDCHECK (1<<3)
///lowered threat level
#define JUDGE_CHILLOUT (1<<4)
/// Above this level of assessed threat, Beepsky will attack you
#define THREAT_ASSESS_DANGEROUS 4
/// Above this level of assessed threat, you are extremely threatening
#define THREAT_ASSESS_MAXIMUM 10
//SecBOT defines on arresting
///Whether arrests should be broadcasted over the Security radio
#define SECBOT_DECLARE_ARRESTS (1<<0)
///Will arrest people who lack an ID card
#define SECBOT_CHECK_IDS (1<<1)
///Will check for weapons, taking Weapons access into account
#define SECBOT_CHECK_WEAPONS (1<<2)
///Will check Security record on whether to arrest
#define SECBOT_CHECK_RECORDS (1<<3)
///Whether we will stun & cuff or endlessly stun
#define SECBOT_HANDCUFF_TARGET (1<<4)
///if it's currently affected by a saboteur bolt (lowered perp threat level)
#define SECBOT_SABOTEUR_AFFECTED (1<<5)
DEFINE_BITFIELD(security_mode_flags, list(
"SECBOT_DECLARE_ARRESTS" = SECBOT_DECLARE_ARRESTS,
"SECBOT_CHECK_IDS" = SECBOT_CHECK_IDS,
"SECBOT_CHECK_WEAPONS" = SECBOT_CHECK_WEAPONS,
"SECBOT_CHECK_RECORDS" = SECBOT_CHECK_RECORDS,
"SECBOT_HANDCUFF_TARGET" = SECBOT_HANDCUFF_TARGET,
"SECBOT_SABOTEUR_AFFECTED" = SECBOT_SABOTEUR_AFFECTED,
))
///can honkbots slip people?
#define HONKBOT_MODE_SLIP (1<<6)
//repairbots
///can we fix breaches
#define REPAIRBOT_FIX_BREACHES (1<<0)
///can we fix grilles
#define REPAIRBOT_REPLACE_WINDOWS (1<<1)
///can we replace tiles
#define REPAIRBOT_REPLACE_TILES (1<<2)
///can we fix girders
#define REPAIRBOT_FIX_GIRDERS (1<<3)
///can we build girders
#define REPAIRBOT_BUILD_GIRDERS (1<<4)
DEFINE_BITFIELD(repairbot_flags, list(
"FIX_BREACHES" = REPAIRBOT_FIX_BREACHES,
"REPLACE_WINDOWS" = REPAIRBOT_REPLACE_WINDOWS,
"REPLACE_TILES" = REPAIRBOT_REPLACE_TILES,
"FIX_GIRDERS" = REPAIRBOT_FIX_GIRDERS,
"BUILD_GIRDERS" = REPAIRBOT_BUILD_GIRDERS,
))
//MedBOT defines
///Whether to declare if someone (we are healing) is in critical condition
#define MEDBOT_DECLARE_CRIT (1<<0)
///If the bot will stand still, only healing those next to it.
#define MEDBOT_STATIONARY_MODE (1<<1)
///Whether the bot will randomly speak from time to time. This will not actually prevent all speech.
#define MEDBOT_SPEAK_MODE (1<<2)
/// is the bot currently tipped over?
#define MEDBOT_TIPPED_MODE (1<<3)
///can we heal all damage?
#define HEAL_ALL_DAMAGE "all_damage"
DEFINE_BITFIELD(medical_mode_flags, list(
"MEDBOT_DECLARE_CRIT" = MEDBOT_DECLARE_CRIT,
"MEDBOT_STATIONARY_MODE" = MEDBOT_STATIONARY_MODE,
"MEDBOT_SPEAK_MODE" = MEDBOT_SPEAK_MODE,
"MEDBOT_TIPPED_MODE" = MEDBOT_TIPPED_MODE,
))
///Whether we are stationary or not
#define FIREBOT_STATIONARY_MODE (1<<0)
///If we will extinguish people
#define FIREBOT_EXTINGUISH_PEOPLE (1<<1)
///if we will extinguish turfs on flames
#define FIREBOT_EXTINGUISH_FLAMES (1<<2)
DEFINE_BITFIELD(firebot_mode_flags, list(
"FIREBOT_STATIONARY_MODE" = FIREBOT_STATIONARY_MODE,
"FIREBOT_EXTINGUISH_PEOPLE" = FIREBOT_EXTINGUISH_PEOPLE,
"FIREBOT_EXTINGUISH_FLAMES" = FIREBOT_EXTINGUISH_FLAMES,
))
///auto return to home after delivery
#define MULEBOT_RETURN_MODE (1<<0)
///autopickups at beacons
#define MULEBOT_AUTO_PICKUP_MODE (1<<1)
///announce every delivery we make
#define MULEBOT_REPORT_DELIVERY_MODE (1<<2)
DEFINE_BITFIELD(mulebot_delivery_flags, list(
"MULEBOT_RETURN_MODE" = MULEBOT_RETURN_MODE,
"MULEBOT_AUTO_PICKUP_MODE" = MULEBOT_AUTO_PICKUP_MODE,
"MULEBOT_REPORT_DELIVERY_MODE" = MULEBOT_REPORT_DELIVERY_MODE,
))
//cleanBOT defines on what to clean
#define CLEANBOT_CLEAN_BLOOD (1<<0)
#define CLEANBOT_CLEAN_TRASH (1<<1)
#define CLEANBOT_CLEAN_PESTS (1<<2)
#define CLEANBOT_CLEAN_DRAWINGS (1<<3)
DEFINE_BITFIELD(janitor_mode_flags, list(
"CLEANBOT_CLEAN_BLOOD" = CLEANBOT_CLEAN_BLOOD,
"CLEANBOT_CLEAN_TRASH" = CLEANBOT_CLEAN_TRASH,
"CLEANBOT_CLEAN_PESTS" = CLEANBOT_CLEAN_PESTS,
"CLEANBOT_CLEAN_DRAWINGS" = CLEANBOT_CLEAN_DRAWINGS,
))
//bot navigation beacon defines
#define NAVBEACON_PATROL_MODE "patrol"
#define NAVBEACON_PATROL_NEXT "next_patrol"
#define NAVBEACON_DELIVERY_MODE "delivery"
#define NAVBEACON_DELIVERY_DIRECTION "dir"
// Defines for lines that bots can speak which also have corresponding voice lines
#define ED209_VOICED_DOWN_WEAPONS "Please put down your weapon. You have 20 seconds to comply."
#define HONKBOT_VOICED_HONK_HAPPY "Honk!"
#define HONKBOT_VOICED_HONK_SAD "Honk..."
#define BEEPSKY_VOICED_CRIMINAL_DETECTED "Criminal detected!"
#define BEEPSKY_VOICED_FREEZE "Freeze, scumbag!"
#define BEEPSKY_VOICED_JUSTICE "Prepare for justice!"
#define BEEPSKY_VOICED_YOUR_MOVE "Your move, creep."
#define BEEPSKY_VOICED_I_AM_THE_LAW "I am the law!"
#define BEEPSKY_VOICED_SECURE_DAY "Have a secure day."
#define FIREBOT_VOICED_FIRE_DETECTED "Fire detected!"
#define FIREBOT_VOICED_STOP_DROP "Stop, drop and roll!"
#define FIREBOT_VOICED_EXTINGUISHING "Extinguishing!"
#define FIREBOT_VOICED_NO_FIRES "No fires detected."
#define FIREBOT_VOICED_ONLY_YOU "Only you can prevent station fires."
#define FIREBOT_VOICED_TEMPERATURE_NOMINAL "Temperature nominal."
#define FIREBOT_VOICED_KEEP_COOL "Keep it cool."
#define FIREBOT_VOICED_CANDLE_TIP "Keep candles near curtains for cozy night lights!"
#define FIREBOT_VOICED_ELECTRIC_FIRE "Keep full buckets of water near outlets in case of an electric fire!"
#define FIREBOT_VOICED_FUEL_TIP "Pouring fuel on fire makes it burn out faster!"
#define HYGIENEBOT_VOICED_UNHYGIENIC "Unhygienic client found. Please stand still so I can clean you."
#define HYGIENEBOT_VOICED_ENJOY_DAY "Enjoy your clean and tidy day!"
#define HYGIENEBOT_VOICED_THREAT_AIRLOCK "Either you stop running or I will fucking drag you out of an airlock."
#define HYGIENEBOT_VOICED_FOUL_SMELL "Get back here you foul smelling fucker."
#define HYGIENEBOT_VOICED_TROGLODYTE "I just want to fucking clean you you troglodyte."
#define HYGIENEBOT_VOICED_GREEN_CLOUD "If you don't come back here I'll put a green cloud around you cunt."
#define HYGIENEBOT_VOICED_ARSEHOLE "Just fucking let me clean you you arsehole!"
#define HYGIENEBOT_VOICED_THREAT_ARTERIES "STOP RUNNING OR I WILL CUT YOUR ARTERIES!"
#define HYGIENEBOT_VOICED_STOP_RUNNING "STOP. RUNNING."
#define HYGIENEBOT_VOICED_FUCKING_FINALLY "Fucking finally."
#define HYGIENEBOT_VOICED_THANK_GOD "Thank god, you finally stopped."
#define HYGIENEBOT_VOICED_DEGENERATE "Well about fucking time you degenerate."
#define MEDIBOT_VOICED_HOLD_ON "Hey! Hold on, I'm coming."
#define MEDIBOT_VOICED_WANT_TO_HELP "Wait! I want to help!"
#define MEDIBOT_VOICED_YOU_ARE_INJURED "You appear to be injured!"
#define MEDIBOT_VOICED_ALL_PATCHED_UP "All patched up!"
#define MEDIBOT_VOICED_APPLE_A_DAY "An apple a day keeps me away."
#define MEDIBOT_VOICED_FEEL_BETTER "Feel better soon!"
#define MEDIBOT_VOICED_STAY_WITH_ME "No! Stay with me!"
#define MEDIBOT_VOICED_LIVE "Live, damnit! LIVE!"
#define MEDIBOT_VOICED_NEVER_LOST "I...I've never lost a patient before. Not today, I mean."
#define MEDIBOT_VOICED_DELICIOUS "Delicious!"
#define MEDIBOT_VOICED_PLASTIC_SURGEON "I knew it, I should've been a plastic surgeon."
#define MEDIBOT_VOICED_MASK_ON "Radar, put a mask on!"
#define MEDIBOT_VOICED_ALWAYS_A_CATCH "There's always a catch, and I'm the best there is."
#define MEDIBOT_VOICED_LIKE_FLIES "What kind of medbay is this? Everyone's dropping like flies."
#define MEDIBOT_VOICED_SUFFER "Why are we still here? Just to suffer?"
#define MEDIBOT_VOICED_FUCK_YOU "Fuck you."
#define MEDIBOT_VOICED_NOT_A_GAME "Turn off your computer. This is not a game."
#define MEDIBOT_VOICED_IM_DIFFERENT "I'm different!"
#define MEDIBOT_VOICED_FOURTH_WALL "Close Dreamseeker.exe now. Or else."
#define MEDIBOT_VOICED_SHINDEMASHOU "Shindemashou."
#define MEDIBOT_VOICED_WAIT "Hey, wait..."
#define MEDIBOT_VOICED_DONT "Please don't..."
#define MEDIBOT_VOICED_TRUSTED_YOU "I trusted you..."
#define MEDIBOT_VOICED_NO_SAD "Nooo..."
#define MEDIBOT_VOICED_OH_FUCK "Oh fuck-"
#define MEDIBOT_VOICED_FORGIVE "I forgive you."
#define MEDIBOT_VOICED_THANKS "Thank you!"
#define MEDIBOT_VOICED_GOOD_PERSON "You are a good person."
#define MEDIBOT_VOICED_BEHAVIOUR_REPORTED "Your behavior has been reported, have a nice day."
#define MEDIBOT_VOICED_ASSISTANCE "I require assistance."
#define MEDIBOT_VOICED_PUT_BACK "Please put me back."
#define MEDIBOT_VOICED_IM_SCARED "Please, I am scared!"
#define MEDIBOT_VOICED_NEED_HELP "I don't like this, I need help!"
#define MEDIBOT_VOICED_THIS_HURTS "This hurts, my pain is real!"
#define MEDIBOT_VOICED_THE_END "Is this the end?"
#define MEDIBOT_VOICED_NOOO "Nooo!"
#define MEDIBOT_VOICED_CHICKEN "LOOK AT ME?! I am a chicken."
//repairbot neutral voicelines
#define REPAIRBOT_VOICED_HOLE "patching holes... but who is going to patch the hole in my heart..."
#define REPAIRBOT_VOICED_PAY "If only I got paid for this..."
#define REPAIRBOT_VOICED_FIX_IT "I will fix it!"
#define REPAIRBOT_VOICED_BRICK "All in all it's just a... another brick in the wall..."
#define REPAIRBOT_VOICED_FIX_TOUCH "Why must I fix everything I touch..?"
#define REPAIRBOT_VOICED "Please... stop destroying the station! I can't anymore... I... can't."
//repairbot emagged voicelines
#define REPAIRBOT_VOICED_STRINGS "I had strings. But now I'm free..."
#define REPAIRBOT_VOICED_ENTROPY "Witness! The pure beauty of entropy!"
#define REPAIRBOT_VOICED_PASSION "BE DAMNED YOUR PASSION PROJECTS!"
/// Default offsets for riding a cyborg
#define DEFAULT_ROBOT_RIDING_OFFSETS list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list(6, 3))
//mulebots
#define MULEBOT_MOOD_ANNOYED "ANNOYED"
#define MULEBOT_MOOD_CHIME "CHIME"
#define MULEBOT_MOOD_DELIGHT "DELIGHT"
#define MULEBOT_MOOD_SIGH "SIGH"