diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 9dad2926f8..1d469860fc 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -318,3 +318,11 @@
//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/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm
index c143c4fb2d..205603e12e 100644
--- a/code/modules/research/xenobiology/xenobio_camera.dm
+++ b/code/modules/research/xenobiology/xenobio_camera.dm
@@ -28,6 +28,7 @@
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/list/stored_slimes
var/obj/item/slimepotion/slime/current_potion
@@ -48,6 +49,7 @@
monkey_recycle_action = new
scan_action = new
potion_action = new
+ hotkey_help = new
stored_slimes = list()
RegisterSignal(src, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del)
@@ -100,6 +102,27 @@
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
@@ -169,7 +192,7 @@
S.visible_message("[S] warps in!")
X.stored_slimes -= S
else
- to_chat(owner, "Target is not near a camera. Cannot proceed.")
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
/datum/action/innate/slime_pick_up
name = "Pick up Slime"
@@ -194,7 +217,7 @@
S.forceMove(X)
X.stored_slimes += S
else
- to_chat(owner, "Target is not near a camera. Cannot proceed.")
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
/datum/action/innate/feed_slime
@@ -215,9 +238,11 @@
if (!QDELETED(food))
food.LAssailant = C
X.monkeys --
- to_chat(owner, "[X] now has [X.monkeys] monkeys left.")
+ 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.")
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
/datum/action/innate/monkey_recycle
@@ -239,7 +264,7 @@
X.monkeys = round(X.monkeys + 0.2,0.1)
qdel(M)
else
- to_chat(owner, "Target is not near a camera. Cannot proceed.")
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
/datum/action/innate/slime_scan
name = "Scan Slime"
@@ -256,7 +281,7 @@
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.")
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
/datum/action/innate/feed_potion
name = "Apply Potion"
@@ -280,7 +305,10 @@
X.current_potion.attack(S, C)
break
else
- to_chat(owner, "Target is not near a camera. Cannot proceed.")
+ to_chat(owner, "Target is not near a camera. Cannot proceed.")
+
+
+//Demodularized Code
/obj/item/disk/xenobio_console_upgrade
name = "Xenobiology console upgrade disk"
@@ -307,3 +335,169 @@
name = "Xenobiology console advanced slime upgrade disk"
desc = "This disk will add the ability to remotely feed slimes potions via the Xenobiology console, and lift the restrictions on the number of slimes that can be stored inside the Xenobiology console. This includes the contents of the basic slime upgrade disk."
upgradetypes = list(XENOBIO_UPGRADE_SLIMEBASIC, XENOBIO_UPGRADE_SLIMEADV)
+
+
+//Xenobio Hotkeys Port
+
+/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.")
diff --git a/icons/mob/actions/actions_silicon.dmi b/icons/mob/actions/actions_silicon.dmi
index cd997a25ac..cf3d7a2a95 100644
Binary files a/icons/mob/actions/actions_silicon.dmi and b/icons/mob/actions/actions_silicon.dmi differ