Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MIRROR] Mob attackedby / check_block refactor, plus some minor cleanup of attack_x procs #692

Merged
merged 1 commit into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion code/__DEFINES/combat.dm
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,22 @@ DEFINE_BITFIELD(status_flags, list(
//slowdown when crawling
#define CRAWLING_ADD_SLOWDOWN 4

//Attack types for checking shields/hit reactions
//Attack types for checking block reactions
/// Attack was made with a melee weapon
#define MELEE_ATTACK 1
/// Attack is a punch or kick.
/// Mob attacks are not classified as unarmed (currently).
#define UNARMED_ATTACK 2
/// A projectile is hitting us.
#define PROJECTILE_ATTACK 3
/// A thrown item is hitting us.
#define THROWN_PROJECTILE_ATTACK 4
/// We're being tackled or leaped at.
#define LEAP_ATTACK 5

/// Used in check block to get what mob is attacking the blocker.
#define GET_ASSAILANT(weapon) (get(weapon, /mob/living))

//attack visual effects
#define ATTACK_EFFECT_PUNCH "punch"
#define ATTACK_EFFECT_KICK "kick"
Expand Down
3 changes: 0 additions & 3 deletions code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,6 @@
#define COMSIG_HUMAN_CORETEMP_CHANGE "human_coretemp_change"
///from /datum/species/handle_fire. Called when the human is set on fire and burning clothes and stuff
#define COMSIG_HUMAN_BURNING "human_burning"
///from /mob/living/carbon/human/proc/check_shields(): (atom/hit_by, damage, attack_text, attack_type, armour_penetration, damage_type)
#define COMSIG_HUMAN_CHECK_SHIELDS "human_check_shields"
#define SHIELD_BLOCK (1<<0)
///from /mob/living/carbon/human/proc/force_say(): ()
#define COMSIG_HUMAN_FORCESAY "human_forcesay"

Expand Down
4 changes: 4 additions & 0 deletions code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@
/// From /datum/ai/behavior/climb_tree/perform() : (mob/living/basic/living_pawn)
#define COMSIG_LIVING_CLIMB_TREE "living_climb_tree"

///from /mob/living/proc/check_block(): (atom/hit_by, damage, attack_text, attack_type, armour_penetration, damage_type)
#define COMSIG_LIVING_CHECK_BLOCK "living_check_block"
#define SUCCESSFUL_BLOCK (1<<0)

/// Sent on a mob from /datum/component/mob_chain when component is attached with it as the "front" : (mob/living/basic/tail)
#define COMSIG_MOB_GAINED_CHAIN_TAIL "living_gained_chain_tail"
/// Sent on a mob from /datum/component/mob_chain when component is detached from it as the "front" : (mob/living/basic/tail)
Expand Down
166 changes: 144 additions & 22 deletions code/_onclick/item_attack.dm
Original file line number Diff line number Diff line change
Expand Up @@ -257,32 +257,154 @@
CRASH("areas are NOT supposed to have attacked_by() called on them!")

/mob/living/attacked_by(obj/item/attacking_item, mob/living/user)
send_item_attack_message(attacking_item, user)
if(!attacking_item.force)
return FALSE

var/targeting = check_zone(user.zone_selected)
if(user != src)
var/zone_hit_chance = 80
if(body_position == LYING_DOWN)
zone_hit_chance += 10
targeting = get_random_valid_zone(targeting, zone_hit_chance)
var/targeting_human_readable = parse_zone(targeting)

send_item_attack_message(attacking_item, user, targeting_human_readable, targeting)

var/armor_block = min(run_armor_check(
def_zone = targeting,
attack_flag = MELEE,
absorb_text = span_notice("Your armor has protected your [targeting_human_readable]!"),
soften_text = span_warning("Your armor has softened a hit to your [targeting_human_readable]!"),
armour_penetration = attacking_item.armour_penetration,
weak_against_armour = attacking_item.weak_against_armour,
), ARMOR_MAX_BLOCK)

var/damage = attacking_item.force
if(mob_biotypes & MOB_ROBOTIC)
damage *= attacking_item.demolition_mod
apply_damage(damage, attacking_item.damtype, attacking_item = attacking_item)
if(attacking_item.damtype == BRUTE && prob(33))

var/wounding = attacking_item.wound_bonus
if((attacking_item.item_flags & SURGICAL_TOOL) && !user.combat_mode && body_position == LYING_DOWN && (LAZYLEN(surgeries) > 0))
wounding = CANT_WOUND

if(user != src)
// This doesn't factor in armor, or most damage modifiers (physiology). Your mileage may vary
if(check_block(attacking_item, damage, "the [attacking_item.name]", MELEE_ATTACK, attacking_item.armour_penetration, attacking_item.damtype))
return FALSE

SEND_SIGNAL(attacking_item, COMSIG_ITEM_ATTACK_ZONE, src, user, targeting)

if(damage <= 0)
return FALSE

if(ishuman(src) || client) // istype(src) is kinda bad, but it's to avoid spamming the blackbox
SSblackbox.record_feedback("nested tally", "item_used_for_combat", 1, list("[attacking_item.force]", "[attacking_item.type]"))
SSblackbox.record_feedback("tally", "zone_targeted", 1, targeting_human_readable)

var/damage_done = apply_damage(
damage = damage,
damagetype = attacking_item.damtype,
def_zone = targeting,
blocked = armor_block,
wound_bonus = wounding,
bare_wound_bonus = attacking_item.bare_wound_bonus,
sharpness = attacking_item.get_sharpness(),
attack_direction = get_dir(user, src),
attacking_item = attacking_item,
)

attack_effects(damage_done, targeting, armor_block, attacking_item, user)

return TRUE

/**
* Called when we take damage, used to cause effects such as a blood splatter.
*
* Return TRUE if an effect was done, FALSE otherwise.
*/
/mob/living/proc/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker)
if(damage_done > 0 && attacking_item.damtype == BRUTE && prob(25 + damage_done * 2))
attacking_item.add_mob_blood(src)
var/turf/location = get_turf(src)
add_splatter_floor(location)
if(get_dist(user, src) <= 1) //people with TK won't get smeared with blood
user.add_mob_blood(src)
return TRUE //successful attack

/mob/living/simple_animal/attacked_by(obj/item/I, mob/living/user)
if(!attack_threshold_check(I.force, I.damtype, MELEE, FALSE))
playsound(loc, 'sound/weapons/tap.ogg', I.get_clamped_volume(), TRUE, -1)
else
return ..()
add_splatter_floor(get_turf(src))
if(get_dist(attacker, src) <= 1)
attacker.add_mob_blood(src)
return TRUE

/mob/living/basic/attacked_by(obj/item/I, mob/living/user)
if(!attack_threshold_check(I.force, I.damtype, MELEE, FALSE))
playsound(loc, 'sound/weapons/tap.ogg', I.get_clamped_volume(), TRUE, -1)
else
return ..()
return FALSE

/mob/living/silicon/robot/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker)
if(damage_done > 0 && attacking_item.damtype != STAMINA && stat != DEAD)
spark_system.start()
. = TRUE
return ..() || .

