Freeze! Adds holding people up with guns. (#47677)

About The Pull Request

Attacking someone with a gun at point blank with grab intent will now hold them at gunpoint. This means you will automatically shoot the target if they move or attack, though they're still free to rummage around in their (or your, if you continue standing next to them) inventory, speak (including using radios), and throw things. The shooter may roam around a 3 tile box radius without breaking the hold-up, allowing them to reposition themselves, though breaking visibility will break the hold-up. In addition, if you're several tiles away and they make a break for it, they may be able to dodge the projectile. Tactical choices!


For the first 3 seconds after the hold-up, any triggered reaction shot will deal normal damage.
Between 3-10 seconds after, any triggered reaction shot will deal double damage.
After 10 seconds, the reaction shot will deal 2.5x damage.

The ramp-up is to give weapons with weaker per-shot damage like disablers, laser guns, and the .38 the ability to be useful for arrests and stick ups, without affecting combat balance too much. If you got someone you're trying to kill to hold still for 2 seconds at point blank range, you may as well just keep shooting them rather than fiddle with holding them up. If you're gonna resist, it's best to do it quickly!

Lastly, if the shooter is attacked and takes damage from anything, they have a chance to flinch and fire in reaction. This is a 50% chance, unless they're hit in the arm holding the gun, in which case it's an 80% chance.

Why It's Good For The Game

Adds in mechanical backing for taking people hostage or being able to arrest people with the threat of violence rather than actually carrying out that violence. Trying to take hostages or hold people up right now will just get you immediately shove spammed mid-"DROP $500 OR FAILRP" unless the other person feels like playing along. This makes cooperating or risking getting shot an actual choice, increasing mechanical depth while also adding more roleplay potential.

Also yes, the target cuffing themselves will not trigger the reaction shot, so holding someone up, backing up, and throwing cuffs at them is a valid move.
This commit is contained in:
Ryll Ryll
2019-11-28 03:30:52 -05:00
committed by oranges
parent 44bce6aa2a
commit 85de1066f0
9 changed files with 213 additions and 11 deletions

View File

@@ -131,6 +131,8 @@
#define COMPONENT_NO_ATTACK_HAND 1 //works on all 3. #define COMPONENT_NO_ATTACK_HAND 1 //works on all 3.
//This signal return value bitflags can be found in __DEFINES/misc.dm //This signal return value bitflags can be found in __DEFINES/misc.dm
#define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" //called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) #define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" //called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels)
#define COMSIG_ATOM_START_PULL "movable_start_pull" //called on a movable (NOT living) when someone starts pulling it (atom/movable/puller, state, force)
#define COMSIG_LIVING_START_PULL "living_start_pull" //called on /living when someone starts pulling it (atom/movable/puller, state, force)
///////////////// /////////////////
@@ -303,6 +305,9 @@
// /obj/item/pen signals // /obj/item/pen signals
#define COMSIG_PEN_ROTATED "pen_rotated" //called after rotation in /obj/item/pen/attack_self(): (rotation, mob/living/carbon/user) #define COMSIG_PEN_ROTATED "pen_rotated" //called after rotation in /obj/item/pen/attack_self(): (rotation, mob/living/carbon/user)
// /obj/item/gun signals
#define COMSIG_MOB_FIRED_GUN "mob_fired_gun" //called in /obj/item/gun/process_fire (user, target, params, zone_override)
// /obj/projectile signals (sent to the firer) // /obj/projectile signals (sent to the firer)
#define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" // from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle) #define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" // from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle)
#define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" // from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target) #define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" // from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target)

View File

@@ -113,6 +113,9 @@
#define STATUS_EFFECT_BOUNTY /datum/status_effect/bounty //rewards the person who added this to the target with refreshed spells and a fair heal #define STATUS_EFFECT_BOUNTY /datum/status_effect/bounty //rewards the person who added this to the target with refreshed spells and a fair heal
#define STATUS_EFFECT_HELDUP /datum/status_effect/heldup // someone is currently pointing a gun at you
#define STATUS_EFFECT_HOLDUP /datum/status_effect/holdup // you are currently pointing a gun at someone
///////////// /////////////
// SLIME // // SLIME //
///////////// /////////////

View File

@@ -0,0 +1,153 @@
#define GUNPOINT_SHOOTER_STRAY_RANGE 2
#define GUNPOINT_DELAY_STAGE_2 25
#define GUNPOINT_DELAY_STAGE_3 75 // cumulative with past stages, so 100 deciseconds
#define GUNPOINT_MULT_STAGE_1 1
#define GUNPOINT_MULT_STAGE_2 2
#define GUNPOINT_MULT_STAGE_3 2.5
/datum/component/gunpoint
dupe_mode = COMPONENT_DUPE_UNIQUE
var/mob/living/target
var/obj/item/gun/weapon
var/stage = 1
var/damage_mult = GUNPOINT_MULT_STAGE_1
var/point_of_no_return = FALSE
// *extremely bad russian accent* no!
/datum/component/gunpoint/Initialize(mob/living/targ, obj/item/gun/wep)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
var/mob/living/shooter = parent
target = targ
weapon = wep
RegisterSignal(targ, list(COMSIG_MOB_ATTACK_HAND, COMSIG_MOB_ITEM_ATTACK, COMSIG_MOVABLE_MOVED, COMSIG_MOB_FIRED_GUN), .proc/trigger_reaction)
RegisterSignal(weapon, list(COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED), .proc/cancel)
shooter.visible_message("<span class='danger'>[shooter] aims [weapon] point blank at [target]!</span>", \
"<span class='danger'>You aim [weapon] point blank at [target]!</span>", target)
to_chat(target, "<span class='userdanger'>[shooter] aims [weapon] point blank at you!</span>")
shooter.apply_status_effect(STATUS_EFFECT_HOLDUP)
target.apply_status_effect(STATUS_EFFECT_HELDUP)
/*
if(target.job == "Captain" && target.stat == CONSCIOUS && is_nuclear_operative(shooter))
if(istype(weapon, /obj/item/gun/ballistic/rocketlauncher) && weapon.chambered)
shooter.client.give_award(/datum/award/achievement/misc/rocket_holdup, shooter)
*/
target.do_alert_animation(target)
target.playsound_local(target.loc, 'sound/machines/chime.ogg', 50, TRUE)
addtimer(CALLBACK(src, .proc/update_stage, 2), GUNPOINT_DELAY_STAGE_2)
/datum/component/gunpoint/Destroy(force, silent)
var/mob/living/shooter = parent
shooter.remove_status_effect(STATUS_EFFECT_HOLDUP)
target.remove_status_effect(STATUS_EFFECT_HELDUP)
return ..()
/datum/component/gunpoint/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/check_deescalate)
RegisterSignal(parent, COMSIG_MOB_APPLY_DAMGE, .proc/flinch)
RegisterSignal(parent, COMSIG_MOB_ATTACK_HAND, .proc/check_shove)
RegisterSignal(parent, list(COMSIG_LIVING_START_PULL, COMSIG_MOVABLE_BUMP), .proc/check_bump)
/datum/component/gunpoint/UnregisterFromParent()
UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
UnregisterSignal(parent, COMSIG_MOB_APPLY_DAMGE)
UnregisterSignal(parent, COMSIG_MOB_ATTACK_HAND)
UnregisterSignal(parent, list(COMSIG_LIVING_START_PULL, COMSIG_MOVABLE_BUMP))
/datum/component/gunpoint/proc/check_bump(atom/B, atom/A)
var/mob/living/T = A
if(T && T == target)
var/mob/living/shooter = parent
shooter.visible_message("<span class='danger'>[shooter] bumps into [target] and fumbles [shooter.p_their()] aim!</span>", \
"<span class='danger'>You bump into [target] and fumble your aim!</span>", target)
to_chat(target, "<span class='userdanger'>[shooter] bumps into you and fumbles [shooter.p_their()] aim!</span>")
qdel(src)
/datum/component/gunpoint/proc/check_shove(mob/living/carbon/shooter, mob/shooter_again, mob/living/T)
if(T == target && (shooter.a_intent == INTENT_DISARM || shooter.a_intent == INTENT_GRAB))
shooter.visible_message("<span class='danger'>[shooter] bumps into [target] and fumbles [shooter.p_their()] aim!</span>", \
"<span class='danger'>You bump into [target] and fumble your aim!</span>", target)
to_chat(target, "<span class='userdanger'>[shooter] bumps into you and fumbles [shooter.p_their()] aim!</span>")
qdel(src)
// if you're gonna try to break away from a holdup, better to do it right away
/datum/component/gunpoint/proc/update_stage(new_stage)
stage = new_stage
if(stage == 2)
to_chat(parent, "<span class='danger'>You steady [weapon] on [target].</span>")
to_chat(target, "<span class='userdanger'>[parent] has steadied [weapon] on you!</span>")
damage_mult = GUNPOINT_MULT_STAGE_2
addtimer(CALLBACK(src, .proc/update_stage, 3), GUNPOINT_DELAY_STAGE_3)
else if(stage == 3)
to_chat(parent, "<span class='danger'>You have fully steadied [weapon] on [target].</span>")
to_chat(target, "<span class='userdanger'>[parent] has fully steadied [weapon] on you!</span>")
damage_mult = GUNPOINT_MULT_STAGE_3
/datum/component/gunpoint/proc/check_deescalate()
if(!can_see(parent, target, GUNPOINT_SHOOTER_STRAY_RANGE - 1))
cancel()
/datum/component/gunpoint/proc/trigger_reaction()
if(point_of_no_return)
return
point_of_no_return = TRUE
var/mob/living/shooter = parent
if(!weapon.can_shoot() || !weapon.can_trigger_gun(shooter) || (weapon.weapon_weight == WEAPON_HEAVY && shooter.get_inactive_held_item()))
shooter.visible_message("<span class='danger'>[shooter] fumbles [weapon]!</span>", \
"<span class='danger'>You fumble [weapon] and fail to fire at [target]!</span>", target)
to_chat(target, "<span class='userdanger'>[shooter] fumbles [weapon] and fails to fire at you!</span>")
qdel(src)
return
if(weapon.chambered && weapon.chambered.BB)
weapon.chambered.BB.damage *= damage_mult
if(weapon.check_botched(shooter))
return
weapon.process_fire(target, shooter)
qdel(src)
/datum/component/gunpoint/proc/cancel()
var/mob/living/shooter = parent
shooter.visible_message("<span class='danger'>[shooter] breaks [shooter.p_their()] aim on [target]!</span>", \
"<span class='danger'>You are no longer aiming [weapon] at [target].</span>", target)
to_chat(target, "<span class='userdanger'>[shooter] breaks [shooter.p_their()] aim on you!</span>")
qdel(src)
/datum/component/gunpoint/proc/flinch(attacker, damage, damagetype, def_zone)
var/mob/living/shooter = parent
var/flinch_chance = 50
var/gun_hand = LEFT_HANDS
if(shooter.held_items[RIGHT_HANDS] == weapon)
gun_hand = RIGHT_HANDS
if((def_zone == BODY_ZONE_L_ARM && gun_hand == LEFT_HANDS) || (def_zone == BODY_ZONE_R_ARM && gun_hand == RIGHT_HANDS))
flinch_chance = 80
if(prob(flinch_chance))
shooter.visible_message("<span class='danger'>[shooter] flinches!</span>", \
"<span class='danger'>You flinch!</span>")
trigger_reaction()
#undef GUNPOINT_SHOOTER_STRAY_RANGE
#undef GUNPOINT_DELAY_STAGE_2
#undef GUNPOINT_DELAY_STAGE_3
#undef GUNPOINT_MULT_STAGE_1
#undef GUNPOINT_MULT_STAGE_2
#undef GUNPOINT_MULT_STAGE_3

