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"