/mob/living/silicon/ai/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker)
if(damage_done > 0 && attacking_item.damtype != STAMINA && stat != DEAD)
spark_system.start()
. = TRUE
return ..() || .

/mob/living/carbon/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker)
var/obj/item/bodypart/hit_bodypart = get_bodypart(hit_zone) || bodyparts[1]
if(!hit_bodypart.can_bleed())
return FALSE

return ..()

/mob/living/carbon/human/attack_effects(damage_done, hit_zone, armor_block, obj/item/attacking_item, mob/living/attacker)
. = ..()
switch(hit_zone)
if(BODY_ZONE_HEAD)
if(.)
if(wear_mask)
wear_mask.add_mob_blood(src)
update_worn_mask()
if(head)
head.add_mob_blood(src)
update_worn_head()
if(glasses && prob(33))
glasses.add_mob_blood(src)
update_worn_glasses()

if(!attacking_item.get_sharpness() && armor_block < 50)
if(prob(damage_done))
adjustOrganLoss(ORGAN_SLOT_BRAIN, 20)
if(stat == CONSCIOUS)
visible_message(
span_danger("[src] is knocked senseless!"),
span_userdanger("You're knocked senseless!"),
)
set_confusion_if_lower(20 SECONDS)
adjust_eye_blur(20 SECONDS)
if(prob(10))
gain_trauma(/datum/brain_trauma/mild/concussion)
else
adjustOrganLoss(ORGAN_SLOT_BRAIN, damage_done * 0.2)

