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