View File

@@ -143,3 +143,27 @@
. = ..() . = ..()
if(.) if(.)
listening_in = tracker listening_in = tracker
// heldup is for the person being aimed at
/datum/status_effect/heldup
id = "heldup"
duration = -1
status_type = STATUS_EFFECT_MULTIPLE
alert_type = /obj/screen/alert/status_effect/heldup
/obj/screen/alert/status_effect/heldup
name = "Held Up"
desc = "Making any sudden moves would probably be a bad idea!"
icon_state = "aimed"
// holdup is for the person aiming
/datum/status_effect/holdup
id = "holdup"
duration = -1
status_type = STATUS_EFFECT_UNIQUE
alert_type = /obj/screen/alert/status_effect/holdup
/obj/screen/alert/status_effect/holdup
name = "Holding Up"
desc = "You're currently pointing a gun at someone."
icon_state = "aimed"

View File

@@ -138,6 +138,9 @@
AMob.grabbedby(src) AMob.grabbedby(src)
return TRUE return TRUE
stop_pulling() stop_pulling()
SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, AM, state, force)
if(AM.pulledby) if(AM.pulledby)
log_combat(AM, AM.pulledby, "pulled from", src) log_combat(AM, AM.pulledby, "pulled from", src)
AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once.

View File

@@ -271,6 +271,9 @@
pulling = AM pulling = AM
AM.pulledby = src AM.pulledby = src
SEND_SIGNAL(src, COMSIG_LIVING_START_PULL, AM, state, force)
if(!supress_message) if(!supress_message)
var/sound_to_play = 'sound/weapons/thudswoosh.ogg' var/sound_to_play = 'sound/weapons/thudswoosh.ogg'
if(ishuman(src)) if(ishuman(src))

