diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index a87ad394b632..46890d72523b 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 7c0931b1e590..60459e9f14cf 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 000000000000..8d30f633578f --- /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 b0317d89f59b..ac442d3d41e9 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 ebb0822e420c..21d8168e317f 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 75f91ddf1209..8441488b42b0 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 f23d26b98b84..365589be4f6d 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 2c3ce079dede..85099e5bdcd7 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 591016523f3d..2d032a89cb64 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 b2439f573693..520751835b1e 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 f253faac27ab..ab1fbf82e8fd 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 3eb9e427b382..e1575b94ddc6 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 57ca6e47f5ac..22edf1baca0e 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 16f401c761b2..ccf26a221f20 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 40f3cefa6fa2..89871775d625 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 9c62ed2055a0..249768b361c0 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 e88d4fe605f9..15009ff4dc17 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 d0425d58b419..4c3ca104aeb5 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 a28b024db458..000000000000 --- 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 7d6319b02df0..32d789f65578 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 8f20d7be0cae..3c6dfa4854a2 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 e2b7772915e6..f73e43104f20 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 b216f9ca19ce..f3a578ba23a4 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 c3424530b638..9f64e3f35360 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 20800284facb..daa7d3591035 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 20e3a7ab5b7f..afe8e2adafe5 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/callback/extra_checks) +/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,6 +45,10 @@ 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 @@ -85,10 +93,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 @@ -99,6 +109,8 @@ if(bar) QDEL_NULL(bar) + if(skill_icon) + QDEL_NULL(skill_icon) return ..() @@ -119,6 +131,7 @@ if(!user_client) //Disconnected, already gone. return user_client.images -= bar + user_client.images -= skill_icon user_client = null @@ -141,6 +154,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) @@ -168,6 +186,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) @@ -179,5 +199,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 0b6bcc355f44..852fd7d92ca3 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 4069a9a9519f..0a4c6a2abb31 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 48767f764ac8..c95d5f5989cc 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 3d6099d25e91..224357ec3d76 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 88e15955d55a..9d75413268af 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 6886963b44f9..70f12ac67119 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 c095f79caf72..bd3904f9be84 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 d412c0c1adfe..26cefb1e00bf 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 903dd10207cb..5d1ea7934579 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 a0ebc576b7d9..0d6dbe1b9d79 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 70a402fd0664..b54109424fb8 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 f8e31fc33643..9c92e5e59938 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 3c393210f154..3e2644baeeec 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 9792539ef6fc..33c82f565fed 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 2aea613e5d82..c028d5b2bcc6 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 741da1e7256b..e262a1d8af55 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 5f202eb38222..fe17d95cb452 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 6594f93cda2a..1e75d3558990 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 7ea784f76bdb..f943027b314c 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 c02c5c61dd9c..68a2e413ee5b 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 bf531f1e6554..c7257b6d44ea 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 b74fab47b9d4..0ffafd146f6f 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 a84f1f2815df..9aa2837e81f1 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 d5de69d59b08..3b125a3f16dc 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 3937d24d5923..793334d4b9cf 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 53778771773e..ab5f8590eeea 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 50af87f221d6..622b39105134 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 d954f53d8d11..d8f73327183a 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 f53c70dbb386..a6cc02015243 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 61fe38fcfae0..50d18b0c6de1 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 1f6a890a010f..e8bacbe9ea96 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 1e888fe5a1bf..74de8046bfed 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 323dafd1e3a9..a8c69a303eb6 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 51a277060ef7..9ed9411eb882 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 62e0e73fdece..d4a0b23e6ed0 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 + (text2num(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 b5878d2ddc29..8206a9821bbf 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 9df34178ac02..fca0d5107e25 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 01444973c938..a2a8859c0c84 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 3b1b842dd426..026452d5da07 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 e413975af63f..a70d2dada643 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 86f2da3512ca..a714b76bf567 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 5beb433c9e41..2538fe34e335 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 de80adfed05c..9c4659922832 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 c24f83422b19..844de03cb3f6 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 b2b714a9d6c1..0c077e05dce5 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 c754aed3dc92..15c101c8eee5 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 9140376187a4..3f50f875df7d 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 6b7a9d4090ad..fe974321614e 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 4d5c7892e270..f4b40c321656 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 21537077bb69..ac705c8dbc02 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 ffd5560039da..de234267b1b1 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 a97b7dca3ab0..ed2aa450be1c 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 fed23668d65b..47b0a97fffc5 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 af917644e4e2..9b392fd37f2b 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 aab47082a52a..999373f05f36 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 918a071ff94d..3ec880243ef4 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 7e0791411c37..67ceaf5c9768 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 a18772061cfe..74659a68cbb9 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 0ecbf2db6693..768ca05f65e5 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 46788af77c40..f704a81aa009 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 e19ac19e5a5f..14dfc26ea694 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 3594372e3062..7dd0b3fa1aa9 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 876399540bd0..20aae3b90130 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 87284f977c61..fccc7939b2e1 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 76a869459e3a..a164c67ac0bf 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 9a2fb5ec28e8..831766fb4f4f 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 69710b36a3f4..06abeb0e403d 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 93079128cad4..a5822c060049 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 2a00e46606ed..950060208206 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 71dbd6c68c36..9d02cfde6563 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 d40d1473418c..037cbb99c868 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 ade9a58e7d29..1c4993f950c8 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 8d16f1f25d68..0382ba8e374a 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 af6cd3aadd05..9830a8120233 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 dabe7b47900e..47b6fcdb2542 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 cbfc194eb95c..9669a7fa63cc 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 19ec66257ac7..e8e40b4405d7 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 e0f1e9c14e9a..9e4c71a63b9d 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 c820c8613671..412cbd4c717d 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 bccb0e77a51c..c2789a3ddf42 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 23d14a470841..2466a71b1c75 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 fd1d1df50a8a..117152d8bcf8 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 392c7538fdbb..145903432448 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 19dcfd4ee5cd..bb46be700fdb 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 e6feab8ec934..46b7fba4e0bc 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 b4a5d18f864b..e98a50323aeb 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 9b8d190853cf..a4e4e5eb95af 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 32352725b522..3ebabec7268d 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 32d1726b112c..b1cac2533e18 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 e26e57d96927..661cc512d147 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 c212f624c91e..fcafcf08026d 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 933c20340f19..e0c09326c23b 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 a2727b553ca9..36d3ca221084 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 fb928ed82f51..41c2ce77c945 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 14046abc0976..113ea23a991a 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 84b09c83a5f1..325e774c1bee 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 5130e39ebb03..48de46aa8689 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 f4c679035a92..5b135725f04a 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 300718ce442e..a81f761f4d10 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 2ad6b8123424..ddbb9cfa3b13 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 f432e4a8a574..4e82973d0565 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 165912ac0c16..e5f90b4aae3b 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 924059fb0b7f..3cc5bea40fdc 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 16f2adf27025..03d963f99da7 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 cdcaeba3fee3..127e9522179e 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 c72e60f39982..0be58b426b26 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 bbc0f11c2870..be7d5dd1162a 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 e644c92d514e..90047b60728c 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 fc8c6ef62f0a..4fde0a765383 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 916adb6e41c2..d60b3cb8ada0 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 3a20534226d9..abe7d252d1e0 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 b23f7a2cae58..76e591b68099 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 358220865d30..82a8adf5e5e6 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 000000000000..41cc04d6738b 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 057f46b84b79..790d4a01421b 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 000000000000..de3ef065d404 --- /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 ( + + + + + + + +