mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-18 11:36:24 +01:00
59f8de91dc
## About The Pull Request 1. Penbang A flashbang disguised as a pen. Clicking the pen arms the flashbang and has no other visual or audio tell besides the pen clicking. The fuse's length is based on the angle the pen cap is twisted. Works as a normal pen otherwise. 2. Camera Flash A camera with a high power flash. Clicking on an adjacent target will flash them, ie, as a handheld flash. Works as a normal camera otherwise - no tell. 3. Dagger Boot A pair of jackboots with a blade embedded in them. Makes your kicks sharp, meaning they will cause bleeding. Looks like normal jackboots otherwise, though has a tell on double-examine 4. Monster Cube Box A box containing 5 monster cubes, which spawn into a random monster when wet. Monsters include Migos, Carps, Bears, Spiders, Wolves... 5. Spider Bite Scroll A martial art focused around kicks and grabs. - Punches against standing, staggered targets will instead kick them, applying the bonus accuracy and damage that you'd expect from a kick. (Also combos with the dagger boots) - All kicks have a chance to disarm the target's active weapon. Chance increases per sequential kick. - Grants you the innate ability to tackle. This form of tackling has a very high skill modifier, meaning you are very likely to get a positive outcome (or at least, not fail). You also get up fast from it. - Your grabs are 20% harder to escape from and deal an additional 10 stam damage on fail. ## Why It's Good For The Game 1, 2, 3: Just some random flavorful items, giving them some more toys to use to complete bounties. 4: Shenanigans, for people who just wanna do "funny thing" instead of focusing on big loot. 5: I figured it would fill a fun niche: Spies are commonly depicted as martial artists, and the only martial arts they can obtain is Krav Maga (it's not unique to Spies) and Sleeping Carp (it's not unique to Spies). This gives them their own thing that people may look forward to acquiring and playing around with. As for the design of it, I wanted to add something that synergizes with one of the other other items, so in the field if you notice both of them pop up you really want to go for both. The rest of the design I just filled in as vaguely useful and flavorful tools a spy might want for kidnapping or detaining people: Better grabs, tackling, and disarms. Then I just went with the flavor of it being something the Spider Clan used to teach to their Spies / Ninjas, kinda like an analog to the real life Ninjutsu. (Maybe we can give it to Space Ninjas as well later... since it doesn't especially benefit Ninjas in any way.) ## Changelog 🆑 Melbert add: Adds 5 rewards to the Spy item pool: Penbang, Camera Flash, Dagger-Boot, Monster Cube Box, and the Spider Bite martial art /🆑
445 lines
14 KiB
Plaintext
445 lines
14 KiB
Plaintext
/datum/martial_art
|
|
/// Player readable name of the martial art
|
|
var/name = "Martial Art"
|
|
/// ID of the martial art
|
|
var/id = ""
|
|
/// The streak of attacks the user has performed
|
|
VAR_FINAL/streak = ""
|
|
/// The maximum length of streaks allowed
|
|
var/max_streak_length = 6
|
|
|
|
/// Are we being actively used by a mob?
|
|
var/active = FALSE
|
|
/// Where this martial art is from, sometimes the same as the holder if it's tied to them
|
|
/// If the origin is deleted, this martial art will be too.
|
|
VAR_PRIVATE/datum/origin
|
|
/// The current mob associated with this martial art datum. Do not set directly.
|
|
VAR_PRIVATE/mob/living/holder
|
|
/// Weakref to the last mob we attacked, for determining when to reset streaks
|
|
VAR_PRIVATE/datum/weakref/current_target
|
|
|
|
/// Path to verb to display help text for this martial art.
|
|
var/help_verb
|
|
/// If TRUE, this martial art smashes tables when performing table slams and head smashes
|
|
var/smashes_tables = FALSE
|
|
/// If TRUE, a combo meter will be displayed on the HUD for the current streak
|
|
var/display_combos = FALSE
|
|
///The Combo HUD given to display comboes, if we're set to display them.
|
|
var/atom/movable/screen/combo/combo_display
|
|
/// The length of time until streaks are auto-reset.
|
|
var/combo_timer = 6 SECONDS
|
|
/// Timer ID for the combo reset timer.
|
|
var/timerid
|
|
/// If TRUE, this style allows you to punch people despite being a pacifist (IE: Boxing, which does no damage)
|
|
var/pacifist_style = FALSE
|
|
/// If TRUE, the user is locked to using this martial art, and can't swap to other ones they know.
|
|
/// If the mob has two locked martial arts, it's first come first serve.
|
|
var/locked_to_use = FALSE
|
|
/// A modifier to the effective grab state for resist grabs of users of this martial art.
|
|
/// IE: grab_state_modifier = 1 means passive grabs are aggro grab difficulty, and aggro grabs are neckgrab difficulty.
|
|
var/grab_state_modifier = 0
|
|
/// A modifier to the damage dealt on a failed grab resist.
|
|
/// IE: grab_damage_modifier = 10 means 10 more stamina damage dealt
|
|
var/grab_damage_modifier = 0
|
|
/// A modifier to the chance of escaping a grab.
|
|
/// IE: grab_escape_chance_modifier = -10 means 10% less chance to escape a grab
|
|
var/grab_escape_chance_modifier = 0
|
|
|
|
/datum/martial_art/serialize_list(list/options, list/semvers)
|
|
. = ..()
|
|
|
|
.["name"] = name
|
|
.["id"] = id
|
|
.["pacifist_style"] = pacifist_style
|
|
|
|
SET_SERIALIZATION_SEMVER(semvers, "1.0.0")
|
|
return .
|
|
|
|
/datum/martial_art/New(datum/new_origin)
|
|
set_origin(new_origin)
|
|
|
|
/datum/martial_art/Destroy()
|
|
if(!isnull(holder))
|
|
unlearn(holder)
|
|
if(!isnull(origin))
|
|
set_origin(null)
|
|
return ..()
|
|
|
|
/datum/martial_art/proc/set_origin(datum/new_origin)
|
|
if(origin)
|
|
UnregisterSignal(origin, COMSIG_QDELETING)
|
|
origin = null
|
|
if(isnull(new_origin))
|
|
return
|
|
src.origin = new_origin
|
|
RegisterSignal(origin, COMSIG_QDELETING, PROC_REF(clear_origin))
|
|
|
|
/datum/martial_art/proc/clear_origin()
|
|
SIGNAL_HANDLER
|
|
qdel(src)
|
|
|
|
/datum/martial_art/proc/clear_holder(datum/source)
|
|
SIGNAL_HANDLER
|
|
unlearn(holder)
|
|
|
|
/// Signal proc for [COMSIG_LIVING_UNARMED_ATTACK] to hook into the appropriate proc
|
|
/datum/martial_art/proc/unarmed_strike(mob/living/source, atom/attack_target, proximity, modifiers)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!proximity || !isliving(attack_target))
|
|
return NONE
|
|
|
|
if(HAS_TRAIT(attack_target, TRAIT_MARTIAL_ARTS_IMMUNE))
|
|
return NONE
|
|
|
|
if(!can_use(source))
|
|
return NONE
|
|
|
|
if(LAZYACCESS(modifiers, RIGHT_CLICK))
|
|
return disarm_act(source, attack_target)
|
|
|
|
if(source.combat_mode)
|
|
if(HAS_TRAIT(source, TRAIT_PACIFISM) && !pacifist_style)
|
|
return NONE
|
|
|
|
return harm_act(source, attack_target)
|
|
|
|
return help_act(source, attack_target)
|
|
|
|
/// Signal proc for [COMSIG_LIVING_GRAB] to hook into the grab
|
|
/datum/martial_art/proc/attempt_grab(mob/living/source, mob/living/grabbing)
|
|
SIGNAL_HANDLER
|
|
|
|
if(HAS_TRAIT(grabbing, TRAIT_MARTIAL_ARTS_IMMUNE))
|
|
return NONE
|
|
|
|
if(!source.can_unarmed_attack()) // For parity with unarmed attacks
|
|
return NONE
|
|
|
|
if(!can_use(source))
|
|
return NONE
|
|
|
|
return grab_act(source, grabbing)
|
|
|
|
/**
|
|
* Called when help-intenting on someone
|
|
*
|
|
* What is checked going into this:
|
|
* Adjacency, [TRAIT_MARTIAL_ARTS_IMMUNE], attacker incapacitated, can_unarmed_attack, can_use
|
|
*
|
|
* What is NOT:
|
|
* check_block
|
|
*
|
|
* Arguments
|
|
* * mob/living/attacker - The mob attacking
|
|
* * mob/living/defender - The mob being attacked
|
|
*
|
|
* Returns
|
|
* * MARTIAL_ATTACK_INVALID - The attack is not valid, do normal unarmed attack
|
|
* * MARTIAL_ATTACK_FAIL - The attack is valid, but failed. No followup attack is made.
|
|
* * MARTIAL_ATTACK_SUCCESS - The attack is valid, and succeeded. No followup attack is made.
|
|
*/
|
|
/datum/martial_art/proc/help_act(mob/living/attacker, mob/living/defender)
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
PROTECTED_PROC(TRUE)
|
|
return MARTIAL_ATTACK_INVALID
|
|
|
|
/**
|
|
* Called when disarm-intenting on someone
|
|
*
|
|
* What is checked going into this:
|
|
* Adjacency, [TRAIT_MARTIAL_ARTS_IMMUNE], attacker incapacitated, can_unarmed_attack, can_use
|
|
*
|
|
* What is NOT:
|
|
* check_block
|
|
*
|
|
* Arguments
|
|
* * mob/living/attacker - The mob attacking
|
|
* * mob/living/defender - The mob being attacked
|
|
*
|
|
* Returns
|
|
* * MARTIAL_ATTACK_INVALID - The attack is not valid, do normal unarmed attack
|
|
* * MARTIAL_ATTACK_FAIL - The attack is valid, but failed. No followup attack is made.
|
|
* * MARTIAL_ATTACK_SUCCESS - The attack is valid, and succeeded. No followup attack is made.
|
|
*/
|
|
/datum/martial_art/proc/disarm_act(mob/living/attacker, mob/living/defender)
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
PROTECTED_PROC(TRUE)
|
|
return MARTIAL_ATTACK_INVALID
|
|
|
|
/**
|
|
* Called when harm-intenting on someone
|
|
*
|
|
* What is checked going into this:
|
|
* Adjacency, [TRAIT_MARTIAL_ARTS_IMMUNE], attacker incapacitated, can_unarmed_attack, can_use
|
|
*
|
|
* What is NOT:
|
|
* check_block
|
|
*
|
|
* Arguments
|
|
* * mob/living/attacker - The mob attacking
|
|
* * mob/living/defender - The mob being attacked
|
|
*
|
|
* Returns
|
|
* * MARTIAL_ATTACK_INVALID - The attack is not valid, do normal unarmed attack
|
|
* * MARTIAL_ATTACK_FAIL - The attack is valid, but failed. No followup attack is made.
|
|
* * MARTIAL_ATTACK_SUCCESS - The attack is valid, and succeeded. No followup attack is made.
|
|
*/
|
|
/datum/martial_art/proc/harm_act(mob/living/attacker, mob/living/defender)
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
PROTECTED_PROC(TRUE)
|
|
return MARTIAL_ATTACK_INVALID
|
|
|
|
/**
|
|
* Called when grabbing someone
|
|
*
|
|
* What is checked going into this:
|
|
* Adjacency, [TRAIT_MARTIAL_ARTS_IMMUNE], attacker incapacitated, can_unarmed_attack, can_use
|
|
*
|
|
* What is NOT:
|
|
* check_block
|
|
*
|
|
* Arguments
|
|
* * mob/living/attacker - The mob attacking
|
|
* * mob/living/defender - The mob being attacked
|
|
*
|
|
* Returns
|
|
* * MARTIAL_ATTACK_INVALID - The attack is not valid, do normal unarmed attack
|
|
* * MARTIAL_ATTACK_FAIL - The attack is valid, but failed. No followup attack is made.
|
|
* * MARTIAL_ATTACK_SUCCESS - The attack is valid, and succeeded. No followup attack is made.
|
|
*/
|
|
/datum/martial_art/proc/grab_act(mob/living/attacker, mob/living/defender)
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
PROTECTED_PROC(TRUE)
|
|
return MARTIAL_ATTACK_INVALID
|
|
|
|
/**
|
|
* Checks if the passed mob can use this martial art.
|
|
*
|
|
* Arguments
|
|
* * mob/living/martial_artist - The mob to check
|
|
*
|
|
* Returns
|
|
* * TRUE - The mob can use this martial art
|
|
* * FALSE - The mob cannot use this martial art
|
|
*/
|
|
/datum/martial_art/proc/can_use(mob/living/martial_artist)
|
|
return TRUE
|
|
|
|
/**
|
|
* Gets what limb is being used going when punching with this martial art.
|
|
*
|
|
* Override get_prefered_attacking_limb() to change the limb used.
|
|
*
|
|
* Arguments
|
|
* * mob/living/martial_artist - The mob using the martial art
|
|
* * mob/living/target - The target of the attack
|
|
*
|
|
* Returns
|
|
* A bodypart, or null if we want to use default behavior (brain determines, or active hand).
|
|
*/
|
|
/datum/martial_art/proc/get_attacking_limb(mob/living/martial_artist, mob/living/target)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
if(!can_use(martial_artist))
|
|
return null
|
|
var/preferred_zone = get_prefered_attacking_limb(martial_artist, target)
|
|
if(!preferred_zone)
|
|
return null
|
|
return martial_artist.get_bodypart(preferred_zone)
|
|
|
|
/**
|
|
* Allows martial arts to have a say which limb the user should be striking with.
|
|
*
|
|
*
|
|
* Arguments
|
|
* * mob/living/martial_artist - The mob using the martial art
|
|
* * mob/living/target - The target of the attack
|
|
*
|
|
* Returns
|
|
* * A body zone, or null if we have no preference.
|
|
*/
|
|
/datum/martial_art/proc/get_prefered_attacking_limb(mob/living/martial_artist, mob/living/target)
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
PROTECTED_PROC(TRUE)
|
|
return null
|
|
|
|
/**
|
|
* Adds the passed element to the current streak, resetting it if the target is not the same as the last target.
|
|
*
|
|
* Arguments
|
|
* * element - The element to add to the streak. This is some one letter string.
|
|
* * mob/living/defender - The mob being attacked
|
|
*/
|
|
/datum/martial_art/proc/add_to_streak(element, mob/living/defender)
|
|
if(!IS_WEAKREF_OF(defender, current_target))
|
|
reset_streak(defender)
|
|
streak += element
|
|
if(length(streak) > max_streak_length)
|
|
streak = copytext(streak, 1 + length(streak[1]))
|
|
if(display_combos)
|
|
timerid = addtimer(CALLBACK(src, PROC_REF(reset_streak), null, FALSE), combo_timer, TIMER_UNIQUE | TIMER_STOPPABLE)
|
|
combo_display.update_icon_state(streak, combo_timer - 2 SECONDS)
|
|
|
|
/**
|
|
* Resets the current streak.
|
|
*
|
|
* Arguments
|
|
* * mob/living/new_target - (Optional) The mob being attacked while the reset is occurring.
|
|
* * update_icon - If TRUE, the combo display will be updated.
|
|
*/
|
|
/datum/martial_art/proc/reset_streak(mob/living/new_target, update_icon = TRUE)
|
|
if(timerid)
|
|
deltimer(timerid)
|
|
current_target = WEAKREF(new_target)
|
|
streak = ""
|
|
if(display_combos && update_icon)
|
|
combo_display.update_icon_state(streak)
|
|
|
|
/datum/martial_art/proc/smash_table(mob/living/source, mob/living/pushed_mob, obj/structure/table/table)
|
|
SIGNAL_HANDLER
|
|
if(smashes_tables)
|
|
table.deconstruct(FALSE)
|
|
|
|
/**
|
|
* Teaches the passed mob this martial art.
|
|
*
|
|
* Arguments
|
|
* * mob/living/new_holder - The mob to teach this martial art to.
|
|
*
|
|
* Returns
|
|
* * TRUE - The martial art was successfully taught.
|
|
* * FALSE - The mob failed to learn the martial art, for whatever reason.
|
|
*/
|
|
/datum/martial_art/proc/teach(mob/living/new_holder)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
if(!can_teach(new_holder) || holder == new_holder)
|
|
return FALSE
|
|
|
|
holder = new_holder
|
|
if(origin != new_holder)
|
|
RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(clear_holder))
|
|
// locked martial arts always get inserted as the next up
|
|
// (so if you learn two locked martial arts, and you get rid of the first, the second will slot itself in)
|
|
if(locked_to_use && LAZYLEN(new_holder.martial_arts) >= 2)
|
|
LAZYINSERT(new_holder.martial_arts, 2, src)
|
|
else
|
|
LAZYADD(new_holder.martial_arts, src)
|
|
if(LAZYLEN(new_holder.martial_arts) >= 2)
|
|
// newly learned martials are preferred to be the active one
|
|
add_verb(new_holder, /mob/living/proc/verb_switch_style)
|
|
// if the active one is locked, this will no-op, which is fine
|
|
new_holder.switch_style(GET_ACTIVE_MARTIAL_ART(new_holder), src)
|
|
else if(!active)
|
|
activate_style(new_holder)
|
|
return TRUE
|
|
|
|
/**
|
|
* Checks if the passed mob can be taught this martial art.
|
|
*
|
|
* Arguments
|
|
* * mob/living/new_holder - The mob to check
|
|
*
|
|
* Returns
|
|
* * TRUE - The mob can be taught this martial art
|
|
* * FALSE - The mob cannot be taught this martial art
|
|
*/
|
|
/datum/martial_art/proc/can_teach(mob/living/new_holder)
|
|
return isliving(new_holder)
|
|
|
|
/**
|
|
* Removes this martial art from the passed mob.
|
|
*
|
|
* Arguments
|
|
* * mob/living/old_holder - The mob to remove this martial art from.
|
|
*/
|
|
/datum/martial_art/proc/unlearn(mob/living/old_holder)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
if(old_holder != holder)
|
|
return FALSE
|
|
|
|
if(LAZYLEN(old_holder.martial_arts) >= 2 && !QDELING(old_holder))
|
|
old_holder.switch_style(src, GET_NEXT_MARTIAL_ART(old_holder))
|
|
else if(active)
|
|
deactivate_style(old_holder)
|
|
if(origin != old_holder)
|
|
UnregisterSignal(old_holder, COMSIG_QDELETING)
|
|
LAZYREMOVE(old_holder.martial_arts, src)
|
|
holder = null
|
|
if(LAZYLEN(old_holder.martial_arts) <= 1)
|
|
remove_verb(old_holder, /mob/living/proc/verb_switch_style)
|
|
return TRUE
|
|
|
|
/**
|
|
* Called when this martial art is added to a mob.
|
|
*/
|
|
/datum/martial_art/proc/activate_style(mob/living/new_holder)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
active = TRUE
|
|
if(help_verb)
|
|
add_verb(new_holder, help_verb)
|
|
RegisterSignal(new_holder, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(unarmed_strike))
|
|
RegisterSignal(new_holder, COMSIG_LIVING_GRAB, PROC_REF(attempt_grab))
|
|
RegisterSignals(new_holder, list(COMSIG_LIVING_TABLE_SLAMMING, COMSIG_LIVING_TABLE_LIMB_SLAMMING), PROC_REF(smash_table))
|
|
if(display_combos)
|
|
if(new_holder.hud_used)
|
|
on_hud_created(new_holder)
|
|
else
|
|
RegisterSignal(new_holder, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
|
|
|
|
/**
|
|
* Called when this martial art is removed from a mob.
|
|
*/
|
|
/datum/martial_art/proc/deactivate_style(mob/living/remove_from)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
active = FALSE
|
|
if(help_verb)
|
|
remove_verb(remove_from, help_verb)
|
|
UnregisterSignal(remove_from, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_LIVING_GRAB, COMSIG_LIVING_TABLE_SLAMMING, COMSIG_LIVING_TABLE_LIMB_SLAMMING))
|
|
if(!isnull(combo_display))
|
|
var/datum/hud/hud_used = remove_from.hud_used
|
|
hud_used.infodisplay -= combo_display
|
|
hud_used.show_hud(hud_used.hud_version)
|
|
QDEL_NULL(combo_display)
|
|
|
|
///Gives the owner of the martial art the combo HUD.
|
|
/datum/martial_art/proc/on_hud_created(mob/source)
|
|
SIGNAL_HANDLER
|
|
var/datum/hud/hud_used = source.hud_used
|
|
combo_display = new(null, hud_used)
|
|
hud_used.infodisplay += combo_display
|
|
hud_used.show_hud(hud_used.hud_version)
|
|
|
|
/mob/living/proc/verb_switch_style()
|
|
set name = "Swap Style"
|
|
set desc = "Switch to a different martial arts style."
|
|
set category = "IC"
|
|
|
|
var/datum/martial_art/current = GET_ACTIVE_MARTIAL_ART(src)
|
|
var/datum/martial_art/next = GET_NEXT_MARTIAL_ART(src)
|
|
|
|
if(current.locked_to_use)
|
|
to_chat(src, span_warning("You can't stop practicing [current]! It's too ingrained in your muscle memory."))
|
|
return
|
|
|
|
switch_style(GET_ACTIVE_MARTIAL_ART(src), GET_NEXT_MARTIAL_ART(src))
|
|
to_chat(src, span_notice("You stop practicing [current] and start practicing [next]."))
|
|
|
|
/// Deactivates the current martial art and activates the next one.
|
|
/mob/living/proc/switch_style(datum/martial_art/current_martial, datum/martial_art/next_martial)
|
|
if(current_martial.locked_to_use)
|
|
return
|
|
// something's wrong if this assertion fails, but not terribly wrong that we need a stack trace
|
|
if(!current_martial.active || next_martial.active)
|
|
return
|
|
|
|
current_martial.deactivate_style(src)
|
|
next_martial.activate_style(src)
|
|
// front of the list with ye
|
|
LAZYREMOVE(martial_arts, next_martial)
|
|
LAZYINSERT(martial_arts, 1, next_martial)
|
|
// back of the list with ye
|
|
LAZYREMOVE(martial_arts, current_martial)
|
|
LAZYADD(martial_arts, current_martial)
|