Merge pull request #14482 from silicons/combat_v7
Combat v7 - Sprint removal, automatic block/parry, turns combat mode into a pure UI/interaction toggle with no side effects, and a truckload of other stuff.
This commit is contained in:
@@ -466,7 +466,7 @@
|
||||
block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + remaining_uses
|
||||
return ..()
|
||||
|
||||
/obj/item/shield/changeling/active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
/obj/item/shield/changeling/directional_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
. = ..()
|
||||
if(--remaining_uses < 1)
|
||||
if(ishuman(loc))
|
||||
|
||||
@@ -90,7 +90,6 @@
|
||||
parry_time_perfect = 2
|
||||
parry_efficiency_perfect = 110 //Very low leeway for counterattacks...
|
||||
parry_efficiency_considered_successful = 0.8
|
||||
parry_efficiency_to_counterattack = 1
|
||||
parry_efficiency_to_counterattack = 110
|
||||
parry_cooldown = 15 //But also very low cooldown..
|
||||
parry_failed_stagger_duration = 2 SECONDS //And relatively small penalties for failing.
|
||||
parry_failed_clickcd_duration = 1 SECONDS
|
||||
|
||||
@@ -183,3 +183,7 @@
|
||||
//world.time of when the crew manifest can be accessed
|
||||
var/crew_manifest_delay
|
||||
|
||||
/// Should go in persistent round player data sometime. This tracks what items have already warned the user on pickup that they can block/parry.
|
||||
var/list/block_parry_hinted = list()
|
||||
/// moused over objects, currently capped at 7. this is awful, and should be replaced with a component to track it using signals for parrying at some point.
|
||||
var/list/moused_over_objects = list()
|
||||
|
||||
@@ -236,10 +236,9 @@
|
||||
parry_efficiency_considered_successful = 0.01
|
||||
parry_efficiency_to_counterattack = INFINITY // no auto counter
|
||||
parry_max_attacks = INFINITY
|
||||
parry_failed_cooldown_duration = 2.25 SECONDS
|
||||
parry_failed_stagger_duration = 2.25 SECONDS
|
||||
parry_failed_cooldown_duration = 1.5 SECONDS
|
||||
parry_failed_stagger_duration = 1.5 SECONDS
|
||||
parry_cooldown = 0
|
||||
parry_failed_clickcd_duration = 0
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/mauler
|
||||
name = "mauler gauntlets"
|
||||
|
||||
@@ -76,3 +76,15 @@
|
||||
else
|
||||
user.mob.dropItemToGround(I)
|
||||
return TRUE
|
||||
|
||||
/datum/keybinding/mob/examine_immediate
|
||||
hotkey_keys = list()
|
||||
classic_keys = list()
|
||||
name = "examine_immediate"
|
||||
full_name = "Examine (Immediate)"
|
||||
description = "Immediately examine anything you're hovering your mouse over."
|
||||
|
||||
/datum/keybinding/mob/examine_immediate/down(client/user)
|
||||
var/atom/A = user.mouseObject
|
||||
if(A)
|
||||
A.attempt_examinate(user.mob)
|
||||
|
||||
@@ -196,7 +196,6 @@
|
||||
parry_imperfect_falloff_percent = 20
|
||||
parry_efficiency_to_counterattack = 100 // perfect parry or you're cringe
|
||||
parry_failed_stagger_duration = 1.5 SECONDS // a good time to reconsider your actions...
|
||||
parry_failed_clickcd_duration = 1.5 SECONDS // or your failures
|
||||
|
||||
/obj/item/kinetic_crusher/glaive/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time) // if you're dumb enough to go for a parry...
|
||||
var/turf/proj_turf = owner.loc // destabilizer bolt, ignoring cooldown
|
||||
|
||||
@@ -307,7 +307,7 @@
|
||||
/obj/item/energy_katana,
|
||||
/obj/item/hierophant_club,
|
||||
/obj/item/his_grace,
|
||||
/obj/item/gun/ballistic/minigun,
|
||||
/obj/item/gun/energy/minigun,
|
||||
/obj/item/gun/ballistic/automatic/l6_saw,
|
||||
/obj/item/gun/magic/staff/chaos,
|
||||
/obj/item/gun/magic/staff/spellblade,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//The effects include: stun, knockdown, unconscious, sleeping, resting, jitteriness, dizziness, ear damage,
|
||||
// eye damage, eye_blind, eye_blurry, druggy, TRAIT_BLIND trait, and TRAIT_NEARSIGHT trait.
|
||||
|
||||
/mob/living/carbon/alien/DefaultCombatKnockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg)
|
||||
/mob/living/carbon/alien/DefaultCombatKnockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg, knocktofloor)
|
||||
return //no
|
||||
|
||||
/////////////////////////////////// STUN ////////////////////////////////////
|
||||
|
||||
@@ -358,10 +358,11 @@
|
||||
return
|
||||
I.item_flags |= BEING_REMOVED
|
||||
breakouttime = I.breakouttime
|
||||
var/datum/cuffbreak_checker/cuffbreak_checker = new(get_turf(src))
|
||||
if(!cuff_break)
|
||||
visible_message("<span class='warning'>[src] attempts to remove [I]!</span>")
|
||||
to_chat(src, "<span class='notice'>You attempt to remove [I]... (This will take around [DisplayTimeText(breakouttime)] and you need to stand still.)</span>")
|
||||
if(do_after(src, breakouttime, 0, target = src, required_mobility_flags = MOBILITY_RESIST))
|
||||
if(do_after_advanced(src, breakouttime, src, NONE, CALLBACK(cuffbreak_checker, /datum/cuffbreak_checker.proc/check_movement), required_mobility_flags = MOBILITY_RESIST))
|
||||
clear_cuffs(I, cuff_break)
|
||||
else
|
||||
to_chat(src, "<span class='warning'>You fail to remove [I]!</span>")
|
||||
@@ -370,15 +371,30 @@
|
||||
breakouttime = 50
|
||||
visible_message("<span class='warning'>[src] is trying to break [I]!</span>")
|
||||
to_chat(src, "<span class='notice'>You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)</span>")
|
||||
if(do_after(src, breakouttime, 0, target = src, required_mobility_flags = MOBILITY_RESIST))
|
||||
if(do_after_advanced(src, breakouttime, src, NONE, CALLBACK(cuffbreak_checker, /datum/cuffbreak_checker.proc/check_movement), required_mobility_flags = MOBILITY_RESIST))
|
||||
clear_cuffs(I, cuff_break)
|
||||
else
|
||||
to_chat(src, "<span class='warning'>You fail to break [I]!</span>")
|
||||
|
||||
else if(cuff_break == INSTANT_CUFFBREAK)
|
||||
clear_cuffs(I, cuff_break)
|
||||
|
||||
QDEL_NULL(cuffbreak_checker)
|
||||
I.item_flags &= ~BEING_REMOVED
|
||||
|
||||
/datum/cuffbreak_checker
|
||||
var/turf/last
|
||||
|
||||
/datum/cuffbreak_checker/New(turf/initial_turf)
|
||||
last = initial_turf
|
||||
|
||||
/datum/cuffbreak_checker/proc/check_movement(atom/user, delay, atom/target, time_left, do_after_flags, required_mobility_flags, required_combat_flags, mob_redirect, stage, initially_held_item, tool, list/passed_in)
|
||||
if(get_turf(user) != last)
|
||||
last = get_turf(user)
|
||||
passed_in[1] = 0.5
|
||||
else
|
||||
passed_in[1] = 1
|
||||
|
||||
/mob/living/carbon/proc/uncuff()
|
||||
if (handcuffed)
|
||||
var/obj/item/W = handcuffed
|
||||
@@ -459,10 +475,6 @@
|
||||
if(HAS_TRAIT(src, TRAIT_CLUMSY))
|
||||
modifier -= 40 //Clumsy people are more likely to hit themselves -Honk!
|
||||
|
||||
//CIT CHANGES START HERE
|
||||
else if(SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
modifier -= 50
|
||||
|
||||
if(modifier < 100)
|
||||
dropItemToGround(I)
|
||||
//END OF CIT CHANGES
|
||||
|
||||
@@ -1055,10 +1055,10 @@
|
||||
return
|
||||
if(!HAS_TRAIT(src, TRAIT_IGNOREDAMAGESLOWDOWN)) //if we want to ignore slowdown from damage, but not from equipment
|
||||
var/scaling = maxHealth / 100
|
||||
var/health_deficiency = ((maxHealth / scaling) - (health / scaling) + (getStaminaLoss()*0.75))//CIT CHANGE - reduces the impact of staminaloss and makes stamina buffer influence it
|
||||
var/health_deficiency = max(((maxHealth / scaling) - (health / scaling)), (getStaminaLoss()*0.75))
|
||||
if(health_deficiency >= 40)
|
||||
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown, TRUE, (health_deficiency-39) / 75)
|
||||
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying, TRUE, (health_deficiency-39) / 25)
|
||||
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown, TRUE, (health_deficiency - 15) / 75)
|
||||
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying, TRUE, (health_deficiency - 15) / 25)
|
||||
else
|
||||
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown)
|
||||
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying)
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
return
|
||||
var/informed = FALSE
|
||||
if(isrobotic(src))
|
||||
apply_status_effect(/datum/status_effect/no_combat_mode/robotic_emp, severity / 20)
|
||||
apply_status_effect(/datum/status_effect/robotic_emp, severity / 20)
|
||||
severity *= 0.5
|
||||
var/do_not_stun = FALSE
|
||||
if(HAS_TRAIT(src, TRAIT_ROBOTIC_ORGANISM))
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
)
|
||||
|
||||
parry_efficiency_considered_successful = 0.01
|
||||
parry_efficiency_to_counterattack = 0.01
|
||||
parry_efficiency_to_counterattack = INFINITY // no counterattacks
|
||||
parry_max_attacks = INFINITY
|
||||
parry_failed_cooldown_duration = 1.5 SECONDS
|
||||
parry_failed_stagger_duration = 1 SECONDS
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
ADD_TRAIT(H, TRAIT_MOBILITY_NOPICKUP, SLIMEPUDDLE_TRAIT)
|
||||
ADD_TRAIT(H, TRAIT_MOBILITY_NOUSE, SLIMEPUDDLE_TRAIT)
|
||||
ADD_TRAIT(H, TRAIT_SPRINT_LOCKED, SLIMEPUDDLE_TRAIT)
|
||||
ADD_TRAIT(H, TRAIT_COMBAT_MODE_LOCKED, SLIMEPUDDLE_TRAIT)
|
||||
ADD_TRAIT(H, TRAIT_MOBILITY_NOREST, SLIMEPUDDLE_TRAIT)
|
||||
ADD_TRAIT(H, TRAIT_ARMOR_BROKEN, SLIMEPUDDLE_TRAIT)
|
||||
H.update_disabled_bodyparts(silent = TRUE) //silently update arms to be paralysed
|
||||
@@ -99,7 +98,6 @@
|
||||
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOPICKUP, SLIMEPUDDLE_TRAIT)
|
||||
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOUSE, SLIMEPUDDLE_TRAIT)
|
||||
REMOVE_TRAIT(H, TRAIT_SPRINT_LOCKED, SLIMEPUDDLE_TRAIT)
|
||||
REMOVE_TRAIT(H, TRAIT_COMBAT_MODE_LOCKED, SLIMEPUDDLE_TRAIT)
|
||||
REMOVE_TRAIT(H, TRAIT_MOBILITY_NOREST, SLIMEPUDDLE_TRAIT)
|
||||
REMOVE_TRAIT(H, TRAIT_ARMOR_BROKEN, SLIMEPUDDLE_TRAIT)
|
||||
REMOVE_TRAIT(H, TRAIT_HUMAN_NO_RENDER, SLIMEPUDDLE_TRAIT)
|
||||
|
||||
@@ -1589,12 +1589,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
var/punchedbrute = target.getBruteLoss()
|
||||
|
||||
//CITADEL CHANGES - makes resting and disabled combat mode reduce punch damage, makes being out of combat mode result in you taking more damage
|
||||
if(!SEND_SIGNAL(target, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
damage *= 1.2
|
||||
if(!CHECK_MOBILITY(user, MOBILITY_STAND))
|
||||
damage *= 0.65
|
||||
if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
damage *= 0.8
|
||||
//END OF CITADEL CHANGES
|
||||
|
||||
var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(user.zone_selected))
|
||||
@@ -1718,12 +1714,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
else
|
||||
user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
|
||||
|
||||
if(HAS_TRAIT(user, TRAIT_PUGILIST))//CITADEL CHANGE - makes disarmspam cause staminaloss, pugilists can do it almost effortlessly
|
||||
if(!user.UseStaminaBuffer(1, warn = TRUE))
|
||||
return
|
||||
else
|
||||
if(!user.UseStaminaBuffer(1, warn = TRUE))
|
||||
return
|
||||
if(!user.UseStaminaBuffer(1, warn = TRUE))
|
||||
return
|
||||
|
||||
if(attacker_style && attacker_style.disarm_act(user,target))
|
||||
return TRUE
|
||||
@@ -1741,12 +1733,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
log_combat(user, target, "disarmed out of grab from")
|
||||
return
|
||||
var/randn = rand(1, 100)
|
||||
if(SEND_SIGNAL(target, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE)) // CITADEL CHANGE
|
||||
randn += -10 //CITADEL CHANGE - being out of combat mode makes it easier for you to get disarmed
|
||||
if(!CHECK_MOBILITY(user, MOBILITY_STAND)) //CITADEL CHANGE
|
||||
randn += 100 //CITADEL CHANGE - No kosher disarming if you're resting
|
||||
if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE)) //CITADEL CHANGE
|
||||
randn += 25 //CITADEL CHANGE - Makes it harder to disarm outside of combat mode
|
||||
if(user.pulling == target)
|
||||
randn -= 20 //If you have the time to get someone in a grab, you should have a greater chance at snatching the thing in their hand. Will be made completely obsolete by the grab rework but i've got a poor track record for releasing big projects on time so w/e i guess
|
||||
if(HAS_TRAIT(user, TRAIT_PUGILIST))
|
||||
@@ -1951,9 +1939,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if(IS_STAMCRIT(user))
|
||||
to_chat(user, "<span class='warning'>You're too exhausted for that.</span>")
|
||||
return
|
||||
if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
to_chat(user, "<span class='warning'>You need combat mode to be active to that!</span>")
|
||||
return
|
||||
if(user.IsKnockdown() || user.IsParalyzed() || user.IsStun())
|
||||
to_chat(user, "<span class='warning'>You can't seem to force yourself up right now!</span>")
|
||||
return
|
||||
@@ -1993,6 +1978,8 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
|
||||
if(CHECK_MOBILITY(target, MOBILITY_STAND))
|
||||
target.adjustStaminaLoss(5)
|
||||
else
|
||||
target.adjustStaminaLoss(target.getStaminaLoss() > 75? 5 : 75)
|
||||
|
||||
if(target.is_shove_knockdown_blocked())
|
||||
return
|
||||
@@ -2035,7 +2022,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
target.visible_message("<span class='danger'>[user.name] shoves [target.name]!</span>",
|
||||
"<span class='danger'>[user.name] shoves you!</span>", null, COMBAT_MESSAGE_RANGE, null,
|
||||
user, "<span class='danger'>You shove [target.name]!</span>")
|
||||
target.Stagger(SHOVE_STAGGER_DURATION)
|
||||
var/obj/item/target_held_item = target.get_active_held_item()
|
||||
if(!target.has_status_effect(STATUS_EFFECT_OFF_BALANCE))
|
||||
if(target_held_item)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
amount = dna.species.spec_stun(src,amount)
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/DefaultCombatKnockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg)
|
||||
/mob/living/carbon/human/DefaultCombatKnockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg, knocktofloor)
|
||||
amount = dna.species.spec_stun(src,amount)
|
||||
return ..()
|
||||
|
||||
|
||||
@@ -512,9 +512,8 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
|
||||
//this updates all special effects: stun, sleeping, knockdown, druggy, stuttering, etc..
|
||||
/mob/living/carbon/handle_status_effects()
|
||||
..()
|
||||
var/combat_mode = SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE)
|
||||
if(getStaminaLoss() && !HAS_TRAIT(src, TRAIT_NO_STAMINA_REGENERATION))
|
||||
adjustStaminaLoss((!CHECK_MOBILITY(src, MOBILITY_STAND) ? ((combat_flags & COMBAT_FLAG_HARD_STAMCRIT) ? STAM_RECOVERY_STAM_CRIT : STAM_RECOVERY_RESTING) : STAM_RECOVERY_NORMAL) * (combat_mode? 0.25 : 1))
|
||||
adjustStaminaLoss((!CHECK_MOBILITY(src, MOBILITY_STAND) ? ((combat_flags & COMBAT_FLAG_HARD_STAMCRIT) ? STAM_RECOVERY_STAM_CRIT : STAM_RECOVERY_RESTING) : STAM_RECOVERY_NORMAL))
|
||||
|
||||
if(!(combat_flags & COMBAT_FLAG_HARD_STAMCRIT) && incomingstammult != 1)
|
||||
incomingstammult = max(0.01, incomingstammult)
|
||||
|
||||
@@ -48,25 +48,23 @@
|
||||
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_IN)
|
||||
|
||||
/mob/living/proc/continue_starting_active_block()
|
||||
if(SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
return DO_AFTER_STOP
|
||||
return (combat_flags & COMBAT_FLAG_ACTIVE_BLOCK_STARTING)? DO_AFTER_CONTINUE : DO_AFTER_STOP
|
||||
|
||||
/mob/living/get_standard_pixel_x_offset()
|
||||
. = ..()
|
||||
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
|
||||
if(dir & EAST)
|
||||
. += 8
|
||||
. += 4
|
||||
if(dir & WEST)
|
||||
. -= 8
|
||||
. -= 4
|
||||
|
||||
/mob/living/get_standard_pixel_y_offset()
|
||||
. = ..()
|
||||
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
|
||||
if(dir & NORTH)
|
||||
. += 8
|
||||
. += 4
|
||||
if(dir & SOUTH)
|
||||
. -= 8
|
||||
. -= 4
|
||||
|
||||
/**
|
||||
* Proc called by keybindings to toggle active blocking.
|
||||
@@ -100,11 +98,6 @@
|
||||
if(!I.can_active_block())
|
||||
to_chat(src, "<span class='warning'>[I] is either not capable of being used to actively block, or is not currently in a state that can! (Try wielding it if it's twohanded, for example.)</span>")
|
||||
return
|
||||
// QOL: Attempt to toggle on combat mode if it isn't already
|
||||
SEND_SIGNAL(src, COMSIG_ENABLE_COMBAT_MODE)
|
||||
if(SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
to_chat(src, "<span class='warning'>You must be in combat mode to actively block!</span>")
|
||||
return FALSE
|
||||
var/datum/block_parry_data/data = I.get_block_parry_data()
|
||||
var/delay = data.block_start_delay
|
||||
combat_flags |= COMBAT_FLAG_ACTIVE_BLOCK_STARTING
|
||||
@@ -147,7 +140,7 @@
|
||||
/**
|
||||
* Calculates FINAL ATTACK DAMAGE after mitigation
|
||||
*/
|
||||
/obj/item/proc/active_block_calculate_final_damage(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
/obj/item/proc/active_block_calculate_final_damage(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, passive = FALSE)
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
var/absorption = data.attack_type_list_scan(data.block_damage_absorption_override, attack_type)
|
||||
var/efficiency = data.attack_type_list_scan(data.block_damage_multiplier_override, attack_type)
|
||||
@@ -156,7 +149,7 @@
|
||||
if(isnull(absorption))
|
||||
absorption = data.block_damage_absorption
|
||||
if(isnull(efficiency))
|
||||
efficiency = data.block_damage_multiplier
|
||||
efficiency = data.block_damage_multiplier * (passive? (1 / data.block_automatic_mitigation_multiplier) : 1)
|
||||
if(isnull(limit))
|
||||
limit = data.block_damage_limit
|
||||
// now we calculate damage to reduce.
|
||||
@@ -172,7 +165,7 @@
|
||||
return final_damage
|
||||
|
||||
/// Amount of stamina from damage blocked. Note that the damage argument is damage_blocked.
|
||||
/obj/item/proc/active_block_stamina_cost(mob/living/owner, atom/object, damage_blocked, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
/obj/item/proc/active_block_stamina_cost(mob/living/owner, atom/object, damage_blocked, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, passive = FALSE)
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
var/efficiency = data.attack_type_list_scan(data.block_stamina_efficiency_override, attack_type)
|
||||
if(isnull(efficiency))
|
||||
@@ -182,7 +175,7 @@
|
||||
multiplier = data.attack_type_list_scan(data.block_resting_stamina_penalty_multiplier_override, attack_type)
|
||||
if(isnull(multiplier))
|
||||
multiplier = data.block_resting_stamina_penalty_multiplier
|
||||
return (damage_blocked / efficiency) * multiplier
|
||||
return (damage_blocked / efficiency) * multiplier * (passive? data.block_automatic_stamina_multiplier : 1)
|
||||
|
||||
/// Apply the stamina damage to our user, notice how damage argument is stamina_amount.
|
||||
/obj/item/proc/active_block_do_stamina_damage(mob/living/owner, atom/object, stamina_amount, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
@@ -214,6 +207,18 @@
|
||||
return
|
||||
|
||||
/obj/item/proc/active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction)
|
||||
return directional_block(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return, override_direction)
|
||||
|
||||
/obj/item/proc/can_passive_block()
|
||||
if(!block_parry_data || !(item_flags & ITEM_CAN_BLOCK))
|
||||
return FALSE
|
||||
var/datum/block_parry_data/data = return_block_parry_datum(block_parry_data)
|
||||
return data.block_automatic_enabled
|
||||
|
||||
/obj/item/proc/passive_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction)
|
||||
return directional_block(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return, override_direction, TRUE)
|
||||
|
||||
/obj/item/proc/directional_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, override_direction, passive = FALSE)
|
||||
if(!can_active_block())
|
||||
return BLOCK_NONE
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
@@ -228,12 +233,12 @@
|
||||
incoming_direction = get_dir(get_turf(attacker) || get_turf(object), src)
|
||||
if(!CHECK_MOBILITY(owner, MOBILITY_STAND) && !(data.block_resting_attack_types_anydir & attack_type) && (!(data.block_resting_attack_types_directional & attack_type) || !can_block_direction(owner.dir, incoming_direction)))
|
||||
return BLOCK_NONE
|
||||
else if(!can_block_direction(owner.dir, incoming_direction))
|
||||
else if(!can_block_direction(owner.dir, incoming_direction, passive))
|
||||
return BLOCK_NONE
|
||||
block_return[BLOCK_RETURN_ACTIVE_BLOCK] = TRUE
|
||||
var/final_damage = active_block_calculate_final_damage(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
var/final_damage = active_block_calculate_final_damage(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return, passive)
|
||||
var/damage_blocked = damage - final_damage
|
||||
var/stamina_cost = active_block_stamina_cost(owner, object, damage_blocked, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
var/stamina_cost = active_block_stamina_cost(owner, object, damage_blocked, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return, passive)
|
||||
active_block_do_stamina_damage(owner, object, stamina_cost, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
block_return[BLOCK_RETURN_ACTIVE_BLOCK_DAMAGE_MITIGATED] = damage - final_damage
|
||||
block_return[BLOCK_RETURN_SET_DAMAGE_TO] = final_damage
|
||||
@@ -261,9 +266,9 @@
|
||||
/**
|
||||
* Gets the block direction bitflags of what we can block.
|
||||
*/
|
||||
/obj/item/proc/blockable_directions()
|
||||
/obj/item/proc/blockable_directions(passive = FALSE)
|
||||
var/datum/block_parry_data/data = get_block_parry_data()
|
||||
return data.can_block_directions
|
||||
return (!isnull(data.block_automatic_directions) && passive)? data.block_automatic_directions : data.can_block_directions
|
||||
|
||||
/**
|
||||
* Checks if we can block from a specific direction from our direction.
|
||||
@@ -272,14 +277,14 @@
|
||||
* * our_dir - our direction.
|
||||
* * their_dir - their direction. Must be a single direction, or NONE for an attack from the same tile. This is incoming direction.
|
||||
*/
|
||||
/obj/item/proc/can_block_direction(our_dir, their_dir)
|
||||
/obj/item/proc/can_block_direction(our_dir, their_dir, passive = FALSE)
|
||||
their_dir = turn(their_dir, 180)
|
||||
if(our_dir != NORTH)
|
||||
var/turn_angle = dir2angle(our_dir)
|
||||
// dir2angle(), ss13 proc is clockwise so dir2angle(EAST) == 90
|
||||
// turn(), byond proc is counterclockwise so turn(NORTH, 90) == WEST
|
||||
their_dir = turn(their_dir, turn_angle)
|
||||
return (DIR2BLOCKDIR(their_dir) & blockable_directions())
|
||||
return (DIR2BLOCKDIR(their_dir) & blockable_directions(passive))
|
||||
|
||||
/**
|
||||
* can_block_direction but for "compound" directions to check all of them and return the number of directions that were blocked.
|
||||
|
||||
@@ -14,18 +14,58 @@
|
||||
/**
|
||||
* Initiates a parrying sequence.
|
||||
*/
|
||||
/mob/living/proc/initiate_parry_sequence()
|
||||
/mob/living/proc/initiate_parry_sequence(silent = FALSE, list/override_method_data)
|
||||
if(parrying)
|
||||
return // already parrying
|
||||
return FALSE // already parrying
|
||||
if(!(mobility_flags & MOBILITY_USE))
|
||||
to_chat(src, "<span class='warning'>You can't move your arms!</span>")
|
||||
return
|
||||
if(!silent)
|
||||
to_chat(src, "<span class='warning'>You can't move your arms!</span>")
|
||||
return FALSE
|
||||
if(!(combat_flags & COMBAT_FLAG_PARRY_CAPABLE))
|
||||
to_chat(src, "<span class='warning'>You are not something that can parry attacks.</span>")
|
||||
return
|
||||
if(!silent)
|
||||
to_chat(src, "<span class='warning'>You are not something that can parry attacks.</span>")
|
||||
return FALSE
|
||||
if(!(mobility_flags & MOBILITY_STAND))
|
||||
to_chat(src, "<span class='warning'>You aren't able to parry without solid footing!</span>")
|
||||
return
|
||||
if(!silent)
|
||||
to_chat(src, "<span class='warning'>You aren't able to parry without solid footing!</span>")
|
||||
return FALSE
|
||||
var/datum/block_parry_data/data
|
||||
var/list/determined = override_method_data || determine_parry_method(FALSE, FALSE)
|
||||
if(!islist(determined))
|
||||
return FALSE
|
||||
var/method = determined[1]
|
||||
data = return_block_parry_datum(determined[2])
|
||||
var/datum/tool = determined[3]
|
||||
var/full_parry_duration = data.parry_time_windup + data.parry_time_active + data.parry_time_spindown
|
||||
// no system in place to "fallback" if out of the 3 the top priority one can't parry due to constraints but something else can.
|
||||
// can always implement it later, whatever.
|
||||
if((data.parry_respect_clickdelay && !CheckActionCooldown()) || ((parry_end_time_last + data.parry_cooldown) > world.time))
|
||||
if(!silent)
|
||||
to_chat(src, "<span class='warning'>You are not ready to parry (again)!</span>")
|
||||
return FALSE
|
||||
// Point of no return, make sure everything is set.
|
||||
parrying = method
|
||||
if(method == ITEM_PARRY)
|
||||
active_parry_item = tool
|
||||
if(!UseStaminaBuffer(data.parry_stamina_cost, TRUE))
|
||||
return FALSE
|
||||
parry_start_time = world.time
|
||||
successful_parries = list()
|
||||
successful_parry_counterattacks = list()
|
||||
addtimer(CALLBACK(src, .proc/end_parry_sequence), full_parry_duration)
|
||||
if(data.parry_flags & PARRY_LOCK_ATTACKING)
|
||||
ADD_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_PARRY_TRAIT)
|
||||
if(data.parry_flags & PARRY_LOCK_SPRINTING)
|
||||
ADD_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_PARRY_TRAIT)
|
||||
handle_parry_starting_effects(data)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Massive snowflake proc for getting something to parry with.
|
||||
*
|
||||
* @return list of [method, data, tool], where method is the parry method define, data is the block_parry_data var that must be run through return_block_parry_data, and tool is the concept/object/martial art/etc used.
|
||||
*/
|
||||
/mob/living/proc/determine_parry_method(silent = TRUE, autoparry = FALSE)
|
||||
// Prioritize item, then martial art, then unarmed.
|
||||
// yanderedev else if time
|
||||
var/obj/item/using_item = get_active_held_item()
|
||||
@@ -55,7 +95,8 @@
|
||||
var/list/other_items = list()
|
||||
var/list/override = list()
|
||||
if(SEND_SIGNAL(src, COMSIG_LIVING_ACTIVE_PARRY_START, method, tool, other_items, override) & COMPONENT_PREVENT_PARRY_START)
|
||||
to_chat(src, "<span class='warning'>Something is preventing you from parrying!</span>")
|
||||
if(!silent)
|
||||
to_chat(src, "<span class='warning'>Something is preventing you from parrying!</span>")
|
||||
return
|
||||
if(length(override))
|
||||
var/datum/thing = override[1]
|
||||
@@ -72,35 +113,10 @@
|
||||
method = ITEM_PARRY
|
||||
data = using_item.block_parry_data
|
||||
if(!method)
|
||||
to_chat(src, "<span class='warning'>You have nothing to parry with!</span>")
|
||||
if(!silent)
|
||||
to_chat(src, "<span class='warning'>You have nothing to parry with!</span>")
|
||||
return FALSE
|
||||
//QOL: Try to enable combat mode if it isn't already
|
||||
SEND_SIGNAL(src, COMSIG_ENABLE_COMBAT_MODE)
|
||||
if(SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
to_chat(src, "<span class='warning'>You must be in combat mode to parry!</span>")
|
||||
return FALSE
|
||||
data = return_block_parry_datum(data)
|
||||
var/full_parry_duration = data.parry_time_windup + data.parry_time_active + data.parry_time_spindown
|
||||
// no system in place to "fallback" if out of the 3 the top priority one can't parry due to constraints but something else can.
|
||||
// can always implement it later, whatever.
|
||||
if((data.parry_respect_clickdelay && !CheckActionCooldown()) || ((parry_end_time_last + data.parry_cooldown) > world.time))
|
||||
to_chat(src, "<span class='warning'>You are not ready to parry (again)!</span>")
|
||||
return FALSE
|
||||
// Point of no return, make sure everything is set.
|
||||
parrying = method
|
||||
if(method == ITEM_PARRY)
|
||||
active_parry_item = using_item
|
||||
if(!UseStaminaBuffer(data.parry_stamina_cost, TRUE))
|
||||
return FALSE
|
||||
parry_start_time = world.time
|
||||
successful_parries = list()
|
||||
addtimer(CALLBACK(src, .proc/end_parry_sequence), full_parry_duration)
|
||||
if(data.parry_flags & PARRY_LOCK_ATTACKING)
|
||||
ADD_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_PARRY_TRAIT)
|
||||
if(data.parry_flags & PARRY_LOCK_SPRINTING)
|
||||
ADD_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_PARRY_TRAIT)
|
||||
handle_parry_starting_effects(data)
|
||||
return TRUE
|
||||
return list(method, data, tool)
|
||||
|
||||
/**
|
||||
* Tries to find a backup parry item.
|
||||
@@ -146,6 +162,7 @@
|
||||
parry_start_time = 0
|
||||
parry_end_time_last = world.time + (successful? 0 : data.parry_failed_cooldown_duration)
|
||||
successful_parries = null
|
||||
successful_parry_counterattacks = null
|
||||
|
||||
/**
|
||||
* Handles starting effects for parrying.
|
||||
@@ -178,17 +195,17 @@
|
||||
/**
|
||||
* Called when an attack is parried using this, whether or not the parry was successful.
|
||||
*/
|
||||
/obj/item/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time)
|
||||
/obj/item/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time, autoparry = FALSE)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried innately, whether or not the parry was successful.
|
||||
*/
|
||||
/mob/living/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time)
|
||||
/mob/living/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time, autoparry = FALSE)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried using this, whether or not the parry was successful.
|
||||
*/
|
||||
/datum/martial_art/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time)
|
||||
/datum/martial_art/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time, autoparry = FALSE)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
|
||||
@@ -205,6 +222,94 @@
|
||||
*/
|
||||
/datum/martial_art/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency, list/effect_text)
|
||||
|
||||
/**
|
||||
* Attempts to automatically parry an attacker.
|
||||
*/
|
||||
/mob/living/proc/attempt_auto_parry(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list())
|
||||
// determine how we'll parry
|
||||
var/list/determined = determine_parry_method(TRUE, TRUE)
|
||||
if(!islist(determined))
|
||||
return BLOCK_NONE
|
||||
var/datum/block_parry_data/data = return_block_parry_datum(determined[2])
|
||||
if(!data.parry_automatic_enabled || (last_autoparry > (world.time - data.autoparry_cooldown_absolute)))
|
||||
return BLOCK_NONE
|
||||
if(attack_type && !(attack_type & data.parry_attack_types))
|
||||
return BLOCK_NONE
|
||||
// before doing anything, check if the user moused over them properly
|
||||
if(!client)
|
||||
return BLOCK_NONE
|
||||
var/found = attacker == client.mouseObject
|
||||
if(!found)
|
||||
for(var/i in client.moused_over_objects)
|
||||
if(i == object)
|
||||
if((client.moused_over_objects[i] + (data.autoparry_mouse_delay_maximum)) >= world.time)
|
||||
found = TRUE
|
||||
break
|
||||
if(!found)
|
||||
return BLOCK_NONE
|
||||
|
||||
// if that works, try to start parry
|
||||
// first, check cooldowns
|
||||
|
||||
// now, depending on if we're doing a single simulation or a full sequence
|
||||
last_autoparry = world.time
|
||||
if(data.autoparry_sequence_simulation)
|
||||
// for full sequence simulation
|
||||
initiate_parry_sequence(TRUE, determined)
|
||||
if(data.autoparry_sequence_start_time == -1)
|
||||
parry_start_time = world.time - data.parry_time_windup
|
||||
else
|
||||
parry_start_time = world.time - data.autoparry_sequence_start_time
|
||||
// recurse back to original
|
||||
return run_parry(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, FALSE)
|
||||
else
|
||||
// yes, this is mostly a copypaste of run_parry.
|
||||
var/efficiency = data.attack_type_list_scan(data.autoparry_single_efficiency_override, attack_type)
|
||||
if(isnull(efficiency))
|
||||
efficiency = data.autoparry_single_efficiency
|
||||
var/method = determined[1]
|
||||
switch(method)
|
||||
if(ITEM_PARRY)
|
||||
var/obj/item/I = determined[3]
|
||||
. = I.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, null, TRUE)
|
||||
if(UNARMED_PARRY)
|
||||
. = on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, null, TRUE)
|
||||
if(MARTIAL_PARRY)
|
||||
. = mind.martial_art.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, null, TRUE)
|
||||
if(!isnull(return_list[BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY])) // one of our procs overrode
|
||||
efficiency = return_list[BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY]
|
||||
if(efficiency <= 0) // Do not allow automatically handled/standardized parries that increase damage for now.
|
||||
return
|
||||
. |= BLOCK_SHOULD_PARTIAL_MITIGATE
|
||||
if(efficiency >= data.parry_efficiency_perfect)
|
||||
. |= data.perfect_parry_block_return_flags
|
||||
if(data.perfect_parry_block_return_list)
|
||||
return_list |= data.perfect_parry_block_return_list
|
||||
else if(efficiency >= data.parry_efficiency_considered_successful)
|
||||
. |= data.imperfect_parry_block_return_flags
|
||||
if(data.imperfect_parry_block_return_list)
|
||||
return_list |= data.imperfect_parry_block_return_list
|
||||
else
|
||||
. |= data.failed_parry_block_return_flags
|
||||
if(data.failed_parry_block_return_list)
|
||||
return_list |= data.failed_parry_block_return_list
|
||||
if(isnull(return_list[BLOCK_RETURN_MITIGATION_PERCENT])) // if one of the on_active_parry procs overrode. We don't have to worry about interference since parries are the first thing checked in the [do_run_block()] sequence.
|
||||
return_list[BLOCK_RETURN_MITIGATION_PERCENT] = clamp(efficiency, 0, 100) // do not allow > 100% or < 0% for now.
|
||||
if((return_list[BLOCK_RETURN_MITIGATION_PERCENT] >= 100) || (damage <= 0))
|
||||
. |= BLOCK_SUCCESS
|
||||
var/list/effect_text
|
||||
var/pacifist_counter_check = TRUE
|
||||
if(HAS_TRAIT(src, TRAIT_PACIFISM))
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
pacifist_counter_check = (!active_parry_item.force || active_parry_item.damtype == STAMINA)
|
||||
else
|
||||
pacifist_counter_check = FALSE //Both martial and unarmed counter attacks generally are harmful, so no need to have the same line twice.
|
||||
if(efficiency >= data.parry_efficiency_to_counterattack && pacifist_counter_check && !return_list[BLOCK_RETURN_FORCE_NO_PARRY_COUNTERATTACK] && (!(attacker in successful_parry_counterattacks) && !data.parry_allow_repeated_counterattacks))
|
||||
effect_text = run_parry_countereffects(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, data)
|
||||
if(data.parry_flags & PARRY_DEFAULT_HANDLE_FEEDBACK)
|
||||
handle_parry_feedback(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, effect_text, data)
|
||||
|
||||
/**
|
||||
* Gets the stage of our parry sequence we're currently in.
|
||||
*/
|
||||
@@ -235,12 +340,20 @@
|
||||
return world.time - parry_start_time
|
||||
|
||||
/// same return values as normal blocking, called with absolute highest priority in the block "chain".
|
||||
/mob/living/proc/run_parry(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list())
|
||||
/mob/living/proc/run_parry(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), allow_auto = TRUE)
|
||||
var/stage = get_parry_stage()
|
||||
if(attack_type & ATTACK_TYPE_PARRY_COUNTERATTACK)
|
||||
return BLOCK_NONE // don't infinite loop
|
||||
if(stage != PARRY_ACTIVE)
|
||||
return BLOCK_NONE
|
||||
// If they're not currently parrying, attempt auto parry
|
||||
if(stage == NOT_PARRYING)
|
||||
if(!allow_auto || SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
return BLOCK_NONE
|
||||
return attempt_auto_parry(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
|
||||
else
|
||||
return BLOCK_NONE
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
if(attack_type && (!(attack_type & data.parry_attack_types) || (attack_type & ATTACK_TYPE_PARRY_COUNTERATTACK))) // if this attack is from a parry do not parry it lest we infinite loop.
|
||||
if(attack_type && !(attack_type & data.parry_attack_types))
|
||||
return BLOCK_NONE
|
||||
var/efficiency = data.get_parry_efficiency(attack_type, get_parry_time())
|
||||
switch(parrying)
|
||||
@@ -281,7 +394,7 @@
|
||||
pacifist_counter_check = (!active_parry_item.force || active_parry_item.damtype == STAMINA)
|
||||
else
|
||||
pacifist_counter_check = FALSE //Both martial and unarmed counter attacks generally are harmful, so no need to have the same line twice.
|
||||
if(efficiency >= data.parry_efficiency_to_counterattack && pacifist_counter_check)
|
||||
if(efficiency >= data.parry_efficiency_to_counterattack && pacifist_counter_check && !return_list[BLOCK_RETURN_FORCE_NO_PARRY_COUNTERATTACK] && (!(attacker in successful_parry_counterattacks) && !data.parry_allow_repeated_counterattacks))
|
||||
effect_text = run_parry_countereffects(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
|
||||
if(data.parry_flags & PARRY_DEFAULT_HANDLE_FEEDBACK)
|
||||
handle_parry_feedback(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, effect_text)
|
||||
@@ -289,8 +402,9 @@
|
||||
if(length(successful_parries) >= data.parry_max_attacks)
|
||||
end_parry_sequence()
|
||||
|
||||
/mob/living/proc/handle_parry_feedback(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency, list/effect_text)
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
/mob/living/proc/handle_parry_feedback(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency, list/effect_text, datum/block_parry_data/data)
|
||||
if(!data)
|
||||
data = get_parry_data()
|
||||
var/knockdown_check = FALSE
|
||||
if(data.parry_data[PARRY_KNOCKDOWN_ATTACKER] && parry_efficiency >= data.parry_efficiency_to_counterattack)
|
||||
knockdown_check = TRUE
|
||||
@@ -299,12 +413,14 @@
|
||||
visible_message("<span class='danger'>[src] parries [attack_text][length(effect_text)? ", [english_list(effect_text)] [attacker]" : ""][length(effect_text) && knockdown_check? " and" : ""][knockdown_check? " knocking them to the ground" : ""]!</span>")
|
||||
|
||||
/// Run counterattack if any
|
||||
/mob/living/proc/run_parry_countereffects(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency)
|
||||
/mob/living/proc/run_parry_countereffects(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency, datum/block_parry_data/data)
|
||||
if(!isliving(attacker))
|
||||
return
|
||||
var/mob/living/L = attacker
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
if(!data)
|
||||
data = get_parry_data()
|
||||
var/list/effect_text = list()
|
||||
successful_parry_counterattacks |= attacker
|
||||
// Always proc so items can override behavior easily
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
var/results
|
||||
if(I == active_block_item)
|
||||
results = I.active_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list, attack_direction)
|
||||
else if(I.can_passive_block() && !SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))
|
||||
results = I.passive_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list, attack_direction)
|
||||
else
|
||||
results = I.run_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
|
||||
. |= results
|
||||
|
||||
@@ -75,7 +75,7 @@ GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
var/block_stamina_buffer_ratio = 1
|
||||
|
||||
/// Stamina dealt directly via UseStaminaBuffer() per SECOND of block.
|
||||
var/block_stamina_cost_per_second = 1.5
|
||||
var/block_stamina_cost_per_second = 1
|
||||
/// Prevent stamina buffer regeneration while block?
|
||||
var/block_no_stambuffer_regeneration = TRUE
|
||||
/// Prevent stamina regeneration while block?
|
||||
@@ -93,20 +93,31 @@ GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
/// Sounds for blocking
|
||||
var/list/block_sounds = list('sound/block_parry/block_metal1.ogg' = 1, 'sound/block_parry/block_metal1.ogg' = 1)
|
||||
|
||||
// Autoblock
|
||||
// Other than for overrides, this mostly just reads from the above vars
|
||||
/// Can this item automatically block?
|
||||
var/block_automatic_enabled = TRUE
|
||||
/// Directions that you can autoblock in. Null to default to normal directions.
|
||||
var/block_automatic_directions = null
|
||||
/// Effectiveness multiplier for automated block. Only applies to efficiency, absorption and limits stay the same!
|
||||
var/block_automatic_mitigation_multiplier = 0.33
|
||||
/// Stamina cost multiplier for automated block
|
||||
var/block_automatic_stamina_multiplier = 1
|
||||
|
||||
/////////// PARRYING ////////////
|
||||
/// Prioriry for [mob/do_run_block()] while we're being used to parry.
|
||||
/// Priority for [mob/do_run_block()] while we're being used to parry.
|
||||
// None - Parry is always highest priority!
|
||||
/// Parry doesn't work if you aren't able to otherwise attack due to clickdelay
|
||||
var/parry_respect_clickdelay = TRUE
|
||||
var/parry_respect_clickdelay = FALSE
|
||||
/// Parry stamina cost
|
||||
var/parry_stamina_cost = 5
|
||||
/// Attack types we can block
|
||||
var/parry_attack_types = ALL
|
||||
/// Parry flags
|
||||
var/parry_flags = PARRY_DEFAULT_HANDLE_FEEDBACK | PARRY_LOCK_ATTACKING
|
||||
var/parry_flags = PARRY_DEFAULT_HANDLE_FEEDBACK
|
||||
|
||||
/// Parry windup duration in deciseconds. 0 to this is windup, afterwards is main stage.
|
||||
var/parry_time_windup = 2
|
||||
var/parry_time_windup = 0
|
||||
/// Parry spindown duration in deciseconds. main stage end to this is the spindown stage, afterwards the parry fully ends.
|
||||
var/parry_time_spindown = 3
|
||||
/// Main parry window in deciseconds. This is between [parry_time_windup] and [parry_time_spindown]
|
||||
@@ -139,7 +150,7 @@ GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
/// Efficiency must be at least this to be considered successful
|
||||
var/parry_efficiency_considered_successful = 0.1
|
||||
/// Efficiency must be at least this to run automatic counterattack
|
||||
var/parry_efficiency_to_counterattack = 0.1
|
||||
var/parry_efficiency_to_counterattack = INFINITY
|
||||
/// Maximum attacks to parry successfully or unsuccessfully (but not efficiency < 0) during active period, hitting this immediately ends the sequence.
|
||||
var/parry_max_attacks = INFINITY
|
||||
/// Visual icon state override for parrying
|
||||
@@ -153,7 +164,7 @@ GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
/// Stagger duration post-parry if you fail to parry an attack
|
||||
var/parry_failed_stagger_duration = 3.5 SECONDS
|
||||
/// Clickdelay duration post-parry if you fail to parry an attack
|
||||
var/parry_failed_clickcd_duration = 2 SECONDS
|
||||
var/parry_failed_clickcd_duration = 0 SECONDS
|
||||
/// Parry cooldown post-parry if failed. This is ADDED to parry_cooldown!!!
|
||||
var/parry_failed_cooldown_duration = 0 SECONDS
|
||||
|
||||
@@ -166,6 +177,29 @@ GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
var/perfect_parry_block_return_list
|
||||
var/imperfect_parry_block_return_list
|
||||
var/failed_parry_block_return_list
|
||||
/// Allow multiple counterattacks per parry sequence. Bad idea.
|
||||
var/parry_allow_repeated_counterattacks = FALSE
|
||||
|
||||
// Auto parry
|
||||
// Anything not specified like cooldowns/clickdelay respecting is pulled from above.
|
||||
/// Can this data automatically parry? This is off by default because this is something that requires thought to balance.
|
||||
var/parry_automatic_enabled = FALSE
|
||||
/// Hard autoparry cooldown
|
||||
var/autoparry_cooldown_absolute = 7.5 SECONDS
|
||||
/// Autoparry : Simulate a parry sequence starting at a certain tick, or simply simulate a single attack parry?
|
||||
var/autoparry_sequence_simulation = FALSE
|
||||
// Single attack simulation:
|
||||
/// Single attack autoparry - efficiency
|
||||
var/autoparry_single_efficiency = 75
|
||||
/// Single attack autoparry - efficiency overrides by attack type, see above
|
||||
var/list/autoparry_single_efficiency_override
|
||||
// Parry sequence simulation:
|
||||
/// Decisecond of sequence to start on. -1 to start to 0th tick of active parry window.
|
||||
var/autoparry_sequence_start_time = -1
|
||||
// Clickdelay/cooldown settings not included, as well as whether or not to lock attack/sprinting/etc. They will be pulled from the above.
|
||||
|
||||
/// ADVANCED - Autoparry requirement for time since last moused over for a specific object
|
||||
var/autoparry_mouse_delay_maximum = 0.35 SECONDS
|
||||
|
||||
/**
|
||||
* Quirky proc to get average of flags in list that are in attack_type because why is attack_type a flag.
|
||||
@@ -308,6 +342,7 @@ GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
RENDER_VARIABLE_SIMPLE(parry_cooldown, "Deciseconds it has to be since the last time a parry sequence <b>ended</b> for you before you can parry again.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_failed_stagger_duration, "Deciseconds you are staggered for at the of the parry sequence if you do not successfully parry anything.")
|
||||
RENDER_VARIABLE_SIMPLE(parry_failed_clickcd_duration, "Deciseconds you are put on attack cooldown at the end of the parry sequence if you do not successfully parry anything.")
|
||||
dat += ""
|
||||
dat += "</div></table>"
|
||||
return dat.Join("")
|
||||
#undef RENDER_VARIABLE_SIMPLE
|
||||
|
||||
@@ -50,8 +50,12 @@
|
||||
var/obj/effect/abstract/parry/parry_visual_effect
|
||||
/// world.time of last parry end
|
||||
var/parry_end_time_last = 0
|
||||
/// Last autoparry
|
||||
var/last_autoparry = 0
|
||||
/// Successful parries within the current parry cycle. It's a list of efficiency percentages.
|
||||
var/list/successful_parries
|
||||
/// Current parry counterattacks. Makes sure we can only counterattack someone once per parry.
|
||||
var/list/successful_parry_counterattacks
|
||||
|
||||
var/confused = 0 //Makes the mob move in random directions.
|
||||
|
||||
|
||||
@@ -267,16 +267,10 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
|
||||
eavesdrop_range = EAVESDROP_EXTRA_RANGE
|
||||
var/list/listening = get_hearers_in_view(message_range+eavesdrop_range, source)
|
||||
var/list/the_dead = list()
|
||||
var/list/yellareas //CIT CHANGE - adds the ability for yelling to penetrate walls and echo throughout areas
|
||||
if(!eavesdrop_range && say_test(message) == "2") //CIT CHANGE - ditto
|
||||
yellareas = get_areas_in_range(message_range*0.5, source) //CIT CHANGE - ditto
|
||||
|
||||
for(var/_M in GLOB.player_list)
|
||||
var/mob/M = _M
|
||||
if(M.stat != DEAD) //not dead, not important
|
||||
if(yellareas) //CIT CHANGE - see above. makes yelling penetrate walls
|
||||
var/area/A = get_area(M) //CIT CHANGE - ditto
|
||||
if(istype(A) && A.ambientsounds != SPACE && (A in yellareas)) //CIT CHANGE - ditto
|
||||
listening |= M //CIT CHANGE - ditto
|
||||
continue
|
||||
if(!M.client || !client) //client is so that ghosts don't have to listen to mice
|
||||
continue
|
||||
@@ -303,6 +297,9 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
|
||||
AM.Hear(rendered, src, message_language, message, null, spans, message_mode, source)
|
||||
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message)
|
||||
|
||||
if(!eavesdrop_range && say_test(message) == "2") // Yell hook
|
||||
process_yelling(listening, rendered, src, message_language, message, spans, message_mode, source)
|
||||
|
||||
//speech bubble
|
||||
var/list/speech_bubble_recipients = list()
|
||||
for(var/mob/M in listening)
|
||||
@@ -312,6 +309,30 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
|
||||
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
|
||||
INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_recipients, 30)
|
||||
|
||||
/atom/movable/proc/process_yelling(list/already_heard, rendered, atom/movable/speaker, datum/language/message_language, message, list/spans, message_mode, obj/source)
|
||||
if(last_yell > (world.time - 10))
|
||||
to_chat(src, "<span class='warning'>Your voice doesn't project as far as you try to yell in such quick succession.") // yeah no, no spamming an expensive floodfill.
|
||||
return
|
||||
last_yell = world.time
|
||||
var/list/overhearing = list()
|
||||
var/list/overhearing_text = list()
|
||||
overhearing = yelling_wavefill(src, yell_power)
|
||||
if(!overhearing.len)
|
||||
overhearing_text = "none"
|
||||
else
|
||||
for(var/mob/M as anything in overhearing)
|
||||
overhearing_text += key_name(M)
|
||||
overhearing_text = english_list(overhearing_text)
|
||||
log_say("YELL: [ismob(src)? key_name(src) : src] yelled [message] with overhearing mobs [overhearing_text]")
|
||||
// overhearing = get_hearers_in_view(35, src) | get_hearers_in_range(5, src)
|
||||
overhearing -= already_heard
|
||||
if(!overhearing.len)
|
||||
return
|
||||
// to_chat(world, "DEBUG: overhearing [english_list(overhearing)]")
|
||||
for(var/_AM in overhearing)
|
||||
var/atom/movable/AM = _AM
|
||||
AM.Hear(rendered, speaker, message_language, message, null, spans, message_mode, source)
|
||||
|
||||
/mob/proc/binarycheck()
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -35,11 +35,10 @@
|
||||
return
|
||||
CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/out_of_combat_timer, out_of_combat_timer)
|
||||
CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/base_regeneration, base_regeneration)
|
||||
CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/combat_regeneration, combat_regeneration)
|
||||
CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/percent_regeneration_out_of_combat, percent_regeneration_out_of_combat)
|
||||
CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/post_action_penalty_delay, post_action_penalty_delay)
|
||||
CONFIG_CACHE_ENTRY_AND_FETCH_VALUE(number/stamina_combat/post_action_penalty_factor, post_action_penalty_factor)
|
||||
var/base_regen = (SEND_SIGNAL(src, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_INACTIVE))? base_regeneration : combat_regeneration
|
||||
var/base_regen = base_regeneration
|
||||
var/time_since_last_action = world.time - stamina_buffer_last_use
|
||||
var/action_penalty = ((time_since_last_action) < (post_action_penalty_delay * 10))? post_action_penalty_factor : 1
|
||||
var/out_of_combat_bonus = (time_since_last_action < (out_of_combat_timer * 10))? 0 : ((buffer_max * percent_regeneration_out_of_combat * 0.01))
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
// ignore_castun = same logic as Paralyze() in general
|
||||
// override_duration = If this is set, does Paralyze() for this duration.
|
||||
// override_stam = If this is set, does this amount of stamina damage.
|
||||
/mob/living/proc/DefaultCombatKnockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg)
|
||||
// knocktofloor - whether to knock them to the ground
|
||||
/mob/living/proc/DefaultCombatKnockdown(amount, updating = TRUE, ignore_canknockdown = FALSE, override_hardstun, override_stamdmg, knocktofloor = TRUE)
|
||||
if(!iscarbon(src))
|
||||
return Paralyze(amount, updating, ignore_canknockdown)
|
||||
if(!ignore_canknockdown && !(status_flags & CANKNOCKDOWN))
|
||||
@@ -13,7 +14,8 @@
|
||||
buckled.unbuckle_mob(src)
|
||||
var/drop_items = amount > 80 //80 is cutoff for old item dropping behavior
|
||||
var/stamdmg = isnull(override_stamdmg)? (amount * 0.25) : override_stamdmg
|
||||
KnockToFloor(drop_items, TRUE, updating)
|
||||
if(knocktofloor)
|
||||
KnockToFloor(drop_items, TRUE, updating)
|
||||
adjustStaminaLoss(stamdmg)
|
||||
if(!isnull(override_hardstun))
|
||||
Paralyze(override_hardstun)
|
||||
|
||||
@@ -45,3 +45,9 @@
|
||||
/datum/movespeed_modifier/status_effect/mkultra
|
||||
multiplicative_slowdown = -2
|
||||
blacklisted_movetypes= FLYING|FLOATING
|
||||
|
||||
/datum/movespeed_modifier/status_effect/stagger
|
||||
variable = TRUE
|
||||
|
||||
/datum/movespeed_modifier/status_effect/off_balance
|
||||
variable = TRUE
|
||||
|
||||
@@ -118,7 +118,7 @@ Contents:
|
||||
ADD_TRAIT(n_hood, TRAIT_NODROP, NINJA_SUIT_TRAIT)
|
||||
n_shoes = H.shoes
|
||||
ADD_TRAIT(n_shoes, TRAIT_NODROP, NINJA_SUIT_TRAIT)
|
||||
n_shoes.slowdown--
|
||||
n_shoes.slowdown -= 0.5
|
||||
n_gloves = H.gloves
|
||||
ADD_TRAIT(n_gloves, TRAIT_NODROP, NINJA_SUIT_TRAIT)
|
||||
return TRUE
|
||||
@@ -139,7 +139,7 @@ Contents:
|
||||
REMOVE_TRAIT(n_hood, TRAIT_NODROP, NINJA_SUIT_TRAIT)
|
||||
if(n_shoes)
|
||||
REMOVE_TRAIT(n_shoes, TRAIT_NODROP, NINJA_SUIT_TRAIT)
|
||||
n_shoes.slowdown++
|
||||
n_shoes.slowdown += 0.5
|
||||
if(n_gloves)
|
||||
n_gloves.icon_state = "s-ninja"
|
||||
n_gloves.item_state = "s-ninja"
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
projectile_type = /obj/item/projectile/beam/laser
|
||||
select_name = "kill"
|
||||
|
||||
/obj/item/ammo_casing/energy/laser/minigun
|
||||
click_cooldown_override = 2
|
||||
|
||||
/obj/item/ammo_casing/energy/lasergun
|
||||
projectile_type = /obj/item/projectile/beam/laser
|
||||
e_cost = 83
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
projectile_type = /obj/item/projectile/energy/electrode/security
|
||||
e_cost = 100
|
||||
|
||||
/obj/item/ammo_casing/energy/electrode/hos
|
||||
projectile_type = /obj/item/projectile/energy/electrode/security/hos
|
||||
e_cost = 100
|
||||
|
||||
/obj/item/ammo_casing/energy/electrode/spec
|
||||
e_cost = 100
|
||||
|
||||
@@ -16,7 +20,6 @@
|
||||
fire_sound = 'sound/weapons/gunshot.ogg'
|
||||
e_cost = 100
|
||||
|
||||
|
||||
/obj/item/ammo_casing/energy/electrode/old
|
||||
e_cost = 1000
|
||||
|
||||
|
||||
@@ -3,9 +3,3 @@
|
||||
ammo_type = /obj/item/ammo_casing/caseless/magspear
|
||||
caliber = "speargun"
|
||||
max_ammo = 1
|
||||
|
||||
/obj/item/ammo_box/magazine/internal/minigun
|
||||
name = "gatling gun fusion core"
|
||||
ammo_type = /obj/item/ammo_casing/caseless/laser/gatling
|
||||
caliber = "gatling"
|
||||
max_ammo = 5000
|
||||
|
||||
@@ -48,12 +48,24 @@
|
||||
|
||||
/obj/item/gun/energy/e_gun/hos
|
||||
name = "\improper X-01 MultiPhase Energy Gun"
|
||||
desc = "This is an expensive, modern recreation of an antique laser gun. This gun has several unique firemodes, but lacks the ability to recharge over time in exchange for inbuilt advanced firearm EMP shielding."
|
||||
desc = "This is an expensive, modern recreation of an antique laser gun. This gun has several unique firemodes, but lacks the ability to recharge over time in exchange for inbuilt advanced firearm EMP shielding. <span class='boldnotice'>Right click in combat mode to fire a taser shot with a cooldown.</span>"
|
||||
icon_state = "hoslaser"
|
||||
force = 10
|
||||
ammo_type = list(/obj/item/ammo_casing/energy/disabler, /obj/item/ammo_casing/energy/laser/hos, /obj/item/ammo_casing/energy/ion/hos)
|
||||
ammo_type = list(/obj/item/ammo_casing/energy/disabler, /obj/item/ammo_casing/energy/laser/hos, /obj/item/ammo_casing/energy/ion/hos, /obj/item/ammo_casing/energy/electrode/hos)
|
||||
ammo_x_offset = 4
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
|
||||
var/last_altfire = 0
|
||||
var/altfire_delay = 0
|
||||
|
||||
/obj/item/gun/energy/e_gun/hos/altafterattack(atom/target, mob/user, proximity_flag, params)
|
||||
. = TRUE
|
||||
if(last_altfire + altfire_delay > world.time)
|
||||
return
|
||||
var/current_index = current_firemode_index
|
||||
set_firemode_to_type(/obj/item/ammo_casing/energy/electrode)
|
||||
process_afterattack(target, user, proximity_flag, params)
|
||||
set_firemode_index(current_index)
|
||||
last_altfire = world.time
|
||||
|
||||
/obj/item/gun/energy/e_gun/hos/emp_act(severity)
|
||||
return
|
||||
|
||||
@@ -11,19 +11,19 @@
|
||||
righthand_file = 'icons/mob/inhands/equipment/backpack_righthand.dmi'
|
||||
slot_flags = ITEM_SLOT_BACK
|
||||
w_class = WEIGHT_CLASS_HUGE
|
||||
var/obj/item/gun/ballistic/minigun/gun
|
||||
var/obj/item/gun/energy/minigun/gun
|
||||
var/armed = 0 //whether the gun is attached, 0 is attached, 1 is the gun is wielded.
|
||||
var/overheat = 0
|
||||
var/overheat_max = 40
|
||||
var/overheat_max = 50
|
||||
var/heat_diffusion = 1
|
||||
|
||||
/obj/item/minigunpack/Initialize()
|
||||
. = ..()
|
||||
gun = new(src)
|
||||
START_PROCESSING(SSobj, src)
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/obj/item/minigunpack/Destroy()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
return ..()
|
||||
|
||||
/obj/item/minigunpack/process()
|
||||
@@ -72,7 +72,6 @@
|
||||
var/obj/screen/inventory/hand/H = over_object
|
||||
M.putItemFromInventoryInHandIfPossible(src, H.held_index)
|
||||
|
||||
|
||||
/obj/item/minigunpack/update_icon_state()
|
||||
if(armed)
|
||||
icon_state = "notholstered"
|
||||
@@ -91,8 +90,7 @@
|
||||
update_icon()
|
||||
user.update_inv_back()
|
||||
|
||||
|
||||
/obj/item/gun/ballistic/minigun
|
||||
/obj/item/gun/energy/minigun
|
||||
name = "laser gatling gun"
|
||||
desc = "An advanced laser cannon with an incredible rate of fire. Requires a bulky backpack power source to use."
|
||||
icon = 'icons/obj/guns/minigun.dmi'
|
||||
@@ -103,17 +101,20 @@
|
||||
slot_flags = null
|
||||
w_class = WEIGHT_CLASS_HUGE
|
||||
custom_materials = null
|
||||
burst_size = 3
|
||||
automatic = 0
|
||||
fire_delay = 1
|
||||
automatic = 0.5
|
||||
fire_delay = 2
|
||||
ammo_type = list(
|
||||
/obj/item/ammo_casing/energy/laser
|
||||
)
|
||||
|
||||
weapon_weight = WEAPON_HEAVY
|
||||
fire_sound = 'sound/weapons/laser.ogg'
|
||||
mag_type = /obj/item/ammo_box/magazine/internal/minigun
|
||||
casing_ejector = FALSE
|
||||
charge_sections = 0
|
||||
shaded_charge = 0
|
||||
item_flags = NEEDS_PERMIT | SLOWS_WHILE_IN_HAND
|
||||
var/obj/item/minigunpack/ammo_pack
|
||||
|
||||
/obj/item/gun/ballistic/minigun/Initialize()
|
||||
/obj/item/gun/energy/minigun/Initialize()
|
||||
if(istype(loc, /obj/item/minigunpack)) //We should spawn inside an ammo pack so let's use that one.
|
||||
ammo_pack = loc
|
||||
else
|
||||
@@ -121,29 +122,29 @@
|
||||
|
||||
return ..()
|
||||
|
||||
/obj/item/gun/ballistic/minigun/attack_self(mob/living/user)
|
||||
/obj/item/gun/energy/minigun/attack_self(mob/living/user)
|
||||
return
|
||||
|
||||
/obj/item/gun/ballistic/minigun/dropped(mob/user)
|
||||
/obj/item/gun/energy/minigun/dropped(mob/user)
|
||||
. = ..()
|
||||
if(ammo_pack)
|
||||
ammo_pack.attach_gun(user)
|
||||
else
|
||||
qdel(src)
|
||||
|
||||
/obj/item/gun/ballistic/minigun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
|
||||
/obj/item/gun/energy/minigun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0, stam_cost = 0)
|
||||
if(ammo_pack)
|
||||
if(ammo_pack.overheat < ammo_pack.overheat_max)
|
||||
ammo_pack.overheat += burst_size
|
||||
ammo_pack.overheat++
|
||||
..()
|
||||
else
|
||||
to_chat(user, "The gun's heat sensor locked the trigger to prevent lens damage.")
|
||||
|
||||
/obj/item/gun/ballistic/minigun/afterattack(atom/target, mob/living/user, flag, params)
|
||||
/obj/item/gun/energy/minigun/afterattack(atom/target, mob/living/user, flag, params)
|
||||
if(!ammo_pack || ammo_pack.loc != user)
|
||||
to_chat(user, "You need the backpack power source to fire the gun!")
|
||||
. = ..()
|
||||
|
||||
/obj/item/gun/ballistic/minigun/dropped(mob/living/user)
|
||||
/obj/item/gun/energy/minigun/dropped(mob/living/user)
|
||||
. = ..()
|
||||
ammo_pack.attach_gun(user)
|
||||
@@ -37,15 +37,17 @@
|
||||
..()
|
||||
|
||||
/obj/item/projectile/energy/electrode/security
|
||||
tase_duration = 30
|
||||
tase_duration = 40
|
||||
knockdown = 0
|
||||
stamina = 10
|
||||
stamina = 0
|
||||
knockdown_stamoverride = 0
|
||||
knockdown_stam_max = 0
|
||||
strong_tase = FALSE
|
||||
|
||||
/obj/item/projectile/energy/electrode/security/hos
|
||||
knockdown = 100
|
||||
knockdown_stamoverride = 30
|
||||
knockdown_stam_max = null
|
||||
tase_duration = 10
|
||||
tase_duration = 40
|
||||
knockdown = 0
|
||||
stamina = 0
|
||||
knockdown_stamoverride = 0
|
||||
knockdown_stam_max = 0
|
||||
strong_tase = FALSE
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
var/spray_range = 3 //the range of tiles the sprayer will reach when in spray mode.
|
||||
var/stream_range = 1 //the range of tiles the sprayer will reach when in stream mode.
|
||||
var/stream_amount = 10 //the amount of reagents transfered when in stream mode.
|
||||
var/spray_delay = 3 //The amount of sleep() delay between each chempuff step.
|
||||
/// Amount of time it takes for a spray to completely travel.
|
||||
var/spray_delay = 8
|
||||
/// Last world.time of spray
|
||||
var/last_spray = 0
|
||||
/// Spray cooldown
|
||||
@@ -72,58 +73,20 @@
|
||||
if((last_spray + spray_cooldown) > world.time)
|
||||
return
|
||||
var/range = clamp(get_dist(src, A), 1, current_range)
|
||||
var/obj/effect/decal/chempuff/D = new /obj/effect/decal/chempuff(get_turf(src))
|
||||
D.create_reagents(amount_per_transfer_from_this, NONE, NO_REAGENTS_VALUE)
|
||||
var/puff_reagent_left = range //how many turf, mob or dense objet we can react with before we consider the chem puff consumed
|
||||
if(stream_mode)
|
||||
reagents.trans_to(D, amount_per_transfer_from_this)
|
||||
puff_reagent_left = 1
|
||||
else
|
||||
reagents.trans_to(D, amount_per_transfer_from_this, 1/range)
|
||||
D.color = mix_color_from_reagents(D.reagents.reagent_list)
|
||||
var/wait_step = CEILING(spray_delay * INVERSE(range), world.tick_lag)
|
||||
var/obj/effect/decal/chempuff/D = new /obj/effect/decal/chempuff(get_turf(src), stream_mode, wait_step, range, stream_mode? 1 : range)
|
||||
var/turf/T = get_turf(src)
|
||||
if(!T)
|
||||
return
|
||||
log_reagent("SPRAY: [key_name(usr)] fired [src] ([REF(src)]) [COORD(T)] at [A] ([REF(A)]) [COORD(A)] (chempuff: [D.reagents.log_list()])")
|
||||
var/wait_step = max(round(2+ spray_delay * INVERSE(range)), 2)
|
||||
D.create_reagents(amount_per_transfer_from_this, NONE, NO_REAGENTS_VALUE)
|
||||
if(stream_mode)
|
||||
reagents.trans_to(D, amount_per_transfer_from_this)
|
||||
else
|
||||
reagents.trans_to(D, amount_per_transfer_from_this, 1/range)
|
||||
D.color = mix_color_from_reagents(D.reagents.reagent_list)
|
||||
last_spray = world.time
|
||||
INVOKE_ASYNC(src, .proc/do_spray, A, wait_step, D, range, puff_reagent_left)
|
||||
return TRUE
|
||||
|
||||
/obj/item/reagent_containers/spray/proc/do_spray(atom/A, wait_step, obj/effect/decal/chempuff/D, range, puff_reagent_left)
|
||||
var/range_left = range
|
||||
for(var/i=0, i<range, i++)
|
||||
range_left--
|
||||
step_towards(D,A)
|
||||
sleep(wait_step)
|
||||
|
||||
for(var/atom/T in get_turf(D))
|
||||
if(T == D || T.invisibility) //we ignore the puff itself and stuff below the floor
|
||||
continue
|
||||
if(puff_reagent_left <= 0)
|
||||
break
|
||||
|
||||
if(stream_mode)
|
||||
if(ismob(T))
|
||||
var/mob/M = T
|
||||
if(!M.lying || !range_left)
|
||||
D.reagents.reaction(M, VAPOR)
|
||||
puff_reagent_left -= 1
|
||||
else if(!range_left)
|
||||
D.reagents.reaction(T, VAPOR)
|
||||
else
|
||||
D.reagents.reaction(T, VAPOR)
|
||||
if(ismob(T))
|
||||
puff_reagent_left -= 1
|
||||
|
||||
if(puff_reagent_left > 0 && (!stream_mode || !range_left))
|
||||
D.reagents.reaction(get_turf(D), VAPOR)
|
||||
puff_reagent_left -= 1
|
||||
|
||||
if(puff_reagent_left <= 0) // we used all the puff so we delete it.
|
||||
qdel(D)
|
||||
return
|
||||
qdel(D)
|
||||
D.run_puff(A)
|
||||
|
||||
/obj/item/reagent_containers/spray/attack_self(mob/user)
|
||||
stream_mode = !stream_mode
|
||||
@@ -207,7 +170,7 @@
|
||||
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
|
||||
volume = 40
|
||||
stream_range = 4
|
||||
spray_delay = 1
|
||||
spray_delay = 2
|
||||
amount_per_transfer_from_this = 5
|
||||
list_reagents = list(/datum/reagent/consumable/condensedcapsaicin = 40)
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
parry_time_perfect = 2
|
||||
parry_time_perfect_leeway = 0.75
|
||||
parry_imperfect_falloff_percent = 7.5
|
||||
parry_efficiency_to_counterattack = 100
|
||||
parry_efficiency_to_counterattack = INFINITY
|
||||
parry_efficiency_considered_successful = 80
|
||||
parry_efficiency_perfect = 120
|
||||
parry_failed_stagger_duration = 3 SECONDS
|
||||
@@ -266,14 +266,15 @@
|
||||
parry_time_perfect = 2
|
||||
parry_time_perfect_leeway = 2
|
||||
parry_failed_stagger_duration = 3 SECONDS
|
||||
parry_failed_clickcd_duration = 3 SECONDS
|
||||
parry_time_windup = 0
|
||||
parry_time_spindown = 0
|
||||
parry_imperfect_falloff_percent = 0
|
||||
parry_efficiency_to_counterattack = 100
|
||||
parry_efficiency_to_counterattack = INFINITY
|
||||
parry_efficiency_considered_successful = 120
|
||||
parry_efficiency_perfect = 120
|
||||
parry_data = list(PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 4)
|
||||
parry_automatic_enabled = TRUE
|
||||
autoparry_single_efficiency = 75
|
||||
|
||||
//unique hammers
|
||||
/obj/item/melee/smith/hammer/toolbox
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
var/max_stamina_damage = 0
|
||||
var/incoming_stam_mult = 1 //Multiplier for incoming staminaloss, decreases when taking staminaloss when the limb is disabled, resets back to 1 when limb is no longer disabled.
|
||||
var/max_damage = 0
|
||||
/// Threshold at which we are disabled. Defaults to max_damage if unset.
|
||||
var/disable_threshold
|
||||
var/stam_heal_tick = 0 //per Life(). Defaults to 0 due to citadel changes
|
||||
|
||||
var/brute_reduction = 0 //Subtracted to brute damage taken
|
||||
@@ -505,6 +507,12 @@
|
||||
return
|
||||
set_disabled(is_disabled(silent), silent)
|
||||
|
||||
/**
|
||||
* Gets the damage at which point we're disabled.
|
||||
*/
|
||||
/obj/item/bodypart/proc/get_disable_threshold()
|
||||
return isnull(disable_threshold)? max_damage : disable_threshold
|
||||
|
||||
/obj/item/bodypart/proc/is_disabled(silent = FALSE)
|
||||
if(!owner)
|
||||
return
|
||||
@@ -514,15 +522,16 @@
|
||||
var/datum/wound/W = i
|
||||
if(W.disabling)
|
||||
return BODYPART_DISABLED_WOUND
|
||||
var/disable_threshold = get_disable_threshold()
|
||||
if(can_dismember() && !HAS_TRAIT(owner, TRAIT_NODISMEMBER))
|
||||
. = disabled //inertia, to avoid limbs healing 0.1 damage and being re-enabled
|
||||
if(get_damage(TRUE) >= max_damage * (HAS_TRAIT(owner, TRAIT_EASYLIMBDISABLE) ? 0.6 : 1)) //Easy limb disable disables the limb at 40% health instead of 0%
|
||||
if(get_damage(TRUE) >= disable_threshold * (HAS_TRAIT(owner, TRAIT_EASYLIMBDISABLE) ? 0.6 : 1)) //Easy limb disable disables the limb at 40% health instead of 0%
|
||||
if(!last_maxed && !silent)
|
||||
owner.emote("scream")
|
||||
last_maxed = TRUE
|
||||
if(!is_organic_limb(FALSE) || stamina_dam >= max_damage)
|
||||
if(!is_organic_limb(FALSE) || stamina_dam >= disable_threshold)
|
||||
return BODYPART_DISABLED_DAMAGE
|
||||
else if(disabled && (get_damage(TRUE) <= (max_damage * 0.8))) // reenabled at 80% now instead of 50% as of wounds update
|
||||
else if(disabled && (get_damage(TRUE) <= (disable_threshold * 0.8))) // reenabled at 80% now instead of 50% as of wounds update
|
||||
last_maxed = FALSE
|
||||
return BODYPART_NOT_DISABLED
|
||||
else
|
||||
|
||||
@@ -59,12 +59,13 @@
|
||||
one though."
|
||||
icon_state = "default_human_l_arm"
|
||||
attack_verb = list("slapped", "punched")
|
||||
max_damage = 50
|
||||
max_damage = 150
|
||||
disable_threshold = 75
|
||||
max_stamina_damage = 50
|
||||
body_zone = BODY_ZONE_L_ARM
|
||||
body_part = ARM_LEFT
|
||||
aux_icons = list(BODY_ZONE_PRECISE_L_HAND = HANDS_PART_LAYER, "l_hand_behind" = BODY_BEHIND_LAYER)
|
||||
body_damage_coeff = 0.75
|
||||
body_damage_coeff = 0.25
|
||||
held_index = 1
|
||||
px_x = -6
|
||||
px_y = 0
|
||||
@@ -120,11 +121,12 @@
|
||||
among humans missing their right arm."
|
||||
icon_state = "default_human_r_arm"
|
||||
attack_verb = list("slapped", "punched")
|
||||
max_damage = 50
|
||||
max_damage = 150
|
||||
disable_threshold = 75
|
||||
body_zone = BODY_ZONE_R_ARM
|
||||
body_part = ARM_RIGHT
|
||||
aux_icons = list(BODY_ZONE_PRECISE_R_HAND = HANDS_PART_LAYER, "r_hand_behind" = BODY_BEHIND_LAYER)
|
||||
body_damage_coeff = 0.75
|
||||
body_damage_coeff = 0.25
|
||||
held_index = 2
|
||||
px_x = 6
|
||||
px_y = 0
|
||||
@@ -182,10 +184,11 @@
|
||||
luck. In this instance, it probably would not have helped."
|
||||
icon_state = "default_human_l_leg"
|
||||
attack_verb = list("kicked", "stomped")
|
||||
max_damage = 50
|
||||
max_damage = 150
|
||||
disable_threshold = 75
|
||||
body_zone = BODY_ZONE_L_LEG
|
||||
body_part = LEG_LEFT
|
||||
body_damage_coeff = 0.75
|
||||
body_damage_coeff = 0.25
|
||||
px_x = -2
|
||||
px_y = 12
|
||||
stam_heal_tick = STAM_RECOVERY_LIMB
|
||||
@@ -240,10 +243,11 @@
|
||||
// alternative spellings of 'pokey' are availible
|
||||
icon_state = "default_human_r_leg"
|
||||
attack_verb = list("kicked", "stomped")
|
||||
max_damage = 50
|
||||
max_damage = 150
|
||||
disable_threshold = 75
|
||||
body_zone = BODY_ZONE_R_LEG
|
||||
body_part = LEG_RIGHT
|
||||
body_damage_coeff = 0.75
|
||||
body_damage_coeff = 0.25
|
||||
px_x = 2
|
||||
px_y = 12
|
||||
max_stamina_damage = 50
|
||||
|
||||
Reference in New Issue
Block a user