View File

@@ -173,7 +173,6 @@
if(pb_knockback > 0) if(pb_knockback > 0)
var/atom/throw_target = get_edge_target_turf(pbtarget, user.dir) var/atom/throw_target = get_edge_target_turf(pbtarget, user.dir)
pbtarget.throw_at(throw_target, pb_knockback, 2) pbtarget.throw_at(throw_target, pb_knockback, 2)
else else
user.visible_message("<span class='danger'>[user] fires [src]!</span>", \ user.visible_message("<span class='danger'>[user] fires [src]!</span>", \
"<span class='danger'>You fire [src]!</span>", \ "<span class='danger'>You fire [src]!</span>", \
@@ -198,6 +197,12 @@
return return
if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected) if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected)
return return
if(ismob(target) && user.a_intent == INTENT_GRAB)
if(user.GetComponent(/datum/component/gunpoint))
to_chat(user, "<span class='warning'>You are already holding someone up!</span>")
return
user.AddComponent(/datum/component/gunpoint, target, src)
return
if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can. if(istype(user))//Check if the user can use the gun, if the user isn't alive(turrets) assume it can.
var/mob/living/L = user var/mob/living/L = user
@@ -213,15 +218,9 @@
shoot_with_empty_chamber(user) shoot_with_empty_chamber(user)
return return
//Exclude lasertag guns from the TRAIT_CLUMSY check. if(check_botched(user))
if(clumsy_check) return
if(istype(user))
if (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40))
to_chat(user, "<span class='userdanger'>You shoot yourself in the foot with [src]!</span>")
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
var/obj/item/bodypart/other_hand = user.has_hand_for_held_index(user.get_inactive_hand_index()) //returns non-disabled inactive hands var/obj/item/bodypart/other_hand = user.has_hand_for_held_index(user.get_inactive_hand_index()) //returns non-disabled inactive hands
if(weapon_weight == WEAPON_HEAVY && (user.get_inactive_held_item() || !other_hand)) if(weapon_weight == WEAPON_HEAVY && (user.get_inactive_held_item() || !other_hand))
to_chat(user, "<span class='warning'>You need two hands to fire \the [src]!</span>") to_chat(user, "<span class='warning'>You need two hands to fire \the [src]!</span>")
@@ -241,7 +240,15 @@
return process_fire(target, user, TRUE, params, null, bonus_spread) return process_fire(target, user, TRUE, params, null, bonus_spread)
/obj/item/gun/proc/check_botched(mob/living/user, params)
if(clumsy_check)
if(istype(user))
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(40))
to_chat(user, "<span class='userdanger'>You shoot yourself in the foot with [src]!</span>")
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 TRUE
/obj/item/gun/can_trigger_gun(mob/living/user) /obj/item/gun/can_trigger_gun(mob/living/user)
. = ..() . = ..()
@@ -300,6 +307,9 @@
return TRUE return TRUE
/obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) /obj/item/gun/proc/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
if(user)
SEND_SIGNAL(user, COMSIG_MOB_FIRED_GUN, user, target, params, zone_override)
add_fingerprint(user) add_fingerprint(user)
if(semicd) if(semicd)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 111 KiB

View File

@@ -384,6 +384,7 @@
#include "code\datums\components\forced_gravity.dm" #include "code\datums\components\forced_gravity.dm"
#include "code\datums\components\forensics.dm" #include "code\datums\components\forensics.dm"
#include "code\datums\components\gps.dm" #include "code\datums\components\gps.dm"
#include "code\datums\components\gunpoint.dm"
#include "code\datums\components\heirloom.dm" #include "code\datums\components\heirloom.dm"
#include "code\datums\components\igniter.dm" #include "code\datums\components\igniter.dm"
#include "code\datums\components\infective.dm" #include "code\datums\components\infective.dm"