diff --git a/components.dm b/components.dm
new file mode 100644
index 0000000000..8e8c7dc55b
--- /dev/null
+++ b/components.dm
@@ -0,0 +1,284 @@
+#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) )
+
+#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) )
+
+//shorthand
+#define GET_COMPONENT_FROM(varname, path, target) var##path/##varname = ##target.GetComponent(##path)
+#define GET_COMPONENT(varname, path) GET_COMPONENT_FROM(varname, path, src)
+
+#define COMPONENT_INCOMPATIBLE 1
+
+// How multiple components of the exact same type are handled in the same datum
+
+#define COMPONENT_DUPE_HIGHLANDER 0 //old component is deleted (default)
+#define COMPONENT_DUPE_ALLOWED 1 //duplicates allowed
+#define COMPONENT_DUPE_UNIQUE 2 //new component is deleted
+#define COMPONENT_DUPE_UNIQUE_PASSARGS 4 //old component is given the initialization args of the new
+
+// All signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// global signals
+// These are signals which can be listened to by any component on any parent
+// start global signals with "!", this used to be necessary but now it's just a formatting choice
+#define COMSIG_GLOB_NEW_Z "!new_z" //from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args)
+#define COMSIG_GLOB_VAR_EDIT "!var_edit" //called after a successful var edit somewhere in the world: (list/args)
+#define COMSIG_GLOB_LIVING_SAY_SPECIAL "!say_special" //global living say plug - use sparingly: (mob/speaker , message)
+//////////////////////////////////////////////////////////////////
+
+// /datum signals
+#define COMSIG_COMPONENT_ADDED "component_added" //when a component is added to a datum: (/datum/component)
+#define COMSIG_COMPONENT_REMOVING "component_removing" //before a component is removed from a datum because of RemoveComponent: (/datum/component)
+#define COMSIG_PARENT_PREQDELETED "parent_preqdeleted" //before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation
+#define COMSIG_PARENT_QDELETED "parent_qdeleted" //after a datum's Destroy() is called: (force, qdel_hint), at this point none of the other components chose to interrupt qdel and Destroy has been called
+
+// /atom signals
+#define COMSIG_PARENT_ATTACKBY "atom_attackby" //from base of atom/attackby(): (/obj/item, /mob/living, params)
+ #define COMPONENT_NO_AFTERATTACK 1 //Return this in response if you don't want afterattack to be called
+#define COMSIG_ATOM_HULK_ATTACK "hulk_attack" //from base of atom/attack_hulk(): (/mob/living/carbon/human)
+#define COMSIG_PARENT_EXAMINE "atom_examine" //from base of atom/examine(): (/mob)
+#define COMSIG_ATOM_GET_EXAMINE_NAME "atom_examine_name" //from base of atom/get_examine_name(): (/mob, list/overrides)
+ //Positions for overrides list
+ #define EXAMINE_POSITION_ARTICLE 1
+ #define EXAMINE_POSITION_BEFORE 2
+ //End positions
+ #define COMPONENT_EXNAME_CHANGED 1
+#define COMSIG_ATOM_ENTERED "atom_entered" //from base of atom/Entered(): (atom/movable/entering, /atom)
+#define COMSIG_ATOM_EXIT "atom_exit" //from base of atom/Exit(): (/atom/movable/exiting, /atom/newloc)
+ #define COMPONENT_ATOM_BLOCK_EXIT 1
+#define COMSIG_ATOM_EXITED "atom_exited" //from base of atom/Exited(): (atom/movable/exiting, atom/newloc)
+#define COMSIG_ATOM_EX_ACT "atom_ex_act" //from base of atom/ex_act(): (severity, target)
+#define COMSIG_ATOM_EMP_ACT "atom_emp_act" //from base of atom/emp_act(): (severity)
+#define COMSIG_ATOM_FIRE_ACT "atom_fire_act" //from base of atom/fire_act(): (exposed_temperature, exposed_volume)
+#define COMSIG_ATOM_BULLET_ACT "atom_bullet_act" //from base of atom/bullet_act(): (/obj/item/projectile, def_zone)
+#define COMSIG_ATOM_BLOB_ACT "atom_blob_act" //from base of atom/blob_act(): (/obj/structure/blob)
+#define COMSIG_ATOM_ACID_ACT "atom_acid_act" //from base of atom/acid_act(): (acidpwr, acid_volume)
+#define COMSIG_ATOM_EMAG_ACT "atom_emag_act" //from base of atom/emag_act(): ()
+#define COMSIG_ATOM_RAD_ACT "atom_rad_act" //from base of atom/rad_act(intensity)
+#define COMSIG_ATOM_NARSIE_ACT "atom_narsie_act" //from base of atom/narsie_act(): ()
+#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): ()
+#define COMSIG_ATOM_RCD_ACT "atom_rcd_act" //from base of atom/rcd_act(): (/mob, /obj/item/construction/rcd, passed_mode)
+#define COMSIG_ATOM_SING_PULL "atom_sing_pull" //from base of atom/singularity_pull(): (S, current_size)
+#define COMSIG_ATOM_SET_LIGHT "atom_set_light" //from base of atom/set_light(): (l_range, l_power, l_color)
+#define COMSIG_ATOM_DIR_CHANGE "atom_dir_change" //from base of atom/setDir(): (old_dir, new_dir)
+#define COMSIG_ATOM_CONTENTS_DEL "atom_contents_del" //from base of atom/handle_atom_del(): (atom/deleted)
+#define COMSIG_ATOM_HAS_GRAVITY "atom_has_gravity" //from base of atom/has_gravity(): (turf/location, list/forced_gravities)
+#define COMSIG_ATOM_RAD_PROBE "atom_rad_probe" //from proc/get_rad_contents(): ()
+ #define COMPONENT_BLOCK_RADIATION 1
+#define COMSIG_ATOM_RAD_CONTAMINATING "atom_rad_contam" //from base of datum/radiation_wave/radiate(): (strength)
+ #define COMPONENT_BLOCK_CONTAMINATION 1
+#define COMSIG_ATOM_RAD_WAVE_PASSING "atom_rad_wave_pass" //from base of datum/radiation_wave/check_obstructions(): (datum/radiation_wave, width)
+ #define COMPONENT_RAD_WAVE_HANDLED 1
+#define COMSIG_ATOM_CANREACH "atom_can_reach" //from internal loop in atom/movable/proc/CanReach(): (list/next)
+ #define COMPONENT_BLOCK_REACH 1
+#define COMSIG_ATOM_SCREWDRIVER_ACT "atom_screwdriver_act" //from base of atom/screwdriver_act(): (mob/living/user, obj/item/I)
+#define COMSIG_ATOM_INTERCEPT_TELEPORT "intercept_teleport" //called when teleporting into a protected turf: (channel, turf/origin)
+ #define COMPONENT_BLOCK_TELEPORT 1
+/////////////////
+#define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost" //from base of atom/attack_ghost(): (mob/dead/observer/ghost)
+#define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand" //from base of atom/attack_hand(): (mob/user)
+#define COMSIG_ATOM_ATTACK_PAW "atom_attack_paw" //from base of atom/attack_paw(): (mob/user)
+ #define COMPONENT_NO_ATTACK_HAND 1 //works on all 3.
+/////////////////
+
+#define COMSIG_ENTER_AREA "enter_area" //from base of area/Entered(): (/area)
+#define COMSIG_EXIT_AREA "exit_area" //from base of area/Exited(): (/area)
+
+#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params, mob/user)
+#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob)
+#define COMSIG_CLICK_CTRL "ctrl_click" //from base of atom/CtrlClickOn(): (/mob)
+#define COMSIG_CLICK_ALT "alt_click" //from base of atom/AltClick(): (/mob)
+#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" //from base of atom/CtrlShiftClick(/mob)
+#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto" //from base of atom/MouseDrop(): (/atom/over, /mob/user)
+ #define COMPONENT_NO_MOUSEDROP 1
+#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto" //from base of atom/MouseDrop_T: (/atom/from, /mob/user)
+
+// /area signals
+#define COMSIG_AREA_ENTERED "area_entered" //from base of area/Entered(): (atom/movable/M)
+#define COMSIG_AREA_EXITED "area_exited" //from base of area/Exited(): (atom/movable/M)
+
+// /turf signals
+#define COMSIG_TURF_CHANGE "turf_change" //from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/transferring_comps)
+#define COMSIG_TURF_HAS_GRAVITY "turf_has_gravity" //from base of atom/has_gravity(): (atom/asker, list/forced_gravities)
+
+// /atom/movable signals
+#define COMSIG_MOVABLE_MOVED "movable_moved" //from base of atom/movable/Moved(): (/atom, dir)
+#define COMSIG_MOVABLE_CROSS "movable_cross" //from base of atom/movable/Cross(): (/atom/movable)
+#define COMSIG_MOVABLE_CROSSED "movable_crossed" //from base of atom/movable/Crossed(): (/atom/movable)
+#define COMSIG_MOVABLE_UNCROSS "movable_uncross" //from base of atom/movable/Uncross(): (/atom/movable)
+ #define COMPONENT_MOVABLE_BLOCK_UNCROSS 1
+#define COMSIG_MOVABLE_UNCROSSED "movable_uncrossed" //from base of atom/movable/Uncrossed(): (/atom/movable)
+#define COMSIG_MOVABLE_BUMP "movable_bump" //from base of atom/movable/Bump(): (/atom)
+#define COMSIG_MOVABLE_IMPACT "movable_impact" //from base of atom/movable/throw_impact(): (/atom/hit_atom, /datum/thrownthing/throwingdatum)
+#define COMSIG_MOVABLE_IMPACT_ZONE "item_impact_zone" //from base of mob/living/hitby(): (mob/living/target, hit_zone)
+#define COMSIG_MOVABLE_BUCKLE "buckle" //from base of atom/movable/buckle_mob(): (mob, force)
+#define COMSIG_MOVABLE_UNBUCKLE "unbuckle" //from base of atom/movable/unbuckle_mob(): (mob, force)
+#define COMSIG_MOVABLE_PRE_THROW "movable_pre_throw" //from base of atom/movable/throw_at(): (list/args)
+ #define COMPONENT_CANCEL_THROW 1
+#define COMSIG_MOVABLE_POST_THROW "movable_post_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin)
+#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" //from base of atom/movable/onTransitZ(): (old_z, new_z)
+#define COMSIG_MOVABLE_HEAR "movable_hear" //from base of atom/movable/Hear(): (message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+#define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source)
+
+// /mob signals
+#define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed)
+#define COMSIG_MOB_ALLOWED "mob_allowed" //from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj
+#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //from base of mob/anti_magic_check(): (magic, holy, protection_sources)
+ #define COMPONENT_BLOCK_MAGIC 1
+#define COMSIG_MOB_HUD_CREATED "mob_hud_created" //from base of mob/create_mob_hud(): ()
+#define COMSIG_MOB_ATTACK_HAND "mob_attack_hand" //from base of
+#define COMSIG_MOB_ITEM_ATTACK "mob_item_attack" //from base of /obj/item/attack(): (mob/M, mob/user)
+#define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters)
+#define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" //from base of mob/RangedAttack(): (atom/A, params)
+#define COMSIG_MOB_THROW "mob_throw" //from base of /mob/throw_item(): (atom/target)
+#define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight" //from base of /mob/update_sight(): ()
+#define COMSIG_MOB_SAY "mob_say" // from /mob/living/say(): (proc args list)
+ #define COMPONENT_UPPERCASE_SPEECH 1
+ // used to access COMSIG_MOB_SAY argslist
+ #define SPEECH_MESSAGE 1
+ // #define SPEECH_BUBBLE_TYPE 2
+ #define SPEECH_SPANS 3
+ /* #define SPEECH_SANITIZE 4
+ #define SPEECH_LANGUAGE 5
+ #define SPEECH_IGNORE_SPAM 6
+ #define SPEECH_FORCED 7 */
+
+// /mob/living signals
+#define COMSIG_LIVING_RESIST "living_resist" //from base of mob/living/resist() (/mob/living)
+#define COMSIG_LIVING_IGNITED "living_ignite" //from base of mob/living/IgniteMob() (/mob/living)
+#define COMSIG_LIVING_EXTINGUISHED "living_extinguished" //from base of mob/living/ExtinguishMob() (/mob/living)
+#define COMSIG_LIVING_ELECTROCUTE_ACT "living_electrocute_act" //from base of mob/living/electrocute_act(): (shock_damage)
+#define COMSIG_LIVING_MINOR_SHOCK "living_minor_shock" //sent by stuff like stunbatons and tasers: ()
+
+// /mob/living/carbon signals
+#define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity))
+
+// /obj signals
+#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled)
+#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag)
+#define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value)
+
+
+// /obj/item signals
+#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user)
+#define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob)
+ #define COMPONENT_NO_INTERACT 1
+#define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob)
+ #define COMPONENT_NO_ATTACK_OBJ 1
+#define COMSIG_ITEM_PRE_ATTACK "item_pre_attack" //from base of obj/item/pre_attack(): (atom/target, mob/user, params)
+ #define COMPONENT_NO_ATTACK 1
+#define COMSIG_ITEM_AFTERATTACK "item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, params)
+#define COMSIG_ITEM_EQUIPPED "item_equip" //from base of obj/item/equipped(): (/mob/equipper, slot)
+#define COMSIG_ITEM_DROPPED "item_drop" //from base of obj/item/dropped(): (mob/user)
+#define COMSIG_ITEM_PICKUP "item_pickup" //from base of obj/item/pickup(): (/mob/taker)
+#define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone" //from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone)
+#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user)
+#define COMSIG_ITEM_HIT_REACT "item_hit_react" //from base of obj/item/hit_reaction(): (list/args)
+
+// /obj/item/clothing signals
+#define COMSIG_SHOES_STEP_ACTION "shoes_step_action" //from base of obj/item/clothing/shoes/proc/step_action(): ()
+
+// /obj/item/implant signals
+#define COMSIG_IMPLANT_ACTIVATED "implant_activated" //from base of /obj/item/implant/proc/activate(): ()
+#define COMSIG_IMPLANT_IMPLANTING "implant_implanting" //from base of /obj/item/implant/proc/implant(): (list/args)
+ #define COMPONENT_STOP_IMPLANTING 1
+#define COMSIG_IMPLANT_OTHER "implant_other" //called on already installed implants when a new one is being added in /obj/item/implant/proc/implant(): (list/args, obj/item/implant/new_implant)
+ //#define COMPONENT_STOP_IMPLANTING 1 //The name makes sense for both
+ #define COMPONENT_DELETE_NEW_IMPLANT 2
+ #define COMPONENT_DELETE_OLD_IMPLANT 4
+#define COMSIG_IMPLANT_EXISTING_UPLINK "implant_uplink_exists" //called on implants being implanted into someone with an uplink implant: (datum/component/uplink)
+ //This uses all return values of COMSIG_IMPLANT_OTHER
+
+// /obj/item/pda signals
+#define COMSIG_PDA_CHANGE_RINGTONE "pda_change_ringtone" //called on pda when the user changes the ringtone: (mob/living/user, new_ringtone)
+ #define COMPONENT_STOP_RINGTONE_CHANGE 1
+
+// /obj/item/radio signals
+#define COMSIG_RADIO_NEW_FREQUENCY "radio_new_frequency" //called from base of /obj/item/radio/proc/set_frequency(): (list/args)
+
+// /obj/item/pen signals
+#define COMSIG_PEN_ROTATED "pen_rotated" //called after rotation in /obj/item/pen/attack_self(): (rotation, mob/living/carbon/user)
+
+
+// /mob/living/carbon/human signals
+#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target)
+#define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker)
+#define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted)
+
+// /datum/species signals
+#define COMSIG_SPECIES_GAIN "species_gain" //from datum/species/on_species_gain(): (datum/species/new_species, datum/species/old_species)
+#define COMSIG_SPECIES_LOSS "species_loss" //from datum/species/on_species_loss(): (datum/species/lost_species)
+
+/*******Component Specific Signals*******/
+//Janitor
+#define COMSIG_TURF_IS_WET "check_turf_wet" //(): Returns bitflags of wet values.
+#define COMSIG_TURF_MAKE_DRY "make_turf_try" //(max_strength, immediate, duration_decrease = INFINITY): Returns bool.
+#define COMSIG_COMPONENT_CLEAN_ACT "clean_act" //called on an object to clean it of cleanables. Usualy with soap: (num/strength)
+
+//Food
+#define COMSIG_FOOD_EATEN "food_eaten" //from base of obj/item/reagent_containers/food/snacks/attack(): (mob/living/eater, mob/feeder)
+
+//Mood
+#define COMSIG_ADD_MOOD_EVENT "add_mood" //Called when you send a mood event from anywhere in the code.
+#define COMSIG_CLEAR_MOOD_EVENT "clear_mood" //Called when you clear a mood event from anywhere in the code.
+
+//NTnet
+#define COMSIG_COMPONENT_NTNET_RECEIVE "ntnet_receive" //called on an object by its NTNET connection component on receive. (sending_id(number), sending_netname(text), data(datum/netdata))
+
+//Nanites
+#define COMSIG_HAS_NANITES "has_nanites" //() returns TRUE if nanites are found
+#define COMSIG_NANITE_GET_PROGRAMS "nanite_get_programs" //(list/nanite_programs) - makes the input list a copy the nanites' program list
+#define COMSIG_NANITE_SET_VOLUME "nanite_set_volume" //(amount) Sets current nanite volume to the given amount
+#define COMSIG_NANITE_ADJUST_VOLUME "nanite_adjust" //(amount) Adjusts nanite volume by the given amount
+#define COMSIG_NANITE_SET_MAX_VOLUME "nanite_set_max_volume" //(amount) Sets maximum nanite volume to the given amount
+#define COMSIG_NANITE_SET_CLOUD "nanite_set_cloud" //(amount(0-100)) Sets cloud ID to the given amount
+#define COMSIG_NANITE_SET_SAFETY "nanite_set_safety" //(amount) Sets safety threshold to the given amount
+#define COMSIG_NANITE_SET_REGEN "nanite_set_regen" //(amount) Sets regeneration rate to the given amount
+#define COMSIG_NANITE_SIGNAL "nanite_signal" //(code(1-9999)) Called when sending a nanite signal to a mob.
+#define COMSIG_NANITE_SCAN "nanite_scan" //(mob/user, full_scan) - sends to chat a scan of the nanites to the user, returns TRUE if nanites are detected
+#define COMSIG_NANITE_UI_DATA "nanite_ui_data" //(list/data, scan_level) - adds nanite data to the given data list - made for ui_data procs
+#define COMSIG_NANITE_ADD_PROGRAM "nanite_add_program" //(datum/nanite_program/new_program, datum/nanite_program/source_program) Called when adding a program to a nanite component
+ #define COMPONENT_PROGRAM_INSTALLED 1 //Installation successful
+ #define COMPONENT_PROGRAM_NOT_INSTALLED 2 //Installation failed, but there are still nanites
+#define COMSIG_NANITE_SYNC "nanite_sync" //(datum/component/nanites, full_overwrite, copy_activation) Called to sync the target's nanites to a given nanite component
+
+// /datum/component/storage signals
+#define COMSIG_CONTAINS_STORAGE "is_storage" //() - returns bool.
+#define COMSIG_TRY_STORAGE_INSERT "storage_try_insert" //(obj/item/inserting, mob/user, silent, force) - returns bool
+#define COMSIG_TRY_STORAGE_SHOW "storage_show_to" //(mob/show_to, force) - returns bool.
+#define COMSIG_TRY_STORAGE_HIDE_FROM "storage_hide_from" //(mob/hide_from) - returns bool
+#define COMSIG_TRY_STORAGE_HIDE_ALL "storage_hide_all" //returns bool
+#define COMSIG_TRY_STORAGE_SET_LOCKSTATE "storage_lock_set_state" //(newstate)
+#define COMSIG_IS_STORAGE_LOCKED "storage_get_lockstate" //() - returns bool. MUST CHECK IF STORAGE IS THERE FIRST!
+#define COMSIG_TRY_STORAGE_TAKE_TYPE "storage_take_type" //(type, atom/destination, amount = INFINITY, check_adjacent, force, mob/user, list/inserted) - returns bool - type can be a list of types.
+#define COMSIG_TRY_STORAGE_FILL_TYPE "storage_fill_type" //(type, amount = INFINITY, force = FALSE) //don't fuck this up. Force will ignore max_items, and amount is normally clamped to max_items.
+#define COMSIG_TRY_STORAGE_TAKE "storage_take_obj" //(obj, new_loc, force = FALSE) - returns bool
+#define COMSIG_TRY_STORAGE_QUICK_EMPTY "storage_quick_empty" //(loc) - returns bool - if loc is null it will dump at parent location.
+#define COMSIG_TRY_STORAGE_RETURN_INVENTORY "storage_return_inventory" //(list/list_to_inject_results_into, recursively_search_inside_storages = TRUE)
+#define COMSIG_TRY_STORAGE_CAN_INSERT "storage_can_equip" //(obj/item/insertion_candidate, mob/user, silent) - returns bool
+
+// /datum/action signals
+#define COMSIG_ACTION_TRIGGER "action_trigger" //from base of datum/action/proc/Trigger(): (datum/action)
+ #define COMPONENT_ACTION_BLOCK_TRIGGER 1
+
+/*******Non-Signal Component Related Defines*******/
+
+//Redirection component init flags
+#define REDIRECT_TRANSFER_WITH_TURF 1
+
+//Arch
+#define ARCH_PROB "probability" //Probability for each item
+#define ARCH_MAXDROP "max_drop_amount" //each item's max drop amount
+
+//Ouch my toes!
+#define CALTROP_BYPASS_SHOES 1
+#define CALTROP_IGNORE_WALKERS 2
+
+//Xenobio hotkeys
+#define COMSIG_XENO_SLIME_CLICK_CTRL "xeno_slime_click_ctrl" //from slime CtrlClickOn(): (/mob)
+#define COMSIG_XENO_SLIME_CLICK_ALT "xeno_slime_click_alt" //from slime AltClickOn(): (/mob)
+#define COMSIG_XENO_SLIME_CLICK_SHIFT "xeno_slime_click_shift" //from slime ShiftClickOn(): (/mob)
+#define COMSIG_XENO_TURF_CLICK_SHIFT "xeno_turf_click_shift" //from turf ShiftClickOn(): (/mob)
+#define COMSIG_XENO_TURF_CLICK_CTRL "xeno_turf_click_alt" //from turf AltClickOn(): (/mob)
+#define COMSIG_XENO_MONKEY_CLICK_CTRL "xeno_monkey_click_ctrl" //from monkey CtrlClickOn(): (/mob)
diff --git a/xenobio_camera.dm b/xenobio_camera.dm
new file mode 100644
index 0000000000..07b25c6de8
--- /dev/null
+++ b/xenobio_camera.dm
@@ -0,0 +1,458 @@
+//Xenobio control console
+/mob/camera/aiEye/remote/xenobio
+ visible_icon = 1
+ icon = 'icons/obj/abductor.dmi'
+ icon_state = "camera_target"
+ var/allowed_area = null
+
+/mob/camera/aiEye/remote/xenobio/Initialize()
+ var/area/A = get_area(loc)
+ allowed_area = A.name
+ . = ..()
+
+/mob/camera/aiEye/remote/xenobio/setLoc(var/t)
+ var/area/new_area = get_area(t)
+ if(new_area && new_area.name == allowed_area || new_area && new_area.xenobiology_compatible)
+ return ..()
+ else
+ return
+
+/obj/machinery/computer/camera_advanced/xenobio
+ name = "Slime management console"
+ desc = "A computer used for remotely handling slimes."
+ networks = list("ss13")
+ circuit = /obj/item/circuitboard/computer/xenobiology
+ var/datum/action/innate/slime_place/slime_place_action
+ var/datum/action/innate/slime_pick_up/slime_up_action
+ var/datum/action/innate/feed_slime/feed_slime_action
+ var/datum/action/innate/monkey_recycle/monkey_recycle_action
+ var/datum/action/innate/slime_scan/scan_action
+ var/datum/action/innate/feed_potion/potion_action
+ var/datum/action/innate/hotkey_help/hotkey_help
+
+ var/datum/component/redirect/listener
+
+ var/list/stored_slimes
+ var/obj/item/slimepotion/slime/current_potion
+ var/max_slimes = 5
+ var/monkeys = 0
+
+ icon_screen = "slime_comp"
+ icon_keyboard = "rd_key"
+
+ light_color = LIGHT_COLOR_PINK
+
+/obj/machinery/computer/camera_advanced/xenobio/Initialize()
+ . = ..()
+ slime_place_action = new
+ slime_up_action = new
+ feed_slime_action = new
+ monkey_recycle_action = new
+ scan_action = new
+ potion_action = new
+ hotkey_help = new
+ stored_slimes = list()
+ listener = AddComponent(/datum/component/redirect, list(COMSIG_ATOM_CONTENTS_DEL = CALLBACK(src, .proc/on_contents_del)))
+
+/obj/machinery/computer/camera_advanced/xenobio/Destroy()
+ stored_slimes = null
+ QDEL_NULL(current_potion)
+ for(var/i in contents)
+ var/mob/living/simple_animal/slime/S = i
+ if(istype(S))
+ S.forceMove(drop_location())
+ return ..()
+
+/obj/machinery/computer/camera_advanced/xenobio/CreateEye()
+ eyeobj = new /mob/camera/aiEye/remote/xenobio(get_turf(src))
+ eyeobj.origin = src
+ eyeobj.visible_icon = 1
+ eyeobj.icon = 'icons/obj/abductor.dmi'
+ eyeobj.icon_state = "camera_target"
+
+/obj/machinery/computer/camera_advanced/xenobio/GrantActions(mob/living/user)
+ ..()
+
+ if(slime_up_action && (upgradetier & XENOBIO_UPGRADE_SLIMEBASIC)) //CIT CHANGE - makes slime-related actions require XENOBIO_UPGRADE_SLIMEBASIC
+ slime_up_action.target = src
+ slime_up_action.Grant(user)
+ actions += slime_up_action
+
+ if(slime_place_action && (upgradetier & XENOBIO_UPGRADE_SLIMEBASIC)) //CIT CHANGE - makes slime-related actions require XENOBIO_UPGRADE_SLIMEBASIC
+ slime_place_action.target = src
+ slime_place_action.Grant(user)
+ actions += slime_place_action
+
+ if(feed_slime_action && (upgradetier & XENOBIO_UPGRADE_MONKEYS)) //CIT CHANGE - makes monkey-related actions require XENOBIO_UPGRADE_MONKEYS
+ feed_slime_action.target = src
+ feed_slime_action.Grant(user)
+ actions += feed_slime_action
+
+ if(monkey_recycle_action && (upgradetier & XENOBIO_UPGRADE_MONKEYS)) //CIT CHANGE - makes monkey-related actions require XENOBIO_UPGRADE_MONKEYS
+ monkey_recycle_action.target = src
+ monkey_recycle_action.Grant(user)
+ actions += monkey_recycle_action
+
+ if(scan_action)
+ scan_action.target = src
+ scan_action.Grant(user)
+ actions += scan_action
+
+ if(potion_action && (upgradetier & XENOBIO_UPGRADE_SLIMEADV)) // CIT CHANGE - makes giving slimes potions via console require XENOBIO_UPGRADE_SLIMEADV
+ potion_action.target = src
+ potion_action.Grant(user)
+ actions += potion_action
+
+ if(hotkey_help)
+ hotkey_help.target = src
+ hotkey_help.Grant(user)
+ actions += hotkey_help
+
+ RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_CTRL, .proc/XenoSlimeClickCtrl)
+ RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_ALT, .proc/XenoSlimeClickAlt)
+ RegisterSignal(user, COMSIG_XENO_SLIME_CLICK_SHIFT, .proc/XenoSlimeClickShift)
+ RegisterSignal(user, COMSIG_XENO_TURF_CLICK_SHIFT, .proc/XenoTurfClickShift)
+ RegisterSignal(user, COMSIG_XENO_TURF_CLICK_CTRL, .proc/XenoTurfClickCtrl)
+ RegisterSignal(user, COMSIG_XENO_MONKEY_CLICK_CTRL, .proc/XenoMonkeyClickCtrl)
+
+/obj/machinery/computer/camera_advanced/xenobio/remove_eye_control(mob/living/user)
+ UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_CTRL)
+ UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_ALT)
+ UnregisterSignal(user, COMSIG_XENO_SLIME_CLICK_SHIFT)
+ UnregisterSignal(user, COMSIG_XENO_TURF_CLICK_SHIFT)
+ UnregisterSignal(user, COMSIG_XENO_TURF_CLICK_CTRL)
+ UnregisterSignal(user, COMSIG_XENO_MONKEY_CLICK_CTRL)
+ ..()
+
+/obj/machinery/computer/camera_advanced/xenobio/proc/on_contents_del(atom/deleted)
+ if(current_potion == deleted)
+ current_potion = null
+ if(deleted in stored_slimes)
+ stored_slimes -= deleted
+
+/obj/machinery/computer/camera_advanced/xenobio/attackby(obj/item/O, mob/user, params)
+ if(istype(O, /obj/item/reagent_containers/food/snacks/monkeycube) && (upgradetier & XENOBIO_UPGRADE_MONKEYS)) //CIT CHANGE - makes monkey-related actions require XENOBIO_UPGRADE_MONKEYS
+ monkeys++
+ to_chat(user, "You feed [O] to [src]. It now has [monkeys] monkey cubes stored.")
+ qdel(O)
+ return
+ else if(istype(O, /obj/item/storage/bag) && (upgradetier & XENOBIO_UPGRADE_MONKEYS)) //CIT CHANGE - makes monkey-related actions require XENOBIO_UPGRADE_MONKEYS
+ var/obj/item/storage/P = O
+ var/loaded = FALSE
+ for(var/obj/G in P.contents)
+ if(istype(G, /obj/item/reagent_containers/food/snacks/monkeycube))
+ loaded = TRUE
+ monkeys++
+ qdel(G)
+ if(loaded)
+ to_chat(user, "You fill [src] with the monkey cubes stored in [O]. [src] now has [monkeys] monkey cubes stored.")
+ return
+ else if(istype(O, /obj/item/slimepotion/slime) && (upgradetier & XENOBIO_UPGRADE_SLIMEADV)) // CIT CHANGE - makes giving slimes potions via console require XENOBIO_UPGRADE_SLIMEADV
+ var/replaced = FALSE
+ if(user && !user.transferItemToLoc(O, src))
+ return
+ if(!QDELETED(current_potion))
+ current_potion.forceMove(drop_location())
+ replaced = TRUE
+ current_potion = O
+ to_chat(user, "You load [O] in the console's potion slot[replaced ? ", replacing the one that was there before" : ""].")
+ return
+ ..()
+
+/datum/action/innate/slime_place
+ name = "Place Slimes"
+ icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "slime_down"
+
+/datum/action/innate/slime_place/Activate()
+ if(!target || !isliving(owner))
+ return
+ var/mob/living/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ for(var/mob/living/simple_animal/slime/S in X.stored_slimes)
+ S.forceMove(remote_eye.loc)
+ S.visible_message("[S] warps in!")
+ X.stored_slimes -= S
+ else
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
+
+/datum/action/innate/slime_pick_up
+ name = "Pick up Slime"
+ icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "slime_up"
+
+/datum/action/innate/slime_pick_up/Activate()
+ if(!target || !isliving(owner))
+ return
+ var/mob/living/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
+ if(X.stored_slimes.len >= X.max_slimes)
+ break
+ if(!S.ckey)
+ if(S.buckled)
+ S.Feedstop(silent = TRUE)
+ S.visible_message("[S] vanishes in a flash of light!")
+ S.forceMove(X)
+ X.stored_slimes += S
+ else
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
+
+
+/datum/action/innate/feed_slime
+ name = "Feed Slimes"
+ icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "monkey_down"
+
+/datum/action/innate/feed_slime/Activate()
+ if(!target || !isliving(owner))
+ return
+ var/mob/living/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ if(X.monkeys >= 1)
+ var/mob/living/carbon/monkey/food = new /mob/living/carbon/monkey(remote_eye.loc, TRUE, owner)
+ if (!QDELETED(food))
+ food.LAssailant = C
+ X.monkeys --
+ to_chat(owner, "[X] now has [X.monkeys] monkey(s) left.")
+ else
+ to_chat(owner, "[X] needs to have at least 1 monkey stored. Currently has [X.monkeys] monkeys stored.")
+ else
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
+
+
+/datum/action/innate/monkey_recycle
+ name = "Recycle Monkeys"
+ icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "monkey_up"
+
+/datum/action/innate/monkey_recycle/Activate()
+ if(!target || !isliving(owner))
+ return
+ var/mob/living/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ for(var/mob/living/carbon/monkey/M in remote_eye.loc)
+ if(M.stat)
+ M.visible_message("[M] vanishes as [p_theyre()] reclaimed for recycling!")
+ X.monkeys = round(X.monkeys + 0.2,0.1)
+ qdel(M)
+ if (X.monkeys == (round(X.monkeys,1)))
+ to_chat(C, "[X] now has [X.monkeys] monkey(s) available.")
+ else
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
+
+/datum/action/innate/slime_scan
+ name = "Scan Slime"
+ icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "slime_scan"
+
+/datum/action/innate/slime_scan/Activate()
+ if(!target || !isliving(owner))
+ return
+ var/mob/living/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+
+ if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
+ slime_scan(S, C)
+ else
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
+
+/datum/action/innate/feed_potion
+ name = "Apply Potion"
+ icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "slime_potion"
+
+/datum/action/innate/feed_potion/Activate()
+ if(!target || !isliving(owner))
+ return
+
+ var/mob/living/C = owner
+ var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = target
+
+ if(QDELETED(X.current_potion))
+ to_chat(owner, "No potion loaded.")
+ return
+
+ if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
+ for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
+ X.current_potion.attack(S, C)
+ break
+ else
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
+
+/datum/action/innate/hotkey_help
+ name = "Hotkey Help"
+ icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "hotkey_help"
+
+/datum/action/innate/hotkey_help/Activate()
+ if(!target || !isliving(owner))
+ return
+ to_chat(owner, "Click shortcuts:")
+ to_chat(owner, "Shift-click a slime to pick it up, or the floor to drop all held slimes. (Requires Basic Slime Console upgrade)")
+ to_chat(owner, "Ctrl-click a slime to scan it.")
+ to_chat(owner, "Alt-click a slime to feed it a potion. (Requires Advanced Slime Console upgrade)")
+ to_chat(owner, "Ctrl-click on a dead monkey to recycle it, or the floor to place a new monkey. (Requires Monkey Console upgrade)")
+
+//
+// Alternate clicks for slime, monkey and open turf if using a xenobio console
+
+// Scans slime
+/mob/living/simple_animal/slime/CtrlClick(mob/user)
+ SEND_SIGNAL(user, COMSIG_XENO_SLIME_CLICK_CTRL, src)
+ ..()
+
+//Feeds a potion to slime
+/mob/living/simple_animal/slime/AltClick(mob/user)
+ SEND_SIGNAL(user, COMSIG_XENO_SLIME_CLICK_ALT, src)
+ ..()
+
+//Picks up slime
+/mob/living/simple_animal/slime/ShiftClick(mob/user)
+ SEND_SIGNAL(user, COMSIG_XENO_SLIME_CLICK_SHIFT, src)
+ ..()
+
+//Place slimes
+/turf/open/ShiftClick(mob/user)
+ SEND_SIGNAL(user, COMSIG_XENO_TURF_CLICK_SHIFT, src)
+ ..()
+
+//Place monkey
+/turf/open/CtrlClick(mob/user)
+ SEND_SIGNAL(user, COMSIG_XENO_TURF_CLICK_CTRL, src)
+ ..()
+
+//Pick up monkey
+/mob/living/carbon/monkey/CtrlClick(mob/user)
+ SEND_SIGNAL(user, COMSIG_XENO_MONKEY_CLICK_CTRL, src)
+ ..()
+
+// Scans slime
+/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickCtrl(mob/living/user, mob/living/simple_animal/slime/S)
+ if(!GLOB.cameranet.checkTurfVis(S.loc))
+ to_chat(user, "Target is not near a camera. Cannot proceed.")
+ return
+ var/mob/living/C = user
+ var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/area/mobarea = get_area(S.loc)
+ if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
+ slime_scan(S, C)
+
+//Feeds a potion to slime
+/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickAlt(mob/living/user, mob/living/simple_animal/slime/S)
+ if(!(upgradetier & XENOBIO_UPGRADE_SLIMEADV)) //CIT CHANGE - makes slime-related actions require XENOBIO_UPGRADE_SLIMEADV
+ to_chat(user, "This console does not have the advanced slime upgrade.")
+ return
+ if(!GLOB.cameranet.checkTurfVis(S.loc))
+ to_chat(user, "Target is not near a camera. Cannot proceed.")
+ return
+ var/mob/living/C = user
+ var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
+ var/area/mobarea = get_area(S.loc)
+ if(QDELETED(X.current_potion))
+ to_chat(C, "No potion loaded.")
+ return
+ if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
+ X.current_potion.attack(S, C)
+
+//Picks up slime
+/obj/machinery/computer/camera_advanced/xenobio/proc/XenoSlimeClickShift(mob/living/user, mob/living/simple_animal/slime/S)
+ if(!(upgradetier & XENOBIO_UPGRADE_SLIMEBASIC)) //CIT CHANGE - makes slime-related actions require XENOBIO_UPGRADE_SLIMEBASIC
+ to_chat(user, "This console does not have the basic slime upgrade.")
+ return
+ if(!GLOB.cameranet.checkTurfVis(S.loc))
+ to_chat(user, "Target is not near a camera. Cannot proceed.")
+ return
+ var/mob/living/C = user
+ var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
+ var/area/mobarea = get_area(S.loc)
+ if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
+ if(X.stored_slimes.len >= X.max_slimes)
+ to_chat(C, "Slime storage is full.")
+ return
+ if(S.ckey)
+ to_chat(C, "The slime wiggled free!")
+ return
+ if(S.buckled)
+ S.Feedstop(silent = TRUE)
+ S.visible_message("[S] vanishes in a flash of light!")
+ S.forceMove(X)
+ X.stored_slimes += S
+
+//Place slimes
+/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickShift(mob/living/user, turf/open/T)
+ if(!(upgradetier & XENOBIO_UPGRADE_SLIMEBASIC)) //CIT CHANGE - makes slime-related actions require XENOBIO_UPGRADE_SLIMEBASIC
+ to_chat(user, "This console does not have the basic slime upgrade.")
+ return
+ if(!GLOB.cameranet.checkTurfVis(T))
+ to_chat(user, "Target is not near a camera. Cannot proceed.")
+ return
+ var/mob/living/C = user
+ var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
+ var/area/turfarea = get_area(T)
+ if(turfarea.name == E.allowed_area || turfarea.xenobiology_compatible)
+ for(var/mob/living/simple_animal/slime/S in X.stored_slimes)
+ S.forceMove(T)
+ S.visible_message("[S] warps in!")
+ X.stored_slimes -= S
+
+//Place monkey
+/obj/machinery/computer/camera_advanced/xenobio/proc/XenoTurfClickCtrl(mob/living/user, turf/open/T)
+ if(!(upgradetier & XENOBIO_UPGRADE_MONKEYS)) // CIT CHANGE - makes monkey-related actions require XENOBIO_UPGRADE_MONKEYS
+ to_chat(user, "This console does not have the monkey upgrade.")
+ return
+ if(!GLOB.cameranet.checkTurfVis(T))
+ to_chat(user, "Target is not near a camera. Cannot proceed.")
+ return
+ var/mob/living/C = user
+ var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
+ var/area/turfarea = get_area(T)
+ if(turfarea.name == E.allowed_area || turfarea.xenobiology_compatible)
+ if(X.monkeys >= 1)
+ var/mob/living/carbon/monkey/food = new /mob/living/carbon/monkey(T, TRUE, C)
+ if (!QDELETED(food))
+ food.LAssailant = C
+ X.monkeys--
+ X.monkeys = round(X.monkeys, 0.1) //Prevents rounding errors
+ to_chat(C, "[X] now has [X.monkeys] monkey(s) stored.")
+ else
+ to_chat(C, "[X] needs to have at least 1 monkey stored. Currently has [X.monkeys] monkeys stored.")
+
+//Pick up monkey
+/obj/machinery/computer/camera_advanced/xenobio/proc/XenoMonkeyClickCtrl(mob/living/user, mob/living/carbon/monkey/M)
+ if(!(upgradetier & XENOBIO_UPGRADE_MONKEYS)) // CIT CHANGE - makes monkey-related actions require XENOBIO_UPGRADE_MONKEYS
+ to_chat(user, "This console does not have the monkey upgrade.")
+ return
+ if(!GLOB.cameranet.checkTurfVis(M.loc))
+ to_chat(user, "Target is not near a camera. Cannot proceed.")
+ return
+ var/mob/living/C = user
+ var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
+ var/area/mobarea = get_area(M.loc)
+ if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
+ if(!M.stat)
+ return
+ M.visible_message("[M] vanishes as [p_theyre()] reclaimed for recycling!")
+ X.monkeys = round(X.monkeys + 0.2,0.1)
+ qdel(M)
+ if (X.monkeys == (round(X.monkeys,1)))
+ to_chat(C, "[X] now has [X.monkeys] monkey(s) available.")