// rev deconversion through blunt trauma.
// this can be signalized to the rev datum
if(mind && stat == CONSCIOUS && src != attacker && prob(damage_done + ((maxHealth - health) * 0.5))) // SKYRAT EDIT CHANGE - ORIGINAL : if(mind && stat == CONSCIOUS && src != attacker && prob(damage_done + ((100 - health) * 0.5)))
var/datum/antagonist/rev/rev = mind.has_antag_datum(/datum/antagonist/rev)
rev?.remove_revolutionary(attacker)

if(BODY_ZONE_CHEST)
if(.)
if(wear_suit)
wear_suit.add_mob_blood(src)
update_worn_oversuit()
if(w_uniform)
w_uniform.add_mob_blood(src)
update_worn_undersuit()

if(stat == CONSCIOUS && !attacking_item.get_sharpness() && armor_block < 50)
if(prob(damage_done))
visible_message(
span_danger("[src] is knocked down!"),
span_userdanger("You're knocked down!"),
)
apply_effect(6 SECONDS, EFFECT_KNOCKDOWN, armor_block)

// Triggers force say events
if(damage_done > 10 || (damage_done >= 5 && prob(33)))
force_say()

/**
* Last proc in the [/obj/item/proc/melee_attack_chain].
Expand Down Expand Up @@ -335,7 +457,7 @@
else
return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100

/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area, obj/item/bodypart/hit_bodypart)
/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area, def_zone)
if(!I.force && !length(I.attack_verb_simple) && !length(I.attack_verb_continuous))
return
var/message_verb_continuous = length(I.attack_verb_continuous) ? "[pick(I.attack_verb_continuous)]" : "attacks"
Expand Down
3 changes: 3 additions & 0 deletions code/_onclick/other_mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@
/mob/living/proc/resolve_right_click_attack(atom/target, list/modifiers)
return target.attack_animal_secondary(src, modifiers)

/**
* Called when a simple animal is unarmed attacking / clicking on this atom.
*/
/atom/proc/attack_animal(mob/user, list/modifiers)
SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_ANIMAL, user)

Expand Down
2 changes: 1 addition & 1 deletion code/datums/actions/mobs/charge.dm
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@
var/mob/living/living_target = target
if(ishuman(living_target))
var/mob/living/carbon/human/human_target = living_target
if(human_target.check_shields(source, 0, "the [source.name]", attack_type = LEAP_ATTACK) && living_source)
if(human_target.check_block(source, 0, "the [source.name]", attack_type = LEAP_ATTACK) && living_source)
living_source.Stun(recoil_duration, ignore_canstun = TRUE)
return

Expand Down
51 changes: 51 additions & 0 deletions code/datums/elements/damage_threshold.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// Applied to living mobs.
/// Adds a force threshold for which attacks will be blocked entirely.
/// IE, if they are hit with an attack that deals less than X damage, the attack does nothing.
/datum/element/damage_threshold
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
/// Incoming attacks beneath this threshold, inclusive, will be blocked entirely
var/force_threshold = -1

/datum/element/damage_threshold/Attach(datum/target, threshold)
. = ..()
if(!isliving(target))
return ELEMENT_INCOMPATIBLE
if(!isnum(threshold) || threshold <= 0)
return ELEMENT_INCOMPATIBLE

RegisterSignal(target, COMSIG_LIVING_CHECK_BLOCK, PROC_REF(check_block))
force_threshold = threshold

/datum/element/damage_threshold/Detach(datum/source, ...)
. = ..()
UnregisterSignal(source, COMSIG_LIVING_CHECK_BLOCK)

/datum/element/damage_threshold/proc/check_block(
mob/living/source,
atom/hitby,
damage,
attack_text,
attack_type,
armour_penetration,
damage_type,
attack_flag,
)
SIGNAL_HANDLER

if(damage <= 0) // Already handled
return NONE

if(damage <= force_threshold)
var/obj/item/item_hitting = hitby
var/tap_vol = istype(item_hitting) ? item_hitting.get_clamped_volume() : 50
source.visible_message(
span_warning("[src] looks unharmed!"),
span_warning("[attack_text] deals no damage to you!"),
span_hear("You hear a thud."),
COMBAT_MESSAGE_RANGE,
)
playsound(source, 'sound/weapons/tap.ogg', tap_vol, TRUE, -1)
return SUCCESSFUL_BLOCK

