diff --git a/code/__DEFINES/~skyrat_defines/signals.dm b/code/__DEFINES/~skyrat_defines/signals.dm
new file mode 100644
index 00000000000..5d82939b5ca
--- /dev/null
+++ b/code/__DEFINES/~skyrat_defines/signals.dm
@@ -0,0 +1,6 @@
+// Movable signals
+#define COMSIG_MOVABLE_RADIO_TALK_INTO "movable_radio_talk_into" //from radio talk_into(): (obj/item/radio/radio, message, channel, list/spans, datum/language/language, direct)
+// Mob signals
+#define COMSIG_MOB_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (obj/item)
+// Living signals
+#define COMSIG_LIVING_UPDATED_MOBILITY "living_updated_mobility" //from base of (/mob/living/proc/update_mobility): (mobility_flags)
diff --git a/code/__DEFINES/~skyrat_defines/traits.dm b/code/__DEFINES/~skyrat_defines/traits.dm
new file mode 100644
index 00000000000..e8581b0f667
--- /dev/null
+++ b/code/__DEFINES/~skyrat_defines/traits.dm
@@ -0,0 +1 @@
+#define TRAIT_NORUNNING "norunning" // You walk!
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index bcdd5c85004..bd34ec85f86 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -80,6 +80,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/hudfix_method = TRUE //TRUE to change anchor to user, FALSE to shift by py_shift
var/py_shift = 0
var/entry_animation = TRUE
+ var/icon_path = 'icons/mob/radial.dmi' //SKYRAT EDIT ADDITION - GUNPOINT
//If we swap to vis_contens inventory these will need a redo
/datum/radial_menu/proc/check_screen_border(mob/user)
@@ -122,6 +123,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/elements_to_add = max_elements - elements.len
for(var/i in 1 to elements_to_add) //Create all elements
var/obj/screen/radial/slice/new_element = new /obj/screen/radial/slice
+ new_element.icon = icon_path //SKYRAT EDIT ADDITION - GUNPOINT
new_element.tooltips = use_tooltips
new_element.parent = src
elements += new_element
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 226a44f3c7b..e6f074c4e98 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -23,6 +23,7 @@
/obj/item/proc/attack_self(mob/user)
if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) & COMPONENT_NO_INTERACT)
return
+ SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK_SELF, src) //SKYRAT EDIT ADDITION - GUNPOINT
interact(user)
/**
diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm
index c02c9c86f3e..f09a42cdfed 100644
--- a/code/game/objects/items/devices/radio/headset.dm
+++ b/code/game/objects/items/devices/radio/headset.dm
@@ -61,7 +61,8 @@ GLOBAL_LIST_INIT(channel_tokens, list(
QDEL_NULL(keyslot2)
return ..()
-/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans, datum/language/language, list/message_mods)
+///obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans, datum/language/language, list/message_mods) - ORIGINAL
+/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans, datum/language/language, list/message_mods, direct = TRUE) //SKYRAT EDIT CHANGE - GUNPOINT
if (!listening)
return ITALICS | REDUCE_RANGE
return ..()
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 5de46b3ae22..dcad8df672b 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -185,7 +185,8 @@
recalculateChannels()
. = TRUE
-/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language, list/message_mods)
+///obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language, list/message_mods) - ORIGINAL
+/obj/item/radio/talk_into(atom/movable/M, message, channel, list/spans, datum/language/language, list/message_mods, direct = TRUE) //SKYRAT EDIT CHANGE - GUNPOINT
if(HAS_TRAIT(M, TRAIT_SIGN_LANG)) //Forces Sign Language users to wear the translation gloves to speak over radios
var/mob/living/carbon/mute = M
if(istype(mute))
@@ -203,6 +204,7 @@
spans = list(M.speech_span)
if(!language)
language = M.get_selected_language()
+ SEND_SIGNAL(M, COMSIG_MOVABLE_RADIO_TALK_INTO, src, message, channel, spans, language, direct) //SKYRAT EDIT ADDITION - GUNPOINT
INVOKE_ASYNC(src, .proc/talk_into_impl, M, message, channel, spans.Copy(), language, message_mods)
return ITALICS | REDUCE_RANGE
@@ -298,7 +300,7 @@
if (idx && (idx % 2) == (message_mods[RADIO_EXTENSION] == MODE_L_HAND))
return
- talk_into(speaker, raw_message, , spans, language=message_language)
+ talk_into(speaker, raw_message, , spans, language=message_language, direct=FALSE) //SKYRAT EDIT CHANGE - GUNPOINT
// Checks if this radio can receive on the given frequency.
/obj/item/radio/proc/can_receive(freq, level)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index c37f098337f..0615d925cce 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -428,6 +428,13 @@
"\[Add comment\]"), "")
else if(isobserver(user))
. += "Traits: [get_quirk_string(FALSE, CAT_QUIRK_ALL)]"
+ //SKYRAT EDIT ADDITION BEGIN - GUNPOINT
+ if(gunpointing)
+ . += "[t_He] [t_is] holding [gunpointing.target.name] at gunpoint with [gunpointing.aimed_gun.name]!\n"
+ if(length(gunpointed))
+ for(var/datum/gunpoint/GP in gunpointed)
+ . += "[GP.source.name] [GP.source.p_are()] holding [t_him] at gunpoint with [GP.aimed_gun.name]!\n"
+ //SKYRAT EDIT ADDITION END
//SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION
for(var/genital in list("penis", "testicles", "vagina", "breasts"))
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 607490e1b26..95ed02846f9 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -116,6 +116,22 @@
if(!(world.time % 5))
to_chat(src, "[L] is restraining [P], you cannot push past.")
return TRUE
+ //SKYRAT EDIT ADDITION BEGIN - GUNPOINT
+ if(L.gunpointed.len)
+ var/is_pointing = FALSE
+ for(var/datum/gunpoint/gp in L.gunpointed)
+ if(gp.source == src)
+ is_pointing = TRUE
+ break
+ if(!is_pointing)
+ if(!(world.time % 5))
+ to_chat(src, "[L] is being held at gunpoint, it's not wise to push him.")
+ return TRUE
+ if(L.gunpointing)
+ if(!(world.time % 5))
+ to_chat(src, "[L] is holding someone at gunpoint, you cannot push past.")
+ return TRUE
+ //SKYRAT EDIT ADDITION END
if(moving_diagonally)//no mob swap during diagonal moves.
return TRUE
@@ -1282,6 +1298,7 @@
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/limbless, multiplicative_slowdown = limbless_slowdown)
else
remove_movespeed_modifier(/datum/movespeed_modifier/limbless)
+ SEND_SIGNAL(src, COMSIG_LIVING_UPDATED_MOBILITY, mobility_flags) //SKYRAT EDIT ADDITION - GUNPOINT
///Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment, through update_mobility()
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 7714c32b124..d75260cad46 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -449,6 +449,11 @@
if(m_intent == MOVE_INTENT_RUN)
m_intent = MOVE_INTENT_WALK
else
+ //SKYRAT EDIT ADDITION BEGIN - GUNPOINT
+ if (HAS_TRAIT(src,TRAIT_NORUNNING))
+ to_chat(src, "You find yourself unable to run.")
+ return FALSE
+ //SKYRAT EDIT ADDITION END
m_intent = MOVE_INTENT_RUN
if(hud_used && hud_used.static_inventory)
for(var/obj/screen/mov_intent/selector in hud_used.static_inventory)
diff --git a/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint.dm b/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint.dm
new file mode 100644
index 00000000000..6a6c0b0fe59
--- /dev/null
+++ b/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint.dm
@@ -0,0 +1,54 @@
+/mob/living
+ ///Who is the person gunpointing at? If they are
+ var/datum/gunpoint/gunpointing
+ ///Who is gunpointing at our person?
+ var/list/gunpointed = list()
+ ///The effect from gunpointing, to make sure we dont create multiples
+ var/obj/effect/overlay/gunpoint_effect/gp_effect
+
+/mob/living/carbon/Move(atom/newloc, direct = 0)
+ . = ..()
+ if(gunpointing)
+ var/dir = get_dir(get_turf(gunpointing.source),get_turf(gunpointing.target))
+ if(dir)
+ setDir(dir)
+
+/mob/living/ShiftMiddleClickOn(atom/A)
+ var/obj/item/gun/G = get_active_held_item()
+ if(istype(G, /obj/item/gun))
+ DoGunpoint(A, G)
+ else
+ src.pointed(A)
+ return
+
+/mob/living/proc/DoGunpoint(atom/A, obj/item/gun/G)
+ if(A == src)
+ return
+ if(!isliving(A))
+ if(gunpointing) //Cancelling a gunpoint by shift + MMB on a non-mob
+ gunpointing.Destroy()
+ return
+ var/mob/living/L = A
+ if(gunpointing && L == gunpointing.target) //Cancelling a gunpoint by shift + MMB on the target
+ gunpointing.ClickDestroy()
+ else
+ if(!CanGunpointAt(L, TRUE))
+ return
+ if(gunpointing) //We're gunpoint and we already know we're locking onto a different, valid target
+ gunpointing.Destroy()
+ gunpointing = new(src, L, G)
+
+/mob/living/proc/CanGunpointAt(mob/living/L, notice = FALSE)
+ if(!(src.mobility_flags & MOBILITY_STAND))
+ if(notice)
+ to_chat(src, "You need to be standing to get a good aim!")
+ return FALSE
+ if(!(L in viewers(8, src)))
+ if(notice)
+ to_chat(src, "Your target is out of your view!")
+ return FALSE
+ if(L.alpha < 70)
+ if(notice)
+ to_chat(src, "You can't quite make out your target!")
+ return FALSE
+ return TRUE
diff --git a/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint_datum.dm b/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint_datum.dm
new file mode 100644
index 00000000000..c3d3f48f5b8
--- /dev/null
+++ b/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint_datum.dm
@@ -0,0 +1,261 @@
+/obj/effect/overlay/gunpoint_effect
+ icon = 'modular_skyrat/modules/gunpoint/icons/targeted.dmi'
+ icon_state = "locking"
+ layer = FLY_LAYER
+ plane = GAME_PLANE
+ appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
+ mouse_opacity = 0
+
+/datum/gunpoint
+ var/mob/living/source
+ var/mob/living/target
+ var/datum/radial_menu/gunpoint/gunpoint_gui
+ var/allow_move = FALSE
+ var/allow_radio = FALSE
+ var/allow_use = FALSE
+
+ var/obj/item/gun/aimed_gun
+
+ var/safeguard_time = 0
+ var/next_autoshot = 0
+ var/locked = FALSE
+ var/was_running = FALSE
+
+ var/moved_counter = 0
+
+ var/static/radio_forbid = image(icon = 'modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi', icon_state = "radial_radio_forbid")
+ var/static/radio_allow = image(icon = 'modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi', icon_state = "radial_radio")
+ var/static/use_forbid = image(icon = 'modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi', icon_state = "radial_use_forbid")
+ var/static/use_allow = image(icon = 'modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi', icon_state = "radial_use")
+ var/static/move_forbid = image(icon = 'modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi', icon_state = "radial_move_forbid")
+ var/static/move_allow = image(icon = 'modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi', icon_state = "radial_move")
+
+/datum/gunpoint/New(user, tar, gun)
+ source = user
+ source.gunpointing = src
+ target = tar
+ target.gunpointed += src
+ aimed_gun = gun
+
+ source.face_atom(target)
+ source.visible_message("[source.name] aims at [target.name] with the [aimed_gun.name]!")
+
+ was_running = (source.m_intent == MOVE_INTENT_RUN)
+ if(was_running)
+ source.toggle_move_intent()
+ ADD_TRAIT(source, TRAIT_NORUNNING, "gunpoint")
+
+ if(!target.gp_effect)
+ target.gp_effect = new
+ target.vis_contents += target.gp_effect
+
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/MovedReact)
+ RegisterSignal(source, COMSIG_MOVABLE_MOVED, .proc/SourceMoved)
+
+ RegisterSignal(target, COMSIG_MOB_FIRED_GUN, .proc/UseReact)
+
+ RegisterSignal(source, COMSIG_LIVING_STATUS_STUN, .proc/SourceCC)
+ RegisterSignal(source, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/SourceCC)
+ RegisterSignal(source, COMSIG_LIVING_STATUS_PARALYZE, .proc/SourceCC)
+ RegisterSignal(source, COMSIG_LIVING_UPDATED_MOBILITY, .proc/SourceUpdatedMobility)
+
+ RegisterSignal(aimed_gun, COMSIG_ITEM_EQUIPPED,.proc/ClickDestroy)
+ RegisterSignal(aimed_gun, COMSIG_ITEM_DROPPED,.proc/ClickDestroy)
+
+ RegisterSignal(target, COMSIG_MOVABLE_RADIO_TALK_INTO,.proc/RadioReact)
+
+ RegisterSignal(target, COMSIG_MOB_ITEM_ATTACK, .proc/UseReact)
+ RegisterSignal(target, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/MeleeAttackReact)
+ RegisterSignal(target, COMSIG_MOB_ITEM_ATTACK_SELF, .proc/UseReact)
+ RegisterSignal(target, COMSIG_MOB_ITEM_AFTERATTACK, .proc/UseReact)
+
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/Destroy)
+ RegisterSignal(source, COMSIG_PARENT_QDELETING, .proc/Destroy)
+
+
+ addtimer(CALLBACK(src, .proc/LockOn), 7)
+
+/datum/gunpoint/proc/MeleeAttackReact(datum/source_datum, atom/target)
+ if(!CheckContinuity())
+ Destroy()
+ return
+ if(!allow_use && CanReact())
+ source.log_message("[source] shot [target] because they attacked/disarmed/pulled", LOG_ATTACK)
+ to_chat(source, "You pull the trigger instinctively to [target.name] actions!")
+ ShootTarget()
+
+/datum/gunpoint/proc/LockOn()
+ if(src) //if we're not present then locking on failed and this datum is deleted
+ if(!CheckContinuity())
+ Destroy()
+ return
+ locked = TRUE
+ log_combat(target, source, "locked onto with aiming")
+ playsound(get_turf(source), 'modular_skyrat/modules/gunpoint/sound/targeton.ogg', 50,1)
+ to_chat(source, "You lock onto [target.name]!")
+ target.visible_message("[source.name] holds [target.name] at gunpoint with the [aimed_gun.name]!", "[source.name] holds you at gunpoint with the [aimed_gun.name]!")
+ if(target.gunpointed.len == 1)//First case
+ to_chat(target, "You'll get shot if you use radio, move or interact with items!")
+ to_chat(target, "You can however take items out, toss harmless items or drop them.")
+ var/list/choice_list = ConstructChoiceList()
+ gunpoint_gui = show_radial_menu_gunpoint(source, target , choice_list, select_proc = CALLBACK(src, .proc/GunpointGuiReact, source), radius = 42)
+ if(target.gp_effect.icon_state != "locked")
+ target.gp_effect.icon_state = "locked"
+ safeguard_time = world.time + 30
+
+/datum/gunpoint/proc/CheckContinuity()
+ if(!target)
+ return FALSE
+ if(source.CanGunpointAt(target))
+ source.face_atom(target)
+ return TRUE
+ return FALSE
+
+/datum/gunpoint/proc/CanReact()
+ if(locked && (world.time >= safeguard_time) && !(target.stat == DEAD || target.stat == HARD_CRIT || target.stat == SOFT_CRIT) && source.next_move <= world.time && next_autoshot <= world.time)
+ return TRUE
+ return FALSE
+
+/datum/gunpoint/Destroy()
+ UnregisterSignal(aimed_gun, list(COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED))
+ UnregisterSignal(target, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_ITEM_AFTERATTACK, COMSIG_MOB_ITEM_ATTACK_SELF, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ITEM_ATTACK, COMSIG_MOVABLE_RADIO_TALK_INTO, COMSIG_MOB_FIRED_GUN, COMSIG_MOVABLE_MOVED))
+ UnregisterSignal(source, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED, COMSIG_LIVING_STATUS_STUN, COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_LIVING_STATUS_PARALYZE, COMSIG_LIVING_UPDATED_MOBILITY))
+
+ REMOVE_TRAIT(source, TRAIT_NORUNNING, "gunpoint")
+ if(was_running)
+ source.toggle_move_intent()
+ if(target.gunpointed.len == 1) //Last instance being deleted
+ target.vis_contents -= target.gp_effect
+ QDEL_NULL(target.gp_effect)
+ target.gunpointed -= src
+ source.gunpointing = null
+ if(locked)
+ QDEL_NULL(gunpoint_gui)
+ target.visible_message("[source.name] no longer holds [target.name] at gunpoint.", "[source.name] no longer holds you at gunpoint.")
+ source = null
+ target = null
+ aimed_gun = null
+ return ..()
+
+/datum/gunpoint/proc/ClickDestroy()
+ if(locked)
+ playsound(get_turf(source), 'modular_skyrat/modules/gunpoint/sound/targetoff.ogg', 50,1)
+ Destroy()
+
+/datum/gunpoint/proc/SourceCC(datum/source, amount, update, ignore)
+ if(amount && !ignore)
+ Destroy()
+
+/datum/gunpoint/proc/ShootTarget()
+ next_autoshot = world.time + 5
+ log_combat(target, source, "auto-shot with aim")
+ aimed_gun.afterattack(target, source)
+
+/datum/gunpoint/proc/RadioReact(datum/datum_source, obj/item/radio/radio, message, channel, list/spans, datum/language/language, direct)
+ if(!allow_radio && CanReact())
+ if(direct)
+ source.log_message("[source] shot [target] because they spoke on radio", LOG_ATTACK)
+ to_chat(source, "You pull the trigger instinctively as [target.name] speaks on the radio!")
+ ShootTarget()
+
+/datum/gunpoint/proc/MovedReact(datum/datum_source, atom/moved, direction, forced)
+ if(!CheckContinuity())
+ Destroy()
+ return
+ if(!allow_move && CanReact() && !(target.pulledby && target.pulledby == source)) //Don't shoot him if we're pulling them
+ MovedShootProc()
+
+/datum/gunpoint/proc/MovedShootProc() //This exists in case of someone moving several tiles in one tick, such as dashes or diagonal movement
+ moved_counter += 1
+ var/count = moved_counter
+ sleep(1)
+ if(src && count == moved_counter)
+ source.log_message("[source] shot [target] because they moved", LOG_ATTACK)
+ ShootTarget()
+
+/datum/gunpoint/proc/UseReact(datum/datum_source)
+ if(!CheckContinuity())
+ Destroy()
+ return
+ if(!allow_use && CanReact())
+ source.log_message("[source] shot [target] because they used an item", LOG_ATTACK)
+ to_chat(source, "You pull the trigger instinctively as [target.name] uses an item!")
+ ShootTarget()
+
+/datum/gunpoint/proc/SourceMoved(datum/datum_source)
+ if(!CheckContinuity())
+ Destroy()
+
+/datum/gunpoint/proc/SourceUpdatedMobility()
+ if(!(source.mobility_flags & MOBILITY_STAND))
+ Destroy()
+
+/datum/gunpoint/proc/ConstructChoiceList()
+ var/image/radio_image = (allow_radio ? radio_allow : radio_forbid)
+ var/image/use_image = (allow_use ? use_allow : use_forbid)
+ var/image/move_image = (allow_move ? move_allow : move_forbid)
+ var/list/L = list("radio" = radio_image, "use" = use_image, "move" = move_image)
+ return L
+
+/datum/gunpoint/proc/GunpointGuiReact(mob/living/user,choice)
+ switch(choice)
+ if("radio")
+ allow_radio = !allow_radio
+ if(allow_radio)
+ var/safe = TRUE
+ for(var/datum/gunpoint/gp in target.gunpointed)
+ if(gp.allow_radio == FALSE)
+ safe = FALSE
+ break
+ if(safe)
+ to_chat(target, "[source.name] signals you can use radio.")
+ else
+ to_chat(target, "[source.name] signals you can use radio, however other people still don't")
+ else
+ var/forbid_counts = 0
+ for(var/datum/gunpoint/gp in target.gunpointed)
+ if(gp.allow_radio == FALSE)
+ forbid_counts += 1
+ if(forbid_counts == 1) //Only first one warns the victim
+ to_chat(target, "[source.name] signals you can't use radio.")
+ if("use")
+ allow_use = !allow_use
+ if(allow_use)
+ var/safe = TRUE
+ for(var/datum/gunpoint/gp in target.gunpointed)
+ if(gp.allow_use == FALSE)
+ safe = FALSE
+ break
+ if(safe)
+ to_chat(target, "[source.name] signals you can interact with items.")
+ else
+ to_chat(target, "[source.name] signals you can interact with items, however other people still don't")
+ else
+ var/forbid_counts = 0
+ for(var/datum/gunpoint/gp in target.gunpointed)
+ if(gp.allow_use == FALSE)
+ forbid_counts += 1
+ if(forbid_counts == 1) //Only first one warns the victim
+ to_chat(target, "[source.name] signals you can't interact with items.")
+ if("move")
+ allow_move = !allow_move
+ if(allow_move)
+ var/safe = TRUE
+ for(var/datum/gunpoint/gp in target.gunpointed)
+ if(gp.allow_move == FALSE)
+ safe = FALSE
+ break
+ if(safe)
+ to_chat(target, "[source.name] signals you can move.")
+ else
+ to_chat(target, "[source.name] signals you can move, however other people still don't")
+ else
+ var/forbid_counts = 0
+ for(var/datum/gunpoint/gp in target.gunpointed)
+ if(gp.allow_move == FALSE)
+ forbid_counts += 1
+ if(forbid_counts == 1) //Only first one warns the victim
+ to_chat(target, "[source.name] signals you can't move.")
+ var/list/new_choices = ConstructChoiceList()
+ gunpoint_gui.entry_animation = FALSE
+ gunpoint_gui.change_choices(new_choices,FALSE)
diff --git a/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint_radial.dm b/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint_radial.dm
new file mode 100644
index 00000000000..f21ca00191c
--- /dev/null
+++ b/modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint_radial.dm
@@ -0,0 +1,45 @@
+/datum/radial_menu/gunpoint
+ var/uniqueid
+ var/datum/callback/select_proc_callback
+ icon_path = 'modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi'
+
+/datum/radial_menu/gunpoint/New()
+
+
+/datum/radial_menu/gunpoint/element_chosen(choice_id,mob/user)
+ select_proc_callback.Invoke(choices_values[choice_id])
+
+
+/datum/radial_menu/gunpoint/proc/change_choices(list/newchoices, tooltips)
+ if(!newchoices.len)
+ return
+ Reset()
+ set_choices(newchoices,tooltips)
+
+/datum/radial_menu/gunpoint/Destroy()
+ QDEL_NULL(select_proc_callback)
+ GLOB.radial_menus -= uniqueid
+ Reset()
+ hide()
+ . = ..()
+
+/proc/show_radial_menu_gunpoint(mob/user, atom/anchor, list/choices, datum/callback/select_proc, uniqueid, radius, tooltips = FALSE)
+ if(!user || !anchor || !length(choices) || !select_proc)
+ return
+ if(!uniqueid)
+ uniqueid = "defmenu_[REF(user)]_[REF(anchor)]"
+
+ if(GLOB.radial_menus[uniqueid])
+ return
+
+ var/datum/radial_menu/gunpoint/menu = new
+ menu.uniqueid = uniqueid
+ GLOB.radial_menus[uniqueid] = menu
+ if(radius)
+ menu.radius = radius
+ menu.select_proc_callback = select_proc
+ menu.anchor = anchor
+ menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud
+ menu.set_choices(choices, tooltips)
+ menu.show_to(user)
+ return menu
diff --git a/modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi b/modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi
new file mode 100644
index 00000000000..71f4f4edb73
Binary files /dev/null and b/modular_skyrat/modules/gunpoint/icons/radial_gunpoint.dmi differ
diff --git a/modular_skyrat/modules/gunpoint/icons/targeted.dmi b/modular_skyrat/modules/gunpoint/icons/targeted.dmi
new file mode 100644
index 00000000000..71d17cbcdef
Binary files /dev/null and b/modular_skyrat/modules/gunpoint/icons/targeted.dmi differ
diff --git a/modular_skyrat/modules/gunpoint/readme.md b/modular_skyrat/modules/gunpoint/readme.md
new file mode 100644
index 00000000000..1ff911ed860
--- /dev/null
+++ b/modular_skyrat/modules/gunpoint/readme.md
@@ -0,0 +1,36 @@
+## Title: Gunpoint
+
+MODULE ID: GUNPOINT
+
+### Description:
+
+Allows anyone with a gun to use SHIFT + MMB to hold someone at gunpoint, much like how baystation does it, but in a more visually clear way
+
+### TG Proc Changes:
+
+ ./modular_skyrat/modules/gunpoint/code/datum/gunpoint/gunpoint.dm > /mob/living/ShiftMiddleClickOn() > CHILD PROC
+ ./code/_onclick/hud/radial.dm > /datum/radial_menu/proc/setup_menu()
+ ./code/_onclick/item_attack.dm > /obj/item/proc/attack_self()
+ ./code/game/objects/items/devices/radio/radio.dm > /obj/item/radio/talk_into()
+ ./code/game/objects/items/devices/radio/headset.dm > /obj/item/radio/headset/talk_into()
+ ./code/modules/mob/living/carbon/human/examine.dm > /mob/living/carbon/human/examine()
+ ./code/modules/mob/living/living.dm > /mob/living/proc/update_mobility()
+ ./code/modules/mob/living/living.dm > /mob/living/proc/MobBump()
+ ./code/modules/mob/mob_movement.dm > /mob/proc/toggle_move_intent()
+
+### Defines:
+
+ ./code/_onclick/hud/radial.dm > /datum/radial_menu - var/icon_path
+ ./code/__DEFINES/~skyrat_defines/signals.dm - COMSIG_MOVABLE_RADIO_TALK_INTO, COMSIG_MOB_ITEM_ATTACK_SELF, COMSIG_LIVING_UPDATED_MOBILITY
+ ./code/__DEFINES/~skyrat_defines/traits.dm - TRAIT_NORUNNING
+
+### Master file additions
+
+- N/A
+
+### Included files that are not contained in this module:
+
+- N/A
+
+### Credits:
+Azarak - original code & porting
diff --git a/modular_skyrat/modules/gunpoint/sound/targetoff.ogg b/modular_skyrat/modules/gunpoint/sound/targetoff.ogg
new file mode 100644
index 00000000000..b8657aab27e
Binary files /dev/null and b/modular_skyrat/modules/gunpoint/sound/targetoff.ogg differ
diff --git a/modular_skyrat/modules/gunpoint/sound/targeton.ogg b/modular_skyrat/modules/gunpoint/sound/targeton.ogg
new file mode 100644
index 00000000000..93a2f68f326
Binary files /dev/null and b/modular_skyrat/modules/gunpoint/sound/targeton.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 4ba38750558..e675acfe1bc 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -131,6 +131,8 @@
#include "code\__DEFINES\dcs\signals.dm"
#include "code\__DEFINES\research\anomalies.dm"
#include "code\__DEFINES\~skyrat_defines\blueshield.dm"
+#include "code\__DEFINES\~skyrat_defines\signals.dm"
+#include "code\__DEFINES\~skyrat_defines\traits.dm"
#include "code\__DEFINES\~skyrat_defines\DNA.dm"
#include "code\__DEFINES\~skyrat_defines\obj_flags.dm"
#include "code\__DEFINES\~skyrat_defines\say.dm"
@@ -3272,6 +3274,9 @@
#include "modular_skyrat\modules\blueshield\code\modules\clothing\under\jobs\security.dm"
#include "modular_skyrat\modules\blueshield\code\modules\clothing\under\jobs\Plasmaman\security.dm"
#include "modular_skyrat\modules\blueshield\code\modules\jobs\job_types\blueshield.dm"
+#include "modular_skyrat\modules\gunpoint\code\datum\gunpoint\gunpoint.dm"
+#include "modular_skyrat\modules\gunpoint\code\datum\gunpoint\gunpoint_datum.dm"
+#include "modular_skyrat\modules\gunpoint\code\datum\gunpoint\gunpoint_radial.dm"
#include "modular_skyrat\modules\blueshield\code\modules\projectiles\guns\energy\special.dm"
#include "modular_skyrat\modules\customization\__DEFINES\inventory.dm"
#include "modular_skyrat\modules\customization\__DEFINES\lists.dm"