diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index a87ad394b6326..46890d72523be 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -250,5 +250,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define IGNORE_INCAPACITATED (1<<3) /// Used to prevent important slowdowns from being abused by drugs like kronkaine #define IGNORE_SLOWDOWNS (1<<4) +/// Used to keep the skill indicator icon without using the built-in delay modifier +#define IGNORE_SKILL_DELAY (1<<5) #define IGNORE_ALL (IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM|IGNORE_INCAPACITATED|IGNORE_SLOWDOWNS) diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 7c0931b1e5903..60459e9f14cf9 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -106,6 +106,7 @@ #define ui_borg_radio "EAST-1:28,SOUTH+1:7" #define ui_borg_intents "EAST-2:26,SOUTH:5" #define ui_language_menu "EAST-5:20,SOUTH:21" +#define ui_skill_menu "EAST-5:20,SOUTH:5" #define ui_move_up "EAST-4:22, SOUTH:21" #define ui_move_down "EAST-4:22, SOUTH:5" diff --git a/code/__DEFINES/skills.dm b/code/__DEFINES/skills.dm new file mode 100644 index 0000000000000..8d30f633578f3 --- /dev/null +++ b/code/__DEFINES/skills.dm @@ -0,0 +1,27 @@ + +/// Medicine and surgery. +#define SKILL_PHYSIOLOGY "physiology" +/// Construction and repair of structures and machinery. +#define SKILL_MECHANICAL "mechanics" +/// Hacking, piloting, and robotic maintenance. +#define SKILL_TECHNICAL "technical" +/// Chemistry, botany, physics, and other sciences. +#define SKILL_SCIENCE "science" +/// Strength, endurance, accuracy. +#define SKILL_FITNESS "fitness" + +/// No experience whatsoever. +#define EXP_NONE 0 +/// Some experience, but not much. +#define EXP_LOW 1 +/// Enough experience to do a decent job. +#define EXP_MID 2 +/// Above average skill level. +#define EXP_HIGH 3 +/// Exceptionally skilled. +#define EXP_MASTER 4 +/// Uniquely gifted. Not obtainable through normal means. +#define EXP_GENIUS 5 + +/// Experience required to increase your skills by one level. Increases exponentially the higher your level already is. +#define EXPERIENCE_PER_LEVEL 500 diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm index b0317d89f59b8..ac442d3d41e95 100644 --- a/code/__DEFINES/tools.dm +++ b/code/__DEFINES/tools.dm @@ -3,6 +3,7 @@ #define TOOL_MULTITOOL "multitool" #define TOOL_SCREWDRIVER "screwdriver" #define TOOL_WIRECUTTER "wirecutter" +#define TOOL_WIRING "wiring" #define TOOL_WRENCH "wrench" #define TOOL_WELDER "welder" #define TOOL_ANALYZER "analyzer" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index ebb0822e420c1..21d8168e317f0 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -109,6 +109,8 @@ #define TRAIT_IGNOREDAMAGESLOWDOWN "ignoredamageslowdown" /// Makes the screen go black and white while illuminating all mobs based on their body temperature #define TRAIT_INFRARED_VISION "infrared_vision" +/// Punches don't stun. Use this instead of setting punchstunchance to zero. +#define TRAIT_NO_PUNCH_STUN "no-punch-stun" //////////////////////////////////////////////////////////////////////////////////// //-------------------------Species Specific defines-------------------------------// @@ -441,6 +443,10 @@ #define TRAIT_PRESENT_VISION "present-vision" #define TRAIT_DISK_VERIFIER "disk-verifier" #define TRAIT_NOMOBSWAP "no-mob-swap" +/// Can allocate 5 points into one skill instead of the usual 4 +#define TRAIT_EXCEPTIONAL_SKILL "exceptional-skill" +/// Acts as an additional skill point for piloting mechs, up to EXP_MASTER. +#define TRAIT_SKILLED_PILOT "skilled-pilot" /// Can examine IDs to see if they are roundstart. #define TRAIT_ID_APPRAISER "id_appraiser" /// Gives us turf, mob and object vision through walls diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 8c44e6f776887..97c216a4f2685 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -319,7 +319,7 @@ GLOBAL_LIST_EMPTY(species_list) * given `delay`. Returns `TRUE` on success or `FALSE` on failure. * Interaction_key is the assoc key under which the do_after is capped, with max_interact_count being the cap. Interaction key will default to target if not set. */ -/proc/do_after(mob/user, delay, atom/target, timed_action_flags = NONE, progress = TRUE, datum/callback/extra_checks, interaction_key, max_interact_count = 1) +/proc/do_after(mob/user, delay, atom/target, timed_action_flags = NONE, progress = TRUE, datum/callback/extra_checks, interaction_key, max_interact_count = 1, skill_check = null) if(!user) return FALSE if(!isnum(delay)) @@ -335,10 +335,13 @@ GLOBAL_LIST_EMPTY(species_list) if(!(timed_action_flags & IGNORE_SLOWDOWNS)) delay *= user.action_speed_modifier * user.do_after_coefficent() //yogs: darkspawn + + if(skill_check && user.mind && !(timed_action_flags & IGNORE_SKILL_DELAY)) + delay *= (12 - user.get_skill(skill_check)) / 10 var/datum/progressbar/progbar if(progress) - progbar = new(user, delay, target || user, timed_action_flags, extra_checks) + progbar = new(user, delay, target || user, timed_action_flags, extra_checks, skill_check) SEND_SIGNAL(user, COMSIG_DO_AFTER_BEGAN) @@ -352,6 +355,9 @@ GLOBAL_LIST_EMPTY(species_list) . = FALSE break + if(skill_check) // get better at things by practicing them + user.add_exp(skill_check, delay) + if(!QDELETED(progbar)) progbar.end_progress() diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index f23d26b98b84e..365589be4f6d3 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -322,6 +322,16 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." var/mob/living/carbon/C = mob_viewer C.take(giver, receiving) +//SKILLS + +/atom/movable/screen/alert/skill_up + name = "Allocate Skill Points" + desc = "You have unspent skill points! Click here to allocate them." + +/atom/movable/screen/alert/skill_up/Click(location, control, params) + . = ..() + mob_viewer.hud_used?.skill_menu?.ui_interact(mob_viewer) + //ALIENS /atom/movable/screen/alert/alien_tox diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 2c3ce079deded..85099e5bdcd7e 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -40,6 +40,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( var/atom/movable/screen/rest_icon var/atom/movable/screen/throw_icon var/atom/movable/screen/module_store_icon + var/atom/movable/screen/skill_menu/skill_menu var/list/static_inventory = list() //the screen objects which are static var/list/toggleable_inventory = list() //the screen objects which can be hidden diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index 591016523f3d6..2d032a89cb644 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -101,6 +101,12 @@ using.screen_loc = UI_BOXAREA static_inventory += using + skill_menu = new /atom/movable/screen/skill_menu(src) + skill_menu.icon = ui_style + if(!widescreen_layout) + skill_menu.screen_loc = UI_BOXAREA + static_inventory += skill_menu + action_intent = new /atom/movable/screen/combattoggle/flashy(src) action_intent.icon = ui_style action_intent.screen_loc = ui_combat_toggle diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index b2439f5736939..520751835b1e7 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -118,6 +118,108 @@ var/datum/language_holder/H = M.get_language_holder() H.open_language_menu(usr) +/atom/movable/screen/skill_menu + name = "skills menu" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "skill_menu" + screen_loc = ui_skill_menu + var/list/allocated_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + var/allocated_points = EXP_NONE + +/atom/movable/screen/skill_menu/Click() + ui_interact(usr) + +/atom/movable/screen/skill_menu/ui_interact(mob/user, datum/tgui/ui) + if(!user.mind) + CRASH("[user.type] ([user]) tried to use the skill menu without a mind!") + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "SkillMenu", "Allocate Skill Points") + ui.open() + +/atom/movable/screen/skill_menu/ui_data(mob/user) + var/list/data = list() + var/list/skill_data = list() + for(var/skill in user.mind.skills) + skill_data.Add(list(list( + "base" = user.get_skill(skill), + "allocated" = allocated_skills[skill], + "exp_progress" = user.mind?.exp_progress[skill], + ))) + data["skills"] = skill_data + data["skill_points"] = user.mind.skill_points + data["allocated_points"] = allocated_points + data["exceptional_skill"] = HAS_MIND_TRAIT(user, TRAIT_EXCEPTIONAL_SKILL) + return data + +/atom/movable/screen/skill_menu/ui_static_data(mob/user) + var/static/list/data = list( + "exp_per_level" = EXPERIENCE_PER_LEVEL + ) + return data + +/atom/movable/screen/skill_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + var/mob/user = usr + if(!user.mind) + CRASH("User ([user]) without a mind attempted to allocate skill points!") + switch(action) + if("confirm") + if(allocated_points > user.mind.skill_points) + stack_trace("[user] attempted to allocate [allocated_points] skill points when they only had [user.mind.skill_points] available!") + message_admins("[key_name_admin(user)] may have attempted an exploit to gain more skill points than intended!") + qdel(allocated_skills) + allocated_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + allocated_points = EXP_NONE + return TRUE + for(var/skill in user.mind.skills) + user.adjust_skill(skill, allocated_skills[skill], max_skill = EXP_GENIUS) + allocated_skills[skill] = EXP_NONE + user.mind.skill_points -= allocated_points + allocated_points = EXP_NONE + if(!user.mind.skill_points) + user.clear_alert("skill points") + return TRUE + if("allocate") + if(allocated_points + params["amount"] > user.mind.skill_points) + return TRUE + if(allocated_points + params["amount"] < 0) + return TRUE + if(allocated_skills[params["skill"]] + params["amount"] + user.get_skill(params["skill"]) > (4 + HAS_MIND_TRAIT(user, TRAIT_EXCEPTIONAL_SKILL))) + return TRUE + if(allocated_skills[params["skill"]] + params["amount"] < 0) + return TRUE + allocated_skills[params["skill"]] += params["amount"] + allocated_points += params["amount"] + return TRUE + +/atom/movable/screen/skill_menu/ui_status(mob/user) + if(!user.mind) + return UI_CLOSE + return UI_INTERACTIVE + +/atom/movable/screen/skill_menu/ui_state(mob/user) + return GLOB.always_state + +/atom/movable/screen/skill_menu/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/crafting), + ) + /atom/movable/screen/ghost/pai name = "pAI Candidate" icon = 'icons/mob/screen_midnight.dmi' diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index f253faac27ab5..ab1fbf82e8fde 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -160,32 +160,32 @@ * Called from [/mob/living/proc/attackby] * * Arguments: - * * mob/living/M - The mob being hit by this item + * * mob/living/target - The mob being hit by this item * * mob/living/user - The mob hitting with this item * * params - Click params of this attack */ -/obj/item/proc/attack(mob/living/M, mob/living/user, params) - var/signal_return = SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, M, user, params) +/obj/item/proc/attack(mob/living/target, mob/living/user, params) + var/signal_return = SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, target, user, params) if(signal_return & COMPONENT_CANCEL_ATTACK_CHAIN) return TRUE if(signal_return & COMPONENT_SKIP_ATTACK) return - SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, M, user, params) + SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, target, user, params) if(item_flags & NOBLUDGEON) return if(tool_behaviour && !user.combat_mode) // checks for combat mode with surgery tool var/list/modifiers = params2list(params) - if(attempt_initiate_surgery(src, M, user, modifiers)) + if(attempt_initiate_surgery(src, target, user, modifiers)) return TRUE - if(iscarbon(M)) - var/mob/living/carbon/C = M + if(iscarbon(target)) + var/mob/living/carbon/C = target for(var/i in C.all_wounds) var/datum/wound/W = i if(W.try_treating(src, user)) return TRUE - to_chat(user, span_warning("You can't perform any surgeries on [M]'s [parse_zone(user.zone_selected)]!")) //yells at you + to_chat(user, span_warning("You can't perform any surgeries on [target]'s [parse_zone(user.zone_selected)]!")) //yells at you return TRUE if(force && !synth_check(user, SYNTH_ORGANIC_HARM)) @@ -199,16 +199,17 @@ else if(hitsound) playsound(loc, hitsound, get_clamped_volume(), 1, -1) - M.lastattacker = user.real_name - M.lastattackerckey = user.ckey + target.lastattacker = user.real_name + target.lastattackerckey = user.ckey if(force) - M.last_damage = name + target.last_damage = name - user.do_attack_animation(M) - M.attacked_by(src, user) + user.do_attack_animation(target) + user.add_exp(SKILL_FITNESS, target.stat == CONSCIOUS ? force : force / 5) // attacking things that can't move isn't very good experience + target.attacked_by(src, user) - log_combat(user, M, "attacked", src.name, "(COMBAT MODE: [user.combat_mode ? "ON" : "OFF"]) (DAMTYPE: [uppertext(damtype)])") + log_combat(user, target, "attacked", src.name, "(COMBAT MODE: [user.combat_mode ? "ON" : "OFF"]) (DAMTYPE: [uppertext(damtype)])") add_fingerprint(user) var/force_multiplier = 1 if(ishuman(user)) @@ -233,6 +234,7 @@ user.changeNext_move(CLICK_CD_MELEE * weapon_stats[SWING_SPEED] * (range_cooldown_mod ? (dist > 0 ? min(dist, weapon_stats[REACH]) * range_cooldown_mod : range_cooldown_mod) : 1)) //range increases attack cooldown by swing speed user.do_attack_animation(attacked_atom) attacked_atom.attacked_by(src, user) + user.add_exp(SKILL_FITNESS, force / 5) user.weapon_slow(src) var/force_multiplier = 1 if(ishuman(user)) diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index 3eb9e427b3823..e1575b94ddc6c 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -523,14 +523,14 @@ SUBSYSTEM_DEF(shuttle) * * dock_id - The ID of the destination (stationary docking port) to move to * * timed - If true, have the shuttle follow normal spool-up, jump, dock process. If false, immediately move to the new location. */ -/datum/controller/subsystem/shuttle/proc/moveShuttle(shuttle_id, dock_id, timed) +/datum/controller/subsystem/shuttle/proc/moveShuttle(shuttle_id, dock_id, timed, skill_multiplier = 1) var/obj/docking_port/mobile/shuttle_port = getShuttle(shuttle_id) var/obj/docking_port/stationary/docking_target = getDock(dock_id) if(!shuttle_port) return DOCKING_NULL_SOURCE if(timed) - if(shuttle_port.request(docking_target)) + if(shuttle_port.request(docking_target, skill_multiplier)) return DOCKING_IMMOBILIZED else if(shuttle_port.initiate_docking(docking_target) != DOCKING_SUCCESS) diff --git a/code/datums/components/blocking.dm b/code/datums/components/blocking.dm index 57ca6e47f5acd..22edf1baca0e5 100644 --- a/code/datums/components/blocking.dm +++ b/code/datums/components/blocking.dm @@ -220,6 +220,8 @@ if(!blocking_component.can_block(defender, incoming, damage, attack_type)) return 0 force_returned = blocking_component.block_force + if(attack_type & (MELEE_ATTACK|UNARMED_ATTACK|THROWN_PROJECTILE_ATTACK|LEAP_ATTACK)) // being stronger provides a small increase to melee blocking + force_returned += defender.get_skill(SKILL_FITNESS) if(HAS_TRAIT(weapon, TRAIT_PARRYING)) force_returned *= PARRY_BONUS return max(force_returned - max(armour_penetration - weapon.armour_penetration, 0) * AP_TO_FORCE, 0) diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm index 16f401c761b29..ccf26a221f204 100644 --- a/code/datums/components/bloodysoles.dm +++ b/code/datums/components/bloodysoles.dm @@ -1,4 +1,5 @@ - +#define FOOTPRINT_INDEX_FILE 1 +#define FOOTPRINT_INDEX_STATE 2 //Component for clothing items that can pick up blood from decals and spread it around everywhere when walking, such as shoes or suits with integrated shoes. @@ -254,20 +255,19 @@ Like its parent but can be applied to carbon mobs instead of clothing items /datum/component/bloodysoles/feet/add_parent_to_footprint(obj/effect/decal/cleanable/blood/footprints/FP) if(ismonkey(wielder)) - FP.species_types |= "monkey" + FP.species_types["monkey"] = list(null, FALSE) return if(!ishuman(wielder)) - FP.species_types |= "unknown" + FP.species_types["unknown"] = list(null, FALSE) return - // Find any leg of our human and add that to the footprint, instead of the default which is to just add the human type - for(var/X in wielder.bodyparts) - var/obj/item/bodypart/affecting = X - if(affecting.body_part == LEG_RIGHT || affecting.body_part == LEG_LEFT) - if(!affecting.bodypart_disabled) - FP.species_types |= affecting.limb_id - break + // Our limbs code is horribly out of date and won't work the normal way, so we do it like this + for(var/obj/item/bodypart/affecting as anything in wielder.bodyparts) + if((affecting.body_part & (LEG_LEFT|LEG_RIGHT)) && !affecting.bodypart_disabled) + var/image/limb_icon = affecting.get_limb_icon(FALSE)[1] + FP.species_types[affecting.species_id] = list(limb_icon.icon, limb_icon.icon_state) + break /datum/component/bloodysoles/feet/is_obscured() diff --git a/code/datums/components/crafting/antag.dm b/code/datums/components/crafting/antag.dm index 40f3cefa6fa28..89871775d6250 100644 --- a/code/datums/components/crafting/antag.dm +++ b/code/datums/components/crafting/antag.dm @@ -46,7 +46,11 @@ tool_behaviors = list(TOOL_WELDER, TOOL_WRENCH, TOOL_WIRECUTTER) time = 1.5 SECONDS category = CAT_WEAPON_RANGED - always_available = FALSE // This was such a bad idea. + skill_requirements = list( + SKILL_MECHANICAL = EXP_MID, + SKILL_TECHNICAL = EXP_HIGH, + SKILL_SCIENCE = EXP_LOW, + ) // this is such a good idea /datum/crafting_recipe/flamethrower name = "Flamethrower" @@ -57,6 +61,10 @@ parts = list(/obj/item/assembly/igniter = 1, /obj/item/weldingtool = 1) tool_behaviors = list(TOOL_SCREWDRIVER) + skill_requirements = list( + SKILL_MECHANICAL = EXP_LOW, + SKILL_SCIENCE = EXP_LOW, + ) time = 1 SECONDS category = CAT_WEAPON_RANGED diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index 9c62ed2055a07..249768b361c0d 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -148,6 +148,17 @@ return TRUE +/datum/component/personal_crafting/proc/check_skills(atom/source, datum/crafting_recipe/recipe) + if(!recipe.skill_requirements?.len) + return TRUE + if(!ismob(source)) + return TRUE + var/mob/user = source + for(var/skill in recipe.skill_requirements) + if(!user.skill_check(skill, recipe.skill_requirements[skill])) + return FALSE + return TRUE + /datum/component/personal_crafting/proc/construct_item(atom/a, datum/crafting_recipe/R) var/list/contents = get_surroundings(a, R.blacklist) var/send_feedback = 1 @@ -155,12 +166,14 @@ return ", missing component." if(!check_tools(a, R, contents)) return ", missing tool." + if(!check_skills(a, R)) + return ", inadequate skills." var/timer = R.time + var/mob/user_mob if(ismob(a)) - var/mob/mob = a - if(mob && HAS_TRAIT(mob, TRAIT_CRAFTY)) - timer *= 0.75 - if(!do_after(a, timer, a)) + user_mob = a + timer *= (10 - (user_mob.get_skill(SKILL_MECHANICAL) + HAS_TRAIT(user_mob, TRAIT_CRAFTY)*2)) / 10 + if(!do_after(a, timer, a, IGNORE_SKILL_DELAY, skill_check = SKILL_MECHANICAL)) return "." contents = get_surroundings(a, R.blacklist) // Double checking since items could no longer be there after the do_after(). if(!check_contents(a, R, contents)) @@ -170,6 +183,9 @@ var/list/parts = del_reqs(R, a) var/atom/movable/I = new R.result(get_turf(a.loc)) I.CheckParts(parts, R) + if(user_mob && R.skill_requirements.len) + for(var/skill in R.skill_requirements) + user_mob.add_exp(skill, R.skill_requirements[skill] * 10) if(send_feedback) SSblackbox.record_feedback("tally", "object_crafted", 1, I.type) return I @@ -332,7 +348,7 @@ for(var/datum/crafting_recipe/recipe as anything in (mode ? GLOB.cooking_recipes : GLOB.crafting_recipes)) if(!is_recipe_available(recipe, user)) continue - if(check_contents(user, recipe, surroundings) && check_tools(user, recipe, surroundings)) + if(check_contents(user, recipe, surroundings) && check_tools(user, recipe, surroundings) && check_skills(user, recipe)) craftability["[REF(recipe)]"] = TRUE if(display_craftable_only) // for debugging only craftability["[REF(recipe)]"] = TRUE @@ -477,6 +493,15 @@ var/id = atoms.Find(req_atom) data["reqs"]["[id]"] = recipe.reqs[req_atom] + // Skills + if(recipe.skill_requirements?.len) + data["skill_requirements"] = list() + for(var/skill in recipe.skill_requirements) + data["skill_requirements"] += list(list( + "skill" = skill, + "level" = recipe.skill_requirements[skill], + )) + return data #undef COOKING diff --git a/code/datums/components/crafting/recipes.dm b/code/datums/components/crafting/recipes.dm index e88d4fe605f9e..15009ff4dc176 100644 --- a/code/datums/components/crafting/recipes.dm +++ b/code/datums/components/crafting/recipes.dm @@ -27,6 +27,8 @@ var/always_available = TRUE /// Should only one object exist on the same turf? var/one_per_turf = FALSE + /// What skill levels are required to craft this? ex. list(SKILL_MECHANICAL = EXP_HIGH, SKILL_SCIENCE = EXP_LOW) + var/list/skill_requirements = list() /datum/crafting_recipe/New() if(!(result in reqs)) diff --git a/code/datums/components/crafting/weapons.dm b/code/datums/components/crafting/weapons.dm index d0425d58b419a..4c3ca104aeb5e 100644 --- a/code/datums/components/crafting/weapons.dm +++ b/code/datums/components/crafting/weapons.dm @@ -6,6 +6,7 @@ reqs = list(/obj/item/gun = 1) parts = list(/obj/item/gun = 1) tool_behaviors = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WIRECUTTER) + skill_requirements = list(SKILL_MECHANICAL = EXP_LOW) time = 5 SECONDS category = CAT_MISC @@ -17,6 +18,10 @@ /obj/item/assembly/igniter = 1, /obj/item/reagent_containers/food/drinks/soda_cans = 1) parts = list(/obj/item/reagent_containers/food/drinks/soda_cans = 1) + skill_requirements = list( + SKILL_TECHNICAL = EXP_LOW, + SKILL_SCIENCE = EXP_LOW, + ) time = 1.5 SECONDS category = CAT_WEAPON_RANGED @@ -27,6 +32,7 @@ /obj/item/assembly/flash/handheld = 1, /obj/item/shield/riot = 1) blacklist = list(/obj/item/shield/riot/buckler, /obj/item/shield/riot/tele) + skill_requirements = list(SKILL_TECHNICAL = EXP_LOW) time = 4 SECONDS category = CAT_WEAPON_MELEE @@ -45,6 +51,7 @@ reqs = list(/obj/item/restraints/handcuffs/cable = 1, /obj/item/stack/rods = 1, /obj/item/assembly/igniter = 1) + skill_requirements = list(SKILL_TECHNICAL = EXP_LOW) time = 4 SECONDS category = CAT_WEAPON_MELEE @@ -55,6 +62,10 @@ /obj/item/stack/rods = 1, /obj/item/assembly/igniter = 1, /obj/item/stack/ore/bluespace_crystal = 1) + skill_requirements = list( + SKILL_TECHNICAL = EXP_MID, + SKILL_SCIENCE = EXP_LOW, + ) time = 4 SECONDS category = CAT_WEAPON_MELEE @@ -131,6 +142,7 @@ reqs = list(/obj/item/pipe = 5, /obj/item/stack/sheet/plastic = 5, /obj/item/weaponcrafting/silkstring = 1) + skill_requirements = list(SKILL_MECHANICAL = EXP_LOW) time = 9 SECONDS category = CAT_WEAPON_RANGED @@ -140,6 +152,7 @@ reqs = list(/obj/item/pipe = 5, /obj/item/stack/tape = 3, /obj/item/stack/cable_coil = 10) + skill_requirements = list(SKILL_MECHANICAL = EXP_LOW) time = 10 SECONDS category = CAT_WEAPON_RANGED @@ -149,6 +162,7 @@ reqs = list(/obj/item/stack/sheet/mineral/wood = 8, /obj/item/stack/sheet/metal = 2, /obj/item/weaponcrafting/silkstring = 1) + skill_requirements = list(SKILL_MECHANICAL = EXP_LOW) time = 7 SECONDS category = CAT_WEAPON_RANGED @@ -161,6 +175,7 @@ /obj/item/weaponcrafting/receiver = 1, /obj/item/weaponcrafting/stock = 1) tool_behaviors = list(TOOL_SCREWDRIVER) + skill_requirements = list(SKILL_MECHANICAL = EXP_LOW) time = 10 SECONDS category = CAT_WEAPON_RANGED @@ -172,6 +187,7 @@ /obj/item/weaponcrafting/stock = 1, /obj/item/stack/packageWrap = 5) tool_behaviors = list(TOOL_SCREWDRIVER) + skill_requirements = list(SKILL_MECHANICAL = EXP_MID) time = 10 SECONDS category = CAT_WEAPON_RANGED @@ -184,6 +200,11 @@ /obj/item/stack/rods = 4, /obj/item/stack/cable_coil = 10) tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WELDER, TOOL_WRENCH) + skill_requirements = list( + SKILL_MECHANICAL = EXP_LOW, + SKILL_TECHNICAL = EXP_LOW, + SKILL_SCIENCE = EXP_LOW, + ) result = /obj/item/gun/ballistic/gauss time = 12 category = CAT_WEAPON_RANGED @@ -195,6 +216,7 @@ /obj/item/weaponcrafting/stock = 1, /obj/item/stack/packageWrap = 5) tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WELDER, TOOL_WRENCH) + skill_requirements = list(SKILL_MECHANICAL = EXP_LOW) result = /obj/item/gun/ballistic/maint_musket time = 10 SECONDS category = CAT_WEAPON_RANGED @@ -206,6 +228,7 @@ /obj/item/stack/sheet/plasteel = 3, /obj/item/stack/sheet/metal = 1) tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WELDER) + skill_requirements = list(SKILL_MECHANICAL = EXP_LOW) result = /obj/item/melee/sledgehammer time = 8 SECONDS category = CAT_WEAPON_MELEE @@ -217,6 +240,7 @@ /obj/item/stack/cable_coil = 3, /obj/item/stack/sheet/plasteel = 5) tool_behaviors = list(TOOL_WELDER) + skill_requirements = list(SKILL_MECHANICAL = EXP_MID) time = 5 SECONDS category = CAT_WEAPON_MELEE @@ -245,6 +269,10 @@ /obj/item/grenade/chem_grenade = 2 ) parts = list(/obj/item/stock_parts/matter_bin = 1, /obj/item/grenade/chem_grenade = 2) + skill_requirements = list( + SKILL_TECHNICAL = EXP_MID, + SKILL_SCIENCE = EXP_MID, + ) time = 3 SECONDS category = CAT_MISC @@ -257,6 +285,10 @@ /obj/item/grenade/chem_grenade = 2 ) parts = list(/obj/item/stock_parts/matter_bin = 1, /obj/item/grenade/chem_grenade = 2) + skill_requirements = list( + SKILL_TECHNICAL = EXP_MID, + SKILL_SCIENCE = EXP_MID, + ) time = 5 SECONDS category = CAT_MISC @@ -333,6 +365,7 @@ reqs = list(/obj/item/stack/sheet/metal = 4, /obj/item/stack/packageWrap = 8, /obj/item/pipe = 2) + skill_requirements = list(SKILL_MECHANICAL = EXP_LOW) time = 5 SECONDS category = CAT_WEAPON_RANGED diff --git a/code/datums/components/mech_pilot.dm b/code/datums/components/mech_pilot.dm deleted file mode 100644 index a28b024db458d..0000000000000 --- a/code/datums/components/mech_pilot.dm +++ /dev/null @@ -1,7 +0,0 @@ -/// A component for clothes that affect one's ability to pilot mechs -/datum/component/mech_pilot - /// Modifier of mech delay, based on percentage 1 = 100%. lower is faster - var/piloting_speed = 1 - -/datum/component/mech_pilot/Initialize(_piloting_speed = 1) - piloting_speed = _piloting_speed diff --git a/code/datums/diseases/advance/symptoms/necropolis.dm b/code/datums/diseases/advance/symptoms/necropolis.dm index 7d6319b02df01..32d789f655788 100644 --- a/code/datums/diseases/advance/symptoms/necropolis.dm +++ b/code/datums/diseases/advance/symptoms/necropolis.dm @@ -57,7 +57,7 @@ fullpower = TRUE H.physiology.punchdamagehigh_bonus += 4 H.physiology.punchdamagelow_bonus += 4 - H.physiology.punchstunthreshold_bonus += 1 //Makes standard punches 5-14 with higher stun chance (1-10, stun on 10 -> 5-14, stun on 11-14) + H.physiology.punchstunchance_bonus += 0.4 //Makes standard punches 5-14 with higher stun chance (1-10, stun on 10 -> 5-14, stun on 11-14) H.physiology.brute_mod *= 0.6 H.physiology.burn_mod *= 0.6 H.physiology.heat_mod *= 0.6 @@ -103,7 +103,7 @@ H.remove_movespeed_modifier(MOVESPEED_ID_NECRO_VIRUS_SLOWDOWN) H.physiology.punchdamagehigh_bonus -= 4 H.physiology.punchdamagelow_bonus -= 4 - H.physiology.punchstunthreshold_bonus -= 1 + H.physiology.punchstunchance_bonus -= 0.4 H.physiology.brute_mod /= 0.6 H.physiology.burn_mod /= 0.6 H.physiology.heat_mod /= 0.6 diff --git a/code/datums/martial.dm b/code/datums/martial.dm index 8f20d7be0caed..3c6dfa4854a20 100644 --- a/code/datums/martial.dm +++ b/code/datums/martial.dm @@ -101,8 +101,8 @@ * used for basic punch attacks */ /datum/martial_art/proc/basic_hit(mob/living/carbon/human/A,mob/living/carbon/human/D) - - var/damage = rand(A.get_punchdamagelow(), A.get_punchdamagehigh()) + var/percentile = rand() + var/damage = LERP(A.get_punchdamagelow(), A.get_punchdamagehigh(), percentile) var/atk_verb = pick(A.dna.species.attack_verbs) var/atk_effect = A.dna.species.attack_effect @@ -129,7 +129,7 @@ log_combat(A, D, "punched") - if((D.stat != DEAD) && damage >= A.get_punchstunthreshold()) + if((D.stat != DEAD) && percentile > (1 - A.get_punchstunchance()) && !HAS_TRAIT(A, TRAIT_NO_PUNCH_STUN)) D.visible_message(span_danger("[A] has knocked [D] down!!"), \ span_userdanger("[A] has knocked [D] down!")) D.apply_effect(40, EFFECT_KNOCKDOWN, armor_block) diff --git a/code/datums/martial/lightning_flow.dm b/code/datums/martial/lightning_flow.dm index e2b7772915e6c..f73e43104f20f 100644 --- a/code/datums/martial/lightning_flow.dm +++ b/code/datums/martial/lightning_flow.dm @@ -8,7 +8,7 @@ id = MARTIALART_LIGHTNINGFLOW no_guns = TRUE help_verb = /mob/living/carbon/human/proc/lightning_flow_help - martial_traits = list(TRAIT_STRONG_GRABBER) + martial_traits = list(TRAIT_STRONG_GRABBER, TRAIT_NO_PUNCH_STUN) var/dashing = FALSE COOLDOWN_DECLARE(action_cooldown) var/list/action_modifiers = list() @@ -147,7 +147,6 @@ var/mob/living/carbon/human/user = H user.physiology.punchdamagelow_bonus += 5 user.physiology.punchdamagehigh_bonus += 5 - user.physiology.punchstunthreshold_bonus += 6 //no knockdowns /datum/martial_art/lightning_flow/on_remove(mob/living/carbon/human/H) UnregisterSignal(H, COMSIG_MOB_CLICKON) @@ -155,7 +154,6 @@ var/mob/living/carbon/human/user = H user.physiology.punchdamagelow_bonus -= 5 user.physiology.punchdamagehigh_bonus -= 5 - user.physiology.punchstunthreshold_bonus -= 6 return ..() #undef ACTION_DELAY diff --git a/code/datums/martial/ultra_violence.dm b/code/datums/martial/ultra_violence.dm index b216f9ca19ce1..f3a578ba23a4b 100644 --- a/code/datums/martial/ultra_violence.dm +++ b/code/datums/martial/ultra_violence.dm @@ -16,7 +16,7 @@ help_verb = /mob/living/carbon/human/proc/ultra_violence_help gun_exceptions = list(/obj/item/gun/ballistic/revolver/ipcmartial) no_gun_message = "This gun is not compliant with Ultra Violence standards." - martial_traits = list(TRAIT_NOSOFTCRIT, TRAIT_IGNOREDAMAGESLOWDOWN, TRAIT_NOLIMBDISABLE, TRAIT_NO_STUN_WEAPONS, TRAIT_NO_BLOCKING, TRAIT_NODISMEMBER, TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE, TRAIT_NO_HOLDUP) + martial_traits = list(TRAIT_NOSOFTCRIT, TRAIT_IGNOREDAMAGESLOWDOWN, TRAIT_NOLIMBDISABLE, TRAIT_NO_STUN_WEAPONS, TRAIT_NO_PUNCH_STUN, TRAIT_NO_BLOCKING, TRAIT_NODISMEMBER, TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE, TRAIT_NO_HOLDUP) ///used to keep track of the dash stuff var/dashing = FALSE var/dashes = 3 @@ -429,7 +429,6 @@ H.dna.species.attack_sound = 'sound/weapons/shotgunshot.ogg' H.dna.species.punchdamagelow += 4 H.dna.species.punchdamagehigh += 4 //no fancy comboes, just punches - H.dna.species.punchstunthreshold += 50 //disables punch stuns H.dna.species.staminamod = 0 //my god, why must you make me add all these additional things, stop trying to disable them, just kill them RegisterSignal(H, COMSIG_MOB_CLICKON, PROC_REF(on_click)) // death to click_intercept H.throw_alert("dash_charge", /atom/movable/screen/alert/ipcmartial, dashes+1) @@ -440,7 +439,6 @@ H.dna.species.attack_sound = initial(H.dna.species.attack_sound) //back to flimsy tin tray punches H.dna.species.punchdamagelow -= 4 H.dna.species.punchdamagehigh -= 4 - H.dna.species.punchstunthreshold -= 50 H.dna.species.staminamod = initial(H.dna.species.staminamod) UnregisterSignal(H, COMSIG_MOB_CLICKON) deltimer(dash_timer) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index c3424530b638d..9f64e3f353609 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -50,6 +50,27 @@ var/list/restricted_roles = list() var/list/datum/objective/objectives = list() + /// The owner of this mind's ability to perform certain kinds of tasks. + var/list/skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + + /// Progress towards increasing their skill level + var/list/exp_progress = list( + SKILL_PHYSIOLOGY = 0, + SKILL_MECHANICAL = 0, + SKILL_TECHNICAL = 0, + SKILL_SCIENCE = 0, + SKILL_FITNESS = 0, + ) + + /// Free skill points to allocate + var/skill_points = 0 + var/linglink var/datum/martial_art/martial_art var/static/default_martial_art = new/datum/martial_art @@ -268,6 +289,9 @@ if (!istype(traitor_mob)) return + traitor_mob.add_skill_points(EXP_LOW) // one extra skill point + ADD_TRAIT(src, TRAIT_EXCEPTIONAL_SKILL, type) + var/list/all_contents = traitor_mob.get_all_contents() var/obj/item/modular_computer/PDA = locate() in all_contents var/obj/item/radio/R = locate() in all_contents @@ -762,6 +786,51 @@ mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist) mind.active = TRUE //indicates that the mind is currently synced with a client +/// Returns the mob's skill level +/mob/proc/get_skill(skill) + if(!mind) + return EXP_NONE + return mind.skills[skill] + +/// Adjusts the mob's skill level +/mob/proc/adjust_skill(skill, amount=0, min_skill = EXP_NONE, max_skill = EXP_MASTER) + if(!mind) + return + mind.skills[skill] = clamp(mind.skills[skill] + amount, min_skill, max_skill) + +/// Checks if the mob's skill level meets a given threshold. +/mob/proc/skill_check(skill, amount) + if(!mind) + return FALSE + return (mind.skills[skill] >= amount) + +/// Adds progress towards increasing skill level. Returns TRUE if it improved the skill. +/mob/proc/add_exp(skill, amount) + if(!mind) + return FALSE + if(mind.skill_points > 0) + return FALSE + var/exp_required = EXPERIENCE_PER_LEVEL * (2**mind.skills[skill]) // exp required scales exponentially + if(mind.exp_progress[skill] + amount >= exp_required) + var/levels_gained = round(log(2, 1 + (mind.exp_progress[skill] + amount) / exp_required)) // in case you gained so much you go up more than one level + var/levels_allocated = hud_used?.skill_menu ? hud_used.skill_menu.allocated_skills[skill] : 0 + if(levels_allocated > 0) // adjust any already allocated skills to prevent shenanigans (you know who you are) + hud_used.skill_menu.allocated_points -= min(levels_gained, levels_allocated) + hud_used.skill_menu.allocated_skills[skill] -= min(levels_gained, levels_allocated) + mind.exp_progress[skill] += amount - exp_required * (2**(levels_gained - 1)) + adjust_skill(skill, levels_gained) + to_chat(src, span_boldnotice("Your [skill] skill is now level [get_skill(skill)]!")) + return TRUE + mind.exp_progress[skill] += amount + return FALSE + +/// Adds skill points to be allocated at will. +/mob/proc/add_skill_points(amount) + if(!mind) + return + mind.skill_points += amount + throw_alert("skill points", /atom/movable/screen/alert/skill_up) + /datum/mind/proc/has_martialart(string) if(martial_art && martial_art.id == string) return martial_art diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index 20800284facbe..daa7d35910358 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -281,7 +281,6 @@ var/strength_punchpower = GET_MUTATION_POWER(src) * 2 - 1 //Normally +1, strength chromosome increases it to +2 owner.physiology.punchdamagehigh_bonus += strength_punchpower owner.physiology.punchdamagelow_bonus += strength_punchpower - owner.physiology.punchstunthreshold_bonus += strength_punchpower //So we dont change the stun chance ADD_TRAIT(owner, TRAIT_QUICKER_CARRY, src) /datum/mutation/human/strong/on_losing(mob/living/carbon/human/owner) @@ -290,7 +289,6 @@ var/strength_punchpower = GET_MUTATION_POWER(src) * 2 - 1 owner.physiology.punchdamagehigh_bonus -= strength_punchpower owner.physiology.punchdamagelow_bonus -= strength_punchpower - owner.physiology.punchstunthreshold_bonus -= strength_punchpower REMOVE_TRAIT(owner, TRAIT_QUICKER_CARRY, src) //Yogs end diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm index bc9df3f6f5d5a..cf79c5518759e 100644 --- a/code/datums/progressbar.dm +++ b/code/datums/progressbar.dm @@ -1,9 +1,13 @@ #define PROGRESSBAR_HEIGHT 6 #define PROGRESSBAR_ANIMATION_TIME 5 +#define SKILL_ICON_OFFSET_X -18 +#define SKILL_ICON_OFFSET_Y -13 /datum/progressbar ///The progress bar visual element. var/image/bar + ///The icon for the skill used. + var/image/skill_icon ///The target where this progress bar is applied and where it is shown. var/atom/bar_loc ///The mob whose client sees the progress bar. @@ -22,7 +26,7 @@ var/progress_ended = FALSE -/datum/progressbar/New(mob/User, goal_number, atom/target, timed_action_flags = NONE) +/datum/progressbar/New(mob/User, goal_number, atom/target, timed_action_flags = NONE, datum/callback/extra_checks, skill_check) . = ..() if (!istype(target)) stack_trace("Invalid target [target] passed in") @@ -41,7 +45,12 @@ bar = image('icons/effects/progessbar.dmi', bar_loc, "prog_bar_0") SET_PLANE_EXPLICIT(bar, ABOVE_HUD_PLANE, User) //yogs change, increased so it draws ontop of ventcrawling overlays bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + if(skill_check) + skill_icon = image('icons/mob/skills.dmi', bar_loc, "[skill_check]_small", pixel_x = SKILL_ICON_OFFSET_X) + SET_PLANE_EXPLICIT(skill_icon, ABOVE_HUD_PLANE, User) + skill_icon.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA user = User + src.extra_checks = extra_checks LAZYADDASSOCLIST(user.progressbars, bar_loc, src) var/list/bars = user.progressbars[bar_loc] @@ -81,10 +90,12 @@ continue progress_bar.listindex-- - progress_bar.bar.pixel_y = 32 + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1)) - var/dist_to_travel = 32 + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1)) - PROGRESSBAR_HEIGHT + var/dist_to_travel = 32 + (PROGRESSBAR_HEIGHT * progress_bar.listindex) - PROGRESSBAR_HEIGHT animate(progress_bar.bar, pixel_y = dist_to_travel, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING) + if(progress_bar.skill_icon) + animate(progress_bar.skill_icon, pixel_y = dist_to_travel + SKILL_ICON_OFFSET_Y, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING) + LAZYREMOVEASSOC(user.progressbars, bar_loc, src) user = null @@ -95,6 +106,8 @@ if(bar) QDEL_NULL(bar) + if(skill_icon) + QDEL_NULL(skill_icon) return ..() @@ -115,6 +128,7 @@ if(!user_client) //Disconnected, already gone. return user_client.images -= bar + user_client.images -= skill_icon user_client = null @@ -137,6 +151,11 @@ bar.pixel_y = 0 bar.alpha = 0 user_client.images += bar + if(skill_icon) + skill_icon.pixel_y = SKILL_ICON_OFFSET_Y + skill_icon.alpha = 0 + user_client.images += skill_icon + animate(skill_icon, pixel_y = 32 + SKILL_ICON_OFFSET_Y + (PROGRESSBAR_HEIGHT * (listindex - 1)), alpha = 255, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING) animate(bar, pixel_y = 32 + (PROGRESSBAR_HEIGHT * (listindex - 1)), alpha = 255, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING) @@ -164,6 +183,8 @@ bar.icon_state = "[bar.icon_state]_fail" animate(bar, alpha = 0, time = PROGRESSBAR_ANIMATION_TIME) + if(skill_icon) + animate(skill_icon, alpha = 0, time = PROGRESSBAR_ANIMATION_TIME) QDEL_IN(src, PROGRESSBAR_ANIMATION_TIME) @@ -175,5 +196,7 @@ return INVOKE_ASYNC(src, PROC_REF(end_progress)) +#undef SKILL_ICON_OFFSET_Y +#undef SKILL_ICON_OFFSET_X #undef PROGRESSBAR_ANIMATION_TIME #undef PROGRESSBAR_HEIGHT diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm index 0b6bcc355f446..852fd7d92ca37 100644 --- a/code/datums/wires/_wires.dm +++ b/code/datums/wires/_wires.dm @@ -4,7 +4,7 @@ if(!I) return - if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL) + if(I.tool_behaviour == TOOL_WIRECUTTER || I.tool_behaviour == TOOL_MULTITOOL || I.tool_behaviour == TOOL_WIRING) return TRUE if(istype(I, /obj/item/assembly)) var/obj/item/assembly/A = I @@ -37,6 +37,28 @@ var/list/colors = list() /// List of attached assemblies. var/list/assemblies = list() + /// Skill required to identify each wire, EXP_GENIUS if not specified here. + var/static/list/wire_difficulty = list( + WIRE_SHOCK = EXP_MID, + WIRE_RESET_MODULE = EXP_MID, + WIRE_ZAP = EXP_MID, + WIRE_ZAP1 = EXP_HIGH, + WIRE_ZAP2 = EXP_HIGH, + WIRE_LOCKDOWN = EXP_HIGH, + WIRE_CAMERA = EXP_HIGH, + WIRE_POWER = EXP_HIGH, + WIRE_POWER1 = EXP_MASTER, + WIRE_POWER2 = EXP_MASTER, + WIRE_IDSCAN = EXP_MASTER, + WIRE_UNBOLT = EXP_MASTER, + WIRE_BACKUP1 = EXP_MASTER, + WIRE_BACKUP2 = EXP_MASTER, + WIRE_LAWSYNC = EXP_MASTER, + WIRE_PANIC = EXP_MASTER, + WIRE_OPEN = EXP_MASTER, + WIRE_HACK = EXP_MASTER, + WIRE_AI = EXP_MASTER, + ) /// If every instance of these wires should be random. Prevents wires from showing up in station blueprints. var/randomize = FALSE @@ -139,6 +161,29 @@ /datum/wires/proc/is_dud_color(color) return is_dud(get_wire(color)) +/datum/wires/proc/is_revealed(color, mob/user) + // Admin ghost can see a purpose of each wire. + if(IsAdminGhost(user)) + return TRUE + + // Same for anyone with an abductor multitool. + else if(user.is_holding_item_of_type(/obj/item/multitool/abductor)) + return TRUE + + // Station blueprints do that too, but only if the wires are not randomized. + else if(!randomize) + if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints)) + return TRUE + else if(user.is_holding_item_of_type(/obj/item/photo)) + var/obj/item/photo/P = user.is_holding_item_of_type(/obj/item/photo) + if(P.picture.has_blueprints) //if the blueprints are in frame + return TRUE + + var/skill_required = wire_difficulty[get_wire(color)] + if(skill_required && user.skill_check(SKILL_TECHNICAL, skill_required)) + return TRUE + return FALSE + /datum/wires/proc/cut(wire) if(is_cut(wire)) cut_wires -= wire @@ -240,30 +285,12 @@ /datum/wires/ui_data(mob/user) var/list/data = list() var/list/payload = list() - var/reveal_wires = FALSE - - // Admin ghost can see a purpose of each wire. - if(IsAdminGhost(user)) - reveal_wires = TRUE - - // Same for anyone with an abductor multitool. - else if(user.is_holding_item_of_type(/obj/item/multitool/abductor)) - reveal_wires = TRUE - - // Station blueprints do that too, but only if the wires are not randomized. - else if(!randomize) - if(user.is_holding_item_of_type(/obj/item/areaeditor/blueprints)) - reveal_wires = TRUE - else if(user.is_holding_item_of_type(/obj/item/photo)) - var/obj/item/photo/P = user.is_holding_item_of_type(/obj/item/photo) - if(P.picture.has_blueprints) //if the blueprints are in frame - reveal_wires = TRUE var/colorblind = HAS_TRAIT(user, TRAIT_COLORBLIND) for(var/color in colors) payload.Add(list(list( "color" = color, - "wire" = ((reveal_wires && !is_dud_color(color) && !colorblind) ? get_wire(color) : null), + "wire" = (!colorblind && !is_dud_color(color) && is_revealed(color, user)) ? get_wire(color) : null, "cut" = is_color_cut(color), "attached" = is_attached(color) ))) @@ -276,45 +303,48 @@ /datum/wires/ui_act(action, params) if(..() || !interactable(usr)) return + if(!holder) // wires with no holder makes no sense to exist and probably breaks things, so catch any instances of that + CRASH("[type] has no holder!") var/target_wire = params["wire"] - var/mob/living/L = usr - var/obj/item/I + var/mob/living/user = usr + var/obj/item/tool + var/tool_delay = max((0.5**user.get_skill(SKILL_TECHNICAL)) SECONDS, 0) + if(tool_delay < 0.2 SECONDS) // effectively already instant + tool_delay = 0 switch(action) if("cut") - I = L.is_holding_tool_quality(TOOL_WIRECUTTER) - if(I || IsAdminGhost(usr)) - if(I && holder) - I.play_tool_sound(holder, 20) + tool = user.is_holding_tool_quality(TOOL_WIRECUTTER) + if(tool?.use_tool(holder, user, tool_delay) || IsAdminGhost(usr)) + tool.play_tool_sound(holder, 20) cut_color(target_wire) . = TRUE - else - to_chat(L, span_warning("You need wirecutters!")) + else if(!tool) + to_chat(user, span_warning("You need wirecutters!")) if("pulse") - I = L.is_holding_tool_quality(TOOL_MULTITOOL) - if(I || IsAdminGhost(usr)) - if(I && holder) - I.play_tool_sound(holder, 20) - pulse_color(target_wire, L) + tool = user.is_holding_tool_quality(TOOL_MULTITOOL) + if(tool?.use_tool(holder, user, tool_delay) || IsAdminGhost(usr)) + tool.play_tool_sound(holder, 20) + pulse_color(target_wire, user) . = TRUE - else - to_chat(L, span_warning("You need a multitool!")) + else if(!tool) + to_chat(user, span_warning("You need a multitool!")) if("attach") if(is_attached(target_wire)) - I = detach_assembly(target_wire) - if(I) - L.put_in_hands(I) - . = TRUE + if(!do_after(user, tool_delay, holder)) + return + user.put_in_hands(detach_assembly(target_wire)) + . = TRUE else - I = L.get_active_held_item() - if(istype(I, /obj/item/assembly)) - var/obj/item/assembly/A = I + tool = user.get_active_held_item() + if(istype(tool, /obj/item/assembly) && tool?.use_tool(holder, user, tool_delay)) + var/obj/item/assembly/A = tool if(A.attachable) - if(!L.temporarilyRemoveItemFromInventory(A)) + if(!user.temporarilyRemoveItemFromInventory(A)) return if(!attach_assembly(target_wire, A)) - A.forceMove(L.drop_location()) + A.forceMove(user.drop_location()) . = TRUE else - to_chat(L, span_warning("You need an attachable assembly!")) + to_chat(user, span_warning("You need an attachable assembly!")) #undef MAXIMUM_EMP_WIRES diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 4069a9a9519ff..0a4c6a2abb31e 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -239,10 +239,10 @@ var/time = base_treat_time playsound(victim, 'sound/surgery/bone1.ogg', 25) - if(!do_after(user, time, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, time, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return - if(prob(65)) + if(prob(60 + user.get_skill(SKILL_PHYSIOLOGY) * 5)) user.visible_message(span_danger("[user] snaps [victim]'s dislocated [limb.name] back into place!"), span_notice("You snap [victim]'s dislocated [limb.name] back into place!"), ignored_mobs=victim) to_chat(victim, span_userdanger("[user] snaps your dislocated [limb.name] back into place!")) victim.emote("scream") @@ -260,10 +260,10 @@ var/time = base_treat_time playsound(victim, 'sound/surgery/bone1.ogg', 25) - if(!do_after(user, time, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, time, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return - if(prob(65)) + if(prob(60 + user.get_skill(SKILL_PHYSIOLOGY))) user.visible_message(span_danger("[user] snaps [victim]'s dislocated [limb.name] with a sickening crack!"), span_danger("You snap [victim]'s dislocated [limb.name] with a sickening crack!"), ignored_mobs=victim) to_chat(victim, span_userdanger("[user] snaps your dislocated [limb.name] with a sickening crack!")) victim.emote("scream") @@ -283,7 +283,7 @@ playsound(victim, 'sound/surgery/bone1.ogg', 25) - if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return playsound(victim, 'sound/surgery/bone3.ogg', 25) @@ -362,7 +362,7 @@ user.visible_message(span_danger("[user] begins hastily applying [I] to [victim]'s' [limb.name]..."), span_warning("You begin hastily applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.name], disregarding the warning label...")) - if(!do_after(user, base_treat_time * 1.5 * (user == victim ? 1.5 : 1), victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, base_treat_time * 1.5 * (user == victim ? 1.5 : 1), victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return I.use(1) @@ -403,7 +403,7 @@ user.visible_message(span_danger("[user] begins applying [I] to [victim]'s' [limb.name]..."), span_warning("You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.name]...")) - if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), victim, extra_checks=CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return if(victim == user) diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm index 48767f764ac85..c95d5f5989cc7 100644 --- a/code/datums/wounds/burns.dm +++ b/code/datums/wounds/burns.dm @@ -207,7 +207,7 @@ /datum/wound/burn/proc/ointment(obj/item/stack/medical/ointment/I, mob/user) user.visible_message(span_notice("[user] begins applying [I] to [victim]'s [limb.name]..."), span_notice("You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.name]...")) playsound(I, pick(I.apply_sounds), 25) - if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return limb.heal_damage(I.heal_brute, I.heal_burn) @@ -228,7 +228,7 @@ return user.visible_message(span_notice("[user] begins wrapping [victim]'s [limb.name] with [I]..."), span_notice("You begin wrapping [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")) playsound(I, pick(I.apply_sounds), 25) - if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return limb.heal_damage(I.heal_brute, I.heal_burn) diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm index 3d6099d25e911..224357ec3d762 100644 --- a/code/datums/wounds/pierce.dm +++ b/code/datums/wounds/pierce.dm @@ -114,7 +114,7 @@ user.visible_message(span_notice("[user] begins stitching [victim]'s [limb.name] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")) playsound(I, pick(I.apply_sounds), 25) - if(!do_after(user, base_treat_time * self_penalty_mult * I.treatment_speed, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, base_treat_time * self_penalty_mult * I.treatment_speed, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return user.visible_message(span_green("[user] stitches up some of the bleeding on [victim]."), span_green("You stitch up some of the bleeding on [user == victim ? "yourself" : "[victim]"].")) @@ -140,7 +140,7 @@ user.visible_message(span_danger("[user] begins cauterizing [victim]'s [limb.name] with [I]..."), span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")) playsound(I, 'sound/surgery/cautery1.ogg', 75, TRUE, falloff_exponent = 1) - if(!do_after(user, base_treat_time * self_penalty_mult * improv_penalty_mult * I.toolspeed, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, base_treat_time * self_penalty_mult * improv_penalty_mult * I.toolspeed, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return playsound(I, 'sound/surgery/cautery2.ogg', 75, TRUE, falloff_exponent = 1) diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm index 88e15955d55a3..9d75413268af8 100644 --- a/code/datums/wounds/slash.dm +++ b/code/datums/wounds/slash.dm @@ -171,7 +171,7 @@ user.visible_message(span_warning("[user] begins aiming [lasgun] directly at [victim]'s [limb.name]..."), span_userdanger("You begin aiming [lasgun] directly at [user == victim ? "your" : "[victim]'s"] [limb.name]...")) playsound(lasgun, 'sound/surgery/cautery1.ogg', 75, TRUE, falloff_exponent = 1) - if(!do_after(user, base_treat_time * self_penalty_mult, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, base_treat_time * self_penalty_mult, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return playsound(lasgun, 'sound/surgery/cautery2.ogg', 75, TRUE, falloff_exponent = 1) @@ -195,7 +195,7 @@ user.visible_message(span_danger("[user] begins cauterizing [victim]'s [limb.name] with [I]..."), span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")) playsound(I, 'sound/surgery/cautery1.ogg', 75, TRUE, falloff_exponent = 1) - if(!do_after(user, base_treat_time * self_penalty_mult * improv_penalty_mult * I.toolspeed, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, base_treat_time * self_penalty_mult * improv_penalty_mult * I.toolspeed, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return playsound(I, 'sound/surgery/cautery2.ogg', 75, TRUE, falloff_exponent = 1) @@ -219,7 +219,7 @@ user.visible_message(span_notice("[user] begins stitching [victim]'s [limb.name] with [I]..."), span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")) playsound(I, pick(I.apply_sounds), 25) - if(!do_after(user, base_treat_time * self_penalty_mult * I.treatment_speed, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)))) + if(!do_after(user, base_treat_time * self_penalty_mult * I.treatment_speed, victim, extra_checks = CALLBACK(src, PROC_REF(still_exists)), skill_check = SKILL_PHYSIOLOGY)) return user.visible_message(span_green("[user] stitches up some of the bleeding on [victim]."), span_green("You stitch up some of the bleeding on [user == victim ? "yourself" : "[victim]"].")) diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm index 6886963b44f9f..70f12ac671197 100644 --- a/code/game/machinery/computer/dna_console.dm +++ b/code/game/machinery/computer/dna_console.dm @@ -357,8 +357,9 @@ . = TRUE - add_fingerprint(usr) - usr.set_machine(src) + var/mob/user = usr + add_fingerprint(user) + user.set_machine(src) switch(action) // Connect this DNA Console to a nearby DNA Scanner @@ -373,7 +374,7 @@ if(!scanner_operational()) return - connected_scanner.toggle_open(usr) + connected_scanner.toggle_open(user) return // Toggle the door bolts on the attached DNA Scanner @@ -395,7 +396,7 @@ scanner_occupant.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA)) scanner_occupant.dna.generate_dna_blocks() - scrambleready = world.time + SCRAMBLE_TIMEOUT + scrambleready = world.time + SCRAMBLE_TIMEOUT * (10 - user.get_skill(SKILL_SCIENCE)) / 10 to_chat(usr,span_notice("DNA scrambled.")) scanner_occupant.radiation += RADIATION_STRENGTH_MULTIPLIER*50/(connected_scanner.damage_coeff ** 2) return @@ -422,7 +423,7 @@ if(!(scanner_occupant == connected_scanner.occupant)) return - check_discovery(params["alias"]) + check_discovery(params["alias"], usr) return // Check all mutations of the occupant and check if any are discovered. @@ -444,7 +445,7 @@ // Go over all standard mutations and check if they've been discovered. for(var/mutation_type in scanner_occupant.dna.mutation_index) var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(mutation_type) - check_discovery(HM.alias) + check_discovery(HM.alias, usr) return @@ -494,7 +495,7 @@ if((newgene == "J") && (jokerready < world.time)) var/truegenes = GET_SEQUENCE(path) newgene = truegenes[genepos] - jokerready = world.time + JOKER_TIMEOUT - (JOKER_UPGRADE * (connected_scanner.precision_coeff-1)) + jokerready = world.time + (JOKER_TIMEOUT - (JOKER_UPGRADE * (connected_scanner.precision_coeff - 1))) * (8 - user.get_skill(SKILL_SCIENCE)) / 8 // If the gene is an X, we want to update the default genes with the new // X to allow highlighting logic to work on the tgui interface. @@ -520,7 +521,7 @@ return // Check if we cracked a mutation - check_discovery(alias) + check_discovery(alias, usr) return @@ -624,9 +625,9 @@ // to improve our injector's radiation generation if(scanner_operational()) I.damage_coeff = connected_scanner.damage_coeff*4 - injectorready = world.time + INJECTOR_TIMEOUT * (1 - 0.1 * connected_scanner.precision_coeff) + injectorready = world.time + INJECTOR_TIMEOUT * (1 - (connected_scanner.precision_coeff + user.get_skill(SKILL_SCIENCE)) / 10) else - injectorready = world.time + INJECTOR_TIMEOUT + injectorready = world.time + INJECTOR_TIMEOUT * (1 - user.get_skill(SKILL_SCIENCE) / 10) else I.name = "[HM.name] mutator" I.doitanyway = TRUE @@ -634,9 +635,9 @@ // to improve our injector's radiation generation if(scanner_operational()) I.damage_coeff = connected_scanner.damage_coeff - injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - 0.1 * connected_scanner.precision_coeff) + injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - (connected_scanner.precision_coeff + user.get_skill(SKILL_SCIENCE)) / 10) else - injectorready = world.time + INJECTOR_TIMEOUT * 5 + injectorready = world.time + INJECTOR_TIMEOUT * 5 * (1 - user.get_skill(SKILL_SCIENCE) / 10) return @@ -1159,7 +1160,7 @@ // If we successfully created an injector, don't forget to set the new // ready timer. if(I) - injectorready = world.time + INJECTOR_TIMEOUT + injectorready = world.time + INJECTOR_TIMEOUT * (1 - user.get_skill(SKILL_SCIENCE) / 10) return @@ -1256,7 +1257,7 @@ len = length(scanner_occupant.dna.unique_identity) if("uf") len = length(scanner_occupant.dna.unique_features) - rad_pulse_timer = world.time + (radduration*10) + rad_pulse_timer = world.time + (radduration * (10 - user.get_skill(SKILL_SCIENCE))) rad_pulse_index = WRAP(text2num(params["index"]), 1, len+1) begin_processing() return @@ -1346,9 +1347,9 @@ // to improve our injector's radiation generation if(scanner_operational()) I.damage_coeff = connected_scanner.damage_coeff - injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - 0.1 * connected_scanner.precision_coeff) + injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - (connected_scanner.precision_coeff + user.get_skill(SKILL_SCIENCE)) / 10) else - injectorready = world.time + INJECTOR_TIMEOUT * 8 + injectorready = world.time + INJECTOR_TIMEOUT * 8 * (1 - user.get_skill(SKILL_SCIENCE) / 10) return @@ -1907,7 +1908,7 @@ * Arguments: * * alias - Alias of the mutation to check (ie "Mutation 51" or "Mutation 12") */ -/obj/machinery/computer/scan_consolenew/proc/check_discovery(alias) +/obj/machinery/computer/scan_consolenew/proc/check_discovery(alias, mob/user) // Note - All code paths that call this have already done checks on the // current occupant to prevent cheese and other abuses. If you call this // proc please also do the following checks first: @@ -1932,6 +1933,7 @@ var/datum/mutation/human/HM = GET_INITIALIZED_MUTATION(path) stored_research.discovered_mutations += path say("Successfully discovered [HM.name].") + user.add_exp(SKILL_SCIENCE, 100) return TRUE return FALSE diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm index c095f79caf720..bd3904f9be840 100644 --- a/code/game/mecha/equipment/mecha_equipment.dm +++ b/code/game/mecha/equipment/mecha_equipment.dm @@ -246,8 +246,8 @@ return 0 // Is the occupant wearing a pilot suit? -/obj/item/mecha_parts/mecha_equipment/proc/check_eva() - return chassis?.check_eva() +/obj/item/mecha_parts/mecha_equipment/proc/check_eva(mob/pilot) + return chassis?.check_eva(pilot) // Some equipment can be used as tools /obj/item/mecha_parts/mecha_equipment/tool_use_check(mob/living/user, amount) diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index d412c0c1adfe9..26cefb1e00bf2 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -374,7 +374,7 @@ . += span_danger("The capacitor did well in preventing too much damage. Repair will be manageable.") if(4) . += span_danger("The capacitor did such a good job in preserving the chassis that you could almost call it functional. But it isn't. Repair should be easy though.") - if(repair_hint && capacitor?.rating) + if(repair_hint && (capacitor?.rating || user.skill_check(SKILL_TECHNICAL, EXP_GENIUS) || user.skill_check(SKILL_MECHANICAL, EXP_GENIUS))) . += repair_hint //Armor tag @@ -1038,7 +1038,7 @@ visible_message("[user] starts to climb into [name].") - if(do_after(user, enter_delay, src)) + if(do_after(user, round(enter_delay * (check_eva(user)**2)), src, IGNORE_SKILL_DELAY, skill_check = SKILL_TECHNICAL)) if(atom_integrity <= 0) to_chat(user, span_warning("You cannot get in the [name], it has been destroyed!")) else if(occupant) @@ -1157,7 +1157,7 @@ /obj/mecha/container_resist(mob/living/user) is_currently_ejecting = TRUE to_chat(occupant, "You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting.") - if(do_after(occupant, exit_delay, src)) + if(do_after(occupant, round(exit_delay * (check_eva(user)**2)), src, IGNORE_SKILL_DELAY, skill_check = SKILL_TECHNICAL)) to_chat(occupant, "You exit the mech.") go_out() else @@ -1372,18 +1372,11 @@ GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? if(QDELETED(I)) return -// Checks the pilot and their clothing for mech speed buffs -/obj/mecha/proc/check_eva() - var/evaNum = 1 - if(ishuman(occupant)) - var/mob/living/carbon/human/H = occupant //if the person is skilled - var/datum/component/mech_pilot/skill = H.GetComponent(/datum/component/mech_pilot) - if(skill) - evaNum *= skill.piloting_speed - - var/obj/item/clothing/under/clothes = H.get_item_by_slot(ITEM_SLOT_ICLOTHING) //if the jumpsuit directly assists the pilot - if(clothes) - var/datum/component/mech_pilot/MP = clothes.GetComponent(/datum/component/mech_pilot) - if(MP) - evaNum *= MP.piloting_speed - return evaNum +// Check the pilot for mech piloting skill +/obj/mecha/proc/check_eva(mob/pilot) + if(!pilot) + pilot = occupant + var/effective_skill = pilot.get_skill(SKILL_TECHNICAL) + if(effective_skill < EXP_MASTER && HAS_TRAIT(pilot, TRAIT_SKILLED_PILOT)) + effective_skill += EXP_LOW // mech pilot suit adds extra pilot skill, up to EXP_MASTER + return (12 - effective_skill) / 10 diff --git a/code/game/mecha/mecha_defense.dm b/code/game/mecha/mecha_defense.dm index 903dd10207cb5..5d1ea7934579c 100644 --- a/code/game/mecha/mecha_defense.dm +++ b/code/game/mecha/mecha_defense.dm @@ -214,7 +214,7 @@ if(wrecked) try_repair(tool, user) else if(atom_integrity < max_integrity) - while(atom_integrity < max_integrity && tool.use_tool(src, user, 1 SECONDS, volume=50, amount=1)) + while(atom_integrity < max_integrity && tool.use_tool(src, user, 1 SECONDS, volume=50, amount=1, skill_check = SKILL_MECHANICAL)) if(internal_damage & MECHA_INT_TANK_BREACH) clearInternalDamage(MECHA_INT_TANK_BREACH) to_chat(user, span_notice("You repair the damaged gas tank.")) @@ -336,9 +336,11 @@ return ..() /obj/mecha/proc/try_repair(obj/item/I, mob/living/user) - if(!capacitor?.rating) + if(!(capacitor || user.skill_check(SKILL_TECHNICAL, EXP_GENIUS) || user.skill_check(SKILL_MECHANICAL, EXP_GENIUS))) // with enough technical wizardry, you can repair ANY mech to_chat(user, span_warning("[src] is damaged beyond repair, there is nothing you can do.")) return + + var/effective_skill = user.get_skill(SKILL_MECHANICAL) + capacitor?.rating switch(repair_state) if(MECHA_WRECK_CUT) @@ -347,7 +349,7 @@ span_notice("[user] begins to weld together \the [src]'s broken parts..."), span_notice("You begin welding together \the [src]'s broken parts..."), ) - if(I.use_tool(src, user, 20 SECONDS / capacitor.rating, amount = 5, volume = 100, robo_check = TRUE)) + if(I.use_tool(src, user, 20 SECONDS / effective_skill, amount = 5, volume = 100, skill_check = SKILL_MECHANICAL)) repair_state = MECHA_WRECK_DENTED repair_hint = span_notice("The chassis has suffered major damage and will require the dents to be smoothed out with a welder.") to_chat(user, span_notice("The parts are loosely reattached, but are dented wildly out of place.")) @@ -358,7 +360,7 @@ span_notice("[user] welds out the many, many dents in \the [src]'s chassis..."), span_notice("You weld out the many, many dents in \the [src]'s chassis..."), ) - if(I.use_tool(src, user, 20 SECONDS / capacitor.rating, amount = 5, volume = 100, robo_check = TRUE)) + if(I.use_tool(src, user, 20 SECONDS / effective_skill, amount = 5, volume = 100, skill_check = SKILL_MECHANICAL)) repair_state = MECHA_WRECK_LOOSE repair_hint = span_notice("The mecha wouldn't make it two steps before falling apart. The bolts must be tightened with a wrench.") to_chat(user, span_notice("The chassis has been repaired, but the bolts are incredibly loose and need to be tightened.")) @@ -369,7 +371,7 @@ span_notice("[user] slowly tightens the bolts of \the [src]..."), span_notice("You slowly tighten the bolts of \the [src]..."), ) - if(I.use_tool(src, user, 18 SECONDS / capacitor.rating, volume = 50, robo_check = TRUE)) + if(I.use_tool(src, user, 18 SECONDS / effective_skill, volume = 50, skill_check = SKILL_MECHANICAL)) repair_state = MECHA_WRECK_UNWIRED repair_hint = span_notice("The mech is nearly ready, but the wiring has been fried and needs repair.") to_chat(user, span_notice("The bolts are tightened and the mecha is looking as good as new, but the wiring was fried in the destruction and needs repair.")) @@ -380,13 +382,14 @@ span_notice("[user] starts repairing the wiring on \the [src]..."), span_notice("You start repairing the wiring on \the [src]..."), ) - if(I.use_tool(src, user, 12 SECONDS / capacitor.rating, amount = 5, volume = 50, robo_check = TRUE)) + if(I.use_tool(src, user, 12 SECONDS / effective_skill, amount = 5, volume = 50, skill_check = SKILL_MECHANICAL)) repair_state = MECHA_WRECK_MISSING_CAPACITOR - repair_hint = span_notice("The wiring is functional, but it's still missing a capacitor.") + repair_hint = span_notice("The wiring is functional, but its capacitor needs to be replaced.") if(MECHA_WRECK_MISSING_CAPACITOR) if(istype(I, /obj/item/stock_parts/capacitor)) - QDEL_NULL(capacitor) + if(capacitor) + QDEL_NULL(capacitor) capacitor = I I.forceMove(src) user.visible_message(span_notice("[user] replaces the capacitor of \the [src].")) diff --git a/code/game/mecha/mecha_wreckage.dm b/code/game/mecha/mecha_wreckage.dm index a0ebc576b7d98..0d6dbe1b9d79f 100644 --- a/code/game/mecha/mecha_wreckage.dm +++ b/code/game/mecha/mecha_wreckage.dm @@ -30,7 +30,12 @@ /obj/structure/mecha_wreckage/examine(mob/user) . = ..() - . += span_danger("There was no capacitor to save this poor mecha from its doomed fate! It cannot be repaired!") + var/damage_msg = "There was no capacitor to save this poor mecha from its doomed fate" + if(user.skill_check(SKILL_MECHANICAL, EXP_GENIUS) || user.skill_check(SKILL_MECHANICAL, EXP_GENIUS)) + damage_msg = ", but you think you could get it working again..." + else + damage_msg = "! It cannot be repaired!" + . += span_warning(damage_msg) /obj/structure/mecha_wreckage/gygax name = "\improper Gygax wreckage" diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index 70a402fd0664c..b54109424fb8e 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -284,7 +284,7 @@ /obj/effect/decal/cleanable/blood/footprints/examine(mob/user) . = ..() - if((shoe_types.len + species_types.len) > 0) + if((species_types.len ||shoe_types.len) && user.skill_check(SKILL_PHYSIOLOGY, EXP_MID)) . += "You recognise the [name] as belonging to:" for(var/sole in shoe_types) var/obj/item/clothing/item = sole @@ -292,14 +292,14 @@ . += "[icon2html(initial(item.icon), user, initial(item.icon_state))] [article] [initial(item.name)]." for(var/species in species_types) // god help me - if(species == "unknown") + if(!species || species == "unknown") . += "Some feet." else if(species == "monkey") . += "[icon2html('icons/mob/monkey.dmi', user, "monkey1")] Some monkey paws." else if(species == SPECIES_HUMAN) . += "[icon2html('icons/mob/human_parts.dmi', user, "default_human_l_leg")] Some human feet." else - . += "[icon2html('icons/mob/human_parts.dmi', user, "[species]_l_leg")] Some [species] feet." + . += "[icon2html(species_types[species][FOOTPRINT_INDEX_FILE], user, species_types[species][FOOTPRINT_INDEX_STATE])] Some [species] feet." /obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/blood/blood_decal) if(blood_state != blood_decal.blood_state || footprint_sprite != blood_decal.footprint_sprite) //We only replace footprints of the same type as us diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index f8e31fc336437..9c92e5e599382 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -957,24 +957,29 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) // Called when a mob tries to use the item as a tool. // Handles most checks. -/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks, robo_check) +/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks, skill_check = null) // No delay means there is no start message, and no reason to call tool_start_check before use_tool. // Run the start check here so we wouldn't have to call it manually. if(!delay && !tool_start_check(user, amount)) return delay *= toolspeed - if(((IS_ENGINEERING(user) || (robo_check && IS_JOB(user, "Roboticist"))) && (tool_behaviour in MECHANICAL_TOOLS)) || (IS_MEDICAL(user) && (tool_behaviour in MEDICAL_TOOLS))) - delay *= 0.8 // engineers and doctors use their own tools faster - if(volume) // Play tool sound at the beginning of tool usage. play_tool_sound(target, volume) if(delay) // Create a callback with checks that would be called every tick by do_after. var/datum/callback/tool_check = CALLBACK(src, PROC_REF(tool_check_callback), user, amount, extra_checks) - - if(!do_after(user, delay, target, extra_checks=tool_check)) + if(!skill_check) + if(is_wire_tool(src)) + skill_check = SKILL_TECHNICAL + else if(tool_behaviour in MECHANICAL_TOOLS) + skill_check = SKILL_MECHANICAL + else if(tool_behaviour in MEDICAL_TOOLS) + skill_check = SKILL_PHYSIOLOGY + else + skill_check = SKILL_FITNESS // hatchets and pickaxes + if(!do_after(user, delay, target, extra_checks=tool_check, skill_check=skill_check)) return else // Invoke the extra checks once, just in case. @@ -1177,7 +1182,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) #define ROBO_LIMB_HEAL_OTHER 1 SECONDS /obj/item/proc/heal_robo_limb(obj/item/I, mob/living/carbon/human/H, mob/user, brute_heal = 0, burn_heal = 0, amount = 0, volume = 0) - if(I.use_tool(H, user, (H == user) ? ROBO_LIMB_HEAL_SELF : ROBO_LIMB_HEAL_OTHER, amount, volume, null, TRUE)) + if(I.use_tool(H, user, (H == user) ? ROBO_LIMB_HEAL_SELF : ROBO_LIMB_HEAL_OTHER, amount, volume, null, skill_check = SKILL_MECHANICAL)) if(item_heal_robotic(H, user, brute_heal, burn_heal)) return heal_robo_limb(I, H, user, brute_heal, burn_heal, amount, volume) return TRUE diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 3c393210f1543..3e2644baeeec2 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -603,7 +603,7 @@ GENE SCANNER /obj/item/analyzer/attack_self(mob/user) add_fingerprint(user) - scangasses(user) //yogs start: Makes the gas scanning able to be used elseware + atmosanalyzer_scan(user, user.loc) //yogs start: Makes the gas scanning able to be used elseware /obj/item/analyzer/afterattack(atom/target as obj, mob/user, proximity) if(!proximity) @@ -773,10 +773,10 @@ GENE SCANNER else combined_msg += span_notice("[target] is empty!") - if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected - var/instability = round(cached_scan_results["fusion"], 0.01) + if(cached_scan_results && cached_scan_results["fusion"] && user.skill_check(SKILL_SCIENCE, EXP_LOW)) //notify the user if a fusion reaction was detected combined_msg += span_boldnotice("Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.") - combined_msg += span_notice("Instability of the last fusion reaction: [instability].") + if(user.skill_check(SKILL_SCIENCE, EXP_MID)) + combined_msg += span_notice("Instability of the last fusion reaction: [round(cached_scan_results["fusion"], 0.01)].") to_chat(user, examine_block(combined_msg.Join("\n"))) return TRUE diff --git a/code/game/objects/items/granters/mech_piloting.dm b/code/game/objects/items/granters/mech_piloting.dm index 9792539ef6fc1..33c82f565fed0 100644 --- a/code/game/objects/items/granters/mech_piloting.dm +++ b/code/game/objects/items/granters/mech_piloting.dm @@ -14,4 +14,4 @@ /obj/item/book/granter/mechpiloting/on_reading_finished(mob/user) . = ..() - user.AddComponent(/datum/component/mech_pilot, 0.8) + user.adjust_skill(SKILL_TECHNICAL, EXP_MID, max_skill = EXP_GENIUS) diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 2aea613e5d822..c028d5b2bcc61 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -25,6 +25,12 @@ if(pinnedLoc) pinnedLoc.forceMove(loc) +/obj/item/target/bullet_act(obj/projectile/P) + . = ..() + if(iscarbon(P.firer)) // gain a bit of experience from shooting targets, based on distance + var/mob/shooter = P.firer + shooter.add_exp(SKILL_FITNESS, max(initial(P.range) - P.range, 1) * 2) + /obj/item/target/welder_act(mob/living/user, obj/item/I) if(I.use_tool(src, user, 0, volume=40)) removeOverlays() diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm index 741da1e7256ba..e262a1d8af55d 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -52,13 +52,13 @@ playsound(src, pick(apply_sounds), 25) if(!silent) user.visible_message(span_notice("[user] starts to apply \the [src] on [user.p_them()]self..."), span_notice("You begin applying \the [src] on yourself...")) - if(!do_after(user, self_delay, M, extra_checks=CALLBACK(M, TYPE_PROC_REF(/mob/living, can_inject), user, TRUE))) + if(!do_after(user, self_delay, M, extra_checks=CALLBACK(M, TYPE_PROC_REF(/mob/living, can_inject), user, TRUE), skill_check = SKILL_PHYSIOLOGY)) return else if(other_delay) playsound(src, pick(apply_sounds), 25) if(!silent) user.visible_message(span_notice("[user] starts to apply \the [src] on [M]."), span_notice("You begin applying \the [src] on [M]...")) - if(!do_after(user, other_delay, M, extra_checks=CALLBACK(M, TYPE_PROC_REF(/mob/living, can_inject), user, TRUE))) + if(!do_after(user, other_delay, M, extra_checks=CALLBACK(M, TYPE_PROC_REF(/mob/living, can_inject), user, TRUE), skill_check = SKILL_PHYSIOLOGY)) return if(heal(M, user)) @@ -180,7 +180,7 @@ /// Use other_delay if healing someone else (usually 1 second) /// Use self_delay if healing yourself (usually 3 seconds) /// Reduce delay by 20% if medical - if(!do_after(user, (user == M ? self_delay : other_delay) * (IS_MEDICAL(user) ? 0.8 : 1), M)) + if(!do_after(user, (user == M ? self_delay : other_delay) * (IS_MEDICAL(user) ? 0.8 : 1), M, skill_check = SKILL_PHYSIOLOGY)) return playsound(src, 'sound/effects/rip1.ogg', 25) diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm index 5f202eb382222..fe17d95cb4521 100644 --- a/code/game/objects/items/tools/weldingtool.dm +++ b/code/game/objects/items/tools/weldingtool.dm @@ -166,7 +166,7 @@ update_appearance(UPDATE_ICON) -/obj/item/weldingtool/use_tool(atom/target, mob/living/user, delay, amount, volume, datum/callback/extra_checks, robo_check) +/obj/item/weldingtool/use_tool(atom/target, mob/living/user, delay, amount, volume, datum/callback/extra_checks, skill_check) target.add_overlay(sparks) . = ..() target.cut_overlay(sparks) diff --git a/code/game/objects/structures/ghost_role_spawners.dm b/code/game/objects/structures/ghost_role_spawners.dm index 6594f93cda2a9..1e75d3558990b 100644 --- a/code/game/objects/structures/ghost_role_spawners.dm +++ b/code/game/objects/structures/ghost_role_spawners.dm @@ -48,6 +48,15 @@ flavour_text = "The wastes are sacred ground, its monsters a blessed bounty. \ You have seen lights in the distance... they foreshadow the arrival of outsiders that seek to tear apart the Necropolis and its domain. Fresh sacrifices for your nest." assignedrole = "Ash Walker" + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = EXP_HIGH + exceptional_skill = TRUE var/datum/team/ashwalkers/team /obj/effect/mob_spawn/human/ash_walker/special(mob/living/new_spawn) @@ -73,6 +82,14 @@ flavour_text = "The wastes are sacred ground, its monsters a blessed bounty. You and your people have become one with the tendril and its land. \ You have seen lights in the distance and from the skies: outsiders that come with greed in their hearts. Fresh sacrifices for your nest." assignedrole = "Ash Walker Shaman" + base_skills = list( + SKILL_PHYSIOLOGY = EXP_HIGH, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = EXP_HIGH /datum/outfit/ashwalker name = "Ashwalker" @@ -174,6 +191,14 @@ var/has_owner = FALSE var/can_transfer = TRUE //if golems can switch bodies to this new shell var/mob/living/owner = null //golem's owner if it has one + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = EXP_GENIUS short_desc = "You are a Free Golem. Your family worships The Liberator." flavour_text = "In his infinite and divine wisdom, he set your clan free to \ travel the stars with a single declaration: \"Yeah go do whatever.\" Though you are bound to the one who created you, it is customary in your society to repeat those same words to newborn \ @@ -282,6 +307,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) icon = 'icons/obj/lavaland/spawners.dmi' icon_state = "cryostasis_sleeper" outfit = /datum/outfit/hermit + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = EXP_MASTER roundstart = FALSE death = FALSE random = TRUE @@ -301,22 +334,26 @@ GLOBAL_LIST_EMPTY(servant_golem_users) only one pod left when you got to the escape bay. You took it and launched it alone, and the crowd of terrified faces crowding at the airlock door as your pod's engines burst to \ life and sent you to this hell are forever branded into your memory." outfit.uniform = /obj/item/clothing/under/rank/civilian/assistantformal + base_skills[SKILL_MECHANICAL] = EXP_MID if(2) flavour_text += "you're an exile from the Tiger Cooperative. Their technological fanaticism drove you to question the power and beliefs of the Exolitics, and they saw you as a \ heretic and subjected you to hours of horrible torture. You were hours away from execution when a high-ranking friend of yours in the Cooperative managed to secure you a pod, \ scrambled its destination's coordinates, and launched it. You awoke from stasis when you landed and have been surviving - barely - ever since." outfit.uniform = /obj/item/clothing/under/rank/prisoner outfit.shoes = /obj/item/clothing/shoes/sneakers/orange + base_skills[SKILL_TECHNICAL] = EXP_MID if(3) flavour_text += "you were a doctor on one of Nanotrasen's space stations, but you left behind that damn corporation's tyranny and everything it stood for. From a metaphorical hell \ to a literal one, you find yourself nonetheless missing the recycled air and warm floors of what you left behind... but you'd still rather be here than there." outfit.uniform = /obj/item/clothing/under/rank/medical outfit.suit = /obj/item/clothing/suit/toggle/labcoat outfit.back = /obj/item/storage/backpack/medic + base_skills[SKILL_PHYSIOLOGY] = EXP_MID if(4) flavour_text += "you were always joked about by your friends for \"not playing with a full deck\", as they so kindly put it. It seems that they were right when you, on a tour \ at one of Nanotrasen's state-of-the-art research facilities, were in one of the escape pods alone and saw the red button. It was big and shiny, and it caught your eye. You pressed \ it, and after a terrifying and fast ride for days, you landed here. You've had time to wisen up since then, and you think that your old friends wouldn't be laughing now." + base_skills[SKILL_FITNESS] = EXP_MID /obj/effect/mob_spawn/human/hermit/Destroy() new/obj/structure/fluff/empty_cryostasis_sleeper(get_turf(src)) @@ -340,6 +377,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) everyone's gone. One of the cats scratched you just a few minutes ago. That's why you were in the pod - to heal the scratch. The scabs are still fresh; you see them right now. So where is \ everyone? Where did they go? What happened to the hospital? And is that smoke you smell? You need to find someone else. Maybe they can tell you what happened." assignedrole = "Translocated Vet" + base_skills = list( + SKILL_PHYSIOLOGY = EXP_MID, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = EXP_MASTER /obj/effect/mob_spawn/human/doctor/alive/lavaland/Destroy() var/obj/structure/fluff/empty_sleeper/S = new(drop_location()) @@ -531,6 +576,15 @@ GLOBAL_LIST_EMPTY(servant_golem_users) death = FALSE icon = 'icons/obj/machines/sleeper.dmi' icon_state = "sleeper_s" + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_MID, + ) + skill_points = EXP_GENIUS + exceptional_skill = TRUE outfit = /datum/outfit/syndicate_empty assignedrole = "Space Syndicate" //I know this is really dumb, but Syndicate operative is nuke ops @@ -585,6 +639,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) short_desc = "You are the captain aboard the syndicate flagship: the SBC Starfury." flavour_text = "Your job is to oversee your crew, defend the ship, and destroy Space Station 13. The ship has an armory, multiple ships, beam cannons, and multiple crewmembers to accomplish this goal." important_info = "As the captain, this whole operation falls on your shoulders. You do not need to nuke the station, causing sufficient damage and preventing your ship from being destroyed will be enough." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_LOW, + SKILL_MECHANICAL = EXP_LOW, + SKILL_TECHNICAL = EXP_LOW, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = EXP_MID outfit = /datum/outfit/syndicate_empty/SBC/assault/captain id_access_list = list(150,151) @@ -608,6 +670,7 @@ GLOBAL_LIST_EMPTY(servant_golem_users) important_info = "Do not abandon the base or give supplies to NT employees under any circumstances." outfit = /datum/outfit/syndicate_empty/icemoon_base assignedrole = "Icemoon Syndicate" + skill_points = EXP_GENIUS // 5 skill points /obj/effect/mob_spawn/human/syndicate/icemoon_syndicate/special(mob/living/new_spawn) //oops! new_spawn.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MIND) @@ -644,6 +707,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) short_desc = "You are a researcher at the Syndicate icemoon outpost." flavour_text = "Perform research for the sake of the Syndicate and advance technology. Do xenobiological or chemical research." important_info = "Do not abandon the base or give supplies to NT employees under any circumstances." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_LOW, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_HIGH, + SKILL_FITNESS = EXP_LOW, + ) + skill_points = EXP_HIGH outfit = /datum/outfit/syndicate_empty/icemoon_base/scientist /datum/outfit/syndicate_empty/icemoon_base/scientist @@ -659,6 +730,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) short_desc = "You are an engineer at the Syndicate icemoon outpost." flavour_text = "Maintain and upgrade the base's systems and equipment. Operate the nuclear reactor and absolutely do not let it melt down." important_info = "Do not abandon the base or give supplies to NT employees under any circumstances." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_MID, + SKILL_TECHNICAL = EXP_MID, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_LOW, + ) + skill_points = EXP_HIGH outfit = /datum/outfit/syndicate_empty/icemoon_base/engineer /datum/outfit/syndicate_empty/icemoon_base/engineer @@ -675,6 +754,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) short_desc = "You are a medical officer at the Syndicate icemoon outpost." flavour_text = "Provide medical aid to the crew of the outpost and keep them all alive." important_info = "Do not abandon the base or give supplies to NT employees under any circumstances." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_HIGH, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_LOW, + ) + skill_points = EXP_HIGH outfit = /datum/outfit/syndicate_empty/icemoon_base/medic /datum/outfit/syndicate_empty/icemoon_base/medic @@ -691,6 +778,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) short_desc = "You are the commander of the Syndicate icemoon outpost." flavour_text = "Direct the agents working under your command to operate the base, and keep it secure. If the situation gets dire, activate the emergency self-destruct located in the control room." important_info = "Do not abandon the base or give supplies to NT employees under any circumstances." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_LOW, + SKILL_MECHANICAL = EXP_LOW, + SKILL_TECHNICAL = EXP_LOW, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = EXP_MID outfit = /datum/outfit/syndicate_empty/icemoon_base/captain id_access_list = list(150,151) @@ -751,6 +846,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) The last thing you remember is the station's Artificial Program telling you that you would only be asleep for eight hours. As you open \ your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod." important_info = "Work as a team with your fellow survivors and do not abandon them." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = EXP_MID uniform = /obj/item/clothing/under/rank/security/officer shoes = /obj/item/clothing/shoes/jackboots id = /obj/item/card/id/away/old/sec @@ -770,6 +873,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) you remember is the station's Artificial Program telling you that you would only be asleep for eight hours. As you open \ your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod." important_info = "Work as a team with your fellow survivors and do not abandon them." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_MID, + SKILL_TECHNICAL = EXP_MID, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = EXP_MID uniform = /obj/item/clothing/under/rank/engineering/engineer shoes = /obj/item/clothing/shoes/workboots id = /obj/item/card/id/away/old/eng @@ -786,6 +897,14 @@ GLOBAL_LIST_EMPTY(servant_golem_users) The last thing you remember is the station's Artificial Program telling you that you would only be asleep for eight hours. As you open \ your eyes, everything seems rusted and broken, a dark feeling swells in your gut as you climb out of your pod." important_info = "Work as a team with your fellow survivors and do not abandon them." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_HIGH, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = EXP_HIGH uniform = /obj/item/clothing/under/rank/rnd/scientist shoes = /obj/item/clothing/shoes/laceup id = /obj/item/card/id/away/old/sci @@ -802,6 +921,15 @@ GLOBAL_LIST_EMPTY(servant_golem_users) mob_name = "a space pirate" mob_species = /datum/species/skeleton outfit = /datum/outfit/pirate/space + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_MID, + ) + skill_points = EXP_GENIUS + exceptional_skill = TRUE roundstart = FALSE death = FALSE anchored = TRUE @@ -827,6 +955,15 @@ GLOBAL_LIST_EMPTY(servant_golem_users) /obj/effect/mob_spawn/human/pirate/captain rank = "Captain" + base_skills = list( + SKILL_PHYSIOLOGY = EXP_LOW, + SKILL_MECHANICAL = EXP_LOW, + SKILL_TECHNICAL = EXP_LOW, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = EXP_MID + exceptional_skill = TRUE outfit = /datum/outfit/pirate/space/captain /obj/effect/mob_spawn/human/pirate/gunner @@ -872,6 +1009,15 @@ GLOBAL_LIST_EMPTY(servant_golem_users) important_info = "Do not abandon the derelict or mess with the main station under any circumstances." icon = 'icons/obj/machines/sleeper.dmi' icon_state = "sleeper_s" + base_skills = list( + SKILL_PHYSIOLOGY = EXP_LOW, + SKILL_MECHANICAL = EXP_MID, + SKILL_TECHNICAL = EXP_MID, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = EXP_HIGH + exceptional_skill = TRUE outfit = /datum/outfit/syndicate_derelict_engi random = TRUE roundstart = FALSE diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index 7ea784f76bdba..f943027b314c3 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -91,6 +91,9 @@ H.real_name = "[team.name] [sub_role]" H.equipOutfit(outfit) + H.adjust_skill(SKILL_PHYSIOLOGY, EXP_GENIUS, max_skill = EXP_GENIUS) + H.adjust_skill(SKILL_TECHNICAL, EXP_GENIUS, max_skill = EXP_GENIUS) + H.adjust_skill(SKILL_SCIENCE, EXP_GENIUS, max_skill = EXP_GENIUS) //Teleport to ship for(var/obj/effect/landmark/abductor/LM in GLOB.landmarks_list) diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm index c02c5c61dd9cf..68a2e413ee5be 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm @@ -225,7 +225,6 @@ additionalmessage = "You have mutated werewolf claws!" user.physiology.punchdamagehigh_bonus += 2.5 user.physiology.punchdamagelow_bonus += 2.5 - user.physiology.punchstunthreshold_bonus += 2.5 mutation = /obj/item/clothing/gloves/wolfclaws slot = ITEM_SLOT_GLOVES if(4) diff --git a/code/modules/antagonists/bloodsuckers/bloodsuckers.dm b/code/modules/antagonists/bloodsuckers/bloodsuckers.dm index bf531f1e65547..c7257b6d44eaf 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsuckers.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsuckers.dm @@ -252,10 +252,10 @@ all_powers.Grant(new_body) var/old_punchdamagelow var/old_punchdamagehigh - var/old_punchstunthreshold + var/old_punchstunchance var/old_species_punchdamagelow var/old_species_punchdamagehigh - var/old_species_punchstunthreshold + var/old_species_punchstunchance if(ishuman(old_body)) var/mob/living/carbon/human/old_user = old_body var/datum/species/old_species = old_user.dna.species @@ -263,15 +263,15 @@ //Keep track of what they were old_punchdamagelow = old_species.punchdamagelow old_punchdamagehigh = old_species.punchdamagehigh - old_punchstunthreshold = old_species.punchstunthreshold + old_punchstunchance = old_species.punchstunchance //Then reset them old_species.punchdamagelow = initial(old_species.punchdamagelow) old_species.punchdamagehigh = initial(old_species.punchdamagehigh) - old_species.punchstunthreshold = initial(old_species.punchstunthreshold) + old_species.punchstunchance = initial(old_species.punchstunchance) //Then save the new, old, original species values so we can use them in the next part. This is starting to get convoluted. old_species_punchdamagelow = old_species.punchdamagelow old_species_punchdamagehigh = old_species.punchdamagehigh - old_species_punchstunthreshold = old_species.punchstunthreshold + old_species_punchstunchance = old_species.punchstunchance if(ishuman(new_body)) var/mob/living/carbon/human/new_user = new_body var/datum/species/new_species = new_user.dna.species @@ -279,7 +279,7 @@ //Adjust new species punch damage new_species.punchdamagelow += (old_punchdamagelow - old_species_punchdamagelow) //Takes whatever DIFFERENCE you had between your punch damage and that of the baseline species new_species.punchdamagehigh += (old_punchdamagehigh - old_species_punchdamagehigh) //and adds it to your new species, thus preserving whatever bonuses you got - new_species.punchstunthreshold += (old_punchstunthreshold - old_species_punchstunthreshold) + new_species.punchstunchance += (old_punchstunchance - old_species_punchstunchance) //Give Bloodsucker Traits if(old_body) @@ -541,7 +541,7 @@ user.dna?.remove_all_mutations() user_species.punchdamagelow += 1 //lowest possible punch damage - 0 user_species.punchdamagehigh += 1 //highest possible punch damage - 9 - user_species.punchstunthreshold += 1 //To not change rng knockdowns + user_species.punchstunchance += 1 //To not change rng knockdowns /// Give Bloodsucker Traits owner.current.add_traits(bloodsucker_traits, BLOODSUCKER_TRAIT) /// No Skittish "People" allowed diff --git a/code/modules/antagonists/bloodsuckers/clans/_clan.dm b/code/modules/antagonists/bloodsuckers/clans/_clan.dm index b74fab47b9d42..0ffafd146f6f4 100644 --- a/code/modules/antagonists/bloodsuckers/clans/_clan.dm +++ b/code/modules/antagonists/bloodsuckers/clans/_clan.dm @@ -182,7 +182,6 @@ user_species.punchdamagelow += 0.5 // This affects the hitting power of Brawn. user_species.punchdamagehigh += 0.5 - user_species.punchstunthreshold += 0.5 // We're almost done - Spend your Rank now. bloodsuckerdatum.bloodsucker_level++ diff --git a/code/modules/antagonists/bloodsuckers/powers/fortitude.dm b/code/modules/antagonists/bloodsuckers/powers/fortitude.dm index a84f1f2815dfb..9aa2837e81f10 100644 --- a/code/modules/antagonists/bloodsuckers/powers/fortitude.dm +++ b/code/modules/antagonists/bloodsuckers/powers/fortitude.dm @@ -106,7 +106,6 @@ to_chat(user, span_notice("Shadow tentacles form and attach themselves to your body, you feel as if your muscles have merged with the shadows!")) user.physiology.punchdamagehigh_bonus += 0.5 * level_current user.physiology.punchdamagelow_bonus += 0.5 * level_current - user.physiology.punchstunthreshold_bonus += 0.5 * level_current //So we dont give them stun baton hands /datum/action/cooldown/bloodsucker/fortitude/shadow/process() . = ..() @@ -125,4 +124,3 @@ bloodsuckerdatum.frenzygrab.remove(user) user.physiology.punchdamagehigh_bonus -= 0.5 * level_current user.physiology.punchdamagelow_bonus -= 0.5 * level_current - user.physiology.punchstunthreshold_bonus -= 0.5 * level_current diff --git a/code/modules/antagonists/bloodsuckers/powers/gangrel.dm b/code/modules/antagonists/bloodsuckers/powers/gangrel.dm index d5de69d59b086..3b125a3f16dcf 100644 --- a/code/modules/antagonists/bloodsuckers/powers/gangrel.dm +++ b/code/modules/antagonists/bloodsuckers/powers/gangrel.dm @@ -78,7 +78,7 @@ playsound(user.loc, 'sound/creatures/gorilla.ogg', 50) user.dna.species.punchdamagelow += 10 user.dna.species.punchdamagehigh += 10 //very stronk - user.dna.species.punchstunthreshold += 10 + user.dna.species.punchstunchance += 10 user.dna.species.action_speed_coefficient *= 1.3 user.dna.species.armor += 15 bloodsuckerdatum.AddBloodVolume(50) diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 3937d24d5923b..793334d4b9cfd 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -34,6 +34,9 @@ W.on_reading_finished(brother) qdel(W) + brother.add_skill_points(EXP_HIGH) // extra skills + ADD_TRAIT(owner, TRAIT_EXCEPTIONAL_SKILL, type) + if(istype(brother)) var/obj/item/storage/box/bloodbrother/T = new() if(brother.equip_to_slot_or_del(T, ITEM_SLOT_BACKPACK)) diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm index 53778771773e0..ab5f8590eeeab 100644 --- a/code/modules/antagonists/ert/ert.dm +++ b/code/modules/antagonists/ert/ert.dm @@ -261,6 +261,8 @@ if(!istype(H)) return H.equipOutfit(outfit) + H.add_skill_points(EXP_GENIUS) // 5 skill points to allocate, you can put it all into fitness or specialize as a medic or pilot + ADD_TRAIT(owner, TRAIT_EXCEPTIONAL_SKILL, type) // allowed to allocate 5 points into a single skill /datum/antagonist/ert/greet() if(!ert_team) diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm index 50af87f221d6a..622b39105134b 100644 --- a/code/modules/antagonists/nukeop/nukeop.dm +++ b/code/modules/antagonists/nukeop/nukeop.dm @@ -36,6 +36,10 @@ H.set_species(/datum/species/human) //Plasamen burn up otherwise, and lizards are vulnerable to asimov AIs H.equipOutfit(nukeop_outfit) + + H.adjust_skill(SKILL_FITNESS, EXP_MID, max_skill = EXP_GENIUS) // base amount of fitness skill all operatives need to have + H.add_skill_points(EXP_GENIUS) // 5 skill points to allocate, you can put it all into fitness or specialize as a medic or pilot + ADD_TRAIT(owner, TRAIT_EXCEPTIONAL_SKILL, ROLE_OPERATIVE) // allowed to allocate 5 points into a single skill return TRUE /datum/antagonist/nukeop/greet() diff --git a/code/modules/asset_cache/assets/crafting.dm b/code/modules/asset_cache/assets/crafting.dm index d954f53d8d116..d8f73327183a7 100644 --- a/code/modules/asset_cache/assets/crafting.dm +++ b/code/modules/asset_cache/assets/crafting.dm @@ -7,6 +7,7 @@ for(var/atom in GLOB.crafting_recipes_atoms) add_atom_icon(atom, id++) add_tool_icons() + InsertAll("", 'icons/mob/skills.dmi', list(SOUTH)) /datum/asset/spritesheet/crafting/cooking name = "cooking" diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm index f53c70dbb386b..a6cc020152435 100644 --- a/code/modules/awaymissions/corpse.dm +++ b/code/modules/awaymissions/corpse.dm @@ -167,6 +167,19 @@ var/backpack_contents = -1 var/suit_store = -1 + /// The base skills of this ghost role. + var/list/base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + /// The skill points this ghost role can allocate. + var/skill_points = EXP_MASTER + /// Whether this ghost role can allocate up to 5 points into a single skill. + var/exceptional_skill = FALSE + var/hair_style var/facial_hair_style var/skin_tone @@ -200,6 +213,11 @@ H.skin_tone = skin_tone else H.skin_tone = random_skin_tone() + for(var/skill in base_skills) + H.adjust_skill(skill, base_skills[skill]) + H.add_skill_points(skill_points) + if(exceptional_skill && H.mind) + ADD_TRAIT(H.mind, TRAIT_EXCEPTIONAL_SKILL, assignedrole) H.update_hair() H.update_body() if(outfit) @@ -336,6 +354,14 @@ icon_state = "sleeper" short_desc = "You are a space doctor!" assignedrole = "Space Doctor" + base_skills = list( + SKILL_PHYSIOLOGY = EXP_MID, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = EXP_HIGH /obj/effect/mob_spawn/human/doctor/alive/equip(mob/living/carbon/human/H) ..() diff --git a/code/modules/clothing/under/costume.dm b/code/modules/clothing/under/costume.dm index 61fe38fcfae00..50d18b0c6de19 100644 --- a/code/modules/clothing/under/costume.dm +++ b/code/modules/clothing/under/costume.dm @@ -270,10 +270,7 @@ fitted = NO_FEMALE_UNIFORM alternate_worn_layer = GLOVES_LAYER //covers hands but gloves can go over it. This is how these things work in my head. can_adjust = FALSE - -/obj/item/clothing/under/costume/mech_suit/Initialize(mapload) - . = ..() - AddComponent(/datum/component/mech_pilot, 0.9) + clothing_traits = list(TRAIT_SKILLED_PILOT) /obj/item/clothing/under/costume/mech_suit/white name = "white mech pilot's suit" diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm index 1f6a890a010fa..e8bacbe9ea963 100644 --- a/code/modules/hydroponics/grown.dm +++ b/code/modules/hydroponics/grown.dm @@ -56,12 +56,14 @@ return 1 return 0 -/obj/item/reagent_containers/food/snacks/grown/examine(user) +/obj/item/reagent_containers/food/snacks/grown/examine(mob/user) . = ..() if(seed) for(var/datum/plant_gene/trait/T in seed.genes) if(T.examine_line) . += T.examine_line + if(user.skill_check(SKILL_SCIENCE, EXP_LOW)) // science skill lets you estimate a plant's stats by examining it + . += seed.get_analyzer_text(user, TRUE) /// Ghost attack proc /obj/item/reagent_containers/food/snacks/grown/attack_ghost(mob/user) @@ -84,26 +86,28 @@ /obj/item/reagent_containers/food/snacks/grown/attackby(obj/item/O, mob/user, params) ..() if (istype(O, /obj/item/plant_analyzer)) - playsound(src, 'sound/effects/fastbeep.ogg', 30) - var/msg = "This is \a [span_name("[src]")].\n" - if(seed) - msg += seed.get_analyzer_text() - var/reag_txt = "" - if(seed) - for(var/reagent_id in seed.reagents_add) - var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] - var/amt = reagents.get_reagent_amount(reagent_id) - reag_txt += "\n[span_info("- [R.name]: [amt]")]" - - if(reag_txt) - msg += reag_txt - msg += "
[span_info("")]" - to_chat(user, examine_block(msg)) + analyze_plant(user) else if(seed) for(var/datum/plant_gene/trait/T in seed.genes) T.on_attackby(src, O, user) +/obj/item/reagent_containers/food/snacks/grown/proc/analyze_plant(mob/user) + playsound(src, 'sound/effects/fastbeep.ogg', 30) + var/msg = "This is \a [span_name("[src]")].\n" + if(seed) + msg += seed.get_analyzer_text(user) + var/reag_txt = "" + if(seed) + for(var/reagent_id in seed.reagents_add) + var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] + var/amt = reagents.get_reagent_amount(reagent_id) + reag_txt += "\n[span_info("- [R.name]: [amt]")]" + + if(reag_txt) + msg += reag_txt + msg += "
[span_info("")]" + to_chat(user, examine_block(msg)) // Various gene procs /obj/item/reagent_containers/food/snacks/grown/attack_self(mob/user) diff --git a/code/modules/hydroponics/grown/kudzu.dm b/code/modules/hydroponics/grown/kudzu.dm index 1e888fe5a1bf5..74de8046bfed5 100644 --- a/code/modules/hydroponics/grown/kudzu.dm +++ b/code/modules/hydroponics/grown/kudzu.dm @@ -47,12 +47,13 @@ plant(user) to_chat(user, span_notice("You plant the kudzu. You monster.")) -/obj/item/seeds/kudzu/get_analyzer_text() +/obj/item/seeds/kudzu/get_analyzer_text(mob/user, check_skills = FALSE) var/text = ..() - var/text_string = "" - for(var/datum/spacevine_mutation/SM in mutations) - text_string += "[(text_string == "") ? "" : ", "][SM.name]" - text += "\n Plant Mutations: [(text_string == "") ? "None" : text_string]" + if(!check_skills || user.skill_check(SKILL_SCIENCE, EXP_HIGH)) + var/text_string = "" + for(var/datum/spacevine_mutation/SM in mutations) + text_string += "[(text_string == "") ? "" : ", "][SM.name]" + text += "\n Plant Mutations: [(text_string == "") ? "None" : text_string]" return text /obj/item/seeds/kudzu/on_chem_reaction(datum/reagents/S) diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm index 323dafd1e3a93..a8c69a303eb6b 100644 --- a/code/modules/hydroponics/growninedible.dm +++ b/code/modules/hydroponics/growninedible.dm @@ -32,6 +32,11 @@ w_class = round((seed.potency / 100) * 2, 1) + 1 //more potent plants are larger add_juice() +/obj/item/grown/examine(mob/user) + . = ..() + if(user.skill_check(SKILL_SCIENCE, EXP_LOW)) + . += seed.get_analyzer_text(user, TRUE) + /// Ghost attack proc /obj/item/grown/attack_ghost(mob/user) ..() diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 51a277060ef74..9ed9411eb882d 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -301,7 +301,7 @@ add_overlay(mutable_appearance('icons/obj/hydroponics/equipment.dmi', "over_harvest3")) -/obj/machinery/hydroponics/examine(user) +/obj/machinery/hydroponics/examine(mob/user) . = ..() if(myseed) . += span_info("It has [span_name("[myseed.plantname]")] planted.") @@ -311,6 +311,9 @@ . += span_info("It's ready to harvest.") else if (plant_health <= (myseed.endurance / 2)) . += span_warning("It looks unhealthy.") + + if(user.skill_check(SKILL_SCIENCE, EXP_LOW)) + . += myseed.get_analyzer_text(user, TRUE) else . += span_info("It's empty.") @@ -327,8 +330,6 @@ to_chat(user, span_warning("It's filled with weeds!")) if(pestlevel >= 5) to_chat(user, span_warning("It's filled with tiny worms!")) - to_chat(user, "" ) - /obj/machinery/hydroponics/proc/weedinvasion() // If a weed growth is sufficient, this happens. dead = 0 @@ -983,9 +984,9 @@ /obj/machinery/hydroponics/soil/update_icon_lights() return // Has no lights -/obj/machinery/hydroponics/soil/attackby(obj/item/O, mob/user, params) - if(O.tool_behaviour == TOOL_SHOVEL && !istype(O, /obj/item/shovel/spade)) //Doesn't include spades because of uprooting plants +/obj/machinery/hydroponics/soil/attackby_secondary(obj/item/weapon, mob/user, params) + if(weapon.tool_behaviour == TOOL_SHOVEL) to_chat(user, span_notice("You clear up [src]!")) qdel(src) - else - return ..() + return SECONDARY_ATTACK_CONTINUE_CHAIN + return ..() diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index 62e0e73fdece6..a3685a7f8cf36 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -288,47 +288,63 @@ C.value = weed_chance -/obj/item/seeds/proc/get_analyzer_text() //in case seeds have something special to tell to the analyzer +/obj/item/seeds/proc/get_analyzer_text(mob/user, check_skills = FALSE) //in case seeds have something special to tell to the analyzer var/text = "" - if(!get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && !get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) - text += "- Plant type: Normal plant\n" - if(get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) - text += "- Plant type: Weed. Can grow in nutrient-poor soil.\n" - if(get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) - text += "- Plant type: Mushroom. Can grow in dry soil.\n" - if(get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) - text += "- Plant type: [span_warning("UNKNOWN")] \n" - if(potency != -1) - text += "- Potency: [potency]\n" - if(yield != -1) - text += "- Yield: [yield]\n" - text += "- Maturation speed: [maturation]\n" - if(yield != -1) - text += "- Production speed: [production]\n" - text += "- Endurance: [endurance]\n" - text += "- Lifespan: [lifespan]\n" - text += "- Weed Growth Rate: [weed_rate]\n" - text += "- Weed Vulnerability: [weed_chance]\n" - if(rarity) - text += "- Species Discovery Value: [rarity]\n" - var/all_traits = "" - for(var/datum/plant_gene/trait/traits in genes) - if(istype(traits, /datum/plant_gene/trait/plant_type)) - continue - all_traits += " [traits.get_name()]" - text += "- Plant Traits:[all_traits]\n" + if(!check_skills || user.skill_check(SKILL_SCIENCE, EXP_LOW)) // basic knowledge will tell you what a + if(!get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism) && !get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) + text += "- Plant type: Normal plant\n" + if(get_gene(/datum/plant_gene/trait/plant_type/weed_hardy)) + text += "- Plant type: Weed. Can grow in nutrient-poor soil.\n" + if(get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) + text += "- Plant type: Mushroom. Can grow in dry soil.\n" + if(get_gene(/datum/plant_gene/trait/plant_type/alien_properties)) + text += "- Plant type: [span_warning("UNKNOWN")] \n" + if(!check_skills || user.skill_check(SKILL_SCIENCE, EXP_HIGH)) + var/inaccuracy = check_skills ? (EXP_GENIUS - user.get_skill(SKILL_SCIENCE)) / (EXP_GENIUS * 2) : 0 + if(potency != -1) + text += "- Potency: [randomize_plant_stat(potency, inaccuracy * 100, 0)]\n" + if(yield != -1) + text += "- Yield: [randomize_plant_stat(yield, inaccuracy * 10, 2)]\n" + text += "- Maturation speed: [randomize_plant_stat(maturation, inaccuracy * 10, 4)]\n" + if(yield != -1) + text += "- Production speed: [randomize_plant_stat(production, inaccuracy * 10, 6)]\n" + text += "- Endurance: [randomize_plant_stat(endurance, inaccuracy * 100, 8)]\n" + text += "- Lifespan: [randomize_plant_stat(lifespan, inaccuracy * 100, 10)]\n" + text += "- Weed Growth Rate: [randomize_plant_stat(weed_rate, inaccuracy * 10, 12)]\n" + text += "- Weed Vulnerability: [randomize_plant_stat(weed_chance, inaccuracy * 10, 14)]\n" + if(rarity) + text += "- Species Discovery Value: [rarity]\n" + if(!check_skills || user.skill_check(SKILL_SCIENCE, EXP_MID)) + var/all_traits = "" + for(var/datum/plant_gene/trait/traits in genes) + if(istype(traits, /datum/plant_gene/trait/plant_type)) + continue + all_traits += " [traits.get_name()]" + text += "- Plant Traits:[all_traits]\n" text += "" return text +/// Randomizes and displays a plant stat. +/obj/item/seeds/proc/randomize_plant_stat(plant_stat, inaccuracy = 0, hash_offset = 0) + if(!inaccuracy) + return plant_stat + hash_offset += 1 + (GLOB.round_id % 16) + var/raw_hash = copytext(md5("[potency]/[yield]/[maturation]/[production]/[endurance]/[lifespan]/[weed_rate]/[weed_chance]/[inaccuracy]/[REF(src)]"), \ + hash_offset, hash_offset + 2) + var/random_offset = round(inaccuracy * hex2num(raw_hash) / 255) + if(plant_stat + random_offset - inaccuracy < 0) // keep it in bounds + random_offset += -(plant_stat + random_offset - inaccuracy) + return "[plant_stat + random_offset - inaccuracy]-[plant_stat + random_offset + inaccuracy]" + /obj/item/seeds/proc/on_chem_reaction(datum/reagents/S) //in case seeds have some special interaction with special chems return /// Ghost attack proc /obj/item/seeds/attack_ghost(mob/user) to_chat(user, span_info("This is \a [span_name("[src]")].")) - var/text = get_analyzer_text() + var/text = get_analyzer_text(user) if(text) to_chat(user, span_notice("[text]")) @@ -336,7 +352,7 @@ if (istype(O, /obj/item/plant_analyzer)) playsound(src, 'sound/effects/fastbeep.ogg', 30) to_chat(user, span_info("This is \a [span_name("[src]")].")) - var/text = get_analyzer_text() + var/text = get_analyzer_text(user) if(text) to_chat(user, span_notice("[text]")) diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index b5878d2ddc291..8206a9821bbfc 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -77,6 +77,18 @@ ///Lazylist of traits added to the liver of the mob assigned this job (used for the classic "cops heal from donuts" reaction, among others) var/list/liver_traits = null + /// Baseline skill levels this job should have + var/list/base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + + /// Number of free skill points to allocate + var/skill_points = 3 + /// Display order of the job var/display_order = JOB_DISPLAY_ORDER_DEFAULT @@ -179,6 +191,10 @@ H.dna.species.after_equip_job(src, H, preference_source) + for(var/skill in base_skills) + H.adjust_skill(skill, base_skills[skill]) + H.add_skill_points(skill_points) + if(!visualsOnly && announce) announce(H) diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm index 9df34178ac02e..fca0d5107e25a 100644 --- a/code/modules/jobs/job_types/ai.dm +++ b/code/modules/jobs/job_types/ai.dm @@ -15,6 +15,15 @@ display_order = JOB_DISPLAY_ORDER_AI var/do_special_check = TRUE + base_skills = list( + SKILL_PHYSIOLOGY = EXP_MASTER, + SKILL_MECHANICAL = EXP_MASTER, + SKILL_TECHNICAL = EXP_MASTER, + SKILL_SCIENCE = EXP_MASTER, + SKILL_FITNESS = EXP_NONE, // it can't fucking MOVE + ) + skill_points = 0 + departments_list = list( /datum/job_department/silicon, ) diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm index 01444973c9386..a2a8859c0c84c 100644 --- a/code/modules/jobs/job_types/atmospheric_technician.dm +++ b/code/modules/jobs/job_types/atmospheric_technician.dm @@ -20,6 +20,15 @@ display_order = JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN minimal_character_age = 24 //Intense understanding of thermodynamics, gas law, gas interaction, construction and safe containment of gases, creation of new ones, math beyond your wildest imagination + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_LOW, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_LOW, + ) + skill_points = 3 + departments_list = list( /datum/job_department/engineering, ) diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm index 3b1b842dd4262..026452d5da07a 100644 --- a/code/modules/jobs/job_types/bartender.dm +++ b/code/modules/jobs/job_types/bartender.dm @@ -20,6 +20,15 @@ display_order = JOB_DISPLAY_ORDER_BARTENDER minimal_character_age = 21 //I shouldn't have to explain this one + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_MID, + ) + skill_points = 2 + departments_list = list( /datum/job_department/service, ) diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm index e413975af63f2..a70d2dada6430 100644 --- a/code/modules/jobs/job_types/botanist.dm +++ b/code/modules/jobs/job_types/botanist.dm @@ -19,6 +19,15 @@ display_order = JOB_DISPLAY_ORDER_BOTANIST minimal_character_age = 22 //Biological understanding of plants and how to manipulate their DNAs and produces relatively "safely". Not just something that comes to you without education + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_MID, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 3 + departments_list = list( /datum/job_department/service, ) diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 86f2da3512ca7..a714b76bf5677 100644 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -30,6 +30,15 @@ /datum/job_department/command, ) + base_skills = list( + SKILL_PHYSIOLOGY = EXP_LOW, + SKILL_MECHANICAL = EXP_LOW, + SKILL_TECHNICAL = EXP_LOW, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_MID, + ) + skill_points = 2 + mind_traits = list(TRAIT_DISK_VERIFIER) mail_goodies = list( diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm index 5beb433c9e41c..2538fe34e3351 100644 --- a/code/modules/jobs/job_types/chemist.dm +++ b/code/modules/jobs/job_types/chemist.dm @@ -24,6 +24,15 @@ display_order = JOB_DISPLAY_ORDER_CHEMIST minimal_character_age = 24 //A lot of experimental drugs plus understanding the facilitation and purpose of several subtances; what treats what and how to safely manufacture it + base_skills = list( + SKILL_PHYSIOLOGY = EXP_LOW, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_MID, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 3 + departments_list = list( /datum/job_department/medical, ) diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm index de80adfed05c1..9c4659922832b 100644 --- a/code/modules/jobs/job_types/chief_engineer.dm +++ b/code/modules/jobs/job_types/chief_engineer.dm @@ -17,6 +17,15 @@ exp_type_department = EXP_TYPE_ENGINEERING alt_titles = list("Engineering Director", "Head of Engineering", "Senior Engineer", "Chief Engineering Officer") + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_MID, + SKILL_TECHNICAL = EXP_MID, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 4 // lots of different skills required + outfit = /datum/outfit/job/ce added_access = list(ACCESS_CAPTAIN, ACCESS_AI_MASTER) diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm index c24f83422b195..844de03cb3f69 100644 --- a/code/modules/jobs/job_types/chief_medical_officer.dm +++ b/code/modules/jobs/job_types/chief_medical_officer.dm @@ -32,6 +32,15 @@ display_order = JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER minimal_character_age = 30 //Do you knoW HOW MANY JOBS YOU HAVE TO KNOW TO DO?? This should really be like 35 or something + base_skills = list( + SKILL_PHYSIOLOGY = EXP_MID, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_MID, + SKILL_FITNESS = EXP_LOW, + ) + skill_points = 4 + departments_list = list( /datum/job_department/medical, /datum/job_department/command, diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm index b2b714a9d6c1a..0c077e05dce5f 100644 --- a/code/modules/jobs/job_types/cyborg.dm +++ b/code/modules/jobs/job_types/cyborg.dm @@ -14,6 +14,15 @@ display_order = JOB_DISPLAY_ORDER_CYBORG + base_skills = list( + SKILL_PHYSIOLOGY = EXP_MID, + SKILL_MECHANICAL = EXP_MID, + SKILL_TECHNICAL = EXP_MASTER, + SKILL_SCIENCE = EXP_MID, + SKILL_FITNESS = EXP_MID, + ) + skill_points = EXP_NONE + departments_list = list( /datum/job_department/silicon, ) diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm index c754aed3dc922..15c101c8eee51 100644 --- a/code/modules/jobs/job_types/detective.dm +++ b/code/modules/jobs/job_types/detective.dm @@ -26,6 +26,15 @@ display_order = JOB_DISPLAY_ORDER_DETECTIVE minimal_character_age = 22 //Understanding of forensics, crime analysis, and theory. Less of a grunt officer and more of an intellectual, theoretically, despite how this is never reflected in-game + base_skills = list( + SKILL_PHYSIOLOGY = EXP_MID, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_MID, + ) + skill_points = 2 + departments_list = list( /datum/job_department/security, ) diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm index 9140376187a49..3f50f875df7df 100644 --- a/code/modules/jobs/job_types/geneticist.dm +++ b/code/modules/jobs/job_types/geneticist.dm @@ -22,6 +22,15 @@ display_order = JOB_DISPLAY_ORDER_GENETICIST minimal_character_age = 24 //Genetics would likely require more education than your average position due to the sheer number of alien physiologies and experimental nature of the field + base_skills = list( + SKILL_PHYSIOLOGY = EXP_LOW, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_HIGH, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 2 + departments_list = list( /datum/job_department/medical, ) diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index 6b7a9d4090ad7..fe974321614e6 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -37,6 +37,15 @@ display_order = JOB_DISPLAY_ORDER_HEAD_OF_SECURITY minimal_character_age = 28 //You need some experience on your belt and a little gruffiness; you're still a foot soldier, not quite a tactician commander back at base + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = 3 + departments_list = list( /datum/job_department/security, /datum/job_department/command, diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm index 4d5c7892e2704..f4b40c3216569 100644 --- a/code/modules/jobs/job_types/medical_doctor.dm +++ b/code/modules/jobs/job_types/medical_doctor.dm @@ -28,6 +28,15 @@ /datum/job_department/medical, ) + base_skills = list( + SKILL_PHYSIOLOGY = EXP_HIGH, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 3 + mail_goodies = list( /obj/item/healthanalyzer/advanced = 15, /obj/effect/spawner/lootdrop/surgery_tool_advanced = 6, diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm index 21537077bb696..ac705c8dbc02f 100644 --- a/code/modules/jobs/job_types/research_director.dm +++ b/code/modules/jobs/job_types/research_director.dm @@ -37,6 +37,15 @@ display_order = JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR minimal_character_age = 26 //Barely knows more than actual scientists, just responsibility and AI things + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_MID, + SKILL_SCIENCE = EXP_HIGH, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 3 + departments_list = list( /datum/job_department/science, /datum/job_department/command, diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm index ffd5560039dad..de234267b1b12 100644 --- a/code/modules/jobs/job_types/roboticist.dm +++ b/code/modules/jobs/job_types/roboticist.dm @@ -23,6 +23,15 @@ display_order = JOB_DISPLAY_ORDER_ROBOTICIST minimal_character_age = 22 //Engineering, AI theory, robotic knowledge and the like + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_LOW, + SKILL_TECHNICAL = EXP_LOW, + SKILL_SCIENCE = EXP_LOW, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 3 + departments_list = list( /datum/job_department/science, ) diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm index a97b7dca3ab02..ed2aa450be1ce 100644 --- a/code/modules/jobs/job_types/scientist.dm +++ b/code/modules/jobs/job_types/scientist.dm @@ -24,6 +24,15 @@ display_order = JOB_DISPLAY_ORDER_SCIENTIST minimal_character_age = 24 //Consider the level of knowledge that spans xenobio, nanites, and toxins + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_HIGH, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 3 + departments_list = list( /datum/job_department/science, ) diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index fed23668d65be..47b0a97fffc52 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -28,6 +28,15 @@ display_order = JOB_DISPLAY_ORDER_SECURITY_OFFICER minimal_character_age = 18 //Just a few months of boot camp, not a whole year + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = 2 + departments_list = list( /datum/job_department/security, ) diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm index af917644e4e2d..9b392fd37f2b1 100644 --- a/code/modules/jobs/job_types/shaft_miner.dm +++ b/code/modules/jobs/job_types/shaft_miner.dm @@ -22,6 +22,15 @@ display_order = JOB_DISPLAY_ORDER_SHAFT_MINER minimal_character_age = 18 //Young and fresh bodies for a high mortality job, what more could you ask for + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = 2 // "unskilled" labor + departments_list = list( /datum/job_department/cargo, ) diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm index aab47082a52a5..999373f05f360 100644 --- a/code/modules/jobs/job_types/station_engineer.dm +++ b/code/modules/jobs/job_types/station_engineer.dm @@ -24,6 +24,15 @@ display_order = JOB_DISPLAY_ORDER_STATION_ENGINEER minimal_character_age = 22 //You need to know a lot of complicated stuff about engines, could theoretically just have a traditional bachelor's + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_MID, + SKILL_TECHNICAL = EXP_LOW, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 3 + departments_list = list( /datum/job_department/engineering, ) diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm index 918a071ff94df..3ec880243ef4a 100644 --- a/code/modules/jobs/job_types/virologist.dm +++ b/code/modules/jobs/job_types/virologist.dm @@ -26,6 +26,15 @@ display_order = JOB_DISPLAY_ORDER_VIROLOGIST minimal_character_age = 24 //Requires understanding of microbes, biology, infection, and all the like, as well as being able to understand how to interface the machines. Epidemiology is no joke of a field + base_skills = list( + SKILL_PHYSIOLOGY = EXP_MID, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_MID, + SKILL_FITNESS = EXP_NONE, + ) + skill_points = 2 + departments_list = list( /datum/job_department/medical, ) diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index 7e0791411c37d..67ceaf5c9768f 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -30,6 +30,15 @@ display_order = JOB_DISPLAY_ORDER_WARDEN minimal_character_age = 20 //You're a sergeant, probably has some experience in the field + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_NONE, + SKILL_FITNESS = EXP_HIGH, + ) + skill_points = 2 + departments_list = list( /datum/job_department/security, ) diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm index a18772061cfec..74659a68cbb95 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -199,6 +199,7 @@ var/unique = 0 //0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified var/title //The real name of the book. var/window_size = null // Specific window size for the book, i.e: "1920x1080", Size x Width + var/list/skill_gain /obj/item/book/attack_self(mob/user) diff --git a/code/modules/mining/lavaland/ruins/gym.dm b/code/modules/mining/lavaland/ruins/gym.dm index 0ecbf2db66937..768ca05f65e59 100644 --- a/code/modules/mining/lavaland/ruins/gym.dm +++ b/code/modules/mining/lavaland/ruins/gym.dm @@ -52,6 +52,7 @@ user.pixel_y = 0 var/finishmessage = pick("You feel stronger!","You feel like you can take on the world!","You feel robust!","You feel indestructible!") SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "exercise", /datum/mood_event/exercise) + user.add_exp(SKILL_FITNESS, 100) icon_state = initial(icon_state) to_chat(user, finishmessage) user.apply_status_effect(STATUS_EFFECT_EXERCISED) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 46788af77c40d..f704a81aa0096 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -106,8 +106,8 @@ GLOBAL_LIST_EMPTY(features_by_species) var/punchdamagelow = 1 ///highest possible punch damage var/punchdamagehigh = 10 - ///damage at which punches from this race will stun //yes it should be to the attacked race but it's not useful that way even if it's logical - var/punchstunthreshold = 10 + ///chance for a punch to stun + var/punchstunchance = 0.1 ///values of inaccuracy that adds to the spread of any ranged weapon var/aiminginaccuracy = 0 ///base electrocution coefficient @@ -1503,7 +1503,7 @@ GLOBAL_LIST_EMPTY(features_by_species) // nutrition decrease and satiety if (H.nutrition > 0 && H.stat != DEAD && !HAS_TRAIT(H, TRAIT_NOHUNGER)) // THEY HUNGER - var/hunger_rate = HUNGER_FACTOR + var/hunger_rate = HUNGER_FACTOR * (EXP_MASTER + H.get_skill(SKILL_FITNESS)) / EXP_MASTER var/datum/component/mood/mood = H.GetComponent(/datum/component/mood) if(mood && mood.sanity > SANITY_DISTURBED) hunger_rate *= max(0.5, 1 - 0.002 * mood.sanity) //0.85 to 0.75 @@ -1733,6 +1733,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(M) M.handle_counter(target, user) return FALSE + user.add_exp(SKILL_FITNESS, 5) if(attacker_style && attacker_style.harm_act(user,target)) return TRUE else @@ -1743,7 +1744,8 @@ GLOBAL_LIST_EMPTY(features_by_species) atk_verb = "kick" atk_effect = ATTACK_EFFECT_KICK user.do_attack_animation(target, atk_effect) - var/damage = rand(user.get_punchdamagelow(), user.get_punchdamagehigh()) + var/percentile = rand() + var/damage = LERP(user.get_punchdamagelow(), user.get_punchdamagehigh(), percentile) var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(user.zone_selected)) @@ -1784,7 +1786,7 @@ GLOBAL_LIST_EMPTY(features_by_species) target.apply_damage(damage*1.5, STAMINA, affecting, armor_block) log_combat(user, target, "punched") - if((target.stat != DEAD) && damage >= user.get_punchstunthreshold()) + if((target.stat != DEAD) && percentile > (1 - user.get_punchstunchance()) && !HAS_TRAIT(user, TRAIT_NO_PUNCH_STUN)) target.visible_message(span_danger("[user] has knocked [target] down!"), \ span_userdanger("[user] has knocked [target] down!"), null, COMBAT_MESSAGE_RANGE) var/knockdown_duration = 40 + (target.getStaminaLoss() + (target.getBruteLoss()*0.5))*0.8 //50 total damage = 40 base stun + 40 stun modifier = 80 stun duration, which is the old base duration diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index e19ac19e5a5fe..14dfc26ea6940 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -363,7 +363,7 @@ if(!appears_dead) if(src != user) - if(HAS_TRAIT(user, TRAIT_EMPATH)) + if(HAS_TRAIT(user, TRAIT_EMPATH) || user.skill_check(SKILL_PHYSIOLOGY, EXP_MID)) if (combat_mode) msg += "[t_He] seem[p_s()] to be on guard.\n" if (getOxyLoss() >= 10) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 3594372e30620..7dd0b3fa1aa94 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -903,23 +903,34 @@ return (ishuman(target) && !(target.mobility_flags & MOBILITY_STAND)) /mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target) - var/carrydelay = 50 //if you have latex you are faster at grabbing var/skills_space = null // Changes depending on glove type + + var/nanochips = FALSE + var/effective_skill = get_skill(SKILL_FITNESS) if(HAS_TRAIT(src, TRAIT_QUICKEST_CARRY)) - carrydelay = 25 - skills_space = "masterfully" + effective_skill += EXP_HIGH + nanochips = TRUE else if(HAS_TRAIT(src, TRAIT_QUICKER_CARRY)) - carrydelay = 30 - skills_space = "expertly" + effective_skill += EXP_MID + nanochips = TRUE else if(HAS_TRAIT(src, TRAIT_QUICK_CARRY)) - carrydelay = 40 - skills_space = "quickly" + effective_skill += EXP_LOW + + var/carrydelay = (25 / (5 + effective_skill)) SECONDS + switch(effective_skill) + if(EXP_MASTER to INFINITY) + skills_space = "masterfully" + if(EXP_MID to EXP_MASTER) + skills_space = "expertly" + if(EXP_LOW to EXP_MID) + skills_space = "quickly" + if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) visible_message(span_notice("[src] starts [skills_space] lifting [target] onto their back.."), //Joe Medic starts quickly/expertly lifting Grey Tider onto their back.. - span_notice("[carrydelay < 35 ? "Using your gloves' nanochips, you" : "You"] [skills_space ? "[skills_space] " : ""]start to lift [target] onto your back[carrydelay == 40 ? ", while assisted by the nanochips in your gloves.." : "..."]")) + span_notice("[nanochips ? "Using your gloves' nanochips, you" : "You"] [skills_space ? "[skills_space] " : ""]start to lift [target] onto your back[carrydelay == 40 ? ", while assisted by the nanochips in your gloves.." : "..."]")) //(Using your gloves' nanochips, you/You) ( /quickly/expertly) start to lift Grey Tider onto your back(, while assisted by the nanochips in your gloves../...) - if(do_after(src, carrydelay, target)) + if(do_after(src, carrydelay, target, IGNORE_SKILL_DELAY, skill_check = SKILL_FITNESS)) //Second check to make sure they're still valid to be carried if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE) && !target.buckled) if(target.loc != loc) @@ -1061,6 +1072,10 @@ return FALSE return ..() +/mob/living/carbon/human/handle_skills(delta_time) + if(IS_SCIENCE(src)) // scientists give a small boost to science points based on science skill, more if they're the RD + SSresearch.science_tech.research_points[TECHWEB_POINT_TYPE_DEFAULT] += get_skill(SKILL_SCIENCE) * (IS_COMMAND(src) ? 2 : 1) + /mob/living/carbon/human/species var/race = null diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 876399540bd01..20aae3b901302 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -26,6 +26,8 @@ else if(bodypart_flag & cover.body_parts_partial_covered) protection += cover.armor.getRating(armor_flag) * 0.5 protection += physiology.armor.getRating(armor_flag) + if(armor_flag == MELEE) + protection = 100 - ((100 - protection) * (50 - get_skill(SKILL_FITNESS)) / 50) // 8% multiplicative armor at EXP_MASTER return protection ///Get all the clothing on a specific body part @@ -86,6 +88,10 @@ if(shield_check & SHIELD_BLOCK) P.on_hit(src, 100, def_zone) return BULLET_ACT_HIT + + if(iscarbon(P.firer) && stat == CONSCIOUS) // gain experience from shooting people, more if they were far away and less if it wasn't a real gun + var/mob/shooter = P.firer + shooter.add_exp(SKILL_FITNESS, max(initial(P.range) - P.range, 1) * ((P.nodamage || !P.damage) ? 2 : 5)) return ..(P, def_zone) diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 87284f977c61e..fccc7939b2e13 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -270,13 +270,13 @@ return dna.species.get_biological_state() /mob/living/carbon/human/proc/get_punchdamagehigh() //Gets the total maximum punch damage - return dna.species.punchdamagehigh + physiology.punchdamagehigh_bonus + return dna.species.punchdamagehigh + physiology.punchdamagehigh_bonus + (get_skill(SKILL_FITNESS) / 2) /mob/living/carbon/human/proc/get_punchdamagelow() //Gets the total minimum punch damage - return dna.species.punchdamagelow + physiology.punchdamagelow_bonus + return dna.species.punchdamagelow + physiology.punchdamagelow_bonus + get_skill(SKILL_FITNESS) -/mob/living/carbon/human/proc/get_punchstunthreshold() //Gets the total punch damage needed to knock down someone - return dna.species.punchstunthreshold + physiology.punchstunthreshold_bonus +/mob/living/carbon/human/proc/get_punchstunchance() //Gets the total chance to knock down someone + return dna.species.punchstunchance + physiology.punchstunchance_bonus /// Fully randomizes everything according to the given flags. /mob/living/carbon/human/proc/randomize_human_appearance(randomize_flags = ALL) diff --git a/code/modules/mob/living/carbon/human/physiology.dm b/code/modules/mob/living/carbon/human/physiology.dm index 76a869459e3af..a164c67ac0bfc 100644 --- a/code/modules/mob/living/carbon/human/physiology.dm +++ b/code/modules/mob/living/carbon/human/physiology.dm @@ -30,7 +30,7 @@ var/punchdamagehigh_bonus = 0 //Increased maximum punch damage var/punchdamagelow_bonus = 0 //Increased minimum punch damage - var/punchstunthreshold_bonus = 0 //Increased stun threshhold on punches so we don't get knockdown hands + var/punchstunchance_bonus = 0 //Increased stun threshhold on punches so we don't get knockdown hands var/crawl_speed = 0 // Movement speed modifier when crawling diff --git a/code/modules/mob/living/carbon/human/species_types/IPC.dm b/code/modules/mob/living/carbon/human/species_types/IPC.dm index 9a2fb5ec28e85..831766fb4f4fb 100644 --- a/code/modules/mob/living/carbon/human/species_types/IPC.dm +++ b/code/modules/mob/living/carbon/human/species_types/IPC.dm @@ -409,7 +409,7 @@ ipc martial arts stuff armor = 10 punchdamagelow = 5 punchdamagehigh = 12 - punchstunthreshold = 12 + punchstunchance = 0.2 mutant_organs = list() inherent_traits = list( TRAIT_RESISTCOLD, @@ -576,7 +576,7 @@ ipc martial arts stuff speedmod = -0.2 punchdamagelow = 10 punchdamagehigh = 19 - punchstunthreshold = 14 //about 50% chance to stun + punchstunchance = 0.5 //50% chance to stun disguise_fail_health = 35 changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN //admin only... sorta diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index 69710b36a3f41..06abeb0e403d9 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -20,7 +20,6 @@ coldmod = 2.0 //Don't extinguish the stars speedmod = -0.1 //Light and energy move quickly punchdamagehigh = 11 //Fire hand more painful - punchstunthreshold = 11 //Still stuns on max hit, but subsequently lower chance to stun overall attack_type = BURN //burn bish damage_overlay_type = "" //We are too cool for regular damage overlays species_traits = list(NOEYESPRITES, EYECOLOR, MUTCOLORS, HAIR, FACEHAIR, HAS_FLESH) // i mean i guess they have blood so they can have wounds too diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index 93079128cad42..a5822c060049e 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -11,7 +11,7 @@ siemens_coeff = 0 punchdamagelow = 5 punchdamagehigh = 14 - punchstunthreshold = 11 //about 40% chance to stun + punchstunchance = 0.4 //40% chance to stun no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE) nojumpsuit = 1 changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC @@ -194,7 +194,7 @@ name = "Silver Golem" id = "silver golem" fixed_mut_color = "#dddddd" - punchstunthreshold = 9 //60% chance, from 40% + punchstunchance = 0.6 //60% chance, from 40% meat = /obj/item/stack/ore/silver info_text = "As a Silver Golem, your attacks have a higher chance of stunning. Being made of silver, your body is immune to most types of magic." prefix = "Silver" @@ -217,7 +217,6 @@ stunmod = 0.4 punchdamagelow = 12 punchdamagehigh = 21 - punchstunthreshold = 18 //still 40% stun chance speedmod = 4 //pretty fucking slow meat = /obj/item/stack/ore/iron info_text = "As a Plasteel Golem, you are slower, but harder to stun, and hit very hard when punching. You also magnetically attach to surfaces and so don't float without gravity and cannot have positions swapped with other beings." @@ -583,7 +582,7 @@ say_mod = "honks" punchdamagelow = 0 punchdamagehigh = 1 - punchstunthreshold = 2 //Harmless and can't stun + punchstunchance = 0 //Harmless and can't stun meat = /obj/item/stack/ore/bananium info_text = "As a Bananium Golem, you are made for pranking. Your body emits natural honks, and you can barely even hurt people when punching them. Your skin also bleeds banana peels when damaged." attack_verbs = list("honk") @@ -796,7 +795,7 @@ burnmod = 2 // don't get burned speedmod = 1 // not as heavy as stone punchdamagelow = 4 - punchstunthreshold = 7 + punchstunchance = 0.2 punchdamagehigh = 8 // not as heavy as stone prefix = "Cloth" special_names = null @@ -1049,7 +1048,7 @@ heatmod = 2 speedmod = 1.5 punchdamagelow = 4 - punchstunthreshold = 7 + punchstunchance = 0.2 punchdamagehigh = 8 var/last_creation = 0 var/brother_creation_cooldown = 300 @@ -1468,7 +1467,6 @@ burnmod = 0.75 speedmod = 1 // not as heavy as stone heatmod = 0.1 //very little damage, but still there. Its like how a candle doesn't melt in seconds but still melts. - punchstunthreshold = 7 punchdamagehigh = 9 // not as heavy as stone prefix = "Wax" special_names = list("Candelabra", "Candle") @@ -1663,7 +1661,7 @@ inherent_traits = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER,TRAIT_NOGUNS) speedmod = 1.5 // Slightly faster armor = 25 - punchstunthreshold = 13 + punchstunchance = 0.2 fixed_mut_color = "48002b" info_text = "As a Tar Golem, you burn very very easily and can temporarily turn yourself into a pool of tar, in this form you are invulnerable to all attacks." random_eligible = FALSE //If false, the golem subtype can't be made through golem mutation toxin diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 1b8ebe15377c5..880a323dd268d 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -230,7 +230,6 @@ burnmod = 1.15 speedmod = -0.1 //similar to ethereals, should help with saving others punchdamagehigh = 7 - punchstunthreshold = 7 action_speed_coefficient = 0.9 //they're smart and efficient unlike other lizards species_language_holder = /datum/language_holder/lizard/shaman var/datum/action/cooldown/spell/touch/heal/lizard_touch @@ -240,6 +239,7 @@ . = ..() lizard_touch = new(C) lizard_touch.Grant(C) + C.adjust_skill(SKILL_PHYSIOLOGY, EXP_HIGH) //removes the heal spell /datum/species/lizard/ashwalker/shaman/on_species_loss(mob/living/carbon/C) @@ -295,8 +295,7 @@ burnmod = 0.8 brutemod = 0.9 //something something dragon scales punchdamagelow = 3 - punchdamagehigh = 12 - punchstunthreshold = 12 //+2 claws of powergaming + punchdamagehigh = 12 //+2 claws of powergaming /datum/species/lizard/draconid/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 71dbd6c68c36c..9d02cfde6563d 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -19,7 +19,7 @@ burnmod = 1.25 //Fluffy and flammable brutemod = 0.9 //Evasive buggers punchdamagehigh = 9 //Weird fluffy bug fist - punchstunthreshold = 10 //No stun punches + punchstunchance = 0 //No stun punches mutanteyes = /obj/item/organ/eyes/moth changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_MAGIC | MIRROR_PRIDE | ERT_SPAWN | RACE_SWAP | SLIME_EXTRACT species_language_holder = /datum/language_holder/mothmen diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm index d40d1473418cb..037cbb99c8684 100644 --- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm @@ -17,7 +17,7 @@ punchdamagelow = 6 punchdamagehigh = 14 - punchstunthreshold = 14 //about 44% chance to stun + punchstunchance = 0.44 //44% chance to stun no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING) diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index ade9a58e7d293..1c4993f950c89 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -20,7 +20,7 @@ burnmod = 0.9 //Plasma is a surprisingly good insulator if not around oxygen heatmod = 1.5 //Don't let the plasma actually heat up though punchdamagehigh = 7 //Bone punches are weak and usually inside soft suit gloves - punchstunthreshold = 7 //Stuns on max hit as usual, somewhat higher stun chance because math + punchstunchance = 0.15 //Stuns on max hit as usual, somewhat higher stun chance because math species_gibs = "plasma" breathid = GAS_PLASMA damage_overlay_type = ""//let's not show bloody wounds or burns over bones. diff --git a/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm b/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm index 8d16f1f25d68d..0382ba8e374a3 100644 --- a/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm +++ b/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm @@ -24,7 +24,6 @@ speedmod = -0.1 //apex predator humanoid hybrid inert_mutation = ACIDSPIT punchdamagehigh = 11 //slightly better high end of damage - punchstunthreshold = 11 //technically slightly worse stunchance damage_overlay_type = "polysmorph" species_gibs = "polysmorph" deathsound = 'sound/voice/hiss6.ogg' diff --git a/code/modules/mob/living/carbon/human/species_types/wy_synths.dm b/code/modules/mob/living/carbon/human/species_types/wy_synths.dm index af6cd3aadd057..9830a81202334 100644 --- a/code/modules/mob/living/carbon/human/species_types/wy_synths.dm +++ b/code/modules/mob/living/carbon/human/species_types/wy_synths.dm @@ -58,7 +58,7 @@ punchdamagehigh = 12 punchdamagelow = 5 - punchstunthreshold = 11 + punchstunchance = 0.2 var/last_warned diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index dabe7b47900ec..47b6fcdb2542f 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -135,7 +135,7 @@ staminamod = 0.5 //difficult to subdue via nonlethal means punchdamagelow = 13 punchdamagehigh = 16 - punchstunthreshold = 17 //pretty good punch damage but no knockdown + punchstunchance = 0 //pretty good punch damage but no knockdown ///no guns or soft crit inherent_traits = list( TRAIT_STABLELIVER, diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index cbfc194eb95ce..9669a7fa63cc3 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -1,7 +1,7 @@ -/mob/living/proc/Life(seconds_per_tick = SSMOBS_DT, times_fired) +/mob/living/proc/Life(delta_time = SSMOBS_DT, times_fired) set waitfor = FALSE - var/signal_result = SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick, times_fired) + var/signal_result = SEND_SIGNAL(src, COMSIG_LIVING_LIFE, delta_time, times_fired) if(signal_result & COMPONENT_LIVING_CANCEL_LIFE_PROCESSING) // mmm less work return @@ -69,6 +69,8 @@ //Yogs end handle_gravity() + handle_skills(delta_time) + if(stat != DEAD) handle_traits() // eye, ear, brain damages handle_status_effects() //all special effects, stun, knockdown, jitteryness, hallucination, sleeping, etc @@ -147,3 +149,6 @@ if(gravity >= GRAVITY_DAMAGE_TRESHOLD) //Aka gravity values of 3 or more var/grav_stregth = gravity - GRAVITY_DAMAGE_TRESHOLD adjustBruteLoss(min(grav_stregth,3)) + +/mob/living/proc/handle_skills(delta_time) + return diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index 19ec66257ac7a..e8e40b4405d7b 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -469,6 +469,7 @@ By design, d1 is the smallest direction and d2 is the highest righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' max_amount = MAXCOIL amount = MAXCOIL + tool_behaviour = TOOL_WIRING merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves color = CABLE_HEX_COLOR_YELLOW desc = "A coil of insulated power cable." diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index e0f1e9c14e9a0..9e4c71a63b9db 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -371,7 +371,7 @@ var/randomized_gun_spread = 0 var/rand_spr = rand() if(spread > 0) - randomized_gun_spread = rand(0,spread) + randomized_gun_spread += rand(0,spread) * (8 - user.get_skill(SKILL_FITNESS)) / 5 if(ishuman(user)) //nice shootin' tex var/mob/living/carbon/human/H = user bonus_spread += H.dna.species.aiminginaccuracy diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index c820c86136719..412cbd4c717d5 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -222,7 +222,7 @@ else progress_flash_divisor-- -/obj/item/gun/energy/plasmacutter/use_tool(atom/target, mob/living/user, delay, amount=1, volume=0, datum/callback/extra_checks, robo_check) +/obj/item/gun/energy/plasmacutter/use_tool(atom/target, mob/living/user, delay, amount=1, volume=0, datum/callback/extra_checks, skill_check) if(amount) var/mutable_appearance/sparks = mutable_appearance('icons/effects/welding_effect.dmi', "welding_sparks", GASFIRE_LAYER, src, ABOVE_LIGHTING_PLANE) target.add_overlay(sparks) diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index bccb0e77a51cc..c2789a3ddf424 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -242,7 +242,7 @@ var/chemname = temp.name if(is_hallucinating && prob(5)) chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" - chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name), "locked" = (dispensable_reagents.Find(temp.type) ? FALSE : TRUE), "tier" = get_tier_for_chemical(temp)))) + chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name), "locked" = !can_display_reagent(user, temp.type), "tier" = get_tier_for_chemical(temp)))) for(var/recipe in saved_recipes) recipes.Add(list(recipe)) data["chemicals"] = chemicals @@ -265,7 +265,7 @@ if(!is_operational() || QDELETED(cell)) return var/reagent = GLOB.name2reagent[params["reagent"]] - if(beaker && dispensable_reagents.Find(reagent)) + if(beaker && can_display_reagent(usr, reagent)) var/datum/reagents/R = beaker.reagents var/free = R.maximum_volume - R.total_volume var/actual = min(amount, (cell.charge * powerefficiency)*10, free) @@ -345,6 +345,17 @@ saved_recipes += list(list("recipe_name" = name, "contents" = recipe)) yogs - removed chem recipes */ +/obj/machinery/chem_dispenser/proc/can_display_reagent(mob/user, reagent_type) + if(dispensable_reagents.Find(reagent_type)) + return TRUE + if(user.skill_check(SKILL_SCIENCE, EXP_GENIUS) && t4_upgrade_reagents.Find(reagent_type)) + return TRUE + if(user.skill_check(SKILL_SCIENCE, EXP_HIGH) && t3_upgrade_reagents.Find(reagent_type)) + return TRUE + if(user.skill_check(SKILL_SCIENCE, EXP_MID) && t2_upgrade_reagents.Find(reagent_type)) + return TRUE + return FALSE + /obj/machinery/chem_dispenser/attackby(obj/item/I, mob/living/user, params) if(default_unfasten_wrench(user, I)) return diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 23d14a470841a..2466a71b1c755 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -1194,14 +1194,12 @@ All effects don't start immediately, but rather get worse over time; the rate is var/mob/living/carbon/human/guy = M guy.physiology.punchdamagehigh_bonus += 2 guy.physiology.punchdamagelow_bonus += 2 - guy.physiology.punchstunthreshold_bonus += 2 /datum/reagent/consumable/ethanol/amasec/on_mob_end_metabolize(mob/living/carbon/M) if(ishuman(M)) var/mob/living/carbon/human/guy = M guy.physiology.punchdamagehigh_bonus -= 2 guy.physiology.punchdamagelow_bonus -= 2 - guy.physiology.punchstunthreshold_bonus -= 2 return ..() /datum/reagent/consumable/ethanol/changelingsting diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index fd1d1df50a8a0..117152d8bcf81 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -168,6 +168,8 @@ Nothing else in the console has ID requirements. SSblackbox.record_feedback("associative", "science_techweb_unlock", 1, list("id" = "[id]", "name" = TN.display_name, "price" = "[json_encode(price)]", "time" = SQLtime())) if(stored_research.research_node_id(id)) say("Successfully researched [TN.display_name].") + for(var/point_type in price) + user.add_exp(SKILL_SCIENCE, price[point_type] / 200) var/logname = "Unknown" if(isAI(user)) logname = "AI: [user.name]" diff --git a/code/modules/ruins/lavaland_ruin_code.dm b/code/modules/ruins/lavaland_ruin_code.dm index 392c7538fdbb0..1459034324481 100644 --- a/code/modules/ruins/lavaland_ruin_code.dm +++ b/code/modules/ruins/lavaland_ruin_code.dm @@ -115,6 +115,15 @@ short_desc = "You are a syndicate science technician, employed in a top secret research facility developing biological weapons." flavour_text = "Unfortunately, your hated enemy, Nanotrasen, has begun mining in this sector. Continue your research as best you can, and try to keep a low profile." important_info = "The base is rigged with explosives, DO NOT abandon it, let it fall into enemy hands, or share your supplies with non-syndicate personnel." + base_skills = list( + SKILL_PHYSIOLOGY = EXP_NONE, + SKILL_MECHANICAL = EXP_NONE, + SKILL_TECHNICAL = EXP_NONE, + SKILL_SCIENCE = EXP_MID, + SKILL_FITNESS = EXP_LOW, + ) + skill_points = EXP_GENIUS + exceptional_skill = TRUE outfit = /datum/outfit/lavaland_syndicate assignedrole = "Lavaland Syndicate" diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm index 19dcfd4ee5cdf..bb46be700fdb3 100644 --- a/code/modules/shuttle/computer.dm +++ b/code/modules/shuttle/computer.dm @@ -184,7 +184,8 @@ log_admin("[usr] attempted to href dock exploit on [src] with target location \"[params["shuttle_id"]]\"") message_admins("[usr] just attempted to href dock exploit on [src] with target location \"[params["shuttle_id"]]\"") return - switch(SSshuttle.moveShuttle(shuttleId, params["shuttle_id"], 1)) + var/mob/user = usr + switch(SSshuttle.moveShuttle(shuttleId, params["shuttle_id"], 1, (10 - user.get_skill(SKILL_TECHNICAL)) / 10)) if(0) say("Shuttle departing. Please stand away from the doors.") return TRUE diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index e6feab8ec934d..46b7fba4e0bcd 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -692,7 +692,7 @@ message_admins("Shuttle [src] repeatedly failed to create transit zone.") //call the shuttle to destination S -/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/S) +/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/S, skill_multiplier = 1) if(!check_dock(S)) testing("check_dock failed on request for [src]") return @@ -703,22 +703,22 @@ switch(mode) if(SHUTTLE_CALL) if(S == destination) - if(timeLeft(1) < callTime * engine_coeff) - setTimer(callTime * engine_coeff) + if(timeLeft(1) < callTime * engine_coeff * skill_multiplier) + setTimer(callTime * engine_coeff * skill_multiplier) else destination = S - setTimer(callTime * engine_coeff) + setTimer(callTime * engine_coeff * skill_multiplier) if(SHUTTLE_RECALL) if(S == destination) - setTimer(callTime * engine_coeff - timeLeft(1)) + setTimer(callTime * engine_coeff * skill_multiplier - timeLeft(1)) else destination = S - setTimer(callTime * engine_coeff) + setTimer(callTime * engine_coeff * skill_multiplier) mode = SHUTTLE_CALL if(SHUTTLE_IDLE, SHUTTLE_IGNITING) destination = S mode = SHUTTLE_IGNITING - setTimer(ignitionTime) + setTimer(ignitionTime * skill_multiplier) //recall the shuttle to where it was previously /obj/docking_port/mobile/proc/cancel() diff --git a/code/modules/surgery/advanced/bioware/ligament_hook.dm b/code/modules/surgery/advanced/bioware/ligament_hook.dm index b4a5d18f864bd..e98a50323aebb 100644 --- a/code/modules/surgery/advanced/bioware/ligament_hook.dm +++ b/code/modules/surgery/advanced/bioware/ligament_hook.dm @@ -17,6 +17,7 @@ name = "reshape ligaments" accept_hand = TRUE time = 12.5 SECONDS + difficulty = EXP_HIGH preop_sound = 'sound/surgery/bone1.ogg' success_sound = 'sound/surgery/bone3.ogg' diff --git a/code/modules/surgery/advanced/bioware/ligament_reinforcement.dm b/code/modules/surgery/advanced/bioware/ligament_reinforcement.dm index 9b8d190853cfd..a4e4e5eb95af6 100644 --- a/code/modules/surgery/advanced/bioware/ligament_reinforcement.dm +++ b/code/modules/surgery/advanced/bioware/ligament_reinforcement.dm @@ -17,6 +17,7 @@ name = "reinforce ligaments" accept_hand = TRUE time = 12.5 SECONDS + difficulty = EXP_HIGH preop_sound = 'sound/surgery/bone1.ogg' success_sound = 'sound/surgery/bone3.ogg' diff --git a/code/modules/surgery/advanced/bioware/muscled_veins.dm b/code/modules/surgery/advanced/bioware/muscled_veins.dm index 32352725b522a..3ebabec7268dc 100644 --- a/code/modules/surgery/advanced/bioware/muscled_veins.dm +++ b/code/modules/surgery/advanced/bioware/muscled_veins.dm @@ -16,6 +16,7 @@ name = "shape vein muscles" accept_hand = TRUE time = 12.5 SECONDS + difficulty = EXP_HIGH preop_sound = 'sound/surgery/organ2.ogg' success_sound = 'sound/surgery/organ1.ogg' diff --git a/code/modules/surgery/advanced/bioware/nerve_grounding.dm b/code/modules/surgery/advanced/bioware/nerve_grounding.dm index 32d1726b112c3..b1cac2533e185 100644 --- a/code/modules/surgery/advanced/bioware/nerve_grounding.dm +++ b/code/modules/surgery/advanced/bioware/nerve_grounding.dm @@ -15,6 +15,7 @@ /datum/surgery_step/ground_nerves name = "ground nerves" accept_hand = TRUE + difficulty = EXP_HIGH time = 155 /datum/surgery_step/ground_nerves/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) diff --git a/code/modules/surgery/advanced/bioware/nerve_splicing.dm b/code/modules/surgery/advanced/bioware/nerve_splicing.dm index e26e57d969271..661cc512d1477 100644 --- a/code/modules/surgery/advanced/bioware/nerve_splicing.dm +++ b/code/modules/surgery/advanced/bioware/nerve_splicing.dm @@ -16,6 +16,7 @@ name = "splice nerves" accept_hand = TRUE time = 15.5 SECONDS + difficulty = EXP_HIGH preop_sound = 'sound/surgery/organ2.ogg' success_sound = 'sound/surgery/organ1.ogg' diff --git a/code/modules/surgery/advanced/bioware/vein_threading.dm b/code/modules/surgery/advanced/bioware/vein_threading.dm index c212f624c91e4..fcafcf08026df 100644 --- a/code/modules/surgery/advanced/bioware/vein_threading.dm +++ b/code/modules/surgery/advanced/bioware/vein_threading.dm @@ -16,6 +16,7 @@ name = "thread veins" accept_hand = TRUE time = 12.5 SECONDS + difficulty = EXP_HIGH preop_sound = 'sound/surgery/organ2.ogg' success_sound = 'sound/surgery/organ1.ogg' diff --git a/code/modules/surgery/advanced/brainwashing.dm b/code/modules/surgery/advanced/brainwashing.dm index 933c20340f195..e0c09326c23b8 100644 --- a/code/modules/surgery/advanced/brainwashing.dm +++ b/code/modules/surgery/advanced/brainwashing.dm @@ -33,6 +33,7 @@ /datum/surgery_step/brainwash name = "brainwash" implements = list(TOOL_HEMOSTAT = 85, TOOL_WIRECUTTER = 50, /obj/item/stack/packageWrap = 35, /obj/item/stack/cable_coil = 15) + difficulty = EXP_GENIUS time = 20 SECONDS preop_sound = 'sound/surgery/hemostat1.ogg' success_sound = 'sound/surgery/hemostat1.ogg' diff --git a/code/modules/surgery/advanced/dna_recovery.dm b/code/modules/surgery/advanced/dna_recovery.dm index a2727b553ca91..36d3ca221084b 100644 --- a/code/modules/surgery/advanced/dna_recovery.dm +++ b/code/modules/surgery/advanced/dna_recovery.dm @@ -44,6 +44,7 @@ /datum/surgery_step/dna_recovery name = "recover DNA" implements = list(/obj/item/reagent_containers/syringe = 100, /obj/item/pen = 30) + difficulty = EXP_HIGH time = 15 SECONDS chems_needed = list(/datum/reagent/medicine/rezadone, /datum/reagent/toxin/amanitin, /datum/reagent/consumable/entpoly) require_all_chems = FALSE diff --git a/code/modules/surgery/advanced/lobotomy.dm b/code/modules/surgery/advanced/lobotomy.dm index fb928ed82f510..41c2ce77c9456 100644 --- a/code/modules/surgery/advanced/lobotomy.dm +++ b/code/modules/surgery/advanced/lobotomy.dm @@ -25,6 +25,7 @@ name = "perform lobotomy" implements = list(TOOL_SCALPEL = 85, /obj/item/melee/transforming/energy/sword = 55, /obj/item/kitchen/knife = 35, /obj/item/shard = 25, /obj/item = 20) + difficulty = EXP_MASTER time = 10 SECONDS preop_sound = 'sound/surgery/scalpel1.ogg' success_sound = 'sound/surgery/scalpel2.ogg' diff --git a/code/modules/surgery/advanced/necrotic_revival.dm b/code/modules/surgery/advanced/necrotic_revival.dm index 14046abc09764..113ea23a991a0 100644 --- a/code/modules/surgery/advanced/necrotic_revival.dm +++ b/code/modules/surgery/advanced/necrotic_revival.dm @@ -21,6 +21,7 @@ /datum/surgery_step/bionecrosis name = "start bionecrosis" implements = list(/obj/item/reagent_containers/syringe = 100, /obj/item/pen = 30) + difficulty = EXP_HIGH time = 50 chems_needed = list(/datum/reagent/toxin/zombiepowder, /datum/reagent/medicine/rezadone) require_all_chems = FALSE diff --git a/code/modules/surgery/advanced/pacification.dm b/code/modules/surgery/advanced/pacification.dm index 84b09c83a5f12..325e774c1bee7 100644 --- a/code/modules/surgery/advanced/pacification.dm +++ b/code/modules/surgery/advanced/pacification.dm @@ -21,6 +21,7 @@ /datum/surgery_step/pacify name = "rewire brain" implements = list(TOOL_HEMOSTAT = 100, TOOL_SCREWDRIVER = 35, /obj/item/pen = 15) + difficulty = EXP_MASTER time = 4 SECONDS preop_sound = 'sound/surgery/hemostat1.ogg' success_sound = 'sound/surgery/hemostat1.ogg' diff --git a/code/modules/surgery/brain_surgery.dm b/code/modules/surgery/brain_surgery.dm index 5130e39ebb037..48de46aa8689a 100644 --- a/code/modules/surgery/brain_surgery.dm +++ b/code/modules/surgery/brain_surgery.dm @@ -41,6 +41,7 @@ /datum/surgery_step/fix_brain name = "fix brain" implements = list(TOOL_HEMOSTAT = 85, TOOL_SCREWDRIVER = 35, /obj/item/pen = 15) //don't worry, pouring some alcohol on their open brain will get that chance to 100 + difficulty = EXP_MASTER // do NOT attempt this without experience! repeatable = TRUE time = 12 SECONDS //long and complicated preop_sound = 'sound/surgery/hemostat1.ogg' @@ -48,13 +49,6 @@ failure_sound = 'sound/surgery/organ2.ogg' fuckup_damage = 20 -/datum/surgery_step/fix_brain/positron - name = "recalibrate brain" - implements = list(TOOL_MULTITOOL = 100, TOOL_SCREWDRIVER = 40, TOOL_HEMOSTAT = 25) //sterilizine doesn't work on IPCs so they get 100% chance, besides it's likely easier than fixing an organic brain - preop_sound = 'sound/items/tape_flip.ogg' - success_sound = 'sound/items/taperecorder_close.ogg' - failure_sound = 'sound/machines/defib_zap.ogg' - /datum/surgery/brain_surgery/can_start(mob/user, mob/living/carbon/target) var/obj/item/organ/brain/B = target.getorganslot(ORGAN_SLOT_BRAIN) if(!B) @@ -95,3 +89,15 @@ else user.visible_message("[user] suddenly notices that the brain [user.p_they()] [user.p_were()] working on is not there anymore.", span_warning("You suddenly notice that the brain you were working on is not there anymore.")) return FALSE + +/datum/surgery_step/fix_brain/positron + name = "recalibrate brain" + implements = list(TOOL_MULTITOOL = 100, TOOL_SCREWDRIVER = 40, TOOL_HEMOSTAT = 25) //sterilizine doesn't work on IPCs so they get 100% chance, besides it's likely easier than fixing an organic brain + preop_sound = 'sound/items/tape_flip.ogg' + success_sound = 'sound/items/taperecorder_close.ogg' + failure_sound = 'sound/machines/defib_zap.ogg' + +/datum/surgery_step/fix_brain/positron/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + . = ..() + if(. && user.skill_check(SKILL_TECHNICAL, EXP_MASTER)) // not really any chance + target.cure_all_traumas(TRAUMA_RESILIENCE_LOBOTOMY) diff --git a/code/modules/surgery/coronary_bypass.dm b/code/modules/surgery/coronary_bypass.dm index f4c679035a922..5b135725f04a9 100644 --- a/code/modules/surgery/coronary_bypass.dm +++ b/code/modules/surgery/coronary_bypass.dm @@ -76,6 +76,7 @@ /datum/surgery_step/coronary_bypass name = "graft coronary bypass" implements = list(TOOL_HEMOSTAT = 90, TOOL_WIRECUTTER = 35, /obj/item/stack/packageWrap = 15, /obj/item/stack/cable_coil = 5) + difficulty = EXP_HIGH time = 9 SECONDS preop_sound = 'sound/surgery/hemostat1.ogg' success_sound = 'sound/surgery/hemostat1.ogg' diff --git a/code/modules/surgery/experimental_dissection.dm b/code/modules/surgery/experimental_dissection.dm index 300718ce442ed..a81f761f4d102 100644 --- a/code/modules/surgery/experimental_dissection.dm +++ b/code/modules/surgery/experimental_dissection.dm @@ -42,7 +42,7 @@ return FALSE . = ..() -/datum/surgery_step/dissection/proc/check_value(mob/living/target, datum/surgery/experimental_dissection/ED) +/datum/surgery_step/dissection/proc/check_value(mob/user, mob/living/target, datum/surgery/experimental_dissection/ED) var/cost = EXPDIS_BASE_REWARD var/multi_surgery_adjust = 0 @@ -77,11 +77,13 @@ //multiply by multiplier in surgery cost *= ED.value_multiplier + cost *= (5 + user.get_skill(SKILL_SCIENCE)) / 5 return (cost-multi_surgery_adjust) /datum/surgery_step/dissection/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/points_earned = check_value(target, surgery) + var/points_earned = check_value(user, target, surgery) user.visible_message("[user] dissects [target], discovering [points_earned] point\s of data!", span_notice("You dissect [target], and write down [points_earned] point\s worth of discoveries!")) + user.add_exp(SKILL_SCIENCE, points_earned / 2) new /obj/item/research_notes(user.loc, points_earned, TECHWEB_POINT_TYPE_GENERIC, "biology") var/obj/item/bodypart/L = target.get_bodypart(BODY_ZONE_CHEST) target.apply_damage(80, BRUTE, L) @@ -91,7 +93,7 @@ /datum/surgery_step/dissection/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) user.visible_message("[user] dissects [target]!", span_notice("You dissect [target], but do not find anything particularly interesting.")) - new /obj/item/research_notes(user.loc, round(check_value(target, surgery)) * 0.01, TECHWEB_POINT_TYPE_GENERIC, "biology") + new /obj/item/research_notes(user.loc, round(check_value(user, target, surgery)) * 0.01, TECHWEB_POINT_TYPE_GENERIC, "biology") var/obj/item/bodypart/L = target.get_bodypart(BODY_ZONE_CHEST) target.apply_damage(80, BRUTE, L) return TRUE diff --git a/code/modules/surgery/hepatectomy.dm b/code/modules/surgery/hepatectomy.dm index 2ad6b8123424f..ddbb9cfa3b13c 100644 --- a/code/modules/surgery/hepatectomy.dm +++ b/code/modules/surgery/hepatectomy.dm @@ -35,6 +35,7 @@ name = "excise damaged lung node" implements = list(TOOL_SCALPEL = 95, /obj/item/melee/transforming/energy/sword = 65, /obj/item/kitchen/knife = 45, /obj/item/shard = 35) + difficulty = EXP_HIGH time = 4.2 SECONDS preop_sound = 'sound/surgery/scalpel1.ogg' success_sound = 'sound/surgery/organ1.ogg' diff --git a/code/modules/surgery/ipc_revival.dm b/code/modules/surgery/ipc_revival.dm index f432e4a8a574d..4e82973d0565b 100644 --- a/code/modules/surgery/ipc_revival.dm +++ b/code/modules/surgery/ipc_revival.dm @@ -24,6 +24,7 @@ time = 5 SECONDS repeatable = TRUE // so you don't have to restart the whole thing if it fails implements = list(TOOL_MULTITOOL = 100, TOOL_WIRECUTTER = 50) + difficulty = EXP_HIGH preop_sound = 'sound/items/tape_flip.ogg' success_sound = 'sound/items/taperecorder_close.ogg' failure_sound = 'sound/machines/defib_zap.ogg' diff --git a/code/modules/surgery/limb_augmentation.dm b/code/modules/surgery/limb_augmentation.dm index 165912ac0c16b..e5f90b4aae3bb 100644 --- a/code/modules/surgery/limb_augmentation.dm +++ b/code/modules/surgery/limb_augmentation.dm @@ -94,7 +94,8 @@ "[user] successfully augments [target]'s [parse_zone(target_zone)] with [tool]!", "[user] successfully augments [target]'s [parse_zone(target_zone)]!") log_combat(user, target, "augmented", addition="by giving him new [parse_zone(target_zone)] COMBAT MODE: [user.combat_mode ? "ON" : "OFF"]") - var/points = 150 * (target.client ? 1 : 0.1) + var/points = 150 * (target.client ? 1 : 0.1) * (5 + user.get_skill(SKILL_SCIENCE)) / 5 + user.add_exp(SKILL_SCIENCE, points / 2) SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = points)) to_chat(user, "The augment uploads diagnostic data to the research cloud, giving a bonus of research points!") else diff --git a/code/modules/surgery/lobectomy.dm b/code/modules/surgery/lobectomy.dm index 924059fb0b7ff..3cc5bea40fdc8 100644 --- a/code/modules/surgery/lobectomy.dm +++ b/code/modules/surgery/lobectomy.dm @@ -35,6 +35,7 @@ name = "excise damaged lung node" implements = list(TOOL_SCALPEL = 95, /obj/item/melee/transforming/energy/sword = 65, /obj/item/kitchen/knife = 45, /obj/item/shard = 35) + difficulty = EXP_HIGH time = 4.2 SECONDS preop_sound = 'sound/surgery/scalpel1.ogg' success_sound = 'sound/surgery/organ1.ogg' diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index 16f2adf270255..03d963f99da73 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -161,7 +161,7 @@ SSblackbox.record_feedback("tally", "surgeries_completed", 1, type) qdel(src) -/datum/surgery/proc/get_probability_multiplier() +/datum/surgery/proc/get_probability_multiplier(mob/user, datum/surgery_step/surgery_step, skill_checked = SKILL_PHYSIOLOGY) var/probability = 0.5 var/turf/T = get_turf(target) @@ -171,7 +171,7 @@ probability = SB.success_chance break - return probability + success_multiplier + return probability + success_multiplier + ((user.get_skill(skill_checked) - surgery_step.difficulty) / 10) /datum/surgery/proc/get_icon() var/mutable_appearance/new_icon = mutable_appearance(icon, icon_state) diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index cdcaeba3fee3f..127e9522179eb 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -34,7 +34,10 @@ /// Sound played if the step succeeded var/success_sound /// Sound played if the step fails - var/failure_sound + var/failure_sound + + /// Level of skill required to not have increased failure chance + var/difficulty = EXP_MID /datum/surgery_step/proc/try_op(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) var/success = FALSE @@ -102,22 +105,24 @@ return FALSE play_preop_sound(user, target, target_zone, tool, surgery) - if(is_species(user, /datum/species/lizard/ashwalker/shaman))//shaman is slightly better at surgeries - speed_mod *= 0.9 - if(istype(user.get_item_by_slot(ITEM_SLOT_GLOVES), /obj/item/clothing/gloves/color/latex)) var/obj/item/clothing/gloves/color/latex/surgicalgloves = user.get_item_by_slot(ITEM_SLOT_GLOVES) speed_mod *= surgicalgloves.surgeryspeed + + var/obj/item/bodypart/operated_bodypart = target.get_bodypart(target_zone) + if(!operated_bodypart) // if the limb is missing, check what it should be attached to instead + operated_bodypart = target.get_bodypart(BODY_ZONE_CHEST) + var/skill_checked = operated_bodypart?.status == BODYPART_ROBOTIC ? SKILL_TECHNICAL : SKILL_PHYSIOLOGY var/previous_loc = user.loc // If we have a tool, use it - if((tool && tool.use_tool(target, user, time * speed_mod, robo_check = TRUE)) || do_after(user, time * speed_mod, target)) + if((tool && tool.use_tool(target, user, time * speed_mod, skill_check = skill_checked)) || do_after(user, time * speed_mod, target, skill_check = skill_checked)) var/prob_chance = 100 if(implement_type) //this means it isn't a require hand or any item step. prob_chance = implements[implement_type] - prob_chance *= surgery.get_probability_multiplier() + prob_chance *= surgery.get_probability_multiplier(user, src, skill_checked) // Blood splatters on tools and user if(tool && prob(bloody_chance)) @@ -138,10 +143,8 @@ target.balloon_alert(user, "Failure!") play_failure_sound(user, target, target_zone, tool, surgery) if(iscarbon(target) && !HAS_TRAIT(target, TRAIT_SURGERY_PREPARED) && target.stat != DEAD && !IS_IN_STASIS(target) && fuckup_damage) //not under the effects of anaesthetics or a strong painkiller, harsh penalty to success chance - if(!issilicon(user) && !HAS_TRAIT(user, TRAIT_SURGEON)) //borgs and abductors are immune to this - var/obj/item/bodypart/operated_bodypart = target.get_bodypart(target_zone) - if(!operated_bodypart || operated_bodypart?.status == BODYPART_ORGANIC) //robot limbs don't feel pain - cause_ouchie(user, target, target_zone, tool, advance) + if(!issilicon(user) && !HAS_TRAIT(user, TRAIT_SURGEON) && (!operated_bodypart || operated_bodypart?.status == BODYPART_ORGANIC)) //robot limbs don't feel pain + cause_ouchie(user, target, target_zone, tool, advance) if(advance && !repeatable) surgery.status++ if(surgery.status > surgery.steps.len) diff --git a/icons/mob/screen_clockwork.dmi b/icons/mob/screen_clockwork.dmi index c72e60f399827..0be58b426b261 100644 Binary files a/icons/mob/screen_clockwork.dmi and b/icons/mob/screen_clockwork.dmi differ diff --git a/icons/mob/screen_detective.dmi b/icons/mob/screen_detective.dmi index bbc0f11c28708..be7d5dd1162af 100644 Binary files a/icons/mob/screen_detective.dmi and b/icons/mob/screen_detective.dmi differ diff --git a/icons/mob/screen_midnight.dmi b/icons/mob/screen_midnight.dmi index e644c92d514e9..90047b60728c3 100644 Binary files a/icons/mob/screen_midnight.dmi and b/icons/mob/screen_midnight.dmi differ diff --git a/icons/mob/screen_obsidian.dmi b/icons/mob/screen_obsidian.dmi index fc8c6ef62f0ab..4fde0a765383b 100644 Binary files a/icons/mob/screen_obsidian.dmi and b/icons/mob/screen_obsidian.dmi differ diff --git a/icons/mob/screen_operative.dmi b/icons/mob/screen_operative.dmi index 916adb6e41c23..d60b3cb8ada0a 100644 Binary files a/icons/mob/screen_operative.dmi and b/icons/mob/screen_operative.dmi differ diff --git a/icons/mob/screen_plasmafire.dmi b/icons/mob/screen_plasmafire.dmi index 3a20534226d9a..abe7d252d1e0f 100644 Binary files a/icons/mob/screen_plasmafire.dmi and b/icons/mob/screen_plasmafire.dmi differ diff --git a/icons/mob/screen_retro.dmi b/icons/mob/screen_retro.dmi index b23f7a2cae585..76e591b68099c 100644 Binary files a/icons/mob/screen_retro.dmi and b/icons/mob/screen_retro.dmi differ diff --git a/icons/mob/screen_slimecore.dmi b/icons/mob/screen_slimecore.dmi index 358220865d307..82a8adf5e5e6b 100644 Binary files a/icons/mob/screen_slimecore.dmi and b/icons/mob/screen_slimecore.dmi differ diff --git a/icons/mob/skills.dmi b/icons/mob/skills.dmi new file mode 100644 index 0000000000000..41cc04d6738bd Binary files /dev/null and b/icons/mob/skills.dmi differ diff --git a/tgui/packages/tgui/interfaces/PersonalCrafting.tsx b/tgui/packages/tgui/interfaces/PersonalCrafting.tsx index 057f46b84b793..790d4a01421b5 100644 --- a/tgui/packages/tgui/interfaces/PersonalCrafting.tsx +++ b/tgui/packages/tgui/interfaces/PersonalCrafting.tsx @@ -100,9 +100,15 @@ type Recipe = { reqs: Atoms; tool_behaviors: string[]; tool_paths: string[]; + skill_requirements: string[]; foodtypes: string[]; }; +type Skill = { + skill: string; + level: number; +}; + type Diet = { liked_food: string[]; disliked_food: string[]; @@ -707,6 +713,14 @@ const RecipeContent = ({ item, craftable, busy, mode, diet }, context) => { ))} )} + {(item.skill_requirements) && ( + + + {item.skill_requirements.map((skill_req, index) => ( + + ))} + + )} @@ -797,6 +811,23 @@ const ToolContent = ({ tool }) => { ) as any; }; +const SkillContent = ({ skill, level }) => { + return ( + + + + {skill} {level} + + + ) as any; +}; + const GroupTitle = ({ title }) => { return ( diff --git a/tgui/packages/tgui/interfaces/SkillMenu.js b/tgui/packages/tgui/interfaces/SkillMenu.js new file mode 100644 index 0000000000000..de3ef065d4049 --- /dev/null +++ b/tgui/packages/tgui/interfaces/SkillMenu.js @@ -0,0 +1,127 @@ +import { classes } from 'common/react'; +import { useBackend } from '../backend'; +import { Box, Button, Icon, LabeledList, ProgressBar, Section, Stack, Tooltip } from '../components'; +import { Window } from '../layouts'; + +export const SkillMenu = (props, context) => { + const { act, data } = useBackend(context); + const { skill_points, allocated_points } = data; + + return ( + + +
+ + + + + + + +
+
+
+ ); +}; + +const AdjustSkill = (props, context) => { + const { act, data } = useBackend(context); + const { skill, name, index, tooltip, color } = props; + const { skills, skill_points, allocated_points, exceptional_skill, exp_per_level } = data; + const { base, allocated, exp_progress } = skills[index]; + + const exp_required = exp_per_level * Math.pow(2, base); + + return ( + + + + + + + +