diff --git a/code/__DEFINES/melee.dm b/code/__DEFINES/melee.dm
index 2b06303386a..8b3a422fc0b 100644
--- a/code/__DEFINES/melee.dm
+++ b/code/__DEFINES/melee.dm
@@ -1,6 +1,7 @@
//Martial arts defines
#define MARTIALART_BOXING "boxing"
+#define MARTIALART_EVIL_BOXING "evil boxing"
#define MARTIALART_CQC "CQC"
#define MARTIALART_KRAVMAGA "krav maga"
#define MARTIALART_MUSHPUNCH "mushroom punch"
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 6a83f33083a..f2cb9069fb6 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -520,6 +520,15 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Prevents you from twohanding weapons.
#define TRAIT_NO_TWOHANDING "no_twohanding"
+/// Improves boxing damage against boxers and athletics experience gain
+#define TRAIT_STRENGTH "strength"
+
+/// Increases the duration of having exercised
+#define TRAIT_STIMMED "stimmed"
+
+/// Indicates that the target is able to be boxed at a boxer's full power.
+#define TRAIT_BOXING_READY "boxing_ready"
+
/// Halves the time of tying a tie.
#define TRAIT_FAST_TYING "fast_tying"
diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm
index 56bcbf3c90d..278510bd23f 100644
--- a/code/__DEFINES/traits/sources.dm
+++ b/code/__DEFINES/traits/sources.dm
@@ -137,6 +137,7 @@
#define LOCKED_HELMET_TRAIT "locked-helmet"
#define NINJA_SUIT_TRAIT "ninja-suit"
#define SLEEPING_CARP_TRAIT "sleeping_carp"
+#define BOXING_TRAIT "boxing"
#define TIMESTOP_TRAIT "timestop"
#define LIFECANDLE_TRAIT "lifecandle"
#define VENTCRAWLING_TRAIT "ventcrawling"
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 985fc8ea930..dd0c89c2211 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -495,6 +495,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_XRAY_VISION" = TRAIT_XRAY_VISION,
"TRAIT_DISCO_DANCER" = TRAIT_DISCO_DANCER,
"TRAIT_MAFIAINITIATE" = TRAIT_MAFIAINITIATE,
+ "TRAIT_STRENGTH" = TRAIT_STRENGTH,
+ "TRAIT_STIMMED" = TRAIT_STIMMED,
+ "TRAIT_BOXING_READY" = TRAIT_BOXING_READY,
),
/obj/item = list(
"TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING,
diff --git a/code/datums/martial/boxing.dm b/code/datums/martial/boxing.dm
index ec111fb3cf0..8e20dfbef9c 100644
--- a/code/datums/martial/boxing.dm
+++ b/code/datums/martial/boxing.dm
@@ -1,87 +1,271 @@
+#define LEFT_RIGHT_COMBO "DH"
+#define RIGHT_LEFT_COMBO "HD"
+#define LEFT_LEFT_COMBO "HH"
+#define RIGHT_RIGHT_COMBO "DD"
+
/datum/martial_art/boxing
name = "Boxing"
id = MARTIALART_BOXING
pacifist_style = TRUE
+ ///Boolean on whether we are sportsmanlike in our tussling; TRUE means we have restrictions
+ var/honorable_boxer = TRUE
+ /// List of traits applied to users of this martial art.
+ var/list/boxing_traits = list(TRAIT_BOXING_READY)
+ /// Balloon alert cooldown for warning our boxer to alternate their blows to get more damage
+ COOLDOWN_DECLARE(warning_cooldown)
/datum/martial_art/boxing/teach(mob/living/new_holder, make_temporary)
if(!ishuman(new_holder))
return FALSE
+ new_holder.add_traits(boxing_traits, BOXING_TRAIT)
+ RegisterSignal(new_holder, COMSIG_LIVING_CHECK_BLOCK, PROC_REF(check_block))
return ..()
-/datum/martial_art/boxing/disarm_act(mob/living/carbon/human/attacker, mob/living/defender)
- attacker.balloon_alert(attacker, "can't disarm while boxing!")
- return MARTIAL_ATTACK_FAIL
+/datum/martial_art/boxing/on_remove(mob/living/remove_from)
+ remove_from.remove_traits(boxing_traits, BOXING_TRAIT)
+ UnregisterSignal(remove_from, list(COMSIG_LIVING_CHECK_BLOCK))
+ return ..()
-/datum/martial_art/boxing/grab_act(mob/living/carbon/human/attacker, mob/living/defender)
- attacker.balloon_alert(attacker, "can't grab while boxing!")
- return MARTIAL_ATTACK_FAIL
+///Unlike most instances of this proc, this is actually called in _proc/tussle()
+///Returns a multiplier on our skill damage bonus.
+/datum/martial_art/boxing/proc/check_streak(mob/living/attacker, mob/living/defender)
+ var/combo_multiplier = 1
+
+ if(findtext(streak, LEFT_LEFT_COMBO) || findtext(streak, RIGHT_RIGHT_COMBO))
+ reset_streak()
+ if(COOLDOWN_FINISHED(src, warning_cooldown))
+ COOLDOWN_START(src, warning_cooldown, 2 SECONDS)
+ attacker.balloon_alert(attacker, "weak combo, alternate your hits!")
+ return combo_multiplier * 0.5
+
+ if(findtext(streak, LEFT_RIGHT_COMBO) || findtext(streak, RIGHT_LEFT_COMBO))
+ reset_streak()
+ return combo_multiplier * 1.5
+
+ return combo_multiplier
+
+/datum/martial_art/boxing/disarm_act(mob/living/attacker, mob/living/defender)
+ if(honor_check(defender))
+ add_to_streak("D", defender)
+ tussle(attacker, defender, "right hook", "right hooked")
+ return MARTIAL_ATTACK_SUCCESS
+
+/datum/martial_art/boxing/grab_act(mob/living/attacker, mob/living/defender)
+ if(honorable_boxer)
+ attacker.balloon_alert(attacker, "no grabbing while boxing!")
+ return MARTIAL_ATTACK_FAIL
+ return MARTIAL_ATTACK_INVALID //UNLESS YOU'RE EVIL
+
+/datum/martial_art/boxing/harm_act(mob/living/attacker, mob/living/defender)
+ if(honor_check(defender))
+ add_to_streak("H", defender)
+ tussle(attacker, defender, "left hook", "left hooked")
+ return MARTIAL_ATTACK_SUCCESS
+
+// Our only boxing move, which occurs on literally all attacks; the tussle. However, quite a lot morphs the results of this proc. Combos, unlike most martial arts attacks, are checked in this proc rather than our standard unarmed procs
+/datum/martial_art/boxing/proc/tussle(mob/living/attacker, mob/living/defender, atk_verb = "blind jab", atk_verbed = "blind jabbed")
+
+ if(honorable_boxer) //Being a good sport, you never hit someone on the ground or already knocked down. It shows you're the better person.
+ if(defender.body_position == LYING_DOWN && defender.getStaminaLoss() >= 100 || defender.IsUnconscious()) //If they're in stamcrit or unconscious, don't bloody punch them
+ attacker.balloon_alert(attacker, "unsportsmanlike behaviour!")
+ return FALSE
-/datum/martial_art/boxing/harm_act(mob/living/carbon/human/attacker, mob/living/defender)
var/obj/item/bodypart/arm/active_arm = attacker.get_active_hand()
+
+ //The values between which damage is rolled for punches
+ var/lower_force = active_arm.unarmed_damage_low
+ var/upper_force = active_arm.unarmed_damage_high
+
+ //Determines knockout potential and armor penetration (if that matters)
+ var/base_unarmed_effectiveness = active_arm.unarmed_effectiveness
+
+ //Determines attack sound based on attacker arm
+ var/attack_sound = active_arm.unarmed_attack_sound
+
+ // Out athletics skill is added as a damage bonus
+ var/athletics_skill = attacker.mind?.get_skill_level(/datum/skill/athletics)
+
+ // If true, grants experience for punching; we only gain experience if we punch another boxer.
+ var/grant_experience = FALSE
+
+ // What type of damage does our kind of boxing do? Defaults to STAMINA, unless you're performing EVIL BOXING
+ var/damage_type = honorable_boxer ? STAMINA : attacker.get_attack_type()
+
attacker.do_attack_animation(defender, ATTACK_EFFECT_PUNCH)
- var/damage = rand(5, 8) + active_arm.unarmed_damage_low
- var/atk_verb = pick("left hook", "right hook", "straight punch")
- if(damage <= 0)
- playsound(defender, active_arm.unarmed_miss_sound, 25, TRUE, -1)
- defender.visible_message(
- span_warning("[attacker]'s [atk_verb] misses [defender]!"),
- span_danger("You avoid [attacker]'s [atk_verb]!"),
- span_hear("You hear a swoosh!"),
- COMBAT_MESSAGE_RANGE,
- attacker,
- )
- to_chat(attacker, span_warning("Your [atk_verb] misses [defender]!"))
- log_combat(attacker, defender, "attempted to hit", atk_verb)
- return MARTIAL_ATTACK_FAIL
+ //Determines damage dealt on a punch. Against a boxing defender, we apply our skill bonus.
+ var/damage = rand(lower_force, upper_force)
- if(defender.check_block(attacker, damage, "[attacker]'s [atk_verb]", UNARMED_ATTACK))
- return MARTIAL_ATTACK_FAIL
+ if(honor_check(defender))
+ var/strength_bonus = HAS_TRAIT(attacker, TRAIT_STRENGTH) ? 2 : 0 //Investing into genetic strength improvements makes you a better boxer
+ damage += round(athletics_skill * check_streak(attacker, defender) + strength_bonus)
+ grant_experience = TRUE
+
+ var/current_atk_verb = atk_verb
+ var/current_atk_verbed = atk_verbed
+
+ if(is_detective_job(attacker.mind?.assigned_role)) //In short: discombobulate
+ current_atk_verb = "discombobulate"
+ current_atk_verbed = "discombulated"
+
+ // Similar to a normal punch, should we have a value of 0 for our lower force, we simply miss outright.
+ if(!lower_force)
+ playsound(defender.loc, active_arm.unarmed_miss_sound, 25, TRUE, -1)
+ defender.visible_message(span_warning("[attacker]'s [current_atk_verb] misses [defender]!"), \
+ span_danger("You avoid [attacker]'s [current_atk_verb]!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, attacker)
+ to_chat(attacker, span_warning("Your [current_atk_verb] misses [defender]!"))
+ log_combat(attacker, defender, "attempted to hit", current_atk_verb)
+ return FALSE
+
+ if(defender.check_block(attacker, damage, "[attacker]'s [current_atk_verb]", UNARMED_ATTACK))
+ return FALSE
var/obj/item/bodypart/affecting = defender.get_bodypart(defender.get_random_valid_zone(attacker.zone_selected))
- var/armor_block = defender.run_armor_check(affecting, MELEE)
+ var/armor_block = defender.run_armor_check(affecting, MELEE, armour_penetration = base_unarmed_effectiveness)
+
+ playsound(defender, attack_sound, 25, TRUE, -1)
- playsound(defender, active_arm.unarmed_attack_sound, 25, TRUE, -1)
defender.visible_message(
- span_danger("[attacker] [atk_verb]ed [defender]!"),
- span_userdanger("You're [atk_verb]ed by [attacker]!"),
+ span_danger("[attacker] [current_atk_verbed] [defender]!"),
+ span_userdanger("You're [current_atk_verbed] by [attacker]!"),
span_hear("You hear a sickening sound of flesh hitting flesh!"),
COMBAT_MESSAGE_RANGE,
attacker,
)
- to_chat(attacker, span_danger("You [atk_verb]ed [defender]!"))
- defender.apply_damage(damage, STAMINA, affecting, armor_block)
+
+ to_chat(attacker, span_danger("You [current_atk_verbed] [defender]!"))
+
+ defender.apply_damage(damage, damage_type, affecting, armor_block)
+
log_combat(attacker, defender, "punched (boxing) ")
- if(defender.getStaminaLoss() > 50 && istype(defender.mind?.martial_art, /datum/martial_art/boxing))
- var/knockout_prob = defender.getStaminaLoss() + rand(-15, 15)
- if(defender.stat != DEAD && prob(knockout_prob))
- defender.visible_message(
- span_danger("[attacker] knocks [defender] out with a haymaker!"),
- span_userdanger("You're knocked unconscious by [attacker]!"),
- span_hear("You hear a sickening sound of flesh hitting flesh!"),
- COMBAT_MESSAGE_RANGE,
- attacker,
- )
- to_chat(attacker, span_danger("You knock [defender] out with a haymaker!"))
- defender.apply_effect(20 SECONDS, EFFECT_KNOCKDOWN, armor_block)
- defender.SetSleeping(10 SECONDS)
- log_combat(attacker, defender, "knocked out (boxing) ")
- return MARTIAL_ATTACK_SUCCESS
+
+ if(grant_experience)
+ skill_experience_adjustment(attacker, (damage/lower_force))
+
+ if(defender.stat == DEAD || !honor_check(defender)) //early returning here so we don't worry about knockout probs
+ return TRUE
+
+ //Determine our attackers athletics level as a knockout probability bonus
+ var/attacker_athletics_skill = (attacker.mind?.get_skill_modifier(/datum/skill/athletics, SKILL_RANDS_MODIFIER) + base_unarmed_effectiveness)
+
+ // Defender boxing skill and armor block are used as a defense here. This has already factored in base_unarmed_effectiveness from the attacker
+ var/defender_athletics_skill = clamp(defender.mind?.get_skill_modifier(/datum/skill/athletics, SKILL_RANDS_MODIFIER), 0, 100)
+
+ //Determine our final probability, using a clamp to stop any prob() weirdness.
+ var/final_knockout_probability = clamp(round(attacker_athletics_skill - defender_athletics_skill), 0 , 100)
+
+ if(!prob(final_knockout_probability))
+ return TRUE
+
+ if(defender.get_timed_status_effect_duration(/datum/status_effect/staggered))
+ defender.visible_message(
+ span_danger("[attacker] knocks [defender] out with a haymaker!"),
+ span_userdanger("You're knocked unconscious by [attacker]!"),
+ span_hear("You hear a sickening sound of flesh hitting flesh!"),
+ COMBAT_MESSAGE_RANGE,
+ attacker,
+ )
+ to_chat(attacker, span_danger("You knock [defender] out with a haymaker!"))
+ defender.apply_effect(20 SECONDS, EFFECT_KNOCKDOWN, armor_block)
+ defender.SetSleeping(10 SECONDS)
+ log_combat(attacker, defender, "knocked out (boxing) ")
+ else
+ defender.visible_message(
+ span_danger("[attacker] staggers [defender] with a haymaker!"),
+ span_userdanger("You're nearly knocked off your feet by [attacker]!"),
+ span_hear("You hear a sickening sound of flesh hitting flesh!"),
+ COMBAT_MESSAGE_RANGE,
+ attacker,
+ )
+ defender.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH, 10 SECONDS)
+ to_chat(attacker, span_danger("You stagger [defender] with a haymaker!"))
+ log_combat(attacker, defender, "staggered (boxing) ")
+
+ playsound(defender, 'sound/effects/coin2.ogg', 40, TRUE)
+ new /obj/effect/temp_visual/crit(get_turf(defender))
+ skill_experience_adjustment(attacker, (damage/lower_force)) //double experience for a successful crit
+
+ return TRUE
+
+/// Returns whether whoever is checked by this proc is complying with the rules of boxing. The boxer cannot block non-boxers, and cannot apply their scariest moves against non-boxers.
+/datum/martial_art/boxing/proc/honor_check(mob/living/possible_boxer)
+ if(!honorable_boxer)
+ return TRUE //You scoundrel!!
+
+ if(!HAS_TRAIT(possible_boxer, TRAIT_BOXING_READY))
+ return FALSE
+
+ return TRUE
+
+/// Handles our instances of experience gain while boxing. It also applies the exercised status effect.
+/datum/martial_art/boxing/proc/skill_experience_adjustment(mob/living/boxer, experience_value)
+ //Boxing in heavier gravity gives you more experience
+ var/gravity_modifier = boxer.has_gravity() > STANDARD_GRAVITY ? 1 : 0.5
+
+ //You gotta sleep before you get any experience!
+ boxer.mind?.adjust_experience(/datum/skill/athletics, experience_value * gravity_modifier)
+ boxer.apply_status_effect(/datum/status_effect/exercised)
+
+/// Handles our blocking signals, similar to hit_reaction() on items. Only blocks while the boxer is in throw mode.
+/datum/martial_art/boxing/proc/check_block(mob/living/boxer, atom/movable/hitby, damage, attack_text, attack_type, ...)
+ SIGNAL_HANDLER
+
+ if(!can_use(boxer) || !boxer.throw_mode || boxer.incapacitated(IGNORE_GRAB))
+ return NONE
+
+ if(attack_type != UNARMED_ATTACK)
+ return NONE
+
+ //Determines unarmed defense against boxers using our current active arm.
+ var/obj/item/bodypart/arm/active_arm = boxer.get_active_hand()
+ var/base_unarmed_effectiveness = active_arm.unarmed_effectiveness
+
+ // Out athletics skill is added to our block potential
+ var/athletics_skill_rands = boxer.mind?.get_skill_modifier(/datum/skill/athletics, SKILL_RANDS_MODIFIER)
+
+ var/block_chance = base_unarmed_effectiveness + athletics_skill_rands
+
+ var/block_text = pick("block", "evade")
+
+ if(!prob(block_chance))
+ return NONE
+
+ var/mob/living/attacker = GET_ASSAILANT(hitby)
+
+ if(!honor_check(attacker))
+ return NONE
+
+ if(istype(attacker) && boxer.Adjacent(attacker))
+ attacker.apply_damage(10, STAMINA)
+ boxer.apply_damage(5, STAMINA)
+
+ boxer.visible_message(
+ span_danger("[boxer] [block_text]s [attack_text]!"),
+ span_userdanger("You [block_text] [attack_text]!"),
+ )
+ if(block_text == "evade")
+ playsound(boxer.loc, active_arm.unarmed_miss_sound, 25, TRUE, -1)
+
+ skill_experience_adjustment(boxer, 1) //just getting hit a bunch doesn't net you much experience
+
+ return SUCCESSFUL_BLOCK
/datum/martial_art/boxing/can_use(mob/living/martial_artist)
if(!ishuman(martial_artist))
return FALSE
return ..()
-/obj/item/clothing/gloves/boxing
+/// Evil Boxing; for sick, evil scoundrels. Has no honor, making it more lethal (therefore unable to be used by pacifists).
+/// Grants Strength and Stimmed to speed up any experience gain.
-/obj/item/clothing/gloves/boxing/Initialize(mapload)
- . = ..()
- var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/extendohand_l, /datum/crafting_recipe/extendohand_r)
+/datum/martial_art/boxing/evil
+ name = "Evil Boxing"
+ id = MARTIALART_EVIL_BOXING
+ pacifist_style = FALSE
+ honorable_boxer = FALSE
+ boxing_traits = list(TRAIT_BOXING_READY, TRAIT_STRENGTH, TRAIT_STIMMED)
- AddComponent(
- /datum/component/slapcrafting,\
- slapcraft_recipes = slapcraft_recipe_list,\
- )
-
- AddComponent(/datum/component/martial_art_giver, /datum/martial_art/boxing)
+#undef LEFT_RIGHT_COMBO
+#undef RIGHT_LEFT_COMBO
+#undef LEFT_LEFT_COMBO
+#undef RIGHT_RIGHT_COMBO
diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm
index de77727a3e4..b45d0a6499e 100644
--- a/code/datums/mutations/body.dm
+++ b/code/datums/mutations/body.dm
@@ -267,15 +267,42 @@
desc = "The user's muscles slightly expand."
quality = POSITIVE
text_gain_indication = "You feel strong."
+ instability = 5
difficulty = 16
+/datum/mutation/human/strong/on_acquiring(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ ADD_TRAIT(owner, TRAIT_STRENGTH, GENETIC_MUTATION)
+
+/datum/mutation/human/strong/on_losing(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ REMOVE_TRAIT(owner, TRAIT_STRENGTH, GENETIC_MUTATION)
+
+
/datum/mutation/human/stimmed
name = "Stimmed"
desc = "The user's chemical balance is more robust."
quality = POSITIVE
text_gain_indication = "You feel stimmed."
+ instability = 5
difficulty = 16
+/datum/mutation/human/stimmed/on_acquiring(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ ADD_TRAIT(owner, TRAIT_STIMMED, GENETIC_MUTATION)
+
+/datum/mutation/human/stimmed/on_losing(mob/living/carbon/human/owner)
+ . = ..()
+ if(.)
+ return
+ REMOVE_TRAIT(owner, TRAIT_STIMMED, GENETIC_MUTATION)
+
/datum/mutation/human/insulated
name = "Insulated"
desc = "The affected person does not conduct electricity."
diff --git a/code/datums/skills/athletics.dm b/code/datums/skills/athletics.dm
new file mode 100644
index 00000000000..2ef336e5ba9
--- /dev/null
+++ b/code/datums/skills/athletics.dm
@@ -0,0 +1,27 @@
+/datum/skill/athletics
+ name = "Athletics"
+ title = "Athlete"
+ desc = "Twinkle twinkle little star, hit the gym and lift the bar."
+ // The skill value modifier effects the max duration that is possible for /datum/status_effect/exercised; The rands modifier determines block probability and crit probability while boxing against boxers
+ modifiers = list(
+ SKILL_VALUE_MODIFIER = list(
+ 1 MINUTES,
+ 1.5 MINUTES,
+ 2 MINUTES,
+ 2.5 MINUTES,
+ 3 MINUTES,
+ 3.5 MINUTES,
+ 5 MINUTES
+ ),
+ SKILL_RANDS_MODIFIER = list(
+ 0,
+ 5,
+ 10,
+ 15,
+ 20,
+ 30,
+ 50
+ )
+ )
+
+ skill_item_path = /obj/item/clothing/gloves/boxing/golden
diff --git a/code/datums/skills/fitness.dm b/code/datums/skills/fitness.dm
deleted file mode 100644
index 32be3f9d211..00000000000
--- a/code/datums/skills/fitness.dm
+++ /dev/null
@@ -1,23 +0,0 @@
-/datum/skill/fitness
- name = "Fitness"
- title = "Powerlifter"
- desc = "Twinkle twinkle little star, hit the gym and lift the bar."
- /// The skill value modifier effects the max duration that is possible for /datum/status_effect/exercised
- modifiers = list(SKILL_VALUE_MODIFIER = list(1 MINUTES, 1.5 MINUTES, 2 MINUTES, 2.5 MINUTES, 3 MINUTES, 3.5 MINUTES, 5 MINUTES))
- /// How much bigger your mob becomes per level (these effects don't stack together)
- var/static/size_boost = list(0, 1/16, 1/8, 3/16, 2/8, 3/8, 4/8)
- // skill_item_path - your mob sprite gets bigger to showoff so we don't get a special item
-
-/datum/skill/fitness/level_gained(datum/mind/mind, new_level, old_level, silent)
- . = ..()
- var/old_gym_size = RESIZE_DEFAULT_SIZE + size_boost[old_level]
- var/new_gym_size = RESIZE_DEFAULT_SIZE + size_boost[new_level]
-
- mind.current.update_transform(new_gym_size / old_gym_size)
-
-/datum/skill/fitness/level_lost(datum/mind/mind, new_level, old_level, silent)
- . = ..()
- var/old_gym_size = RESIZE_DEFAULT_SIZE + size_boost[old_level]
- var/new_gym_size = RESIZE_DEFAULT_SIZE + size_boost[new_level]
-
- mind.current.update_transform(new_gym_size / old_gym_size)
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 6cd180ab597..5784739cd9c 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -195,6 +195,9 @@
if(HAS_TRAIT(new_owner, TRAIT_HULK))
modifier += 0.5
+ if(HAS_TRAIT(new_owner, TRAIT_STIMMED)) // Naturally produces stimulants to help get you PUMPED
+ modifier += 1
+
if(HAS_TRAIT(new_owner, TRAIT_FAT)) // less xp until you get into shape
modifier -= 0.5
@@ -209,10 +212,10 @@
if(new_owner.reagents.has_reagent(workout_reagent))
food_boost += supplementary_reagents_bonus[workout_reagent]
- var/skill_level_boost = (new_owner.mind.get_skill_level(/datum/skill/fitness) - 1) * 2 SECONDS
+ var/skill_level_boost = (new_owner.mind.get_skill_level(/datum/skill/athletics) - 1) * 2 SECONDS
bonus_time = (bonus_time + food_boost + skill_level_boost) * modifier
- var/exhaustion_limit = new_owner.mind.get_skill_modifier(/datum/skill/fitness, SKILL_VALUE_MODIFIER) + world.time
+ var/exhaustion_limit = new_owner.mind.get_skill_modifier(/datum/skill/athletics, SKILL_VALUE_MODIFIER) + world.time
if(duration + bonus_time >= exhaustion_limit)
duration = exhaustion_limit
to_chat(new_owner, span_userdanger("Your muscles are exhausted! Might be a good idea to sleep..."))
@@ -228,10 +231,10 @@
/datum/status_effect/exercised/refresh(mob/living/new_owner, bonus_time)
duration += workout_duration(new_owner, bonus_time)
new_owner.clear_mood_event("exercise") // we need to reset the old mood event in case our fitness skill changes
- new_owner.add_mood_event("exercise", /datum/mood_event/exercise, new_owner.mind.get_skill_level(/datum/skill/fitness))
+ new_owner.add_mood_event("exercise", /datum/mood_event/exercise, new_owner.mind.get_skill_level(/datum/skill/athletics))
/datum/status_effect/exercised/on_apply()
- owner.add_mood_event("exercise", /datum/mood_event/exercise, owner.mind.get_skill_level(/datum/skill/fitness))
+ owner.add_mood_event("exercise", /datum/mood_event/exercise, owner.mind.get_skill_level(/datum/skill/athletics))
return ..()
/datum/status_effect/exercised/on_remove()
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index b2a37356e53..ad5696e787f 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -224,7 +224,7 @@
var/datum/status_effect/exercised/exercised = carbon_owner.has_status_effect(/datum/status_effect/exercised)
if(exercised && carbon_owner.mind)
// the better you sleep, the more xp you gain
- carbon_owner.mind.adjust_experience(/datum/skill/fitness, seconds_between_ticks * sleep_quality * SLEEP_QUALITY_WORKOUT_MULTIPLER)
+ carbon_owner.mind.adjust_experience(/datum/skill/athletics, seconds_between_ticks * sleep_quality * SLEEP_QUALITY_WORKOUT_MULTIPLER)
carbon_owner.adjust_timed_status_effect(-1 * seconds_between_ticks * sleep_quality * SLEEP_QUALITY_WORKOUT_MULTIPLER, /datum/status_effect/exercised)
if(prob(2))
to_chat(carbon_owner, span_notice("You feel your fitness improving!"))
diff --git a/code/game/objects/structures/gym/punching_bag.dm b/code/game/objects/structures/gym/punching_bag.dm
index f441887f2c1..ef0db16e953 100644
--- a/code/game/objects/structures/gym/punching_bag.dm
+++ b/code/game/objects/structures/gym/punching_bag.dm
@@ -57,9 +57,12 @@
stamina_exhaustion = 2
if (is_heavy_gravity)
stamina_exhaustion *= 1.5
+
+ if(HAS_TRAIT(user, TRAIT_STRENGTH)) //The strong get reductions to stamina damage taken while exercising
+ stamina_exhaustion *= 0.5
user.adjustStaminaLoss(stamina_exhaustion)
- user.mind?.adjust_experience(/datum/skill/fitness, is_heavy_gravity ? 0.2 : 0.1)
+ user.mind?.adjust_experience(/datum/skill/athletics, is_heavy_gravity ? 0.2 : 0.1)
user.apply_status_effect(/datum/status_effect/exercised)
/obj/structure/punching_bag/wrench_act_secondary(mob/living/user, obj/item/tool)
diff --git a/code/game/objects/structures/gym/weight_machine.dm b/code/game/objects/structures/gym/weight_machine.dm
index d3614ee6815..3c531f04889 100644
--- a/code/game/objects/structures/gym/weight_machine.dm
+++ b/code/game/objects/structures/gym/weight_machine.dm
@@ -109,7 +109,7 @@
if(do_after(user, 8 SECONDS, src) && user.has_gravity())
// with enough dedication, even clowns can overcome their handicaps
- var/clumsy_chance = 30 - (user.mind.get_skill_level(/datum/skill/fitness) * 5)
+ var/clumsy_chance = 30 - (user.mind.get_skill_level(/datum/skill/athletics) * 5)
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(clumsy_chance))
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
to_chat(user, span_warning("Your hand slips, causing the [name] to smash you!"))
@@ -135,7 +135,7 @@
if(iscarbon(user))
var/gravity_modifier = user.has_gravity() > STANDARD_GRAVITY ? 2 : 1
// remember the real xp gain is from sleeping after working out
- user.mind.adjust_experience(/datum/skill/fitness, WORKOUT_XP * gravity_modifier)
+ user.mind.adjust_experience(/datum/skill/athletics, WORKOUT_XP * gravity_modifier)
user.apply_status_effect(/datum/status_effect/exercised, EXERCISE_STATUS_DURATION)
end_workout()
@@ -168,9 +168,13 @@
return TRUE // No weight? I could do this all day
var/gravity_modifier = affected_gravity > STANDARD_GRAVITY ? 0.75 : 1
// the amount of workouts you can do before you hit stamcrit
- var/workout_reps = total_workout_reps[user.mind.get_skill_level(/datum/skill/fitness)] * gravity_modifier
+ var/workout_reps = total_workout_reps[user.mind.get_skill_level(/datum/skill/athletics)] * gravity_modifier
// total stamina drain of 1 workout calculated based on the workout length
var/stamina_exhaustion = FLOOR(user.maxHealth / workout_reps / WORKOUT_LENGTH, 0.1)
+
+ if(HAS_TRAIT(user, TRAIT_STRENGTH)) //The strong get reductions to stamina damage taken while exercising
+ stamina_exhaustion *= 0.5
+
user.adjustStaminaLoss(stamina_exhaustion * seconds_per_tick)
return TRUE
diff --git a/code/game/objects/structures/lavaland/gulag_vent.dm b/code/game/objects/structures/lavaland/gulag_vent.dm
index aa13a1621d8..b564908cdc6 100644
--- a/code/game/objects/structures/lavaland/gulag_vent.dm
+++ b/code/game/objects/structures/lavaland/gulag_vent.dm
@@ -34,9 +34,10 @@
occupied = FALSE
if (!succeeded)
return
- living_user.mind?.adjust_experience(/datum/skill/fitness, 10)
+ var/stamina_damage_to_inflict = HAS_TRAIT(user, TRAIT_STRENGTH) ? 60 : 120 //Decreases the amount of stamina damage inflicted by half if you're STRONG
+ living_user.mind?.adjust_experience(/datum/skill/athletics, 10)
living_user.apply_status_effect(/datum/status_effect/exercised)
new spawned_boulder(get_turf(living_user))
living_user.visible_message(span_notice("[living_user] hauls a boulder out of [src]."))
- living_user.apply_damage(120, STAMINA)
+ living_user.apply_damage(stamina_damage_to_inflict, STAMINA)
playsound(src, 'sound/weapons/genhit.ogg', vol = 50, vary = TRUE)
diff --git a/code/modules/clothing/gloves/boxing.dm b/code/modules/clothing/gloves/boxing.dm
index deed90b1696..03b1cbb5bf7 100644
--- a/code/modules/clothing/gloves/boxing.dm
+++ b/code/modules/clothing/gloves/boxing.dm
@@ -6,6 +6,25 @@
equip_delay_other = 60
species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion
clothing_traits = list(TRAIT_CHUNKYFINGERS)
+ /// Determines the version of boxing (or any martial art for that matter) that the boxing gloves gives
+ var/style_to_give = /datum/martial_art/boxing
+
+/obj/item/clothing/gloves/boxing/Initialize(mapload)
+ . = ..()
+ var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/extendohand_l, /datum/crafting_recipe/extendohand_r)
+
+ AddComponent(
+ /datum/component/slapcrafting,\
+ slapcraft_recipes = slapcraft_recipe_list,\
+ )
+
+ AddComponent(/datum/component/martial_art_giver, style_to_give)
+
+/obj/item/clothing/gloves/boxing/evil
+ name = "evil boxing gloves"
+ desc = "These strange gloves radiate an unsually evil aura."
+ greyscale_colors = "#21211f"
+ style_to_give = /datum/martial_art/boxing/evil
/obj/item/clothing/gloves/boxing/green
icon_state = "boxinggreen"
@@ -18,3 +37,16 @@
/obj/item/clothing/gloves/boxing/yellow
icon_state = "boxingyellow"
greyscale_colors = "#d2a800"
+
+/obj/item/clothing/gloves/boxing/golden
+ name = "golden gloves"
+ desc = "The reigning champ of the station!"
+ icon_state = "boxinggold"
+ custom_materials = list(/datum/material/gold = SHEET_MATERIAL_AMOUNT*1) //LITERALLY GOLD
+ material_flags = MATERIAL_EFFECTS | MATERIAL_AFFECT_STATISTICS
+ equip_delay_other = 120
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE
+
+/obj/item/clothing/gloves/boxing/golden/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/skill_reward, /datum/skill/athletics)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index b3e41c89d48..07135351a91 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -915,7 +915,7 @@
var/carrydelay = 5 SECONDS //if you have latex you are faster at grabbing
var/skills_space
- var/fitness_level = mind.get_skill_level(/datum/skill/fitness) - 1
+ var/fitness_level = mind.get_skill_level(/datum/skill/athletics) - 1
if(HAS_TRAIT(src, TRAIT_QUICKER_CARRY))
carrydelay -= 2 SECONDS
else if(HAS_TRAIT(src, TRAIT_QUICK_CARRY))
diff --git a/icons/mob/clothing/hands.dmi b/icons/mob/clothing/hands.dmi
index d0e5093d459..a670c33b7dc 100644
Binary files a/icons/mob/clothing/hands.dmi and b/icons/mob/clothing/hands.dmi differ
diff --git a/icons/obj/clothing/gloves.dmi b/icons/obj/clothing/gloves.dmi
index d8a2fc0bd13..d5099c64e7f 100644
Binary files a/icons/obj/clothing/gloves.dmi and b/icons/obj/clothing/gloves.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 3410a0622f3..a0c82a3c54a 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1774,9 +1774,9 @@
#include "code\datums\shuttles\starfury.dm"
#include "code\datums\shuttles\whiteship.dm"
#include "code\datums\skills\_skill.dm"
+#include "code\datums\skills\athletics.dm"
#include "code\datums\skills\cleaning.dm"
#include "code\datums\skills\fishing.dm"
-#include "code\datums\skills\fitness.dm"
#include "code\datums\skills\gaming.dm"
#include "code\datums\skills\mining.dm"
#include "code\datums\station_traits\_station_trait.dm"