diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 58e3305a45b..e36d5c027f5 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -34,7 +34,8 @@ SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, M, user) if(flags & (NOBLUDGEON)) return 0 - + if(check_martial_counter(M, user)) + return 0 if(can_operate(M)) //Checks if mob is lying down on table for surgery if(istype(src,/obj/item/robot_parts))//popup override for direct attach if(!attempt_initiate_surgery(src, M, user,1)) diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm index ed3536f8183..8e69face174 100644 --- a/code/datums/uplink_item.dm +++ b/code/datums/uplink_item.dm @@ -764,6 +764,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) excludefrom = list(/datum/game_mode/nuclear) refundable = TRUE +/datum/uplink_item/stealthy_weapons/cqc + name = "CQC Manual" + desc = "A manual that teaches a single user tactical Close-Quarters Combat before self-destructing. Does not restrict weapon usage, but cannot be used alongside Gloves of the North Star." + reference = "CQC" + item = /obj/item/CQC_manual + cost = 9 + /datum/uplink_item/stealthy_weapons/throwingweapons name = "Box of Throwing Weapons" desc = "A box of shurikens and reinforced bolas from ancient Earth martial arts. They are highly effective \ diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index c1ff654c246..f8798d77c5d 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -563,3 +563,10 @@ var/global/image/fire_overlay = image("icon" = 'icons/goonstation/effects/fire.d /obj/item/attack_hulk(mob/living/carbon/human/user) return FALSE + +/obj/item/proc/check_martial_counter(mob/living/carbon/human/target, mob/living/carbon/human/user) //handles block for CQC + if(target.check_block()) + target.visible_message("[target.name] blocks [src] and twists [user]'s arm behind [user.p_their()] back!", + "You block the attack!") + user.Stun(2) + return TRUE \ No newline at end of file diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm index 231645ed758..f16ca21682f 100644 --- a/code/game/objects/items/weapons/stunbaton.dm +++ b/code/game/objects/items/weapons/stunbaton.dm @@ -166,6 +166,8 @@ if(ishuman(L)) var/mob/living/carbon/human/H = L + if(check_martial_counter(L, user)) + return if(H.check_shields(0, "[user]'s [name]", src, MELEE_ATTACK)) //No message; check_shields() handles that playsound(L, 'sound/weapons/Genhit.ogg', 50, 1) return diff --git a/code/modules/martial_arts/cqc.dm b/code/modules/martial_arts/cqc.dm new file mode 100644 index 00000000000..54f9ac97cd9 --- /dev/null +++ b/code/modules/martial_arts/cqc.dm @@ -0,0 +1,204 @@ +#define SLAM_COMBO "HG" +#define KICK_COMBO "HH" +#define RESTRAIN_COMBO "GG" +#define PRESSURE_COMBO "DG" +#define CONSECUTIVE_COMBO "DDH" + +/datum/martial_art/cqc + name = "CQC" + help_verb = /mob/living/carbon/human/proc/CQC_help + block_chance = 75 + +/datum/martial_art/cqc/can_use(mob/living/carbon/human/H) + if(istype(H.gloves, /obj/item/clothing/gloves/fingerless/rapid)) + return FALSE + return ..() + +/datum/martial_art/cqc/proc/drop_restraining() + restraining = FALSE + +/datum/martial_art/cqc/proc/check_streak(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(!can_use(A)) + return FALSE + if(findtext(streak, SLAM_COMBO)) + streak = "" + Slam(A, D) + return TRUE + if(findtext(streak, KICK_COMBO)) + streak = "" + Kick(A, D) + return TRUE + if(findtext(streak, RESTRAIN_COMBO)) + streak = "" + Restrain(A, D) + return TRUE + if(findtext(streak, PRESSURE_COMBO)) + streak = "" + Pressure(A, D) + return TRUE + if(findtext(streak, CONSECUTIVE_COMBO)) + streak = "" + Consecutive(A, D) + return FALSE + +/datum/martial_art/cqc/proc/Slam(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(!can_use(A)) + return FALSE + if(!D.stat || !D.weakened) + D.visible_message("[A] slams [D] into the ground!", \ + "[A] slams you into the ground!") + playsound(get_turf(A), 'sound/weapons/slam.ogg', 50, 1, -1) + D.apply_damage(10, BRUTE) + D.Weaken(6) + add_attack_logs(A, D, "Melee attacked with martial-art [src] : Slam", ATKLOG_ALL) + else //if target can't be slammed, do a regular grab attack then clear the streak + streak = "" + grab_act(A, D) + streak = "" + return TRUE + +/datum/martial_art/cqc/proc/Kick(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(!can_use(A)) + return FALSE + if(!D.stat || !D.weakened) + D.visible_message("[A] kicks [D] back!", \ + "[A] kicks you back!") + playsound(get_turf(A), 'sound/weapons/cqchit1.ogg', 50, 1, -1) + var/atom/throw_target = get_edge_target_turf(D, A.dir) + D.throw_at(throw_target, 1, 14, A) + D.apply_damage(10, BRUTE) + add_attack_logs(A, D, "Melee attacked with martial-art [src] : Kick", ATKLOG_ALL) + if(D.weakened && !D.stat) + D.visible_message("[A] kicks [D]'s head, knocking [D.p_them()] out!", \ + "[A] kicks your head, knocking you out!") + playsound(get_turf(A), 'sound/weapons/genhit1.ogg', 50, 1, -1) + D.SetSleeping(15) + D.adjustBrainLoss(15, 150) + add_attack_logs(A, D, "Knocked out with martial-art [src] : Kick", ATKLOG_ALL) + else //if target can't be kicked, do a regular harm attack then clear the streak + streak = "" + harm_act(A, D) + streak = "" + return TRUE + +/datum/martial_art/cqc/proc/Pressure(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(!can_use(A)) + return FALSE + D.visible_message("[A] forces their arm on [D]'s neck!") + D.adjustStaminaLoss(60) + playsound(get_turf(A), 'sound/weapons/cqchit1.ogg', 50, 1, -1) + add_attack_logs(A, D, "Melee attacked with martial-art [src] : Pressure", ATKLOG_ALL) + return TRUE + +/datum/martial_art/cqc/proc/Restrain(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(restraining) + return + if(!can_use(A)) + return FALSE + if(!D.stat) + D.visible_message("[A] locks [D] into a restraining position!", \ + "[A] locks you into a restraining position!") + D.adjustStaminaLoss(20) + D.Stun(5) + restraining = TRUE + addtimer(CALLBACK(src, .proc/drop_restraining), 50, TIMER_UNIQUE) + add_attack_logs(A, D, "Melee attacked with martial-art [src] : Restrain", ATKLOG_ALL) + return TRUE + +/datum/martial_art/cqc/proc/Consecutive(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(!can_use(A)) + return FALSE + if(!D.stat) + D.visible_message("[A] strikes [D]'s abdomen, neck and back consecutively", \ + "[A] strikes your abdomen, neck and back consecutively!") + playsound(get_turf(D), 'sound/weapons/cqchit2.ogg', 50, 1, -1) + var/obj/item/I = D.get_active_hand() + if(I && D.drop_item()) + A.put_in_hands(I) + D.adjustStaminaLoss(50) + D.apply_damage(25, BRUTE) + add_attack_logs(A, D, "Melee attacked with martial-art [src] : Consecutive", ATKLOG_ALL) + else //if target can't be hit, do a regular harm attack then clear the streak + streak = "" + harm_act(A, D) + streak = "" + return TRUE + +/datum/martial_art/cqc/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(!can_use(A)) + return FALSE + add_to_streak("G", D) + if(check_streak(A, D)) + return TRUE + var/obj/item/grab/G = D.grabbedby(A, 1) + if(G) + G.state = GRAB_AGGRESSIVE //Instant aggressive grab + return TRUE + +/datum/martial_art/cqc/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(!can_use(A)) + return FALSE + add_to_streak("H", D) + if(check_streak(A, D)) + return TRUE + A.do_attack_animation(D) + var/picked_hit_type = pick("CQC'd", "neck chopped", "gut punched") + var/bonus_damage = 13 + if(D.weakened || D.resting || D.lying) + bonus_damage += 5 + picked_hit_type = "stomps on" + D.apply_damage(bonus_damage, BRUTE) + if(picked_hit_type == "kicks" || picked_hit_type == "stomps on") + playsound(get_turf(D), 'sound/weapons/cqchit2.ogg', 50, 1, -1) + else + playsound(get_turf(D), 'sound/weapons/cqchit1.ogg', 50, 1, -1) + D.visible_message("[A] [picked_hit_type] [D]!", \ + "[A] [picked_hit_type] you!") + add_attack_logs(A, D, "Melee attacked with martial-art [src]", ATKLOG_ALL) + if(A.resting && !D.stat && !D.weakened) + D.visible_message("[A] leg sweeps [D]!", \ + "[A] leg sweeps you!") + playsound(get_turf(A), 'sound/effects/hit_kick.ogg', 50, 1, -1) + D.apply_damage(10, BRUTE) + D.Weaken(3) + add_attack_logs(A, D, "Melee attacked with martial-art [src] : Leg sweep", ATKLOG_ALL) + return TRUE + +/datum/martial_art/cqc/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) + if(!can_use(A)) + return FALSE + add_to_streak("D", D) + if(check_streak(A, D)) + return TRUE + if(!restraining) + D.visible_message("[A] strikes [D]'s jaw with their hand!", \ + "[A] strikes your jaw, disorienting you!") + playsound(get_turf(D), 'sound/weapons/cqchit1.ogg', 50, 1, -1) + D.Jitter(2) + D.apply_damage(5, BRUTE) + add_attack_logs(A, D, "Melee attacked with martial-art [src] : Disarm", ATKLOG_ALL) + if(restraining) + D.visible_message("[A] puts [D] into a chokehold!", \ + "[A] puts you into a chokehold!") + D.SetSleeping(20) + restraining = FALSE + add_attack_logs(A, D, "Knocked out with martial-art [src] : Choke hold", ATKLOG_ALL) + else + restraining = FALSE + return FALSE + return TRUE + +/mob/living/carbon/human/proc/CQC_help() + set name = "Remember The Basics" + set desc = "You try to remember some of the basics of CQC." + set category = "CQC" + to_chat(usr, "You try to remember some of the basics of CQC.") + + to_chat(usr, "Slam: Harm Grab. Slam opponent into the ground, knocking them down.") + to_chat(usr, "CQC Kick: Harm Harm. Knocks opponent away. Knocks out stunned or knocked down opponents.") + to_chat(usr, "Restrain: Grab Switch Hand Grab. Locks opponent into a restraining position, stunning them.") + to_chat(usr, "Choke Hold: Restrain Disarm. Knocks out an opponent you have restrained.") + to_chat(usr, "Pressure: Disarm Grab. Decent stamina damage.") + to_chat(usr, "Consecutive CQC: Disarm Disarm Harm. Mainly offensive move, huge damage and decent stamina damage.") + + to_chat(usr, "In addition, by having your throw mode on when being attacked, you enter an active defense mode where you have a chance to block and sometimes even counter melee attacks done to you.") \ No newline at end of file diff --git a/code/modules/martial_arts/martial.dm b/code/modules/martial_arts/martial.dm index 26e6a2e873c..aed823b372b 100644 --- a/code/modules/martial_arts/martial.dm +++ b/code/modules/martial_arts/martial.dm @@ -6,6 +6,8 @@ var/temporary = 0 var/datum/martial_art/base = null // The permanent style var/deflection_chance = 0 //Chance to deflect projectiles + var/block_chance = 0 //Chance to block melee attacks using items while on throw mode. + var/restraining = 0 //used in cqc's disarm_act to check if the disarmed is being restrained and so whether they should be put in a chokehold or not var/help_verb = null var/no_guns = FALSE //set to TRUE to prevent users of this style from using guns (sleeping carp, highlander). They can still pick them up, but not fire them. var/no_guns_message = "" //message to tell the style user if they try and use a gun while no_guns = TRUE (DISHONORABRU!) @@ -22,6 +24,9 @@ /datum/martial_art/proc/help_act(var/mob/living/carbon/human/A, var/mob/living/carbon/human/D) return 0 +/datum/martial_art/proc/can_use(mob/living/carbon/human/H) + return TRUE + /datum/martial_art/proc/add_to_streak(var/element,var/mob/living/carbon/human/D) if(D != current_target) current_target = D @@ -184,6 +189,24 @@ new /obj/effect/decal/cleanable/ash(get_turf(src)) qdel(src) +/obj/item/CQC_manual + name = "old manual" + desc = "A small, black manual. There are drawn instructions of tactical hand-to-hand combat." + icon = 'icons/obj/library.dmi' + icon_state = "cqcmanual" + +/obj/item/CQC_manual/attack_self(mob/living/carbon/human/user) + if(!istype(user) || !user) + return + to_chat(user, "You remember the basics of CQC.") + + var/datum/martial_art/cqc/CQC = new(null) + CQC.teach(user) + user.drop_item() + visible_message("[src] beeps ominously, and a moment later it bursts up in flames.") + new /obj/effect/decal/cleanable/ash(get_turf(src)) + qdel(src) + /obj/item/twohanded/bostaff name = "bo staff" desc = "A long, tall staff made of polished wood. Traditionally used in ancient old-Earth martial arts. Can be wielded to both kill and incapacitate." diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 98274ea7e1f..ae5fe905735 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -156,6 +156,10 @@ emp_act return 1 return 0 +/mob/living/carbon/human/proc/check_block() + if(martial_art && prob(martial_art.block_chance) && martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) + return TRUE + /mob/living/carbon/human/emp_act(severity) for(var/obj/O in src) if(!O) continue diff --git a/code/modules/mob/living/carbon/human/species/_species.dm b/code/modules/mob/living/carbon/human/species/_species.dm index 37b67d40f29..a728c2bbfd8 100644 --- a/code/modules/mob/living/carbon/human/species/_species.dm +++ b/code/modules/mob/living/carbon/human/species/_species.dm @@ -198,7 +198,7 @@ var/obj/item/organ/internal/ears/ears = H.get_int_organ(/obj/item/organ/internal/ears) if(ears) qdel(ears) - + ears = new mutantears(H) /datum/species/proc/breathe(mob/living/carbon/human/H) @@ -318,6 +318,9 @@ user.do_cpr(target) /datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) + if(target.check_block()) //cqc + target.visible_message("[target] blocks [user]'s grab attempt!") + return FALSE if(attacker_style && attacker_style.grab_act(user, target)) return TRUE else @@ -344,6 +347,9 @@ add_attack_logs(user, target, "vampirebit") return //end vampire codes + if(target.check_block()) //cqc + target.visible_message("[target] blocks [user]'s attack!") + return FALSE if(attacker_style && attacker_style.harm_act(user, target)) return TRUE else @@ -382,6 +388,9 @@ target.forcesay(GLOB.hit_appends) /datum/species/proc/disarm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) + if(target.check_block()) //cqc + target.visible_message("[target] blocks [user]'s disarm attempt!") + return FALSE if(attacker_style && attacker_style.disarm_act(user, target)) return TRUE else diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi index ec038f3477b..20588d3617c 100644 Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ diff --git a/paradise.dme b/paradise.dme index 726317a6dc0..69340ade5c7 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1556,6 +1556,7 @@ #include "code\modules\map_fluff\metastation.dm" #include "code\modules\martial_arts\adminfu.dm" #include "code\modules\martial_arts\brawling.dm" +#include "code\modules\martial_arts\cqc.dm" #include "code\modules\martial_arts\krav_maga.dm" #include "code\modules\martial_arts\martial.dm" #include "code\modules\martial_arts\mimejutsu.dm" diff --git a/sound/weapons/cqchit1.ogg b/sound/weapons/cqchit1.ogg new file mode 100644 index 00000000000..afb5d2d16cf Binary files /dev/null and b/sound/weapons/cqchit1.ogg differ diff --git a/sound/weapons/cqchit2.ogg b/sound/weapons/cqchit2.ogg new file mode 100644 index 00000000000..cbf0da0d239 Binary files /dev/null and b/sound/weapons/cqchit2.ogg differ diff --git a/sound/weapons/slam.ogg b/sound/weapons/slam.ogg new file mode 100644 index 00000000000..f533f44e56f Binary files /dev/null and b/sound/weapons/slam.ogg differ