Files
John Willard 751bfc08bb Removes the IC tab (#95893)
## About The Pull Request

Removes the IC tab in its entirety

- Some mobs (Blob Minion & Lightgeist) had manually removed the "pull"
verb, this replaces it with taking away their pull force.
- Lightgeist had their "Me" verb manually removed, this is replaced by
giving them Emotemute trait (the same that mime's bane gives)
- Floor Changer: You can now RMB the arrow buttons to look in a
direction w/o moving. Added a tip to help with this.
- Exit Hivemind: Already an action button and was removed without
further edits

### Removed entirely

Rest
Resist
Look Up/Down
Move Upwards/Down
Activate Held Object
Open Language Menu
Memories
View Skills

### Moved to command bar (secret verb ooh) because I am biased

Sleep
Navigate

### Martial Arts

Martial Arts had a button to see your current skills & the ability to
cycle between artstyles. This was replaced with 1 action button that
does both of these.

<img width="317" height="148" alt="image"
src="https://github.com/user-attachments/assets/afed910f-b6b6-441d-86cb-dade0e253044"
/>


Name auto-updates as you swap artstyle to whatever help verb the current
style uses. Mention of RMB in the name is only there if you have more
than 1 martial art, but the desc will remain just so players can see it
even if they only have one, for future reference.

## Why It's Good For The Game

Brings greater visibility to Martial Art stuff, and especially since the
stat panel is being made optional we should not be hiding ANYTHING in
there.

This also helps me with my project listed in
https://hackmd.io/443_dE5lRWeEAp9bjGcKYw?view

And also fixes some sorta-removed features due to removing verbs not
matching people's actual powers due to new features like "Custom Me" and
"Ctrl Clicking".

## Changelog

🆑 SirNightKnight (sprites), JohnFulpWillard
del: Deleted the "IC" tab of the stat panel.
fix: Lightgeists can't emote or pull as intended.
fix: Blob minions can't pull again.
qol: Martial Art users now have an action button for their
artstyle-related businesses, rather than it being in the stat panel.
qol: You can now right-click the move up/down UI buttons to look in the
direction without moving.
/🆑
2026-05-26 16:27:09 -07:00

441 lines
15 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 = "Remember the Basics"
/// 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 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)
return
timerid = addtimer(CALLBACK(src, PROC_REF(reset_streak), null, FALSE), combo_timer, TIMER_UNIQUE | TIMER_STOPPABLE)
var/atom/movable/screen/combo/combo_display = holder.hud_used?.screen_objects[HUD_MOB_COMBO]
if(istype(combo_display))
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 = ""
var/atom/movable/screen/combo/combo_display = holder.hud_used?.screen_objects[HUD_MOB_COMBO]
if(istype(combo_display) && 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) == 1 ? get_style_help() : !(locate(/datum/action/swap_arts) in new_holder.actions))
var/datum/action/swap_arts/new_action = new(new_holder, src)
new_action.Grant(new_holder)
if(LAZYLEN(new_holder.martial_arts) >= 2)
// 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) //no more arts, we check above to switch style already.
var/datum/action/swap_arts/swap_button = locate() in old_holder.actions
qdel(swap_button)
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
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)
RegisterSignal(new_holder, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
if(new_holder.hud_used)
on_hud_created(new_holder)
/**
* 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
UnregisterSignal(remove_from, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_LIVING_GRAB, COMSIG_LIVING_TABLE_SLAMMING, COMSIG_LIVING_TABLE_LIMB_SLAMMING))
remove_from.hud_used?.remove_screen_object(HUD_MOB_COMBO)
///Gives the owner of the martial art the combo HUD.
/datum/martial_art/proc/on_hud_created(mob/source)
SIGNAL_HANDLER
source.hud_used.add_screen_object(/atom/movable/screen/combo, HUD_MOB_COMBO, HUD_GROUP_INFO, update_screen = TRUE)
/mob/living/proc/cycle_style()
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(current, next)
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
var/datum/action/swap_arts/swap_button = locate() in actions
swap_button.current_used_art = next_martial
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)
///To be overwritten for artstyle help.
/datum/martial_art/proc/get_style_help()
return FALSE