#define HAS_COMBOS LAZYLEN(combos) #define COMBO_ALIVE_TIME 5 SECONDS // How long the combo stays alive when no new attack is done /datum/martial_art var/name = "Martial Art" var/streak = "" var/temporary = FALSE var/owner_UID /// The permanent style. var/datum/martial_art/base = null /// Chance to deflect projectiles while on throw mode. var/deflection_chance = 0 /// Can it reflect projectiles in a random direction? var/reroute_deflection = FALSE var/help_verb = null /// 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 = FALSE /// Message to tell the style user if they try and use a gun while no_guns = TRUE (DISHONORABRU!) var/no_guns_message = "" /// If the martial art has it's own explaination verb. var/has_explaination_verb = FALSE /// What combos can the user do? List of combo types. var/list/combos = list() /// What combos are currently (possibly) being performed. var/list/datum/martial_art/current_combos = list() /// Stores the timer_id for the combo timeout timer var/combo_timer /// If the user is preparing a martial arts stance. var/in_stance = FALSE /// If the martial art allows parrying. var/can_parry = FALSE /// Set to TRUE to prevent users of this style from using stun batons (and stunprods) var/no_baton = FALSE /// The priority of which martial art is picked from all the ones someone knows, the higher the number, the higher the priority. var/weight = 0 /// Message displayed when someone uses a baton when its forbidden by a martial art var/no_baton_reason = "Your martial arts training prevents you from wielding batons." /// Whether or not you can grab someone while horizontal with this Martial Art var/can_horizontally_grab = TRUE /datum/martial_art/New() . = ..() reset_combos() /datum/martial_art/proc/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) return act(MARTIAL_COMBO_STEP_DISARM, A, D) /datum/martial_art/proc/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) return act(MARTIAL_COMBO_STEP_HARM, A, D) /datum/martial_art/proc/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D) return act(MARTIAL_COMBO_STEP_GRAB, A, D) /datum/martial_art/proc/help_act(mob/living/carbon/human/A, mob/living/carbon/human/D) return act(MARTIAL_COMBO_STEP_HELP, A, D) /datum/martial_art/proc/can_use(mob/living/carbon/human/H) return !HAS_TRAIT(H, TRAIT_PACIFISM) /datum/martial_art/proc/act(step, mob/living/carbon/human/user, mob/living/carbon/human/target, could_start_new_combo = TRUE) if(!can_use(user)) return MARTIAL_ARTS_CANNOT_USE if(combo_timer) deltimer(combo_timer) combo_timer = addtimer(CALLBACK(src, PROC_REF(reset_combos)), COMBO_ALIVE_TIME, TIMER_UNIQUE | TIMER_STOPPABLE) if(HAS_COMBOS) streak += intent_to_streak(step) var/mob/living/carbon/human/owner = locateUID(owner_UID) if(istype(owner) && !QDELETED(owner)) if(owner.hud_used) owner.hud_used.combo_display.update_icon(ALL, streak) return check_combos(step, user, target, could_start_new_combo) return FALSE /datum/martial_art/proc/reset_combos(mob/living/carbon/human/H) current_combos.Cut() streak = "" var/mob/living/carbon/human/owner if(H) owner = H else owner = locateUID(owner_UID) if(istype(owner) && !QDELETED(owner)) owner.hud_used.combo_display.update_icon(ALL, streak) for(var/combo_type in combos) current_combos.Add(new combo_type()) /datum/martial_art/proc/check_combos(step, mob/living/carbon/human/user, mob/living/carbon/human/target, could_start_new_combo = TRUE) . = FALSE for(var/thing in current_combos) var/datum/martial_combo/MC = thing if(!MC.check_combo(step, target)) current_combos -= MC // It failed so remove it else switch(MC.progress_combo(user, target, src)) if(MARTIAL_COMBO_FAIL) current_combos -= MC if(MARTIAL_COMBO_DONE_NO_CLEAR) . = MARTIAL_ARTS_ACT_SUCCESS current_combos -= MC if(MARTIAL_COMBO_DONE) reset_combos() return MARTIAL_ARTS_ACT_SUCCESS if(MARTIAL_COMBO_DONE_BASIC_HIT) basic_hit(user, target) reset_combos() return MARTIAL_ARTS_ACT_SUCCESS if(MARTIAL_COMBO_DONE_CLEAR_COMBOS) combos.Cut() reset_combos() return MARTIAL_ARTS_ACT_SUCCESS if(!LAZYLEN(current_combos)) reset_combos() if(HAS_COMBOS && could_start_new_combo) act(step, user, target, could_start_new_combo = FALSE) /datum/martial_art/proc/basic_hit(mob/living/carbon/human/A, mob/living/carbon/human/D) var/damage = rand(A.dna.species.punchdamagelow, A.dna.species.punchdamagehigh) var/datum/unarmed_attack/attack = A.get_unarmed_attack() var/atk_verb = "[pick(attack.attack_verb)]" if(IS_HORIZONTAL(D)) atk_verb = "kick" switch(atk_verb) if("kick") A.do_attack_animation(D, ATTACK_EFFECT_KICK) else A.do_attack_animation(D, attack.animation_type) if(!damage) playsound(D.loc, attack.miss_sound, 25, TRUE, -1) D.visible_message(SPAN_WARNING("[A] has attempted to [atk_verb] [D]!")) return FALSE var/obj/item/organ/external/affecting = D.get_organ(ran_zone(A.zone_selected)) var/armor_block = D.run_armor_check(affecting, MELEE) playsound(D.loc, attack.attack_sound, 25, TRUE, -1) D.visible_message(SPAN_DANGER("[A] has [atk_verb]ed [D]!"), \ SPAN_USERDANGER("[A] has [atk_verb]ed [D]!")) D.apply_damage(damage, BRUTE, affecting, armor_block) add_attack_logs(A, D, "Melee attacked with martial-art [src]", (damage > 0) ? null : ATKLOG_ALL) if((D.stat != DEAD) && damage >= A.dna.species.punchstunthreshold) D.visible_message(SPAN_DANGER("[A] has weakened [D]!!"), \ SPAN_USERDANGER("[A] has weakened [D]!")) D.apply_effect(8 SECONDS, WEAKEN, armor_block) return TRUE /datum/martial_art/proc/teach(mob/living/carbon/human/H, make_temporary = FALSE) if(!H.mind) return for(var/datum/martial_art/MA in H.mind.known_martial_arts) if(istype(MA, src)) return if(has_explaination_verb) add_verb(H, /mob/living/carbon/human/proc/martial_arts_help) temporary = make_temporary owner_UID = H.UID() H.mind.known_martial_arts.Add(src) H.mind.martial_art = get_highest_weight(H) /datum/martial_art/proc/remove(mob/living/carbon/human/H) var/datum/martial_art/MA = src if(!H.mind) return if(H.hud_used) reset_combos() deltimer(combo_timer) H.mind.known_martial_arts.Remove(MA) H.mind.martial_art = get_highest_weight(H) remove_verb(H, /mob/living/carbon/human/proc/martial_arts_help) /// Returns the martial art with the highest weight from all the ones someone knows. /datum/martial_art/proc/get_highest_weight(mob/living/carbon/human/H) var/datum/martial_art/highest_weight = null for(var/datum/martial_art/MA in H.mind.known_martial_arts) if(!highest_weight || MA.weight > highest_weight.weight) highest_weight = MA return highest_weight /mob/living/carbon/human/proc/martial_arts_help() set name = "Show Info" set desc = "Gives information about the martial arts you know." set category = "Martial Arts" var/mob/living/carbon/human/H = usr if(istype(H)) H.mind.martial_art.give_explaination(H) return if(isobserver(H) || iscameramob(H)) to_chat(usr, SPAN_WARNING("You shouldn't have access to this verb. Report this as a bug to the github please.")) return if(isanimal(H)) to_chat(usr, SPAN_NOTICE("Your beastial form isn't compatible with any martial arts you know.")) return if(issilicon(H)) to_chat(usr, SPAN_NOTICE("Your malformed steel body can barely perform basic tasks, let alone complex martial arts.")) return if(isalien(H)) to_chat(usr, SPAN_NOTICE("The hivemind's fighting style has been blessed upon you, you have no need for this useless style.")) return /datum/martial_art/proc/give_explaination(user = usr) explaination_header(user) explaination_combos(user) explaination_footer(user) // Put after the header and before the footer in the explaination text /datum/martial_art/proc/explaination_combos(user) if(HAS_COMBOS) for(var/combo_type in combos) var/datum/martial_combo/MC = new combo_type() MC.give_explaination(user) // Put on top of the explaination text /datum/martial_art/proc/explaination_header(user) return // Put below the combos in the explaination text /datum/martial_art/proc/explaination_footer(user) return /datum/martial_art/proc/try_deflect(mob/user) return prob(deflection_chance) /datum/martial_art/proc/intent_to_streak(intent) switch(intent) if(MARTIAL_COMBO_STEP_HARM) return "E" // these hands are rated E for everyone if(MARTIAL_COMBO_STEP_DISARM) return "D" if(MARTIAL_COMBO_STEP_GRAB) return "G" if(MARTIAL_COMBO_STEP_HELP) return "H" /datum/action/defensive_stance name = "Defensive Stance - Ready yourself to be attacked, allowing you to parry incoming melee hits." button_icon_state = "block" /datum/action/defensive_stance/Trigger(left_click) var/mob/living/carbon/human/H = owner var/datum/martial_art/MA = H.mind.martial_art //This should never be available to non-martial-arts users anyway if(!MA.can_parry) to_chat(H, SPAN_WARNING("You can't parry right now.")) return if(H.incapacitated()) to_chat(H, SPAN_WARNING("You can't defend yourself while you're incapacitated.")) return var/obj/item/slapper/parry/slap = new(H) if(H.put_in_hands(slap)) H.visible_message(SPAN_DANGER("[H] assumes a defensive stance!"), "You drop back into a defensive stance.") else to_chat(H, SPAN_WARNING("Your hands are full.")) /obj/item/storage/belt/champion/wrestling name = "Wrestling Belt" layer_over_suit = TRUE var/datum/martial_art/wrestling/style /obj/item/storage/belt/champion/wrestling/Initialize(mapload) . = ..() style = new() /obj/item/storage/belt/champion/wrestling/equipped(mob/user, slot) if(!ishuman(user)) return if(slot == ITEM_SLOT_BELT) var/mob/living/carbon/human/H = user if(HAS_TRAIT(user, TRAIT_PACIFISM)) to_chat(user, SPAN_WARNING("In spite of the grandiosity of the belt, you don't feel like getting into any fights.")) return style.teach(H, TRUE) to_chat(user, SPAN_SCIRADIO("You have an urge to flex your muscles and get into a fight. You have the knowledge of a thousand wrestlers before you. You can remember more by using the verb in the martial arts tab.")) return /obj/item/storage/belt/champion/wrestling/dropped(mob/user) ..() if(!ishuman(user)) return var/mob/living/carbon/human/H = user if(H.get_item_by_slot(ITEM_SLOT_BELT) == src) style.remove(H) to_chat(user, SPAN_SCIRADIO("You no longer have an urge to flex your muscles.")) /obj/item/plasma_fist_scroll name = "frayed scroll" desc = "An aged and frayed scrap of paper written in shifting runes. There are hand-drawn illustrations of pugilism." icon = 'icons/obj/wizard.dmi' icon_state ="scroll2" var/used = FALSE /obj/item/plasma_fist_scroll/attack_self__legacy__attackchain(mob/user as mob) if(!ishuman(user)) return if(!used) var/mob/living/carbon/human/H = user var/datum/martial_art/plasma_fist/F = new/datum/martial_art/plasma_fist(null) F.teach(H) to_chat(H, SPAN_BOLDANNOUNCEIC("You have learned the ancient martial art of Plasma Fist.")) used = TRUE desc = "It's completely blank." name = "empty scroll" icon_state = "blankscroll" /obj/item/sleeping_carp_scroll name = "mysterious scroll" desc = "A scroll filled with strange markings. It seems to be drawings of some sort of martial art." icon = 'icons/obj/wizard.dmi' icon_state = "scroll2" /obj/item/sleeping_carp_scroll/attack_self__legacy__attackchain(mob/living/carbon/human/user as mob) if(!istype(user) || !user) return if(user.mind) //Prevents changelings and vampires from being able to learn it if(IS_CHANGELING(user) || IS_MINDFLAYER(user)) to_chat(user, "We try multiple times, but we are not able to comprehend the contents of the scroll!") return else if(user.mind.has_antag_datum(/datum/antagonist/vampire)) //Vampires to_chat(user, "Your blood lust distracts you too much to be able to concentrate on the contents of the scroll!") return else if(IS_HERETIC(user)) to_chat(user, SPAN_HIEROPHANT_WARNING("You and everyone else are already dreaming. You need to wake up, not sleep more...")) return var/datum/martial_art/the_sleeping_carp/theSleepingCarp = new(null) theSleepingCarp.teach(user) user.drop_item() visible_message(SPAN_WARNING("[src] lights up in fire and quickly burns to ash.")) 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__legacy__attackchain(mob/living/carbon/human/user) if(!istype(user) || !user) return if(user.mind) //Prevents changelings and vampires from being able to learn it if(IS_CHANGELING(user) || IS_MINDFLAYER(user)) to_chat(user, SPAN_WARNING("We try multiple times, but we simply cannot grasp the basics of CQC!")) return else if(user.mind.has_antag_datum(/datum/antagonist/vampire)) //Vampires to_chat(user, SPAN_WARNING("Your blood lust distracts you from the basics of CQC!")) return else if(IS_HERETIC(user)) to_chat(user, SPAN_HIEROPHANT_WARNING("The mansus remembers the basics of CQC. You do not need to.")) return else if(HAS_TRAIT(user, TRAIT_PACIFISM)) to_chat(user, SPAN_WARNING("The mere thought of combat, let alone CQC, makes your head spin!")) return to_chat(user, SPAN_BOLDANNOUNCEIC("You remember the basics of CQC.")) var/datum/martial_art/cqc/CQC = new(null) CQC.teach(user) user.drop_item() visible_message(SPAN_WARNING("[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/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." icon = 'icons/obj/weapons/melee.dmi' icon_state = "bostaff0" base_icon_state = "bostaff" lefthand_file = 'icons/mob/inhands/staves_lefthand.dmi' righthand_file = 'icons/mob/inhands/staves_righthand.dmi' force = 10 w_class = WEIGHT_CLASS_BULKY slot_flags = ITEM_SLOT_BACK throwforce = 20 attack_verb = list("smashed", "slammed", "whacked", "thwacked") /obj/item/bostaff/Initialize(mapload) . = ..() AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) AddComponent(/datum/component/two_handed, force_wielded = 24, force_unwielded = force, icon_wielded = "[base_icon_state]1") /obj/item/bostaff/update_icon_state() icon_state = "[base_icon_state]0" /obj/item/bostaff/attack__legacy__attackchain(mob/target, mob/living/user) add_fingerprint(user) if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) to_chat(user, "You club yourself over the head with [src].") user.Weaken(6 SECONDS) if(ishuman(user)) var/mob/living/carbon/human/H = user H.apply_damage(2*force, BRUTE, "head") else user.take_organ_damage(2*force) return if(isrobot(target)) return ..() if(!isliving(target)) return ..() var/mob/living/carbon/C = target if(C.stat) to_chat(user, SPAN_WARNING("It would be dishonorable to attack a foe while [C.p_they()] cannot retaliate.")) return if(HAS_TRAIT(user, TRAIT_PACIFISM)) to_chat(user, SPAN_WARNING("You feel violence is not the answer.")) return switch(user.a_intent) if(INTENT_DISARM) if(!HAS_TRAIT(src, TRAIT_WIELDED)) return ..() if(!ishuman(target)) return ..() var/mob/living/carbon/human/H = target var/list/fluffmessages = list("[user] clubs [H] with [src]!", \ "[user] smacks [H] with the butt of [src]!", \ "[user] broadsides [H] with [src]!", \ "[user] smashes [H]'s head with [src]!", \ "[user] beats [H] with front of [src]!", \ "[user] twirls and slams [H] with [src]!") H.visible_message(SPAN_WARNING("[pick(fluffmessages)]"), \ SPAN_USERDANGER("[pick(fluffmessages)]")) playsound(get_turf(user), 'sound/effects/woodhit.ogg', 75, TRUE, -1) H.apply_damage(rand(13,20), STAMINA) if(prob(10)) H.visible_message(SPAN_WARNING("[H] collapses!"), \ SPAN_USERDANGER("Your legs give out!")) H.Weaken(8 SECONDS) if(H.getStaminaLoss() && !H.IsSleeping()) var/total_health = (H.health - H.getStaminaLoss()) if(total_health <= HEALTH_THRESHOLD_CRIT && !H.stat) H.visible_message(SPAN_WARNING("[user] delivers a heavy hit to [H]'s head, knocking [H.p_them()] out cold!"), \ SPAN_USERDANGER("[user] knocks you unconscious!")) H.SetSleeping(60 SECONDS) H.adjustBrainLoss(25) return else return ..() /obj/item/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) if(HAS_TRAIT(src, TRAIT_WIELDED)) return ..() return FALSE /atom/movable/screen/combo icon_state = "" mouse_opacity = MOUSE_OPACITY_TRANSPARENT screen_loc = UI_COMBO layer = ABOVE_HUD_LAYER var/streak /atom/movable/screen/combo/proc/clear_streak() cut_overlays() streak = "" icon_state = "" /atom/movable/screen/combo/update_icon(updates, _streak) streak = _streak return ..() /atom/movable/screen/combo/update_overlays() . = list() for(var/i in 1 to length(streak)) var/intent_text = copytext(streak, i, i + 1) var/image/intent_icon = image(icon, src, "combo_[intent_text]") intent_icon.pixel_x = 16 * (i - 1) - 8 * length(streak) . += intent_icon /atom/movable/screen/combo/update_icon_state() icon_state = "" if(!streak) return icon_state = "combo" #undef HAS_COMBOS #undef COMBO_ALIVE_TIME