return NONE
1 change: 0 additions & 1 deletion code/datums/martial/_martial.dm
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
var/max_streak_length = 6
var/current_target
var/datum/martial_art/base // The permanent style. This will be null unless the martial art is temporary
var/block_chance = 0 //Chance to block melee attacks using items while on throw mode.
var/help_verb
var/allow_temp_override = TRUE //if this martial art can be overridden by temporary martial arts
var/smashes_tables = FALSE //If the martial art smashes tables when performing table slams and head smashes
Expand Down
31 changes: 29 additions & 2 deletions code/datums/martial/cqc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@
name = "CQC"
id = MARTIALART_CQC
help_verb = /mob/living/proc/CQC_help
block_chance = 75
smashes_tables = TRUE
display_combos = TRUE
var/old_grab_state = null
var/mob/restraining_mob
/// Probability of successfully blocking attacks while on throw mode
var/block_chance = 75

/datum/martial_art/cqc/teach(mob/living/cqc_user, make_temporary)
. = ..()
RegisterSignal(cqc_user, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
RegisterSignal(cqc_user, COMSIG_LIVING_CHECK_BLOCK, PROC_REF(check_block))

/datum/martial_art/cqc/on_remove(mob/living/cqc_user)
UnregisterSignal(cqc_user, COMSIG_ATOM_ATTACKBY)
UnregisterSignal(cqc_user, list(COMSIG_ATOM_ATTACKBY, COMSIG_LIVING_CHECK_BLOCK))
. = ..()

///Signal from getting attacked with an item, for a special interaction with touch spells
Expand All @@ -41,6 +43,31 @@
INVOKE_ASYNC(touch_spell, TYPE_PROC_REF(/datum/action/cooldown/spell/touch, do_hand_hit), touch_weapon, attacker, attacker)
return COMPONENT_NO_AFTERATTACK

/datum/martial_art/cqc/proc/check_block(mob/living/cqc_user, atom/movable/hitby, damage, attack_text, attack_type, ...)
SIGNAL_HANDLER

if(!can_use(cqc_user) || !cqc_user.throw_mode || cqc_user.incapacitated(IGNORE_GRAB))
return NONE
if(attack_type == PROJECTILE_ATTACK)
return NONE
if(!prob(block_chance))
return NONE

var/mob/living/attacker = GET_ASSAILANT(hitby)
if(istype(attacker) && cqc_user.Adjacent(attacker))
cqc_user.visible_message(
span_danger("[cqc_user] blocks [attack_text] and twists [attacker]'s arm behind [attacker.p_their()] back!"),
span_userdanger("You block [attack_text]!"),
)
attacker.Stun(4 SECONDS)
else
cqc_user.visible_message(
span_danger("[cqc_user] blocks [attack_text]!"),
span_userdanger("You block [attack_text]!"),
)
return SUCCESSFUL_BLOCK


/datum/martial_art/cqc/reset_streak(mob/living/new_target)
if(new_target && new_target != restraining_mob)
restraining_mob = null
Expand Down
9 changes: 3 additions & 6 deletions code/game/objects/items/melee/baton.dm
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,10 @@
target.visible_message(desc["visible"], desc["local"])

/obj/item/melee/baton/proc/check_parried(mob/living/carbon/human/human_target, mob/living/user)
if(!ishuman(human_target))
return
if (human_target.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
if (human_target.check_block(src, 0, "[user]'s [name]", MELEE_ATTACK))
playsound(human_target, 'sound/weapons/genhit.ogg', 50, TRUE)
return TRUE
if(check_martial_counter(human_target, user))
return TRUE
return FALSE

/obj/item/melee/baton/proc/finalize_baton_attack(mob/living/target, mob/living/user, modifiers, in_attack_chain = TRUE)
if(!in_attack_chain && HAS_TRAIT_FROM(target, TRAIT_IWASBATONED, REF(user)))
Expand Down Expand Up @@ -632,7 +629,7 @@

/obj/item/melee/baton/security/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
if(active && prob(throw_stun_chance) && isliving(hit_atom))
if(!. && active && prob(throw_stun_chance) && isliving(hit_atom))
finalize_baton_attack(hit_atom, thrownby?.resolve(), in_attack_chain = FALSE)

/obj/item/melee/baton/security/emp_act(severity)
Expand Down
Loading
Loading