diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index 59c80b87ed849..8a9a468055691 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -469,7 +469,7 @@
if(show_in_chat)
to_chat(viewer, "[text].")
-/atom/proc/balloon_alert_to_viewers(message, self_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs)
+/atom/proc/balloon_alert_to_viewers(message, self_message, vision_distance = DEFAULT_MESSAGE_RANGE, list/ignored_mobs, show_in_chat = TRUE)
var/list/hearers = get_hearers_in_view(vision_distance, src)
hearers -= ignored_mobs
@@ -477,7 +477,7 @@
if (hearer.is_blind())
continue
- balloon_alert(hearer, (hearer == src && self_message) || message)
+ balloon_alert(hearer, (hearer == src && self_message) || message, show_in_chat = show_in_chat)
/datum/chatmessage/balloon_alert
tgt_color = "#ffffff" //default color
diff --git a/code/datums/components/aiming.dm b/code/datums/components/aiming.dm
index 4fbc1b08f03c0..a7f53dc4da89a 100644
--- a/code/datums/components/aiming.dm
+++ b/code/datums/components/aiming.dm
@@ -1,5 +1,10 @@
// Aiming component, ported from NSV
+// Defines for aiming "levels"
+#define GUN_NOT_AIMED 0
+#define GUN_AIMED 1
+#define GUN_AIMED_POINTBLANK 2
+
// Defines for stages and radial choices
#define START "start"
#define RAISE_HANDS "raise_hands"
@@ -9,6 +14,8 @@
#define SHOOT "shoot"
#define SURRENDER "surrender"
#define IGNORE "ignore"
+#define POINTBLANK "pointblank"
+#define LET_GO "let_go"
/datum/component/aiming
can_transfer = FALSE
@@ -17,6 +24,7 @@
var/obj/item/target_held = null
var/datum/radial_menu/persistent/choice_menu // Radial menu for the user
var/datum/radial_menu/persistent/choice_menu_target // Radial menu for the target
+ var/holding_at_gunpoint = FALSE // used for boosting shot damage
COOLDOWN_DECLARE(aiming_cooldown) // 5 second cooldown so you can't spam aiming for faster bullets
COOLDOWN_DECLARE(voiceline_cooldown) // 2 seconds, prevents spamming commands
COOLDOWN_DECLARE(notification_cooldown) // 5 seconds, prevents spamming the equip notification/sound
@@ -131,6 +139,21 @@ Methods to alert the aimer about events (Surrendering/equipping an item/dropping
user.balloon_alert(user, "You can't see [target] anymore!")
stop_aiming()
+/datum/component/aiming/proc/on_resist()
+ SIGNAL_HANDLER
+ target.balloon_alert(user, "Tries to break free!")
+
+/datum/component/aiming/proc/stop_holding()
+ SIGNAL_HANDLER
+ var/obj/item/held = user.get_active_held_item()
+ user.visible_message("[user] stops holding \the [held] at [target]'s temple!", \
+ "You stop holding \the [held] at [target]'s temple", ignored_mobs = list(target))
+ to_chat(target, "[user] stops holding \the [held] at your temple!")
+ user.balloon_alert_to_viewers("Lets go of [target]", "Let go of [target]", ignored_mobs = list(target), show_in_chat = FALSE)
+ user.balloon_alert(target, "[user] Lets go of you", show_in_chat = FALSE)
+ remove_pointblank()
+ show_ui(user, target, START)
+
/**
Method to show a radial menu to the person who's aiming, in stages:
@@ -148,16 +171,26 @@ AIMING_DROP_WEAPON means they selected the "drop your weapon" command
if(START)
possible_actions += RAISE_HANDS
possible_actions += DROP_WEAPON
+ possible_actions += POINTBLANK
if(RAISE_HANDS)
possible_actions += DROP_TO_FLOOR
possible_actions += RAISE_HANDS
+ possible_actions += POINTBLANK
if(DROP_WEAPON)
possible_actions += DROP_TO_FLOOR
possible_actions += DROP_WEAPON
possible_actions += RAISE_HANDS
+ possible_actions += POINTBLANK
if(DROP_TO_FLOOR)
possible_actions += DROP_TO_FLOOR
possible_actions += DROP_WEAPON
+ possible_actions += POINTBLANK
+ if(LET_GO)
+ possible_actions += RAISE_HANDS
+ possible_actions += DROP_WEAPON
+ possible_actions += POINTBLANK
+ if(holding_at_gunpoint)
+ possible_actions += LET_GO
for(var/option in possible_actions)
options[option] = image(icon = 'icons/effects/aiming.dmi', icon_state = option)
if(choice_menu)
@@ -187,7 +220,10 @@ AIMING_DROP_WEAPON means they selected the "drop your weapon" command
stop_aiming()
return
if(SHOOT)
- shoot()
+ if(holding_at_gunpoint)
+ shoot(4)
+ else
+ shoot()
return
if(RAISE_HANDS)
user.say(pick("Put your hands above your head!", "Hands! Now!", "Hands up!"), forced = "Weapon aiming")
@@ -195,10 +231,64 @@ AIMING_DROP_WEAPON means they selected the "drop your weapon" command
user.say(pick("Drop your weapon!", "Weapon down! Now!", "Drop it!"), forced = "Weapon aiming")
if(DROP_TO_FLOOR)
user.say(pick("On the ground! Now!", "Lie down and place your hands behind your head!", "Get down on the ground!"), forced = "Weapon aiming")
+ if(POINTBLANK)
+ if(get_dist(target, user) > 1)
+ to_chat(user, "You need to be closer to [target] to hold [target.p_them()] at gunpoint!")
+ return
+ if(isdead(target))
+ to_chat(user, "You can't hold dead things at gunpoint!")
+ return
+ var/obj/item/held = user.get_active_held_item()
+ if(!held)
+ to_chat(user, "You can't hold someone at gunpoint with an empty hand!")
+ return
+ if(!user.pulling || user.pulling != target)
+ to_chat(user, "You start to grab \the [target].")
+ to_chat(target, "[user] starts to grab you!")
+ if(!do_after(user, 2 SECONDS, target))
+ to_chat(user, "You start to strengthen your grip on [target].")
+ to_chat(target, "[user] starts to strengthen their grip on you!")
+ if(!do_after(user, 2 SECONDS, target))
+ to_chat(user, "[user] lines up \the [held] with [target]'s temple!", \
+ "You line up \the [held] with [target]'s temple", ignored_mobs = list(target))
+ to_chat(target, "[user] lines up \the [held] with your temple!")
+ user.balloon_alert_to_viewers("Holds [target] at gunpoint!", "Holding [target] at gunpoint!", ignored_mobs = list(target), show_in_chat = FALSE)
+ user.balloon_alert(target, "Holds you at gunpoint!", show_in_chat = FALSE)
+ setup_pointblank()
+ if(LET_GO)
+ stop_holding()
aim_react(target)
COOLDOWN_START(src, voiceline_cooldown, 2 SECONDS)
show_ui(user, target, choice)
+/datum/component/aiming/proc/setup_pointblank()
+ holding_at_gunpoint = TRUE
+ RegisterSignal(src.target, COMSIG_LIVING_RESIST, PROC_REF(on_resist))
+ RegisterSignal(src.target, COMSIG_MOVABLE_NO_LONGER_PULLED, PROC_REF(stop_holding))
+
+/datum/component/aiming/proc/remove_pointblank()
+ if(!holding_at_gunpoint)
+ return
+ holding_at_gunpoint = FALSE
+ UnregisterSignal(src.target, COMSIG_LIVING_RESIST)
+ UnregisterSignal(src.target, COMSIG_MOVABLE_NO_LONGER_PULLED)
+
/datum/component/aiming/proc/shoot()
var/obj/item/held = user.get_active_held_item()
if(held != parent)
@@ -206,7 +296,10 @@ AIMING_DROP_WEAPON means they selected the "drop your weapon" command
return FALSE
if(istype(parent, /obj/item/gun)) // If we have a gun, shoot it at the target
var/obj/item/gun/G = parent
- G.afterattack(target, user, null, null, TRUE)
+ if(holding_at_gunpoint)
+ G.afterattack(target, user, null, null, GUN_AIMED_POINTBLANK)
+ else
+ G.afterattack(target, user, null, null, GUN_AIMED)
stop_aiming()
return TRUE
if(isitem(parent)) // Otherwise, just wave it at them
@@ -227,6 +320,7 @@ AIMING_DROP_WEAPON means they selected the "drop your weapon" command
// Clean up the menu if it's still open
QDEL_NULL(choice_menu)
QDEL_NULL(choice_menu_target)
+ remove_pointblank()
target = null
/datum/component/aiming/proc/aim_react(mob/target)
@@ -269,3 +363,5 @@ AIMING_DROP_WEAPON means they selected the "drop your weapon" command
#undef SHOOT
#undef SURRENDER
#undef IGNORE
+#undef POINTBLANK
+#undef LET_GO
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 95b1c2af7f007..a390265072591 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -277,10 +277,19 @@
if(clumsy_check)
if(istype(user))
if (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40))
- to_chat(user, "You shoot yourself in the foot with [src]!")
- var/shot_leg = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
- process_fire(user, user, FALSE, params, shot_leg)
- user.dropItemToGround(src, TRUE)
+ if(aimed == GUN_AIMED_POINTBLANK)
+ to_chat(user, "In a cruel twist of fate you fumble your grip and accidentally shoot yourself in the head!")
+ process_fire(user, user, FALSE, params, BODY_ZONE_HEAD)
+ user.dropItemToGround(src, TRUE)
+ if(chambered.harmful)
+ var/obj/item/organ/brain/target_brain = user.getorganslot(ORGAN_SLOT_BRAIN)
+ target_brain.Remove(user) //Rip you, unlucky
+ target_brain.forceMove(get_turf(user))
+ else
+ to_chat(user, "You shoot yourself in the foot with [src]!")
+ var/shot_leg = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ process_fire(user, user, FALSE, params, shot_leg)
+ user.dropItemToGround(src, TRUE)
return
if(weapon_weight == WEAPON_HEAVY && !is_wielded)
@@ -300,7 +309,11 @@
loop_counter++
addtimer(CALLBACK(G, TYPE_PROC_REF(/obj/item/gun, process_fire), target, user, TRUE, params, null, bonus_spread, flag), loop_counter)
- process_fire(target, user, TRUE, params, null, bonus_spread, aimed)
+ var/zone_override = null
+ if(aimed == GUN_AIMED_POINTBLANK)
+ zone_override = BODY_ZONE_HEAD //Shooting while pressed against someone's temple
+
+ process_fire(target, user, TRUE, params, zone_override, bonus_spread, aimed)
/obj/item/gun/can_trigger_gun(mob/living/user)
. = ..()
@@ -695,9 +708,12 @@
//Happens before the actual projectile creation
/obj/item/gun/proc/before_firing(atom/target, mob/user, aimed)
- if(aimed && chambered?.BB)
+ if(aimed == GUN_AIMED && chambered?.BB)
chambered.BB.speed = initial(chambered.BB.speed) * 0.75 // Faster bullets to account for the fact you've given the target a big warning they're about to be shot
chambered.BB.damage = initial(chambered.BB.damage) * 1.25
+ if(aimed == GUN_AIMED_POINTBLANK)
+ chambered.BB.speed = initial(chambered.BB.speed) * 0.25 // Much faster bullets because you're holding them literally at the barrel of the gun
+ chambered.BB.damage = initial(chambered.BB.damage) * 4 // Execution
/////////////
// ZOOMING //
diff --git a/icons/effects/aiming.dmi b/icons/effects/aiming.dmi
index 55df5abc37241..0b46a1f7ee219 100644
Binary files a/icons/effects/aiming.dmi and b/icons/effects/aiming.dmi differ