Skills System Revival: The Things She Remembered Had Never Been Her Own (#21853)

This PR is a revisit to the previously derelict PR #20159 that has been
unfinished for sometime now. More details about it in general can be
found here:
https://github.com/orgs/Aurorastation/projects/2?pane=issue&itemId=53167153

For awhile I've been talking about "Things I've been doing but it would
be really nice to do them with a skills system", or "And here's how I
would put this into the skills system when it's done". The main thing
that was stopping me from building it myself was having poor real life
skills in UI code and in DB code. However, I've gotten permission to
resume this PR, which has already completed the steps I would not have
been able to do myself. The rest of the PR fits well into my skillset as
a dev.

I'm opening this PR as a draft so as to enable my dev environment to
locally track all the previously modified files. I'll take this PR out
of draft and give this a full writeup when I have more work to show for
the PR this weekend.

### TODO

- [x] Rework a decent chunk of the currently existing skills to no
longer require hardcoded inserts into other systems. EG, converting from
classical ss13 methods, to modern /tg/-style ECS coding methods that
work off of component-signal patterns.
- [x] Make sure all of the existing skills have actual game
functionality (I won't PR a 2016 Baystation12 situation where 90% of the
skills are fluff only)
- [x] Add the various skills not yet made but are necessary for
completion sake, EG: Pilot (Spacecraft), Gunnery, Pilot (Walkers).
- [x] Examine each existing job in the game and assess whether it should
have a skill made with it in mind, or if it's covered by an existing
skill.
- [x] TO DISCUSS, BUT NOT ESSENTIAL: Additional skill proposals not
currently in the pre-existing TODO list, proposing subcategories.
- [x] Ensure that the previous TODO list is completed.

### Current Skills
The current list of skills, checkmarked for if I've completed them/they
have actual game mechanics. Or if we're just relegating them to separate
PRs. Originally this list was going to be forced to visit for a bare
minimum "does at least one thing" requirement, but now that is being
forgone due to this PR ballooning out of control and in complexity, as
well as development time overruns.

- [x] Bartending
- [x] Cooking
- [x] Gardening
- [x] Entertaining
- [x] Electrical Engineering
- [x] Mechanical Engineering
- [x] Atmospherics Systems
- [x] Reactor Systems
- [x] Medicine
- [x] Surgery
- [x] Pharmacology
- [x] Anatomy
- [x] Forensics
- [x] Robotics
- [x] Pilot: Spacecraft
- [x] Pilot: Exosuits
- [x] Research
- [x] Xenobotany
- [x] Xenoarchaeology
- [x] Xenobiology
- [x] Unarmed Combat
- [x] Armed Combat
- [x] Firearms
- [x] Leadership

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Matt Atlas <liermattia@gmail.com>
Co-authored-by: FabianK3 <21039694+FabianK3@users.noreply.github.com>
Co-authored-by: Matt Atlas <mattiathebest2000@hotmail.it>
This commit is contained in:
VMSolidus
2026-04-18 14:33:48 +00:00
committed by GitHub
co-authored by Matt Atlas FabianK3 Matt Atlas
parent 2948b1dc87
commit 260f744906
107 changed files with 2965 additions and 185 deletions
+33
View File
@@ -1,2 +1,35 @@
#define COMSIG_BASENAME_RENAME "base_name_rename"
#define COMSIG_BASENAME_SETNAME "base_name_setname"
#define ARMED_COMBAT_SKILL_COMPONENT /datum/component/skill/armed_combat
#define FIREARMS_SKILL_COMPONENT /datum/component/skill/firearms
#define UNARMED_COMBAT_SKILL_COMPONENT /datum/component/skill/unarmed_combat
#define PILOT_MECHS_SKILL_COMPONENT /datum/component/skill/pilot_mechs
#define PILOT_SPACECRAFT_SKILL_COMPONENT /datum/component/skill/pilot_spacecraft
#define ROBOTICS_SKILL_COMPONENT /datum/component/skill/robotics
#define ARCHAOLOGY_SKILL_COMPONENT /datum/component/skill/archaology
#define RESEARCH_SKILL_COMPONENT /datum/component/skill/research
#define XENOBIOLOGY_SKILL_COMPONENT /datum/component/skill/xenobiology
#define XENOBOTANY_SKILL_COMPONENT /datum/component/skill/xenobotany
#define BARTENDING_SKILL_COMPONENT /datum/component/skill/bartending
#define COOKING_SKILL_COMPONENT /datum/component/skill/cooking
#define ENTERTAINING_SKILL_COMPONENT /datum/component/skill/entertaining
#define GARDENING_SKILL_COMPONENT /datum/component/skill/gardening
#define MEDICINE_SKILL_COMPONENT /datum/component/skill/medicine
#define SKILL_COMPONENT /datum/component/skill
#define MORALE_COMPONENT /datum/component/morale
#define ELECTRICAL_ENGINEERING_SKILL_COMPONENT /datum/component/skill/electrical_engineering
#define MECHANICAL_ENGINEERING_SKILL_COMPONENT /datum/component/skill/electrical_engineering
#define ATMOSPHERICS_SYSTEMS_SKILL_COMPONENT /datum/component/skill/atmospherics_systems
#define REACTOR_SYSTEMS_SKILL_COMPONENT /datum/component/skill/reactor_systems
#define LEADERSHIP_SKILL_COMPONENT /datum/component/skill/leadership
#define ANATOMY_SKILL_COMPONENT /datum/component/skill/anatomy
#define FORENSICS_SKILL_COMPONENT /datum/component/skill/forensics
#define PHARMACOLOGY_SKILL_COMPONENT /datum/component/skill/pharmacology
#define SURGERY_SKILL_COMPONENT /datum/component/skill/surgery
/**
* Trinary-Boolean helper that either returns null, or the skill level of a given skill component(which can be zero).
* It will ALWAYS be null if you try to use this to check for something that isn't a skill component, so just don't. Refer to the list above this function to see what your options are.
* Therefore, merely checking if (!this) is not sufficient to use this function, and instead you should be using isnull() on it to determine whether or not the skill exists.
*/
#define GET_SKILL_LEVEL(user, comp) astype(user.GetComponent(comp), SKILL_COMPONENT)?.skill_level
+37
View File
@@ -89,3 +89,40 @@
/*******Component Specific Signals*******/
// /obj/item/gun signals
#define COMSIG_GUN_TOGGLE_FIRING_MODE "gun_toggle_firing_mode"
#define COMSIG_BEFORE_GUN_FIRE "before_gun_fire"
// Mech signals
#define COMSIG_MECH_MOVE_WASD "mech_move_wasd"
#define COMSIG_MECH_MOVE_STRAFE "mech_move_strafe"
#define COMSIG_MECH_TOGGLE_POWER "mech_toggle_power"
// Unarmed Combat Signals
#define COMSIG_UNARMED_HARM_ATTACKER "unarmed_harm_attacker"
#define COMSIG_UNARMED_HARM_DEFENDER "unarmed_harm_defender"
#define COMSIG_UNARMED_DISARM_ATTACKER "unarmed_disarm_attacker"
#define COMSIG_UNARMED_DISARM_DEFENDER "unarmed_disarm_defender"
// Bartending Skill Signals
#define COMSIG_CONTAINER_DRANK "container_drank"
// Hydroponics Signals
/// Signal raised against the harvester of a plant during the Harvest() proc.
#define COMSIG_PLANT_HARVESTER "plant_harvester"
/// Signal raised against the plant being harvested during the Harvest() proc.
#define COMSIG_PLANT_HARVESTED "plant_harvested"
// Armed Combat Signals
#define COMSIG_APPLY_HIT_EFFECT "apply_hit_effect"
// Various computer signals for interrupting via skill or other effects.
#define COMSIG_USE_REACTOR_COMPUTER "use_reactor_computer"
#define COMSIG_USE_MECH_FAB "use_mech_fab"
+2
View File
@@ -73,6 +73,8 @@
#define QDEL_NULL_LIST(x) if(x) { for(var/y in x) { qdel(y) }}; if(x) {x.Cut(); x = null } // Second x check to handle items that LAZYREMOVE on qdel.
#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); }
/// Variant on QDEL_LIST that applies the force optional input to the qdel() args.
#define QDEL_LIST_FORCE(L) if(L) { for(var/I in L) qdel(I, force = TRUE); L.Cut(); }
#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(______qdel_list_wrapper), L), time, TIMER_STOPPABLE)
#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); }
#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); }
+39
View File
@@ -0,0 +1,39 @@
/// You don't know anything about this subject.
#define SKILL_LEVEL_UNFAMILIAR 1
/// You're familiar with this subject, either by reading into it or by doing some courses.
#define SKILL_LEVEL_FAMILIAR 2
/// You've been formally trained in this subject. Typically, this is the minimum level for a job.
#define SKILL_LEVEL_TRAINED 3
/// You have some training and a good amount of experience in this subject.
#define SKILL_LEVEL_PROFESSIONAL 4
/// An everyday skill is a skill that can be picked up normally. Think like mixing a drink, growing a plant, cooking, and so on.
#define SKILL_CATEGORY_EVERYDAY "Everyday"
#define SKILL_SUBCATEGORY_SERVICE "Service"
#define SKILL_SUBCATEGORY_MUNDANE "Mundane"
/// Occupational skills are the skills necessary for you to do your job to a minimum degree.
#define SKILL_CATEGORY_OCCUPATIONAL "Occupational"
#define SKILL_SUBCATEGORY_MEDICAL "Medical"
#define SKILL_SUBCATEGORY_ENGINEERING "Engineering"
#define SKILL_SUBCATEGORY_SCIENCE "Science"
#define SKILL_SUBCATEGORY_OPERATIONS "Operations"
///A combat skill is a skill that has a direct effect in combat. These have an increased cost.
#define SKILL_CATEGORY_COMBAT "Combat"
#define SKILL_SUBCATEGORY_RANGED "Ranged"
#define SKILL_SUBCATEGORY_MELEE "Melee"
#define SKILL_SUBCATEGORY_SUPPORT "Support"
#define BASE_SKILL_POINTS_COMBAT 4
#define BASE_SKILL_POINTS_OCCUPATIONAL 8
#define BASE_SKILL_POINTS_EVERYDAY 8
/// Critical success on a difficulty class. Obtained by rolling a nat 20 or +10 over the DC. Used for skills at the moment, will be used for more later.
#define ROLL_RESULT_CRITICAL_SUCCESS 2
/// Normal success on a difficulty class. Obtained by rolling equal or higher than the DC. Used for skills at the moment, will be used for more later.
#define ROLL_RESULT_SUCCESS 1
/// Normal failure on a difficulty class. Obtained by rolling less than the DC. Used for skills at the moment, will be used for more later.
#define ROLL_RESULT_FAILURE 0
/// Critical failure on a difficulty class. Obtained by rolling a nat 1 or -10 under the DC. Used for skills at the moment, will be used for more later.
#define ROLL_RESULT_CRITICAL_FAILURE -1
+15
View File
@@ -1228,3 +1228,18 @@ GLOBAL_LIST_INIT(wall_items, typecacheof(list(
if(final_x || final_y)
return locate(final_x, final_y, T.z)
/**
* Basically just transforms the ROLL_RESULT defines into text.
*/
/proc/roll_result_text(roll)
switch(roll)
if(ROLL_RESULT_CRITICAL_SUCCESS)
return "critical success"
if(ROLL_RESULT_SUCCESS)
return "success"
if(ROLL_RESULT_FAILURE)
return "failure"
if(ROLL_RESULT_CRITICAL_FAILURE)
return "critical failure"
crash_with("Roll result given invalid roll: [roll]")
+1
View File
@@ -188,6 +188,7 @@ This calls [atom/proc/tool_act], among others.
//Called when a weapon is used to make a successful melee attack on a mob. Returns whether damage was dealt.
/obj/item/proc/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone)
var/power = force
SEND_SIGNAL(user, COMSIG_APPLY_HIT_EFFECT, target, src, &power, &hit_zone)
if((user.mutations & HULK))
power *= 1.25
if(user.is_berserk())
+32
View File
@@ -0,0 +1,32 @@
SUBSYSTEM_DEF(skills)
name = "Skills"
flags = SS_NO_FIRE
/// This is essentially the list we use to read skills in the character setup.
var/list/skill_tree = list()
/// The set of all skills that are "forced" in order to guarantee necessary components are applied.
var/list/required_skills = list()
/datum/controller/subsystem/skills/Initialize()
// Initialize the skill category lists first.
// This creates linked lists as follows: "Science" -> empty list
for(var/singleton/skill_category/skill_category as anything in GET_SINGLETON_SUBTYPE_LIST(/singleton/skill_category))
skill_tree[skill_category] = list()
// Now, initialize all the skills.
// What actually goes on here: we want a tree that we can traverse programmatically.
// To do that, we first of all make empty lists above with all the categories (they're singletons so we can easily iterate over them).
// Next, we add the empty subcategory lists if they're not present. At this point, the tree would look like "Combat" -> "Melee" -> empty list
// After that's done, if our skill is not present, add it to the empty list of the subcategory.
for(var/singleton/skill/skill as anything in GET_SINGLETON_SUBTYPE_LIST(/singleton/skill))
if (skill.required)
required_skills += skill.type
var/singleton/skill_category/skill_category = GET_SINGLETON(skill.category)
if(!(skill.subcategory in skill_tree[skill_category]))
skill_tree[skill_category] |= skill.subcategory
skill_tree[skill_category][skill.subcategory] = list()
if(!(skill in skill_tree[skill_category][skill.subcategory]))
skill_tree[skill_category][skill.subcategory] |= skill
return SS_INIT_SUCCESS
+83
View File
@@ -0,0 +1,83 @@
/**
* Container for a single moodlet to be associated with a Morale Component.
* Only one of each type of moodlet is allowed to exist in a mood component at a time.
* If you're making a new source of moodlets, add a new child of this type.
*/
ABSTRACT_TYPE(/datum/moodlet)
/**
* How much this moodlet modifies its owner's morale by.
* This is simple summed exactly once when the moodlet is created.
* If anything needs to Set the value of a moodlet, they have to do so by calling set_moodlet().
* You may only Get this value by calling get_morale_modifier().
*
* This is similar to { get; private set; }. Under no circumstances are outside functions ever allowed to directly change this var because other vars depend on it.
* But there are public procs available to interact with it which obey its own internal rules.
*/
VAR_PRIVATE/morale_modifier = 0.0 // Positive and negative floating points are allowed.
/**
*
*/
var/moodlet_descriptor = "It's a moodlet!"
/**
* How long this moodlet will last if not refreshed. For Aurora's purposes, moodlets are targeted as having "Small effect, extreme duration".
* This is by design to encourage players to "Visit the chef at least once a round to do a little RP", while avoiding having moodlet collection interrupt the flow of expeditions.
*/
var/duration = 2.0 HOURS
/**
* The target time (in real life seconds) that the moodlet will self-terminate on.
* This is set automatically during the New() creation of moodlets.
* This can be updated by calling refresh_moodlet() to reset the time to die.
*/
var/time_to_die = 0.0
/**
* Moodlets should always have an associated morale component, but the component owns the moodlets, not the other way around.
* For the convenience of refreshing individual moodlets, they hold a weakref to their owner.
* This is set to private here so that we can assert that it will always be a morale component.
*/
VAR_PRIVATE/datum/weakref/morale_component
/datum/moodlet/New(datum/component/morale/_morale_component, set_points)
time_to_die = duration + REALTIMEOFDAY
morale_component = WEAKREF(_morale_component)
if (set_points) morale_modifier = set_points
_morale_component.add_morale_points(morale_modifier)
/datum/moodlet/Destroy(force)
if (force)
// This will be forced when a Morale Component is deleted directly, as it QDEL_NULL_LIST's its own moodlets.
return ..()
// Else if the moodlet is deleted directly rather than its parent.
var/datum/component/morale/parent = morale_component.resolve()
if (!parent || !parent.moodlets[src])
return ..()
// Clean the effects of this moodlet from the parent.
parent.moodlets -= src
parent.add_morale_points(-morale_modifier)
return ..()
/datum/moodlet/proc/get_morale_modifier()
return morale_modifier
/datum/moodlet/proc/set_moodlet(new_modifier)
// We can skip the istype() in this case since the held weakref is asserted by VAR_PRIVATE to always be a morale component.
var/datum/component/morale/possible_morale = morale_component.resolve()
if (!possible_morale && !QDELING(src))
// Owner didn't exist, the moodlet has no need to exist either.
qdel(src)
return
// Add the difference between the new modifier and the old morale points.
// This should come out to no change if old and new are the same.
possible_morale.add_morale_points(new_modifier - morale_modifier)
// Then set the current morale points to the new ones.
morale_modifier = new_modifier
/datum/moodlet/proc/refresh_moodlet()
time_to_die = REALTIMEOFDAY + duration
@@ -0,0 +1,230 @@
/**
* Having a Morale Component allows a character to receive and benefit from Moodlets, providing a variety of buffs(or debuffs) depending on the total morale points.
* This component acts as both proof a mob can be affected by morale, as well as a method of tracking the effects of morale on each system.
*/
/datum/component/morale
/**
* The set of all moodlets associated with this Morale Component. These are also tightly controlled in their initialization, but are not private.
* That doesn't mean you need to be setting them anywhere other than in moodlets.dm.
* load_moodlet() is your best bet for "Add or Get" a moodlet, and is 100% of the time what you want to use if you're trying to make sure someone has a moodlet.
*/
var/list/datum/moodlet/moodlets = list()
/**
* The current sum total of morale_points. This var is intentionally private because it is self-managed by the component, and should never be set directly.
* If you need the contents of this var outside of the component, you MUST use get_morale_points().
*/
VAR_PRIVATE/morale_points = 0.0 // Positive and negative floating points are allowed.
/**
* The current "Morale Ratio" calculated in advance as the Hyperbolic Tangent of morale_points. This var is self-managed by the component and should never be set directly.
* This gets updated whenever morale_points are changed, and is used by the Morale Component to handle fast calculations of its various effects.
* If you need the contents of this var outside of the component, you MUST use get_morale_ratio().
*
* morale_ratio is NEVER to be set by anything outside of the component.
*/
VAR_PRIVATE/morale_ratio = 0.0 // Positive and negative floating points are allowed.
/**
* The "B" constant in the equation for y = Atanh(Bx + C).
* This constant is not arbitrary, it was carefully selected such that the equation will give "75% of its effect" at 50 morale points, and "96% of its effect" at 100 morale.
* This allows for there to be an effect of diminishing returns for chasing ever increasingly more morale points, while front-loading the bulk of the effects at a specific amount of moodlets.
* Since the effects of morale are a "Logistic Curve", "100% of the morale effect" is only ever obtained at +INFINITY.
* This also goes for the opposite direction, morale penalties max out only at -INFINITY points, but get to "75% of the penalty effect" at -50 points.
*
* The actual "Effects" of morale are to be per-signal, and are defined by the A value in y = Atanh(Bx + C)
* This is private for a reason, if you need to change it, do so by using set_beta_value(), which will also make the component recalculate its morale ratio.
*/
VAR_PRIVATE/beta_value = 0.0195
// By default, all of these values are roughly equivalent to "up to half" a skill rank.
/// How much this morale component contributes to signal based unarmed values.
var/unarmed_chance_contribution = 2.5
/// How many effective ranks of unarmed combat skill this morale component can contribute
var/unarmed_rank_contribution = 0.5
/**
* The maximum possible panic chance from negative morale point sums.
* Since negative moodlets are unique to psychic damage, the effects of psychically induced panic are uniquely stronger than simply being unskilled.
*/
var/panic_chance_ceiling = 10
/datum/component/morale/proc/get_morale_ratio()
return morale_ratio
/datum/component/morale/proc/get_morale_points()
return morale_points
/datum/component/morale/proc/add_morale_points(input)
morale_points += input
morale_ratio = ftanh(beta_value * morale_points)
/datum/component/morale/proc/set_beta_value(input)
beta_value = input
morale_ratio = ftanh(beta_value * morale_points)
/**
* Your one-stop-shop for making moodlets. This proc returns the pre-existing moodlet of a given type.
* If it doesn't already exist, then one will be created.
*/
/datum/component/morale/proc/load_moodlet(datum/moodlet/moodlet_type, set_points)
RETURN_TYPE(moodlet_type)
var/datum/moodlet/loaded_moodlet = locate(moodlet_type) in moodlets
if (!loaded_moodlet)
loaded_moodlet = new moodlet_type(src, set_points)
moodlets.Add(loaded_moodlet)
return loaded_moodlet
if (set_points) loaded_moodlet.set_moodlet(set_points)
return loaded_moodlet
/datum/component/morale/Initialize()
. = ..()
if (!parent)
return
// Behold my wall of RegisterSignal()
RegisterSignal(parent, COMSIG_APPLY_HIT_EFFECT, PROC_REF(modify_hit_effect), override = TRUE)
RegisterSignal(parent, COMSIG_BEFORE_GUN_FIRE, PROC_REF(handle_accuracy), override = TRUE)
RegisterSignal(parent, COMSIG_GUN_TOGGLE_FIRING_MODE, PROC_REF(safety_fumble), override = TRUE)
RegisterSignal(parent, COMSIG_UNARMED_HARM_ATTACKER, PROC_REF(handle_harm_attack), override = TRUE)
RegisterSignal(parent, COMSIG_UNARMED_HARM_DEFENDER, PROC_REF(handle_harm_defend), override = TRUE)
RegisterSignal(parent, COMSIG_UNARMED_DISARM_ATTACKER, PROC_REF(handle_disarm_attack), override = TRUE)
RegisterSignal(parent, COMSIG_UNARMED_DISARM_DEFENDER, PROC_REF(handle_disarm_defend), override = TRUE)
RegisterSignal(parent, COMSIG_MECH_MOVE_WASD, PROC_REF(handle_user_move), override = TRUE)
RegisterSignal(parent, COMSIG_MECH_MOVE_STRAFE, PROC_REF(handle_user_strafe), override = TRUE)
RegisterSignal(parent, COMSIG_MECH_TOGGLE_POWER, PROC_REF(handle_mech_toggle_power), override = TRUE)
/datum/component/morale/Destroy()
QDEL_LIST_FORCE(moodlets)
if (!parent)
return ..()
// Behold my wall of UnregisterSignal()
UnregisterSignal(parent, COMSIG_APPLY_HIT_EFFECT)
UnregisterSignal(parent, COMSIG_BEFORE_GUN_FIRE)
UnregisterSignal(parent, COMSIG_GUN_TOGGLE_FIRING_MODE)
UnregisterSignal(parent, COMSIG_UNARMED_HARM_ATTACKER)
UnregisterSignal(parent, COMSIG_UNARMED_HARM_DEFENDER)
UnregisterSignal(parent, COMSIG_UNARMED_DISARM_ATTACKER)
UnregisterSignal(parent, COMSIG_UNARMED_DISARM_DEFENDER)
UnregisterSignal(parent, COMSIG_MECH_MOVE_WASD)
UnregisterSignal(parent, COMSIG_MECH_MOVE_STRAFE)
UnregisterSignal(parent, COMSIG_MECH_TOGGLE_POWER)
return ..()
/datum/component/morale/process(seconds_per_tick)
var/current_time = REALTIMEOFDAY
var/list_trimmed = FALSE
for (var/datum/moodlet/moodlet as anything in moodlets)
if (moodlet.time_to_die < current_time || QDELING(moodlet))
continue
morale_points -= moodlet.get_morale_modifier()
qdel(moodlet, TRUE)
moodlets.Remove(moodlet)
list_trimmed = TRUE
if (!list_trimmed)
return
morale_ratio = ftanh(morale_points)
/*
AND NOW THE GIANT WALL OF SIGNAL HANDLERS
LOOK UPON MY SIGNALS HANDLERS YE MIGHTY AND DESPAIR
Remember how I said low morale will exclusively come from psychic damage?
The Night has shed a tear to tell you of fear and of sorrow and pain that you shall never outgrow.
Oh yea, the negative morale effects will be !!!FUN!!! in exchange for being extremely rare
Otherwise, morale effects are generally equivalent to "up to half a skill rank"
for a large variety of numerical effects related to skills.
*/
/datum/component/morale/proc/modify_hit_effect(owner, mob/living/target, obj/item/weapon, power, hit_zone)
SIGNAL_HANDLER
*power = *power * (1 + (0.05 * morale_ratio))
/datum/component/morale/proc/handle_accuracy(mob/shooter, accuracy_decrease, dispersion_increase)
SIGNAL_HANDLER
*accuracy_decrease = *accuracy_decrease - morale_ratio
*dispersion_increase = *dispersion_increase - 10 * morale_ratio
/datum/component/morale/proc/safety_fumble(mob/shooter, obj/item/gun/shoota, cancelled)
SIGNAL_HANDLER
// Up to 50% chance to fumble a safety when in a psionically-induced panic.
if (cancelled || morale_points >= 0 || !prob(floor(5 * panic_chance_ceiling * -morale_ratio)))
return
*cancelled = TRUE
shooter.visible_message(
SPAN_DANGER("\The [shooter] fumbles with \the [shoota]'s safety in a blind panic!"),
SPAN_DANGER("You fumble with \the [shoota]'s safety in a blind panic!"))
/datum/component/morale/proc/handle_harm_attack(mob/attacker, mob/defender, attacker_skill_level, miss_chance, rand_damage, block_chance)
SIGNAL_HANDLER
*attacker_skill_level = *attacker_skill_level + (unarmed_rank_contribution * morale_ratio)
*miss_chance = *miss_chance - unarmed_chance_contribution * morale_ratio
*block_chance = *block_chance - unarmed_chance_contribution * morale_ratio
/datum/component/morale/proc/handle_harm_defend(mob/defender, mob/attacker, defender_skill_level, miss_chance, rand_damage, block_chance)
SIGNAL_HANDLER
*defender_skill_level = *defender_skill_level - (unarmed_rank_contribution * morale_ratio)
*miss_chance = *miss_chance + unarmed_chance_contribution * morale_ratio
*block_chance = *block_chance + unarmed_chance_contribution * morale_ratio
/datum/component/morale/proc/handle_disarm_attack(mob/attacker, mob/defender, attacker_skill_level, disarm_cost, push_chance, disarm_chance)
SIGNAL_HANDLER
*attacker_skill_level = *attacker_skill_level + (unarmed_rank_contribution * morale_ratio)
*push_chance = *push_chance - unarmed_chance_contribution * morale_ratio
*disarm_chance = *disarm_chance - unarmed_chance_contribution * morale_ratio
/datum/component/morale/proc/handle_disarm_defend(mob/defender, mob/attacker, defender_skill_level, disarm_cost, push_chance, disarm_chance)
SIGNAL_HANDLER
*defender_skill_level = *defender_skill_level + (unarmed_rank_contribution * morale_ratio)
*push_chance = *push_chance + unarmed_chance_contribution * morale_ratio
*disarm_chance = *disarm_chance + unarmed_chance_contribution * morale_ratio
/datum/component/morale/proc/handle_user_move(mob/living/user, direction, delay_modifier)
SIGNAL_HANDLER
if (parent != user)
return
if (morale_points < 0 && prob(floor(panic_chance_ceiling * -morale_ratio)))
user.visible_message(
SPAN_DANGER("\The [user] fumbles with their mech's controls in a blind panic!"),
SPAN_DANGER("You fumble with your mech's controls in a blind panic!"))
*direction = pick(GLOB.cardinals)
if (direction == NORTH)
return
*delay_modifier = *delay_modifier - 0.5 * morale_ratio
/datum/component/morale/proc/handle_user_strafe(mob/living/user, direction, delay_modifier)
SIGNAL_HANDLER
if (parent != user)
return
if (morale_points < 0 && prob(floor(panic_chance_ceiling * -morale_ratio)))
user.visible_message(
SPAN_DANGER("\The [user] fumbles with their mech's controls in a blind panic!"),
SPAN_DANGER("You fumble with your mech's controls in a blind panic!"))
*direction = angle2dir(dir2angle(direction) + 180)
*delay_modifier = *delay_modifier - 0.5 * morale_ratio
/datum/component/morale/proc/handle_mech_toggle_power(mob/user, cancelled, delay)
SIGNAL_HANDLER
if (parent != user || cancelled || morale_points >= 0)
return
if (prob(floor(panic_chance_ceiling * -morale_ratio)))
to_chat(user, SPAN_DANGER("The pressure on your mind overwhelms you completely. You can't even think to find the power switch..."))
*cancelled = TRUE
return
*delay = *delay - (5 * morale_ratio) SECONDS
to_chat(user, SPAN_WARNING("The pressure on your mind causes you to stumble in searching for the power switch..."))
@@ -0,0 +1,23 @@
/**
* Component used for the Armed Combat skill. For its initial implementation, this component works by *slightly* modifying the damage dealt by attacks done with melee weapons.
* For the majority of non-security crew, this basically means a small nerf to damage if they didn't invest any points into the skill.
*/
/datum/component/skill/armed_combat
/datum/component/skill/armed_combat/Initialize(level)
. = ..()
if (!parent)
return
RegisterSignal(parent, COMSIG_APPLY_HIT_EFFECT, PROC_REF(modify_hit_effect), override = TRUE)
/datum/component/skill/armed_combat/Destroy(force)
if (!parent)
return ..()
UnregisterSignal(parent, COMSIG_APPLY_HIT_EFFECT)
return ..()
/datum/component/skill/armed_combat/proc/modify_hit_effect(owner, mob/living/target, obj/item/weapon, power, hit_zone)
SIGNAL_HANDLER
*power = *power * (1 + 0.1 * (skill_level - skill_diff_reference))
@@ -0,0 +1,63 @@
/**
* Component used for the Firearms skill. Mobs with this component will have their weapon handling characteristics modified by their skill rank
* but only if this component is present. Essentially it provides a penalty to gun accuracy at ranks below the "Skill Diff", and a bonus for ranks above it.
*/
/datum/component/skill/firearms
/**
* Accuracy modifier to fired guns per point of "Skill Diff".
* As an "Effective increase" in tiles to the target being shot.
*/
var/accuracy_per_skill_diff = 2
/**
* Dispersion modifier to fired guns per point of "Skill Diff".
* As an arc-length in Degrees.
*/
var/dispersion_per_skill_diff = 30
/// %chance per point of "Skill Diff" to fumble changing a weapon's safety.
var/safety_fumble_per_skill_diff = 15
/// %chance for a completely untrained person to shoot themself in the foot accidentally.
var/footgun_chance = 1
/datum/component/skill/firearms/Initialize(var/level = SKILL_LEVEL_UNFAMILIAR)
. = ..()
if (!parent)
return
RegisterSignal(parent, COMSIG_BEFORE_GUN_FIRE, PROC_REF(handle_accuracy), override = TRUE)
RegisterSignal(parent, COMSIG_GUN_TOGGLE_FIRING_MODE, PROC_REF(safety_fumble), override = TRUE)
/datum/component/skill/firearms/Destroy()
if (!parent)
return ..()
UnregisterSignal(parent, COMSIG_BEFORE_GUN_FIRE)
UnregisterSignal(parent, COMSIG_GUN_TOGGLE_FIRING_MODE)
return ..()
/datum/component/skill/firearms/proc/handle_accuracy(mob/shooter, accuracy_decrease, dispersion_increase)
SIGNAL_HANDLER
var/skill_diff = skill_diff_reference - skill_level
// Count the target as being one tile further away from the shooter
// per the difference between a skilled professional, and the shooter's skill level
*accuracy_decrease = *accuracy_decrease + accuracy_per_skill_diff * skill_diff
// Unskilled shooters get an increased firing arc for their guns
// to a maximum of 30 degrees when fully untrained.
*dispersion_increase = *dispersion_increase + dispersion_per_skill_diff * skill_diff
/datum/component/skill/firearms/proc/safety_fumble(mob/shooter, obj/item/gun/shoota, cancelled)
SIGNAL_HANDLER
if (cancelled || skill_level >= skill_diff_reference)
return // Trained and up will never fumble the safety. Except if morale has anything to say about that...
if (!prob(safety_fumble_per_skill_diff * (skill_diff_reference - skill_level)))
return // if they pass the skill check.
*cancelled = TRUE
shooter.visible_message(
SPAN_DANGER("\The [shooter] fumbles with \the [shoota]'s safety!"),
SPAN_DANGER("You fumble with \the [shoota]'s safety!"))
@@ -0,0 +1,6 @@
/**
* Component used for the Leadership Skill. This is not currently implemented, and will be handled in a separate PR to avoid scope creep.
* The way this will be intended to work is that having the component grants access to an "Inspire" action. Activating it will prompt the user to select a person, then prompt them to "Say something inspiring!".
* If the target can "Hear" the inspirational speech, they gain a morale bonus which scales with the actor's Leadership Skill.
*/
/datum/component/skill/leadership
@@ -0,0 +1,53 @@
/datum/component/skill/unarmed_combat
/// Percent chance modifier for harm intent
var/harm_miss_chance_per_skill_diff = 2
/// Percent chance modifier for blocking unarmed attacks
var/block_chance_per_skill_diff = 2
/// Push chance modifier for disarm intent
var/push_chance_per_skill_diff = 2
/// Disarm chance modifier for disarm intent
var/disarm_chance_per_skill_diff = 2
/datum/component/skill/unarmed_combat/Initialize()
. = ..()
if (!parent)
return
RegisterSignal(parent, COMSIG_UNARMED_HARM_ATTACKER, PROC_REF(handle_harm_attack), override = TRUE)
RegisterSignal(parent, COMSIG_UNARMED_HARM_DEFENDER, PROC_REF(handle_harm_defend), override = TRUE)
RegisterSignal(parent, COMSIG_UNARMED_DISARM_ATTACKER, PROC_REF(handle_disarm_attack), override = TRUE)
RegisterSignal(parent, COMSIG_UNARMED_DISARM_DEFENDER, PROC_REF(handle_disarm_defend), override = TRUE)
/datum/component/skill/unarmed_combat/Destroy(force)
if (!parent)
return ..()
UnregisterSignal(parent, COMSIG_UNARMED_HARM_ATTACKER)
UnregisterSignal(parent, COMSIG_UNARMED_HARM_DEFENDER)
UnregisterSignal(parent, COMSIG_UNARMED_DISARM_ATTACKER)
UnregisterSignal(parent, COMSIG_UNARMED_DISARM_DEFENDER)
return ..()
/datum/component/skill/unarmed_combat/proc/handle_harm_attack(mob/attacker, mob/defender, attacker_skill_level, miss_chance, rand_damage, block_chance)
SIGNAL_HANDLER
*attacker_skill_level = *attacker_skill_level + skill_level - 1
*miss_chance = *miss_chance + (skill_diff_reference - skill_level) * harm_miss_chance_per_skill_diff
*block_chance = *block_chance + (skill_diff_reference - skill_level) * block_chance_per_skill_diff
/datum/component/skill/unarmed_combat/proc/handle_harm_defend(mob/defender, mob/attacker, defender_skill_level, miss_chance, rand_damage, block_chance)
SIGNAL_HANDLER
*defender_skill_level = *defender_skill_level + skill_level - 1
*miss_chance = *miss_chance - (skill_diff_reference - skill_level) * harm_miss_chance_per_skill_diff
*block_chance = *block_chance - (skill_diff_reference - skill_level) * block_chance_per_skill_diff
/datum/component/skill/unarmed_combat/proc/handle_disarm_attack(mob/attacker, mob/defender, attacker_skill_level, disarm_cost, push_chance, disarm_chance)
SIGNAL_HANDLER
*attacker_skill_level = *attacker_skill_level + skill_level - 1
*push_chance = *push_chance - (skill_diff_reference - skill_level) * push_chance_per_skill_diff
*disarm_chance = *disarm_chance - (skill_diff_reference - skill_level) * disarm_chance_per_skill_diff
/datum/component/skill/unarmed_combat/proc/handle_disarm_defend(mob/defender, mob/attacker, defender_skill_level, disarm_cost, push_chance, disarm_chance)
SIGNAL_HANDLER
*defender_skill_level = *defender_skill_level + skill_level - 1
*push_chance = *push_chance + (skill_diff_reference - skill_level) * push_chance_per_skill_diff
*disarm_chance = *disarm_chance + (skill_diff_reference - skill_level) * push_chance_per_skill_diff
@@ -0,0 +1 @@
/datum/component/skill/atmospherics_systems
@@ -0,0 +1 @@
/datum/component/skill/electrical_engineering
@@ -0,0 +1 @@
/datum/component/skill/mechanical_engineering
@@ -0,0 +1,23 @@
/datum/component/skill/reactor_systems
/datum/component/skill/reactor_systems/Initialize()
. = ..()
if (!parent)
return
RegisterSignal(parent, COMSIG_USE_REACTOR_COMPUTER, PROC_REF(use_reactor_computer), override = TRUE)
/datum/component/skill/reactor_systems/Destroy(force)
if (!parent)
return ..()
UnregisterSignal(parent, COMSIG_USE_REACTOR_COMPUTER)
return ..()
/datum/component/skill/reactor_systems/proc/use_reactor_computer(mob/user, cancelled)
SIGNAL_HANDLER
if (skill_level >= SKILL_LEVEL_TRAINED)
return
*cancelled = TRUE
to_chat(user, SPAN_WARNING("There's just so many buttons... You have no idea where to even begin with using this machine."))
@@ -0,0 +1,7 @@
/**
* Not currently implemented anywhere. Finish this in its own PR so as to avoid Scope Creep.
*
* This skill should influence the information a character receives when medically examining another person (with or without a health analyzer).
* With extremely high ranks in the skill giving more detailed information about a character's injuries at a glance to make diagnosing injuries easier.
*/
/datum/component/skill/anatomy
@@ -0,0 +1,6 @@
/**
* Not currently implemented anywhere. Finish this in its own PR so as to avoid Scope Creep.
*
* To be honest just port and componentize Baystation12's Forensics.
*/
/datum/component/skill/forensics
@@ -0,0 +1,5 @@
/**
* Component used for the Medicine Skill.
* This skill is meant to influence a character's effectiveness when performing first aid treatments, such as bandaging, CPR, etc.
*/
/datum/component/skill/medicine
@@ -0,0 +1 @@
/datum/component/skill/pharmacology
@@ -0,0 +1,5 @@
/**
* Component used for the Surgery Skill. A character's rank in this component is used to determine which surgical procedures they can perform.
* This skill only governs "Organic" surgery. IPC surgery is instead handled by the Robotics skill.
*/
/datum/component/skill/surgery
@@ -0,0 +1,62 @@
/datum/component/skill/pilot_mechs
/// The %chance per move input to scramble the input into a random direction. This only applies for Unfamiliar mech pilots.
var/move_scramble_chance = 10
/// Extra move delay added to
var/move_delay_per_skill_diff = 0.5
/datum/component/skill/pilot_mechs/Initialize()
. = ..()
if (!parent)
return
RegisterSignal(parent, COMSIG_MECH_MOVE_WASD, PROC_REF(handle_user_move), override = TRUE)
RegisterSignal(parent, COMSIG_MECH_MOVE_STRAFE, PROC_REF(handle_user_strafe), override = TRUE)
RegisterSignal(parent, COMSIG_MECH_TOGGLE_POWER, PROC_REF(handle_toggle_power), override = TRUE)
/datum/component/skill/pilot_mechs/Destroy(force)
if (!parent)
return ..()
UnregisterSignal(parent, COMSIG_MECH_MOVE_WASD)
UnregisterSignal(parent, COMSIG_MECH_MOVE_STRAFE)
UnregisterSignal(parent, COMSIG_MECH_TOGGLE_POWER)
return ..()
/datum/component/skill/pilot_mechs/proc/handle_user_move(mob/living/user, direction, delay_modifier)
SIGNAL_HANDLER
if (parent != user)
return
// Potentially scramble the direction if the pilot is minimally skilled.
if (skill_level == SKILL_LEVEL_UNFAMILIAR && prob(move_scramble_chance))
to_chat(user, SPAN_WARNING("You fumble with the controls!"))
*direction = pick(GLOB.cardinals)
// Don't modify the "Forward" throttle no matter the skill level. All other directions are slower than this for mechs.
if (direction == NORTH)
return
*delay_modifier = *delay_modifier + (skill_diff_reference - skill_level) * move_delay_per_skill_diff
/datum/component/skill/pilot_mechs/proc/handle_user_strafe(mob/living/user, direction, delay_modifier)
SIGNAL_HANDLER
if (parent != user)
return
// Potentially flip the strafe direction if the pilot is minimally skilled.
if (skill_level == SKILL_LEVEL_UNFAMILIAR && prob(move_scramble_chance))
to_chat(user, SPAN_WARNING("You fumble with the controls!"))
*direction = angle2dir(dir2angle(direction) + 180)
*delay_modifier = *delay_modifier + (skill_diff_reference - skill_level) * move_delay_per_skill_diff
/datum/component/skill/pilot_mechs/proc/handle_toggle_power(mob/user, cancelled, delay)
SIGNAL_HANDLER
if (parent != user || cancelled || skill_level != SKILL_LEVEL_UNFAMILIAR)
return
*delay = *delay + (5 SECONDS)
to_chat(user, SPAN_NOTICE("You struggle with searching all these buttons for the power switch."))
playsound(user.loc, SFX_KEYBOARD, 30, TRUE)
@@ -0,0 +1 @@
/datum/component/skill/pilot_spacecraft
@@ -0,0 +1,27 @@
/**
* Component used for the Robotics skill. Like the Surgery skill, this component is used to determine which surgical procedures a character is allowed to perform on IPCs.
* This skill does not apply to surgeries performed on "Organics", but it can allow for repairs to prosthetic limbs.
*/
/datum/component/skill/robotics
/datum/component/skill/robotics/Initialize()
. = ..()
if (!parent)
return
RegisterSignal(parent, COMSIG_USE_MECH_FAB, PROC_REF(use_mech_fab), override = TRUE)
/datum/component/skill/robotics/Destroy()
if (!parent)
return ..()
UnregisterSignal(parent, COMSIG_USE_MECH_FAB)
return ..()
/datum/component/skill/robotics/proc/use_mech_fab(mob/user, cancelled)
SIGNAL_HANDLER
if (cancelled || skill_level >= SKILL_LEVEL_TRAINED)
return
*cancelled = TRUE
to_chat(user, SPAN_WARNING("You have no idea how this machine works."))
@@ -0,0 +1 @@
/datum/component/skill/archaology
@@ -0,0 +1 @@
/datum/component/skill/research
@@ -0,0 +1 @@
/datum/component/skill/xenobiology
@@ -0,0 +1 @@
/datum/component/skill/xenobotany
@@ -0,0 +1,69 @@
/datum/component/skill/bartending
/datum/moodlet/bartender_drink
/datum/component/drink_moodlet_provider
/// The morale boosting value of the moodlet this drink will provide.
var/moodlet_value = 0
/// Whether the DrinkMoodletProvider is allowed to overwrite stronger moodlets with weaker moodlets.
var/overwrite_moodlet = FALSE
/// Original name of the drink before the component changed it.
var/initial_name
/datum/component/drink_moodlet_provider/Initialize(value = 5.0, overwrite = FALSE, drink_quality)
. = ..()
if (!parent)
return
moodlet_value = value
overwrite_moodlet = overwrite
RegisterSignal(parent, COMSIG_CONTAINER_DRANK, PROC_REF(handle_drank), override = TRUE)
if (!isatom(parent))
return
var/atom/owner = parent
initial_name = owner.name
switch (drink_quality)
if (-INFINITY to 5)
owner.name = "inferior " + initial_name
if (5 to 10)
owner.name = "cheap " + initial_name
if (10 to 15)
owner.name = "finely-mixed " + initial_name
if (15 to 20)
owner.name = "superior quality " + initial_name
if (20 to INFINITY)
owner.name = "masterful " + initial_name
/datum/component/drink_moodlet_provider/Destroy()
if (!parent)
return ..()
UnregisterSignal(parent, COMSIG_CONTAINER_DRANK)
if (initial_name && istype(parent, /atom))
parent:name = initial_name
return ..()
/datum/component/drink_moodlet_provider/proc/handle_drank(obj/item/reagent_containers/owner, mob/user)
SIGNAL_HANDLER
if (QDELING(src))
return
if (!owner.reagents.total_volume)
qdel(src)
// No return here because total volume can be empty at this step (if the person drank the last of a cup)
var/datum/component/morale/morale_comp = user.GetComponent(MORALE_COMPONENT)
if (!morale_comp)
return
if (!overwrite_moodlet && astype(morale_comp.moodlets[/datum/moodlet/bartender_drink], /datum/moodlet)?.get_morale_modifier() > moodlet_value)
// Return if they already have a better drink moodlet.
return
var/datum/moodlet/new_moodlet = morale_comp.load_moodlet(/datum/moodlet/bartender_drink, moodlet_value)
new_moodlet.refresh_moodlet() // Reset the duration when they drink it.
@@ -0,0 +1 @@
/datum/component/skill/cooking
@@ -0,0 +1 @@
/datum/component/skill/entertaining
@@ -0,0 +1,22 @@
/datum/component/skill/gardening
var/bonus_yield_per_rank = 1
var/harvest_speedup_per_rank = 0.333 SECONDS
/datum/component/skill/gardening/Initialize(level)
. = ..()
if(!parent)
return
RegisterSignal(parent, COMSIG_PLANT_HARVESTER, PROC_REF(modify_yield), override = TRUE)
/datum/component/skill/gardening/Destroy(force)
if (!parent)
return ..()
UnregisterSignal(parent, COMSIG_PLANT_HARVESTER)
return ..()
/datum/component/skill/gardening/proc/modify_yield(owner, datum/seed/plant, total_yield, cancelled, doafter)
SIGNAL_HANDLER
*total_yield = *total_yield + bonus_yield_per_rank * (skill_level - 1)
*doafter = *doafter - harvest_speedup_per_rank * (skill_level - 1)
@@ -0,0 +1,31 @@
/**
* The base type for Componentized skills, containing only the information extracted from Skill preferences that would be required to function.
* Children of this component can be added to a character from skill singletons by overriding that singleton's on_spawn() proc.
*/
ABSTRACT_TYPE(/datum/component/skill)
/**
* How many ranks a player has purchased in a given skill.
* How this is actually used is entirely up to the implementation of individual components.
*/
var/skill_level = SKILL_LEVEL_UNFAMILIAR
/**
* Reference value used for checking "Skill Diff"
* "Skill Diff" is the distance from the actual skill level to the reference.
*
* This can essentially be thought of as the "baseline competence" for how a vanilla character would function prior to the introduction of Skill Components.
* Any datum that is *missing* a given skill component can be logically assumed to be at this skill level.
* Therefore, characters who both have the component, and are at a level lower than this can be assumed to be "less competent".
* While characters at a level above this can be assumed to be "more competent".
*/
var/skill_diff_reference = SKILL_LEVEL_TRAINED
/**
* Always use . = ..() at the start of a NameSkillComponent's Initialize() proc.
* Skills MUST have their skill_level set first during initialization.
* Do this by setting var/level to the 2nd arg of AddComponent()
*/
/datum/component/skill/Initialize(var/level = SKILL_LEVEL_UNFAMILIAR)
SHOULD_CALL_PARENT(TRUE)
. = ..()
skill_level = level
+29
View File
@@ -0,0 +1,29 @@
/singleton/skill_category
/// The name of the skill category.
var/name
/// The description and purpose of the skill category, displayed in the info tab.
var/desc
/// The hex colour of the skill category. Shows as background on the skills tab.
var/color
/// The number of base points a character gets in this category.
var/base_skill_points
/singleton/skill_category/proc/calculate_skill_points(datum/species/species, age, singleton/origin_item/culture, singleton/origin_item/origin)
var/list/species_modifiers = species.modify_skill_points(src, age)
return base_skill_points * species_modifiers[name]
/singleton/skill_category/everyday
name = SKILL_CATEGORY_EVERYDAY
desc = "An everyday skill is a skill that can be picked up normally. Think like mixing a drink, growing a plant, cooking, and so on."
base_skill_points = BASE_SKILL_POINTS_EVERYDAY
/singleton/skill_category/occupational
name = SKILL_CATEGORY_OCCUPATIONAL
desc = "Occupational skills are the skills necessary for you to do your job. They typically lock certain aspects of your department if you aren't proficient enough."
base_skill_points = BASE_SKILL_POINTS_OCCUPATIONAL
/singleton/skill_category/combat
name = SKILL_CATEGORY_COMBAT
desc = "A combat skill is a skill that has a direct effect in combat. These have an increased cost."
base_skill_points = BASE_SKILL_POINTS_COMBAT
+80
View File
@@ -0,0 +1,80 @@
/singleton/skill
/// The displayed name of the skill.
var/name
/// A description of what this skill entails.
var/description
/// A detailed description of what a character should expect with their current level in this skill. Assoc list of skill level to string.
var/alist/skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You are clueless.",
SKILL_LEVEL_FAMILIAR = "You have read up on the subject or have prior real experience.",
SKILL_LEVEL_TRAINED = "You have received some degree of official training on the subject, whether through certifications or courses.",
SKILL_LEVEL_PROFESSIONAL = "You are an expert in this field, devoting many years of study or practice to it."
)
/// Map of skill names and descriptions by their index.
var/list/skill_level_map = list(
"Unfamiliar",
"Familiar",
"Trained",
"Professional"
)
/// The maximum level someone with no education can reach in this skill. Typically, this should be FAMILIAR on occupational skills.
/// If null, then there is no cap.
var/uneducated_skill_cap
/// The maximum level this skill can reach.
var/maximum_level = SKILL_LEVEL_TRAINED
/// The category of this skill. Used for sorting, typically.
var/category
/// The sub-category of this skill. Used to better sort skills.
var/subcategory
/**
* Required skills are always included in the user's saved skills preference, even if it's at the lowest rank.
* This is needed for skills that rely on components.
*/
var/required = FALSE
/// The component datum that this skill will add during character spawning
var/component_type = null
/// Map of skill levels to level costs. How many skill points it takes to purchase this rank in a skill.
var/alist/skill_cost_map = alist(
SKILL_LEVEL_UNFAMILIAR = 0,
SKILL_LEVEL_FAMILIAR = 2,
SKILL_LEVEL_TRAINED = 4,
SKILL_LEVEL_PROFESSIONAL = 8
)
/**
* Returns the maximum level a character can have in this skill depending on education.
*/
/singleton/skill/proc/get_maximum_level(singleton/education/education)
if(!istype(education))
crash_with("SKILL: Invalid [education] fed to get_maximum_level!")
// If there is no uneducated skill cap, it means we can always pick the maximum level.
if(!uneducated_skill_cap)
return maximum_level
// Otherwise, we need to check the education...
if(type in education.skills)
return education.skills[type]
return uneducated_skill_cap
/**
* Returns the cost of this skill, modified by its difficulty modifier.
*/
/singleton/skill/proc/get_cost(level)
return skill_cost_map[level]
/**
* ECS hook for Skills, based on a one-shot-on-spawn pattern common to traits/disabilities/loadouts, etc.
* Skills may optionally include this, but are not required to.
* This is primarily useful for making skills that offload all of their functional logic to a Component.
*
* It will be called during the process of spawning a player character in.
*/
/singleton/skill/proc/on_spawn(mob/owner, skill_level)
SHOULD_CALL_PARENT(TRUE)
if (!owner || !component_type)
return
owner.AddComponent(component_type, skill_level)
+80
View File
@@ -0,0 +1,80 @@
/*
All of the combat skills in this file are meant to be presented to the players as a "Bonus" to certain aspects of combat. However,
factually they are actually a penalty to combat skills for anyone not in Security. This lets us have better balancing levers against antags,
giving non-security leeway in being allowed to fight antags since they will be mechanically far weaker than actual security. Players can then be encouraged,
rather than discouraged from picking a rifle up off the floor to fight mercs, since they get hit with a huge accuracy penalty when using it for not being security.
Players are much happier anyways if you tell them something provides a bonus if they invest in it, even if the baseline before the bonus was less than what they had before.
A lot like the "Rested Experience Effect" in video game design.
As a general rule of thumb, having rank 3 in a combat skill is equivalent to "Vanilla" stats. No penalties, no bonuses.
Rank 1 and 2 give penalties to combat characteristics. Rank 4; which isn't accessible to crew and will come in a later PR via implants/modifiers,
instead provides a bonus to combat characteristics. All 3 combat skills and their relevant checks are also influenced by the Morale component,
which provides up to an equivalent +/- 0.5 ranks in combat skills depending on how much positive or negative morale has been accumulated.
*/
/singleton/skill/unarmed_combat
name = "Unarmed Combat"
description = "Unarmed combat represents your training in hand-to-hand combat, or without a weapon. It also influences your ability to resist actions like disarms or martial arts. " \
+ "Higher ranks in Unarmed Combat provide bonuses when attacking with your fists, as well as bonuses to defending against others unarmed attacks and disarms."
maximum_level = SKILL_LEVEL_PROFESSIONAL
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have rarely, if ever, fought someone in your life.<br>" \
+ " - You are more likely to miss when attempting to punch anywhere not the torso.<br>" \
+ " - You are less likely to block unarmed attacks that you are aware of.<br>" \
+ " - Enemies are more likely to block your unarmed attacks.",
SKILL_LEVEL_FAMILIAR = "You have some experience throwing punches. You are no stranger to a close-quarters fight, though anyone with real training is likely to overwhelm you.<br>" \
+ " - You are slightly more likely to miss when attempting to punch anywhere not the torso.<br>" \
+ " - You are slightly less likely to block unarmed attacks that you are aware of.<br>" \
+ " - Enemies are slightly more likely to block your unarmed attacks.",
SKILL_LEVEL_TRAINED = "You have been trained, whether by being in the military, taking close-quarters-combat classes or simply through experience, to both keep calm in a close-quarters fight and also fight well.<br>" \
+ " - You suffer no maluses to your close-quarters combat. <br>" \
+ " - You have no bonuses to unarmed fighting either.",
SKILL_LEVEL_PROFESSIONAL = "You have had many years of martial arts experience. Probably having a Black Belt equivalent in one or more martial arts.<br>" \
+ " - You are slightly less likely to miss when attempting to punch anywhere not the torso.<br>" \
+ " - You are slightly more likely to block unarmed attacks that you are aware of.<br>" \
+ " - Enemies are slightly less likely to block your unarmed attacks.",
)
category = /singleton/skill_category/combat
subcategory = SKILL_SUBCATEGORY_MELEE
required = TRUE
component_type = UNARMED_COMBAT_SKILL_COMPONENT
/// Currently just a simple percent modifier to melee weapon damage. -20% at rank 1, -10% at rank 2, 0% at rank 3, +10% at rank 4.
/singleton/skill/armed_combat
name = "Armed Combat"
description = "Armed Combat influences your effectiveness when fighting with any melee weapon. Having low ranks in this skill slightly decreases damage dealt with melee weapons, while higher ranks can slightly increase it."
maximum_level = SKILL_LEVEL_PROFESSIONAL
category = /singleton/skill_category/combat
subcategory = SKILL_SUBCATEGORY_MELEE
component_type = ARMED_COMBAT_SKILL_COMPONENT
/singleton/skill/firearms
name = "Firearms"
description = "Firearms represents your training in using all forms of ranged weapons. Having a high rank in in this skill provides bonuses to accuracy when shooting."
maximum_level = SKILL_LEVEL_PROFESSIONAL
category = /singleton/skill_category/combat
subcategory = SKILL_SUBCATEGORY_RANGED
required = TRUE
component_type = FIREARMS_SKILL_COMPONENT
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have no knowledge of how firearms work at all.<br>" \
+ " - Firearms you shoot have a 60 degree spread-angle increase, making them very inaccurate.<br>" \
+ " - You have a decent chance of failing to find the safety when attempting to switch it on or off.",
SKILL_LEVEL_FAMILIAR = "You have fired a gun once or twice in your life, but are by no means fully trained. At least you know how to not shoot yourself in the foot or get scope-eye.<br>" \
+ " - Firearms you shoot have a 30 degree spread-angle increase, making them somewhat less accurate.",
SKILL_LEVEL_TRAINED = "You have both training and actual experience with firearms. Equivalent to a few years of experience in roles such as military, police, or armed security.<br>" \
+ " - You suffer no maluses to firearms use. <br>" \
+ " - You have no bonuses to firearms use either.",
SKILL_LEVEL_PROFESSIONAL = "You have many years of experience with firearms, potentially even in actual combat. Retired special forces, police marksmen, and hardened mercenaries fall under this category.<br>" \
+ " - Firearms you shoot have a 30 degree spread-angle decrease, making them somewhat more accurate. This generally doesn't apply to weapons fired in semi-auto, but will make burst and automatic fire more manageable." \
)
// Temporarily commented because this is a little too complicated to catch in the initial release.
// /singleton/skill/leadership
// name = "Leadership"
// description = "Leadership skill grants access to a unique 'Inspire' action, which lets you say something inspiring and give the target a positive moodlet."
// category = /singleton/skill_category/combat
// subcategory = SKILL_SUBCATEGORY_SUPPORT
// required = TRUE
// component_type = LEADERSHIP_SKILL_COMPONENT
+44
View File
@@ -0,0 +1,44 @@
/singleton/skill/bartending
name = "Bartending"
description = "Users of this skill can create mixed drinks of varying quality, which provide a long lasting morale bonus to anyone who consumes them. This skill is activated by first mixing a drink in a Drink Mixer, then pressing Z to shake it."
maximum_level = SKILL_LEVEL_PROFESSIONAL
category = /singleton/skill_category/everyday
subcategory = SKILL_SUBCATEGORY_SERVICE
component_type = BARTENDING_SKILL_COMPONENT
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "Drinks you produce have no effects.",
SKILL_LEVEL_FAMILIAR = "Drinks you produce are slightly higher quality.",
SKILL_LEVEL_TRAINED = "Drinks you produce are of moderately higher quality.",
SKILL_LEVEL_PROFESSIONAL = "Drinks you produce are of a significantly higher quality."
)
/singleton/skill/cooking
name = "Cooking"
description = "Currently unimplemented."
maximum_level = SKILL_LEVEL_PROFESSIONAL
category = /singleton/skill_category/everyday
subcategory = SKILL_SUBCATEGORY_SERVICE
component_type = COOKING_SKILL_COMPONENT
/singleton/skill/gardening
name = "Gardening"
description = "Users of this skill gain bonuses when harvesting grown plants."
maximum_level = SKILL_LEVEL_PROFESSIONAL
category = /singleton/skill_category/everyday
subcategory = SKILL_SUBCATEGORY_SERVICE
component_type = GARDENING_SKILL_COMPONENT
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have no modifiers to gardening.",
SKILL_LEVEL_FAMILIAR = "You gain +1 produce when harvesting plants",
SKILL_LEVEL_TRAINED = "You gain +2 produce when harvesting plants",
SKILL_LEVEL_PROFESSIONAL = "You gain +3 produce when harvesting plants"
)
// Temporarily commented because this is a little too complicated to catch in the initial PR release.
// /singleton/skill/entertaining
// name = "Entertaining"
// description = "Entertainers are able to generate positive moodlets by playing instruments."
// maximum_level = SKILL_LEVEL_PROFESSIONAL
// category = /singleton/skill_category/everyday
// subcategory = SKILL_SUBCATEGORY_SERVICE
// component_type = ENTERTAINING_SKILL_COMPONENT
@@ -0,0 +1,69 @@
/singleton/skill/electrical_engineering
name = "Electrical Engineering"
description = "Not currently implemented"
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_FAMILIAR
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_ENGINEERING
required = TRUE
component_type = ELECTRICAL_ENGINEERING_SKILL_COMPONENT
/singleton/skill/mechanical_engineering
name = "Mechanical Engineering"
description = "Mechanical engineering has to do with general construction of objects, walls, windows, and so on. It is also necessary for the usage of heavy machinery \
such as emitters. This skill is also commonly used for crafting items out of raw materials, and heavily governs the quality of said objects."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_FAMILIAR
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_ENGINEERING
required = TRUE
component_type = MECHANICAL_ENGINEERING_SKILL_COMPONENT
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have little to no experience working with physical machinery.<br>" \
+ " - Items you craft will typically be of low quality.<br>" \
+ " - You take twice as long to craft anything.<br>" \
+ " - You cannot craft any object that requires this skill.<br>" \
+ " - You cannot interact with most Engineering machinery.",
SKILL_LEVEL_FAMILIAR = "You have some experience working with physical machinery, though are not formally trained.<br>" \
+ " - Items you craft will be of slightly lower than average quality.<br>" \
+ " - You take 50% longer to craft anything.<br>" \
+ " - You can interact with some Engineering-related machinery.",
SKILL_LEVEL_TRAINED = "You have formal training in general Engineering concepts, equivalent to a Bachelor's Degree. <br>" \
+ " - You can craft items at the standard speed.<br>" \
+ " - Items you craft are generally at average quality.<br>" \
+ " - You can interact with most if not all Engineering equipment.",
SKILL_LEVEL_PROFESSIONAL = "You have many years of training in general Engineering concepts, equivalent to a Master's Degree or better.<br>" \
+ " - You can craft items significantly faster.<br>" \
+ " - Items you craft are of much higher quality on average.<br>" \
)
/singleton/skill/atmospherics_systems
name = "Atmospherics Systems"
description = "Not currently implemented."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_FAMILIAR
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_ENGINEERING
required = TRUE
component_type = ATMOSPHERICS_SYSTEMS_SKILL_COMPONENT
/singleton/skill/reactor_systems
name = "Reactor Systems"
description = "Reactor systems envelops anything used for reactors, such as the computers and gyrotrons for the INDRA. It is also necessary to correctly interpret information \
from reactor monitoring programs."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_FAMILIAR
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_ENGINEERING
required = TRUE
component_type = REACTOR_SYSTEMS_SKILL_COMPONENT
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have little to no experience working with Reactor systems.<br>" \
+ " - You cannot interact with Reactor computers such as the INDRA controllers.",
SKILL_LEVEL_FAMILIAR = "You have some experience working with physical machinery, though are not formally trained.<br>" \
+ " - You cannot interact with Reactor computers such as the INDRA controller.",
SKILL_LEVEL_TRAINED = "You have formal training in general Engineering concepts, equivalent to a Bachelor's Degree. <br>" \
+ " - This is the minimum skill level required to operate the INDRA.",
SKILL_LEVEL_PROFESSIONAL = "You have many years of training in general Engineering concepts, equivalent to a Master's Degree or better.<br>" \
+ " - Not currently implemented differently from Trained." \
)
@@ -0,0 +1,83 @@
/singleton/skill/medicine
name = "Medicine"
description = "Governs the user's ability to perform basic first-aid, as well as operate medical equipment."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_MEDICAL
required = TRUE
component_type = MEDICINE_SKILL_COMPONENT
/singleton/skill/surgery
name = "Surgery"
description = "Governs the user's ability to perform surgical procedures on organic humanoids, as well as what complexity of procedures can be performed. " \
+ "This does not affect \"surgeries\" performed on mechanical prosthetics, robots, or synthetics in general."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_FAMILIAR // Only the most basic of all surgeries could be bought into, you'll need a real doctor education to do anything more.
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_MEDICAL
component_type = SURGERY_SKILL_COMPONENT
required = TRUE
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have zero training or experience with surgery.<br>" \
+ " - You cannot perform any surgical procedures on organic humanoids.",
SKILL_LEVEL_FAMILIAR = "You have minimal training on the basics of surgery. This is equivalent to a fresh med school graduate, or a military corpsman.<br>" \
+ "You can perform the following procedures: <br>" \
+ " - Opening or Closing incisions.<br>" \
+ " - Clamp bleeders.<br>" \
+ " - Fixing Arterial Bleeding<br>" \
+ " - Amputating a limb.",
SKILL_LEVEL_TRAINED = "You have years of formal training and experience with surgery. This is equivalent to a fully licensed surgeon.<br>" \
+ "You can perform the following procedures:<br>" \
+ " - Opening or Closing incisions.<br>" \
+ " - Clamp bleeders.<br>" \
+ " - Fixing Arterial Bleeding<br>" \
+ " - Amputating a limb." \
+ " - Repairing broken bones or muscle tendons.<br>" \
+ " - Sawing and retracting a ribcage.<br>" \
+ " - Facial Reconstructions only on organic humanoids, not synthetics.<br>" \
+ " - Remove or Insert implants.<br>" \
+ " - Re-attach (organic) limbs. Robotic limbs require the Robotics skill instead.<br>" \
+ " - Repair non-necrotic organs other than the brain, or mechanical prosthetics.<br>",
SKILL_LEVEL_PROFESSIONAL = "You are a world class surgeon with decades worth of training and experience.<br>" \
+ "You can perform the following procedures:<br>" \
+ " - Opening or Closing incisions.<br>" \
+ " - Clamp bleeders.<br>" \
+ " - Fixing Arterial Bleeding<br>" \
+ " - Amputating a limb." \
+ " - Repairing broken bones or muscle tendons.<br>" \
+ " - Sawing and retracting a ribcage.<br>" \
+ " - Facial Reconstructions only on organic humanoids, not synthetics.<br>" \
+ " - Remove or Insert implants.<br>" \
+ " - Re-attach (organic) limbs. Robotic limbs require the Robotics skill instead.<br>" \
+ " - Repair non-necrotic organs other than the brain, or mechanical prosthetics.<br>" \
+ " - Repair and debride necrotic organs.<br>" \
+ " - Repair damaged brains."
)
/singleton/skill/pharmacology
name = "Pharmacology"
description = "Not currently implemented."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_MEDICAL
component_type = PHARMACOLOGY_SKILL_COMPONENT
/singleton/skill/anatomy
name = "Anatomy"
description = "Not currently implemented."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_MEDICAL
component_type = ANATOMY_SKILL_COMPONENT
/singleton/skill/forensics
name = "Forensics"
description = "Not currently implemented."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_MEDICAL
component_type = FORENSICS_SKILL_COMPONENT
@@ -0,0 +1,89 @@
/singleton/skill/robotics
name = "Robotics"
description = "Governs the user's ability to perform surgical procedures on synthetics, as well as the complexity of what procedures can be performed."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_FAMILIAR
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_OPERATIONS
component_type = ROBOTICS_SKILL_COMPONENT
required = TRUE
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have zero training or experience with synthetics.<br>" \
+ " - You cannot perform any surgical procedures on synthetics.",
SKILL_LEVEL_FAMILIAR = "You have minimal training on the basics of synthetic repair and maintenance. This could be the level of a hobbyist, or someone currently pursuing a degree in robotics.<br>" \
+ "You can perform the following procedures: <br>" \
+ " - Opening or closing external maintenance panels to make superficial repairs.<br>" \
+ " - Repairing basic damage with a welder or cables.<br>" \
+ " - Repairing external damage to mechanical limbs.<br>" \
+ " - Cutting someone out of a hardsuit.",
SKILL_LEVEL_TRAINED = "You have years of formal training or experience on repairing and maintaining synthetics equivalent to a Bachelor's degree in Robotics.<br>" \
+ "You can perform the following procedures:<br>" \
+ " - Opening or closing external maintenance panels to make superficial repairs.<br>" \
+ " - Repairing basic damage with a welder or cables.<br>" \
+ " - Repairing external damage to mechanical limbs.<br>" \
+ " - Cutting someone out of a hardsuit." \
+ " - Fully opening or closing mechanical parts to access internal systems, allowing repairs to any amount of damage.<br>" \
+ " - Repairing, removing, or adding mechanical organs.<br>" \
+ " - Facial Reconstructions performed on Shell IPCs.<br>" \
+ " - Attaching mechanical limbs (including to organics).<br>" \
+ " - Re-attach (organic) limbs. Robotic limbs require the Robotics skill instead.<br>" \
+ " - Perform all forms of internal repairs to IPCs.<br>" \
+ " - Prepare an MMI for cyborgification.",
SKILL_LEVEL_PROFESSIONAL = "Not currently implemented, functions exactly as per Trained.<br>" \
+ "You can perform the following procedures:<br>" \
+ " - Opening or closing external maintenance panels to make superficial repairs.<br>" \
+ " - Repairing basic damage with a welder or cables.<br>" \
+ " - Repairing external damage to mechanical limbs.<br>" \
+ " - Cutting someone out of a hardsuit." \
+ " - Fully opening or closing mechanical parts to access internal systems, allowing repairs to any amount of damage.<br>" \
+ " - Repairing, removing, or adding mechanical organs.<br>" \
+ " - Facial Reconstructions performed on Shell IPCs.<br>" \
+ " - Attaching mechanical limbs (including to organics).<br>" \
+ " - Re-attach (organic) limbs. Robotic limbs require the Robotics skill instead.<br>" \
+ " - Perform all forms of internal repairs to IPCs.<br>" \
+ " - Prepare an MMI for cyborgification."
)
/singleton/skill/pilot_spacecraft
name = "Pilot: Spacecraft"
description = "Governs the user's ability to pilot spacecraft of any size, and is required to do so in the first place."
maximum_level = SKILL_LEVEL_FAMILIAR
uneducated_skill_cap = SKILL_LEVEL_FAMILIAR
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_OPERATIONS
skill_level_map = list(
"Unlicensed",
"Licensed Pilot"
)
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You are incapable of piloting spacecraft.",
SKILL_LEVEL_FAMILIAR = "You are capable of piloting spacecraft."
)
skill_cost_map = alist(
SKILL_LEVEL_UNFAMILIAR = 0,
SKILL_LEVEL_FAMILIAR = 6
)
required = TRUE
component_type = PILOT_SPACECRAFT_SKILL_COMPONENT
/singleton/skill/pilot_mechs
name = "Pilot: Exosuits"
description = "Governs the user's ability to pilot mechs of any kind."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_OPERATIONS
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "What's a mech?<br>" \
+ " - You have a 10% chance to move in a wrong direction when controlling mechs.<br>" \
+ " - Mech handling characteristics such as strafing and reverse speeds are significantly worse for you.<br>" \
+ " - It takes you awhile to find the power switch in a mech.",
SKILL_LEVEL_FAMILIAR = "You have a license to pilot mechs, but are relatively inexperienced.<br>" \
+ " - You have a small penalty to mech handling characteristics, such as slightly slower strafe and reverse speeds.",
SKILL_LEVEL_TRAINED = "You have a decent amount of experience piloting mechs.<br>" \
+ " - You have no penalties or bonuses for piloting mechs.",
SKILL_LEVEL_PROFESSIONAL = "Look on the bright side kid, you get to keep all the money.<br>" \
+ " - You have a small bonus to mech handling characteristics. Turning, Strafing, and Reverse speeds are all improved when piloting mechs. Forward speeds are still unchanged."
)
required = TRUE
component_type = PILOT_MECHS_SKILL_COMPONENT
@@ -0,0 +1,38 @@
/singleton/skill/research
name = "Research"
description = "Not currently implemented"
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_SCIENCE
component_type = RESEARCH_SKILL_COMPONENT
/singleton/skill/xenobotany
name = "Xenobotany"
description = "Not currently implemented."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_SCIENCE
component_type = XENOBOTANY_SKILL_COMPONENT
/singleton/skill/archaeology
name = "Xenoarchaeology"
description = "Not currently implemented."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_SCIENCE
component_type = ARCHAOLOGY_SKILL_COMPONENT
/singleton/skill/xenobiology
name = "Xenobiology"
description = "Xenobiology is the study of the research and cataloguing of alien lifeforms. It is necessary not only for the proper detailing of \
alien creatures, but also for their processing, such as with slimes. " \
+ "Having this skill at least at the \"Trained\" rank is required to extract cores from slimes."
maximum_level = SKILL_LEVEL_PROFESSIONAL
uneducated_skill_cap = SKILL_LEVEL_TRAINED
category = /singleton/skill_category/occupational
subcategory = SKILL_SUBCATEGORY_SCIENCE
component_type = XENOBOTANY_SKILL_COMPONENT
required = TRUE
+4
View File
@@ -43,6 +43,10 @@ GLOBAL_DATUM(actors, /datum/antagonist/actor)
player.update_eyes()
player.regenerate_icons()
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in player._datum_components)
qdel(skill)
return TRUE
/obj/effect/landmark/actor_spawn
+4
View File
@@ -52,6 +52,10 @@ GLOBAL_DATUM(burglars, /datum/antagonist/burglar)
player.update_eyes()
player.regenerate_icons()
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in player._datum_components)
qdel(skill)
return TRUE
/datum/antagonist/burglar/get_antag_radio()
+4
View File
@@ -51,6 +51,10 @@ GLOBAL_DATUM(jockeys, /datum/antagonist/jockey)
player.update_eyes()
player.regenerate_icons()
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in player._datum_components)
qdel(skill)
return TRUE
/datum/antagonist/jockey/get_antag_radio()
+4
View File
@@ -54,4 +54,8 @@ GLOBAL_DATUM(loners, /datum/antagonist/loner)
player.update_eyes()
player.regenerate_icons()
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in player._datum_components)
qdel(skill)
return TRUE
@@ -53,6 +53,11 @@ GLOBAL_DATUM(mercs, /datum/antagonist/mercenary)
player.regenerate_icons()
give_codewords(player)
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in player._datum_components)
qdel(skill)
return TRUE
/datum/antagonist/mercenary/get_antag_radio()
+5
View File
@@ -116,6 +116,11 @@ GLOBAL_DATUM(ninjas, /datum/antagonist/ninja)
player.force_update_limbs()
player.update_eyes()
player.regenerate_icons()
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in player._datum_components)
qdel(skill)
return TRUE
/datum/antagonist/ninja/proc/generate_ninja_directive(side)
+4
View File
@@ -138,6 +138,10 @@ GLOBAL_DATUM(raiders, /datum/antagonist/raider)
give_codewords(player)
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in player._datum_components)
qdel(skill)
return 1
/datum/antagonist/raider/get_antag_radio()
@@ -59,6 +59,11 @@ GLOBAL_DATUM(raider_techno, /datum/antagonist/raider_techno)
player.force_update_limbs()
player.update_eyes()
player.regenerate_icons()
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in player._datum_components)
qdel(skill)
return TRUE
/datum/antagonist/raider_techno/get_antag_radio()
@@ -40,6 +40,10 @@ GLOBAL_DATUM(technomancers, /datum/antagonist/technomancer)
technomancer_mob.preEquipOutfit(/obj/outfit/admin/techomancer, FALSE)
technomancer_mob.equipOutfit(/obj/outfit/admin/techomancer, FALSE)
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in technomancer_mob._datum_components)
qdel(skill)
return TRUE
/datum/antagonist/technomancer/proc/equip_apprentice(var/mob/living/carbon/human/technomancer_mob)
+4
View File
@@ -113,6 +113,10 @@ GLOBAL_DATUM(traitors, /datum/antagonist/traitor)
//Begin code phrase.
give_codewords(traitor_mob)
// Offship antags wipe skill components so that they can bypass skill restrictions entirely.
for(var/datum/component/skill/skill in traitor_mob._datum_components)
qdel(skill)
/datum/antagonist/traitor/proc/spawn_uplink(var/mob/living/carbon/human/traitor_mob)
if(!istype(traitor_mob))
return
+12
View File
@@ -26,6 +26,10 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca
blacklisted_species = list(SPECIES_TAJARA, SPECIES_TAJARA_MSAI, SPECIES_TAJARA_ZHAN, SPECIES_UNATHI, SPECIES_DIONA, SPECIES_IPC, SPECIES_IPC_G1, SPECIES_IPC_G2, SPECIES_IPC_XION, SPECIES_IPC_ZENGHU, SPECIES_IPC_BISHOP, SPECIES_IPC_SHELL, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_ATTENDANT, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER, SPECIES_DIONA, SPECIES_DIONA_COEUS)
skill_requirements = alist(
/singleton/skill/pilot_spacecraft = SKILL_LEVEL_FAMILIAR
)
/obj/outfit/job/captain
name = "Captain"
jobtype = /datum/job/captain
@@ -114,6 +118,10 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca
blacklisted_species = list(SPECIES_TAJARA_ZHAN, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_ATTENDANT, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/pilot_spacecraft = SKILL_LEVEL_FAMILIAR
)
/obj/outfit/job/xo
name = "Executive Officer"
jobtype = /datum/job/xo
@@ -172,6 +180,10 @@ GLOBAL_DATUM_INIT(captain_announcement, /datum/announcement/minor, new(do_newsca
blacklisted_species = list(SPECIES_TAJARA_ZHAN, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_ATTENDANT, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/pilot_spacecraft = SKILL_LEVEL_FAMILIAR
)
/obj/outfit/job/bridge_crew
name = "Bridge Crew"
jobtype = /datum/job/bridge_crew
+17
View File
@@ -362,6 +362,11 @@
blacklisted_species = list(SPECIES_TAJARA_MSAI, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_ATTENDANT, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/pilot_spacecraft = SKILL_LEVEL_FAMILIAR,
/singleton/skill/pilot_mechs = SKILL_LEVEL_FAMILIAR
)
/obj/outfit/job/operations_manager
name = "Operations Manager"
jobtype = /datum/job/operations_manager
@@ -414,6 +419,10 @@
blacklisted_species = list(SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/pilot_mechs = SKILL_LEVEL_FAMILIAR
)
/obj/outfit/job/hangar_tech
name = "Hangar Technician"
jobtype = /datum/job/hangar_tech
@@ -467,6 +476,10 @@
blacklisted_species = list(SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/pilot_spacecraft = SKILL_LEVEL_FAMILIAR
)
/obj/outfit/job/mining
name = "Shaft Miner"
jobtype = /datum/job/mining
@@ -527,6 +540,10 @@
blacklisted_species = list(SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/robotics = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/machinist
name = "Machinist"
jobtype = /datum/job/machinist
+28
View File
@@ -36,6 +36,13 @@
blacklisted_species = list(SPECIES_TAJARA_MSAI, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_ATTENDANT, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_TRAINED,
/singleton/skill/reactor_systems = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/chief_engineer
name = "Chief Engineer"
jobtype = /datum/job/chief_engineer
@@ -104,6 +111,13 @@
blacklisted_species = list(SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_TRAINED,
/singleton/skill/reactor_systems = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/engineer
name = "Ship Engineer"
jobtype = /datum/job/engineer
@@ -178,6 +192,13 @@
outfit = /obj/outfit/job/atmos
blacklisted_species = list(SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_TRAINED,
/singleton/skill/reactor_systems = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/atmos
name = "Atmospheric Technician"
jobtype = /datum/job/atmos
@@ -251,6 +272,13 @@
SPECIES_SKRELL_AXIORI = 58
)
skill_requirements = alist(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_TRAINED,
/singleton/skill/reactor_systems = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/intern_eng
name = "Engineering Apprentice"
jobtype = /datum/job/intern_eng
+5
View File
@@ -76,6 +76,11 @@
var/aide_job
var/bodyguard_job
/**
* Associated list of /singleton/skill/skill_name = skill_level that this job requires.
*/
var/alist/skill_requirements = alist()
//Only override this proc
/datum/job/proc/pre_spawn(mob/abstract/new_player/player)
return
+29
View File
@@ -35,6 +35,10 @@
blacklisted_species = list(SPECIES_TAJARA_MSAI, SPECIES_TAJARA_ZHAN, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_ATTENDANT, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/medicine = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/cmo
name = "Chief Medical Officer"
jobtype = /datum/job/cmo
@@ -83,6 +87,11 @@
minimal_access = list(ACCESS_MEDICAL, ACCESS_MEDICAL_EQUIP, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_GENETICS, ACCESS_EVA)
outfit = /obj/outfit/job/doctor
blacklisted_species = list(SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/surgery = SKILL_LEVEL_TRAINED,
/singleton/skill/medicine = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED
)
/datum/job/surgeon
title = "Surgeon"
@@ -107,6 +116,11 @@
minimal_access = list(ACCESS_MEDICAL, ACCESS_MEDICAL_EQUIP, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_GENETICS, ACCESS_EVA)
outfit = /obj/outfit/job/doctor/surgeon
blacklisted_species = list(SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/surgery = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/medicine = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/doctor
name = "Physician"
@@ -172,6 +186,11 @@
minimal_access = list(ACCESS_MEDICAL, ACCESS_MEDICAL_EQUIP, ACCESS_PHARMACY, ACCESS_VIROLOGY)
outfit = /obj/outfit/job/pharmacist
blacklisted_species = list(SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/pharmacology = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/medicine = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/pharmacist
name = "Pharmacist"
@@ -282,6 +301,11 @@
outfit = /obj/outfit/job/med_tech
blacklisted_species = list(SPECIES_DIONA, SPECIES_DIONA_COEUS, SPECIES_IPC_G2, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/surgery = SKILL_LEVEL_FAMILIAR,
/singleton/skill/medicine = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/med_tech
name = "Paramedic"
@@ -358,6 +382,11 @@
)
outfit = /obj/outfit/job/intern_med
blacklisted_species = list(SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/surgery = SKILL_LEVEL_FAMILIAR,
/singleton/skill/medicine = SKILL_LEVEL_FAMILIAR,
/singleton/skill/anatomy = SKILL_LEVEL_FAMILIAR
)
/obj/outfit/job/intern_med
name = "Medical Intern"
+4
View File
@@ -139,6 +139,10 @@
outfit = /obj/outfit/job/scientist/xenoarchaeologist
blacklisted_species = list(SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/pilot_spacecraft = SKILL_LEVEL_FAMILIAR
)
/obj/outfit/job/scientist/xenoarchaeologist
name = "Xenoarchaeologist"
jobtype = /datum/job/xenoarchaeologist
+25
View File
@@ -35,6 +35,11 @@
blacklisted_species = list(SPECIES_TAJARA_ZHAN, SPECIES_DIONA, SPECIES_DIONA_COEUS, SPECIES_IPC_G1, SPECIES_IPC_G2, SPECIES_IPC_XION, SPECIES_VAURCA_WORKER, SPECIES_VAURCA_WARRIOR, SPECIES_VAURCA_ATTENDANT, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
// Make sure security at bare minimum won't footgun themselves...
skill_requirements = alist(
/singleton/skill/firearms = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/hos
name = "Head of Security"
jobtype = /datum/job/hos
@@ -101,6 +106,11 @@
blacklisted_species = list(SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
// Make sure security at bare minimum won't footgun themselves...
skill_requirements = alist(
/singleton/skill/firearms = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/warden
name = "Warden"
jobtype = /datum/job/warden
@@ -164,6 +174,11 @@
minimal_player_age = 3
outfit = /obj/outfit/job/forensics
blacklisted_species = list(SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_VAURCA_BREEDER)
skill_requirements = alist(
/singleton/skill/surgery = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_FAMILIAR,
/singleton/skill/forensics = SKILL_LEVEL_PROFESSIONAL
)
/obj/outfit/job/forensics
name = "Investigator"
@@ -224,6 +239,11 @@
blacklisted_species = list(SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
// Make sure security at bare minimum won't footgun themselves...
skill_requirements = alist(
/singleton/skill/firearms = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/officer
name = "Security Officer"
jobtype = /datum/job/officer
@@ -291,6 +311,11 @@
blacklisted_species = list(SPECIES_IPC_XION_REMOTE, SPECIES_VAURCA_BULWARK, SPECIES_DIONA_COEUS, SPECIES_VAURCA_BREEDER)
// Make sure security at bare minimum won't footgun themselves...
skill_requirements = alist(
/singleton/skill/firearms = SKILL_LEVEL_TRAINED
)
/obj/outfit/job/intern_sec
name = "Security Cadet"
jobtype = /datum/job/intern_sec
+7
View File
@@ -125,6 +125,13 @@
to_chat(user, SPAN_WARNING("Access denied."))
return
do_hair_pull(user)
// Yes I'm doing this after do_hair_pull() intentionally, you can totally fail the skill check and still get your hair stuck in a blender.
var/cancelled = FALSE
SEND_SIGNAL(user, COMSIG_USE_MECH_FAB, &cancelled)
if (cancelled)
return
ui_interact(user)
/obj/machinery/mecha_part_fabricator/ui_interact(mob/user, datum/tgui/ui)
+10 -5
View File
@@ -150,12 +150,13 @@ Contains:
user.visible_message(SPAN_NOTICE("\The [user] starts treating [target_mob]'s [affecting.name]."), \
SPAN_NOTICE("You start treating [target_mob]'s [affecting.name]."))
var/used = 0
var/medicine_skill = user.GetComponent(MEDICINE_SKILL_COMPONENT)?.skill_level
for (var/datum/wound/W in affecting.wounds)
if(W.bandaged)
continue
if(used == amount)
break
if(!do_mob(user, target_mob, W.damage/5))
if(!do_mob(user, target_mob, W.damage/(5 + (medicine_skill ? medicine_skill - SKILL_LEVEL_TRAINED : 0))))
to_chat(user, SPAN_NOTICE("You must stand still to bandage wounds."))
break
if (W.current_stage <= W.max_bleeding_stage)
@@ -226,7 +227,8 @@ Contains:
user.visible_message(SPAN_NOTICE("\The [user] starts salving wounds on [target_mob]'s [affecting.name]."), \
SPAN_NOTICE("You start salving the wounds on [target_mob]'s [affecting.name]."))
playsound(src, pick(apply_sounds), 25)
if(!do_mob(user, target_mob, 10))
var/medicine_skill = user.GetComponent(MEDICINE_SKILL_COMPONENT)?.skill_level
if(!do_mob(user, target_mob, 10 + (medicine_skill ? 5 * (SKILL_LEVEL_TRAINED - medicine_skill) : 0)))
to_chat(user, SPAN_NOTICE("You must stand still to salve wounds."))
return 1
user.visible_message(SPAN_NOTICE("[user] salved wounds on [target_mob]'s [affecting.name]."), \
@@ -282,7 +284,8 @@ Contains:
continue
if(used == amount)
break
if(!do_mob(user, target_mob, W.damage/5))
var/medicine_skill = user.GetComponent(MEDICINE_SKILL_COMPONENT)?.skill_level
if(!do_mob(user, target_mob, W.damage/(5 + (medicine_skill ? medicine_skill - SKILL_LEVEL_TRAINED : 0))))
to_chat(user, SPAN_NOTICE("You must stand still to bandage wounds."))
break
if (W.current_stage <= W.max_bleeding_stage)
@@ -350,7 +353,8 @@ Contains:
user.visible_message(SPAN_NOTICE("\The [user] starts salving wounds on [target_mob]'s [affecting.name]."), \
SPAN_NOTICE("You start salving the wounds on [target_mob]'s [affecting.name]."))
playsound(src, pick(apply_sounds), 25)
if(!do_mob(user, target_mob, 10))
var/medicine_skill = user.GetComponent(MEDICINE_SKILL_COMPONENT)?.skill_level
if(!do_mob(user, target_mob, 10 + (medicine_skill ? 5 * (SKILL_LEVEL_TRAINED - medicine_skill) : 0)))
to_chat(user, SPAN_NOTICE("You must stand still to salve wounds."))
return 1
user.visible_message(SPAN_NOTICE("[user] covers wounds on [target_mob]'s [affecting.name] with regenerative membrane."), \
@@ -405,7 +409,8 @@ Contains:
to_chat(user, SPAN_DANGER("You can't apply a splint to the arm you're using!"))
return
user.visible_message(SPAN_DANGER("[user] starts to apply \the [src] to their [limb]."), SPAN_DANGER("You start to apply \the [src] to your [limb]."), SPAN_DANGER("You hear something being wrapped."))
if(do_after(user, 5 SECONDS, target_mob))
var/medicine_skill = user.GetComponent(MEDICINE_SKILL_COMPONENT)?.skill_level
if(do_after(user, 5 SECONDS + 25 * (medicine_skill ? SKILL_LEVEL_TRAINED - medicine_skill : 0), target_mob))
if (target_mob != user)
user.visible_message(SPAN_DANGER("[user] finishes applying \the [src] to [target_mob]'s [limb]."), SPAN_DANGER("You finish applying \the [src] to [target_mob]'s [limb]."), SPAN_DANGER("You hear something being wrapped."))
else
+61 -6
View File
@@ -99,11 +99,43 @@
if(locate(/datum/stack_recipe) in recipes_sublist)
var/sublist_title = sublist ? " ([capitalize_first_letters(sublist.title)])" : ""
t1 += "<h2>Recipes[sublist_title]</h2>"
// Cache "skill visits" to avoid having to constantly recheck for components.
var/alist/checked_skills = alist()
var/visited_skill = null // Trinary boolean as null/0/nonzero
for(var/datum/stack_recipe/R in recipes_sublist)
var/lacks_skill = FALSE
// If you put anything other than a skill component in the required skills for a recipe, I will destroy you.
for(var/skill_comp, skill_level_requirement in R.required_skills_hard)
visited_skill = checked_skills[skill_comp]
// ** Trinary Check for Null, 0, or Nonzero. **
// Case for skill hasn't been checked yet.
if (isnull(visited_skill))
// null if no component at all, otherwise equal to the skill level (which can be any real number).
var/visited_comp = astype(user.GetComponent(skill_comp), SKILL_COMPONENT)?.skill_level
// Case for "Non-Player Characters", which will never have the component at all.
if (isnull(visited_comp))
checked_skills[skill_comp] = SKILL_LEVEL_PROFESSIONAL
continue // Not a player character, just assume they can craft everything.
// Player characters will always have the component if relevant.
checked_skills[skill_comp] = visited_comp
if (visited_comp < skill_level_requirement)
lacks_skill = TRUE
break
// Case for skill has been checked AND the character has the component.
else if (visited_skill < skill_level_requirement)
lacks_skill = TRUE
break
var/max_multiplier = round(src.get_amount() / R.req_amount)
var/title = ""
var/can_build = TRUE
can_build = (max_multiplier > 0)
if(lacks_skill)
can_build = FALSE
if(R.res_amount > 1)
title += "[R.res_amount]x [R.title]\s"
@@ -116,7 +148,7 @@
var/sublist_var = sublist ? "[REF(sublist)]" : ""
t1 += "<a href='byond://?src=[REF(src)];make=[REF(R)];sublist=[sublist_var];multiplier=1'>[title]</a>"
else
t1 += "<div class='no-build inline'>[title]</div><br>"
t1 += "[lacks_skill ? "<span class='warning'>Missing Skill — " : ""]<div class='no-build inline'>[title]</div>[lacks_skill ? "</span>" : ""]<br>"
continue
if(R.max_res_amount > 1 && max_multiplier > 1)
@@ -157,14 +189,22 @@
if (recipe.on_floor && !isfloor(user.loc))
to_chat(user, SPAN_WARNING("\The [recipe.title] must be constructed on the floor!"))
return
var/skill_diff = 0
for (var/skill_type, required_level in recipe.required_skills_soft)
skill_diff += required_level - astype(user.GetComponent(skill_type), SKILL_COMPONENT)?.skill_level
to_chat(user, SPAN_NOTICE("Building [recipe.title]..."))
if (recipe.time)
if (!do_after(user, recipe.time, do_flags = DO_REPAIR_CONSTRUCT))
var/doafter_time = recipe.time
if (doafter_time)
// Approximately no crafting time if you beat the skill req by 4, approximately twice as long to craft if you're under by 4.
// No need to do an expensive min because it is mathematically impossible for this to ever be negative.
doafter_time += doafter_time * ftanh(0.5 * skill_diff)
if (!do_after(user, doafter_time, do_flags = DO_REPAIR_CONSTRUCT))
return
if (use(required))
recipe.Produce(produced, user.loc, user.dir, user)
recipe.Produce(produced, user.loc, user.dir, user, skill_diff)
/obj/item/stack/Topic(href, href_list)
..()
@@ -365,8 +405,21 @@
var/one_per_turf = 0
var/on_floor = 0
var/use_material
/**
* Assoc list of required skills to required skill levels. Requirements are taken as a "soft" requirement used to generate a "skill difference", which is applied as a modifier to a d20 roll.
* This roll currently just affects the name of the resulting item and is for fun, but should be later expanded to have more unique effects.
* THIS MAY ONLY HAVE /datum/component/skill/skill_name in it. I WILL DESTROY YOU IF YOU PUT ANYTHING ELSE IN HERE.
*/
var/alist/required_skills_soft
/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1, time = 0, one_per_turf = 0, on_floor = 0, supplied_material = null)
/**
* Assoc list of required skills to required skill levels. This is the MINIMUM to craft the item.
* You must have ALL these skills at that level to see the item as craftable.
* THIS MAY ONLY HAVE /datum/component/skill/skill_name in it. I WILL DESTROY YOU IF YOU PUT ANYTHING ELSE IN HERE.
*/
var/alist/required_skills_hard
/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1, time = 0, one_per_turf = 0, on_floor = 0, supplied_material = null, alist/required_skills_soft, alist/required_skills_hard)
src.title = title
src.result_type = result_type
if(ispath(result_type, /obj/structure))
@@ -380,8 +433,10 @@
src.one_per_turf = one_per_turf
src.on_floor = on_floor
src.use_material = supplied_material
src.required_skills_soft = required_skills_soft
src.required_skills_hard = required_skills_hard
/datum/stack_recipe/proc/Produce(var/amount = 1, var/loc = null, var/dir = NORTH, var/user = null)
/datum/stack_recipe/proc/Produce(var/amount = 1, var/loc = null, var/dir = NORTH, var/user = null, var/skill_diff = 0)
if(amount < 1)
return null
@@ -0,0 +1,18 @@
/singleton/education
/// The name of this education type.
var/name
/// The description of this education type. It should ideally match what's on the Aurora wiki, but from an IC point of view.
var/description
/// Age requirement for this education. Should match the job this is intended for. This doesn't need to be here per se, but it helps to filter results.
var/list/minimum_character_age = list()
/// The jobs this education type allows you to access.
var/list/jobs = list()
/// The given skills for this education type. Linked list of skill type to level.
var/list/skills = list()
/// Species that CANNOT take this education, if necessary. This is a list of names, must match what's in the species pref variable.
/// Empty list means no restrictions.
var/list/species_restriction = list()
/singleton/education/high_school
name = "High School Diploma"
description = "Your character has a high school diploma."
@@ -0,0 +1,86 @@
/singleton/education/mechanical_engineering
name = "Mechanical Engineering"
description = "You are at least 25 years of age, with either a Bachelor's degree in Mechanical Engineering, or otherwise have functionally equivalent working experience. \
You specialize in constructing structural systems, lathing, and the more manual pleasures of engineering, such as welding and wrenching."
jobs = list("Engineer")
minimum_character_age = list(
SPECIES_HUMAN = 25,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/electrical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_TRAINED,
/singleton/skill/reactor_systems = SKILL_LEVEL_TRAINED
)
/singleton/education/electrical_engineering
name = "Electrical Engineering Background"
description = "You are at least 25 years of age, with a Bachelor's degree in Electrical Engineering, or otherwise have functionally equivalent working experience. \
You specialize in variable-voltage cabling, grid management, electronic hardware, and other electrical systems."
jobs = list("Engineer")
minimum_character_age = list(
SPECIES_HUMAN = 25,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_TRAINED,
/singleton/skill/reactor_systems = SKILL_LEVEL_TRAINED
)
/singleton/education/atmospherics_engineer
name = "Atmospherics Systems Background"
description = "You are at least 25 years of age, with a Bachelor's degree in Atmospherics Systems, or otherwise have functionally equivalent working experience. \
You specialize in everything to do with atmospherics systems, whether that's the delivery of gases, usage of atmospherics machines, or simply how to use a pipe wrench."
jobs = list("Atmospherics Technician")
minimum_character_age = list(
SPECIES_HUMAN = 25,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/reactor_systems = SKILL_LEVEL_TRAINED
)
/singleton/education/reactors_engineer
name = "Reactor Systems Background"
description = "You are at least 25 years of age, with a Bachelor's degree in Reactor Systems, or otherwise have functionally equivalent working experience. \
You specialize in everything to do with a reactor's systems, whether you are looking at a Supermatter crystal, a fusion reactor, or a combustion chamber."
jobs = list("Engineer")
minimum_character_age = list(
SPECIES_HUMAN = 25,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_TRAINED,
/singleton/skill/reactor_systems = SKILL_LEVEL_PROFESSIONAL
)
/singleton/education/experienced_engineer
name = "Engineering Certification"
description = "You are at least 25 years of age. You may not have an Engineering degree or a specialized background, but you had enough fundamental experience for the Conglomerate to validate it instead \
of a degree. You do not have the same specialization as your fellow Engineers with a degree, making up for it by being a jack of all trades. \
You could probably fix a car, whereas they might not be able to."
jobs = list("Engineer")
minimum_character_age = list(
SPECIES_HUMAN = 25,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/mechanical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_TRAINED,
/singleton/skill/atmospherics_systems = SKILL_LEVEL_TRAINED,
/singleton/skill/reactor_systems = SKILL_LEVEL_TRAINED
)
@@ -0,0 +1,102 @@
/singleton/education/surgical_degree
name = "MD, Surgery Track"
description = "You are 30 years of age or older, with an applicable MD from accredited school and you have completed 2 years of residency at an \
accredited hospital or clinic."
jobs = list("Surgeon")
minimum_character_age = list(
SPECIES_HUMAN = 30,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/surgery = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/medicine = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED
)
/singleton/education/physician_degree
name = "MD, Physician Track"
description = "You are at least 30 years of age, with an applicable MD from an accredited school and you have completed 2 years of residency at an \
accredited hospital or clinic."
jobs = list("Physician")
minimum_character_age = list(
SPECIES_HUMAN = 30,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/surgery = SKILL_LEVEL_TRAINED,
/singleton/skill/medicine = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED
)
/singleton/education/pharmacist_degree
name = "Doctor of Pharmacy"
description = "You are at least 25 years of age, with an applicable Masters from an accredited school, along with 2 years of residency at an \
accredited hospital or clinic."
jobs = list("Pharmacist")
minimum_character_age = list(
SPECIES_HUMAN = 30,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/pharmacology = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/medicine = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED
)
/singleton/education/psychologist_degree
name = "Doctor of Psychology"
description = "You are at least 30 years of age, with at least a doctorate from an accredited university in an applicable field. " \
+ "This is more of a research degree that has medical applications, as opposed to a true medical degree. " \
+ "As such, it is only tangentially involved with actual medicine. A character with only this education is not legally considered a licensed doctor." \
+ "You are however qualified to perform psychological evaluations on behalf of the SCC, as well as perform psychotherapy."
jobs = list("Psychologist")
minimum_character_age = list(
SPECIES_HUMAN = 30,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/* Uncomment this block after finishing the Leadership skill. Psychologists should be able to give people morale bonuses as a mechanic.
/singleton/skill/leadership = SKILL_LEVEL_TRAINED,
*/
/singleton/skill/pharmacology = SKILL_LEVEL_FAMILIAR,
/singleton/skill/medicine = SKILL_LEVEL_FAMILIAR,
/singleton/skill/anatomy = SKILL_LEVEL_FAMILIAR,
/singleton/skill/research = SKILL_LEVEL_TRAINED
)
/singleton/education/psychiatrist_degree
name = "MD, Psychiatry Track"
description = "You are at least 30 years of age, with an applicable MD from an accredited school and you have completed 2 years of residency at an \
accredited hospital or clinic. Unlike Psychology, this is an actual medical degree, and a character with this education is considered a licensed doctor."
jobs = list("Psychiatrist")
minimum_character_age = list(
SPECIES_HUMAN = 30,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/surgery = SKILL_LEVEL_FAMILIAR,
/singleton/skill/medicine = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED,
/singleton/skill/pharmacology = SKILL_LEVEL_TRAINED
)
/singleton/education/paramedic
name = "Paramedic Certification"
description = "You are at least 18 years of age, with a Paramedic certification."
jobs = list("Paramedic")
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 55,
SPECIES_SKRELL_AXIORI = 55
)
skills = list(
/* Can perform only the most basic surgeries up to arterial bleeds. */
/singleton/skill/surgery = SKILL_LEVEL_FAMILIAR,
/singleton/skill/medicine = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_TRAINED
)
+19
View File
@@ -0,0 +1,19 @@
/singleton/education/finance
name = "Finance Background"
description = "You are at least 21 years of age. You possibly graduated in a field related to finance, whether that is Business Management or something else. Otherwise, you likely found your knack in the ordinary course of life such that you have comparable expertise.\
You can count very well, and you have a statistician's eye - especially for credits."
minimum_character_age = list(
SPECIES_HUMAN = 21,
SPECIES_SKRELL = 55,
SPECIES_SKRELL_AXIORI = 55
)
/singleton/education/arts
name = "Humanities Background"
description = "You are at least 21 years of age. You possibly graduated in a field related to the humanities, whether that is music, arts, linguistics, or a bachelor's in \
psychology. Otherwise, your life has provided ample opportunity for these pursuits such that you have comparable expertise. You are likely very well-read on foreign cultures and on the human mind."
minimum_character_age = list(
SPECIES_HUMAN = 21,
SPECIES_SKRELL = 55,
SPECIES_SKRELL_AXIORI = 55
)
@@ -0,0 +1,79 @@
/singleton/education/research_and_development
name = "Research & Development Degree"
description = "You are at least 30 years of age, with a PhD in an applicable field for Research and Development. This may range from a Firearms Engineering degree \
to a Bluespace Engineering degree or even Aerospace Engineering. Space is the limit for your research."
jobs = list("Scientist")
minimum_character_age = list(
SPECIES_HUMAN = 30,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/research = SKILL_LEVEL_PROFESSIONAL
)
/singleton/education/robotics_masters
name = "Robotics Master's"
description = "You are at least 25 years of age, with a Master's in Robotics. Your specialization is in building and repairing IPCs and other smaller robots, though \
you are also capable of building exoskeletons and mechs. You're also proficient with some fundamental engineering skills, though you prefer the \
theoretical aspect and robots in general."
jobs = list("Roboticist")
minimum_character_age = list(
SPECIES_HUMAN = 25,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/research = SKILL_LEVEL_FAMILIAR,
/singleton/skill/robotics = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/electrical_engineering = SKILL_LEVEL_FAMILIAR,
/singleton/skill/mechanical_engineering = SKILL_LEVEL_FAMILIAR,
)
/singleton/education/mechatronics_masters
name = "Mechatronics Master's"
description = "You are at least 25 years of age, with a Master's in Mechatronics. Your specialization is with building large human-sized exoskeletons and mechs, though \
you've also learnt how to repair IPCs and simpler robots as well. You're more proficient with the mechanical aspects of engineering as well."
jobs = list("Roboticist")
minimum_character_age = list(
SPECIES_HUMAN = 25,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/research = SKILL_LEVEL_FAMILIAR,
/singleton/skill/robotics = SKILL_LEVEL_TRAINED,
/singleton/skill/electrical_engineering = SKILL_LEVEL_FAMILIAR,
/singleton/skill/mechanical_engineering = SKILL_LEVEL_PROFESSIONAL
)
/singleton/education/xenobotany_degree
name = "Xenobotany Degree"
description = "You are at least 30 years of age, with a PhD in Xenobotany. Your specialization is with discovering, sequencing, and creating alien flora... though \
you can also grow some potatoes in your spare time."
jobs = list("Xenobotanist")
minimum_character_age = list(
SPECIES_HUMAN = 30,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/research = SKILL_LEVEL_FAMILIAR,
/singleton/skill/gardening = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/xenobotany = SKILL_LEVEL_PROFESSIONAL
)
/singleton/education/xenobiology_degree
name = "Xenobiology Degree"
description = "You are at least 30 years of age, with a PhD in Xenobiology. Your specialization is with discovering and cataloguing alien animals."
jobs = list("Xenobiologist")
minimum_character_age = list(
SPECIES_HUMAN = 30,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/research = SKILL_LEVEL_FAMILIAR,
/singleton/skill/xenobiology = SKILL_LEVEL_PROFESSIONAL
)
@@ -0,0 +1,67 @@
/singleton/education/forensics_degree
name = "Forensics Science Degree"
description = "You are 25 years of age or older, with a degree in Forensics Science. You specialize in the medical procedures required to understand why someone died. " \
+ "While not necessarily a medical degree, there's not much difference in suturing a body whether or not its approaching room temperature."
jobs = list("Investigator")
minimum_character_age = list(
SPECIES_HUMAN = 25,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/surgery = SKILL_LEVEL_TRAINED,
/singleton/skill/anatomy = SKILL_LEVEL_FAMILIAR,
/singleton/skill/forensics = SKILL_LEVEL_PROFESSIONAL,
/singleton/skill/firearms = SKILL_LEVEL_TRAINED
)
/singleton/education/military_basic
name = "Military Training"
description = "You have finished at least one full contract of military service. Alternatively, this could be equivalent experience from mercenary work (legal or otherwise), \
or or simply surviving for long in a grim environment where self-discipline matters as much as skill."
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/unarmed_combat = SKILL_LEVEL_TRAINED,
/singleton/skill/armed_combat = SKILL_LEVEL_TRAINED,
/singleton/skill/firearms = SKILL_LEVEL_TRAINED
)
/singleton/education/military_corpsman
name = "Corpsman Training"
description = "After completing basic military training (or having lived a life that taught you comparable lessons), you received advanced individual training in battlefield medicine. " \
+ "Your combat skills are not as sharp as others, but you made up for it by knowing how to keep your comrades in arms from bleeding out on the battlefield. " \
+ "A character with this training is NOT legally considered a medical doctor. You're on the hook for manslaughter if you attempt and fail to save them yourself instead of taking them to a real doctor."
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/* Significantly worse combat skills than other security educations, though not so bad they'll footgun themselves.
Alternatively, this is a plausible though less specialized alternative for paramedic training, as is common in real life. */
/singleton/skill/armed_combat = SKILL_LEVEL_FAMILIAR,
/singleton/skill/firearms = SKILL_LEVEL_TRAINED,
/singleton/skill/surgery = SKILL_LEVEL_FAMILIAR, /* Only enough to repair an artery. */
/singleton/skill/medicine = SKILL_LEVEL_FAMILIAR
)
/singleton/education/police_academy
name = "Police Academy Graduate"
description = "You are a police academy graduate, or else you have certified through (or survived) a local institution of comparable sort. \
Your combat skills are not as stringent as actual military service, though this is made up for with more generalized training suitable for a first-responder."
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 60,
SPECIES_SKRELL_AXIORI = 60
)
skills = list(
/singleton/skill/unarmed_combat = SKILL_LEVEL_FAMILIAR,
/singleton/skill/firearms = SKILL_LEVEL_TRAINED,
/singleton/skill/forensics = SKILL_LEVEL_FAMILIAR, /* Very basic crime investigation skills. */
/singleton/skill/medicine = SKILL_LEVEL_FAMILIAR /* Police are also trained in basic first aid. */
)
@@ -0,0 +1,68 @@
/singleton/education/mixing
name = "Mixologist"
description = "You know everyone's favorite and how to make it. Whether you successfully passed the test for an Idris mixing license or have tended enough bars to have seen it all, \
you are a specialist in mixing cocktails, mocktails, and whatever else. Time to mix drinks and save lives."
jobs = list("Bartender")
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 50,
SPECIES_SKRELL_AXIORI = 50
)
skills = list(
/singleton/skill/bartending = SKILL_LEVEL_PROFESSIONAL,
)
/singleton/education/cooking_degree
name = "Culinary Artist"
description = "You possibly obtained a degree in Culinary Arts or else you've survived enough kitchens to compete with the best. Pancakes, steaks, and cultural food - you've learnt about how to cook it all."
jobs = list("Chef")
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 50,
SPECIES_SKRELL_AXIORI = 50
)
skills = list(
/singleton/skill/cooking = SKILL_LEVEL_PROFESSIONAL,
)
/singleton/education/cooking_certification
name = "Culinary Background"
description = "You either obtained an Idris certification to work as a cook, or you've been on dinner duty for a long enough time to get pretty good at it. \
You won't be as good as a professional chef, but you can pour your soul out into a good breakfast."
jobs = list("Chef")
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 50,
SPECIES_SKRELL_AXIORI = 50
)
skills = list(
/singleton/skill/cooking = SKILL_LEVEL_TRAINED,
)
/singleton/education/hydroponics_degree
name = "Hydroponics Specialization"
description = "You either obtained a degree to work as a hydroponicist or gardener or you have enough of a gift and the experience that you could do just as well. \
Your background covered both manual and hydroponics gardening of just about every plant known to your species, alongside plants that are more typical to other cultures in the Spur."
jobs = list("Gardener")
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 50,
SPECIES_SKRELL_AXIORI = 50
)
skills = list(
/singleton/skill/gardening = SKILL_LEVEL_PROFESSIONAL,
)
/singleton/education/hydroponics_certification
name = "Hydroponics Background"
description = "You either obtained an Idris certification to work as a hydroponicist or gardener, or you have worked with enough plant life to hit the same mark. \
Although you might not be as much of an expert as someone with a Hydroponics degree, you can still plant just about everything if you give it your all."
jobs = list("Gardener")
minimum_character_age = list(
SPECIES_HUMAN = 18,
SPECIES_SKRELL = 50,
SPECIES_SKRELL_AXIORI = 50
)
skills = list(
/singleton/skill/gardening = SKILL_LEVEL_TRAINED,
)
+5 -1
View File
@@ -1,11 +1,15 @@
/singleton/origin_item
var/name = "generic origin item"
var/desc = DESC_PARENT
var/important_information //Big red text. Should only be used if not following it would incur a bwoink.
/// Big red text. Should only be used if not following it would incur a bwoink.
var/important_information
/// A list of the origin traits given by this culture item.
var/list/origin_traits = list()
/// Format for the following list: "Characters from this origin: [list entry], [list entry]."
/// One list item per trait.
var/list/origin_traits_descriptions = list()
/// A list of skills given by this origin. Assoc list of skill singleton type to level.
var/list/given_skills = list()
/singleton/origin_item/culture
name = "generic culture"
@@ -219,6 +219,26 @@
if(C.name in job.blacklisted_citizenship)
dat += "<del>[dispRank]</del></td><td><b> \[BACKGROUND RESTRICTED]</b></td></tr>"
continue
// Skill requirement handling.
var/skills_length = length(job.skill_requirements)
var/skill_index = 0
var/missing_skills = ""
for (var/key,value in job.skill_requirements)
skill_index++
if (!key || pref.skills[key] >= value)
continue
var/singleton/skill/skill = GET_SINGLETON(key)
missing_skills += "[skill.name] [value]"
if (skill_index != skills_length)
missing_skills += "\n"
if (missing_skills != "")
dat += "<del>[dispRank]</del></td><td> MISSING SKILLS: [missing_skills]</td></tr>"
continue
// End of skill requirement handling.
if(job.alt_titles && (LAZYLEN(pref.GetValidTitles(job)) > 1))
dispRank = "<span width='60%' align='center'>&nbsp<a href='byond://?src=[REF(src)];select_alt_title=[REF(job)]'>\[[pref.GetPlayerAltTitle(job)]\]</a></span>"
if((pref.job_civilian_low & ASSISTANT) && (rank != "Assistant"))
@@ -70,27 +70,32 @@
var/datum/species/S = GLOB.all_species[pref.species]
if(!istext(pref.culture) || !ispath(text2path(pref.culture), /singleton/origin_item/culture))
var/singleton/origin_item/culture/CI = S.possible_cultures[1]
pref.culture = "[CI]"
pref.culture = "[CI.type]"
var/singleton/origin_item/culture/our_culture = GET_SINGLETON(text2path(pref.culture))
if(!istext(pref.origin) || !ispath(text2path(pref.origin), /singleton/origin_item/origin))
var/singleton/origin_item/origin/OI = pick(our_culture.possible_origins)
pref.origin = "[OI]"
pref.origin = "[OI.type]"
else
var/singleton/origin_item/origin/origin_check = text2path(pref.origin)
if(!(origin_check in our_culture.possible_origins))
to_client_chat(SPAN_WARNING("Your origin has been reset due to it being incompatible with your culture!"))
var/singleton/origin_item/origin/OI = pick(our_culture.possible_origins)
pref.origin = "[OI]"
pref.origin = "[OI.type]"
var/singleton/origin_item/origin/our_origin = GET_SINGLETON(text2path(pref.origin))
if(!(pref.citizenship in our_origin.possible_citizenships))
to_client_chat(SPAN_WARNING("Your previous citizenship is invalid for this origin! Resetting."))
pref.citizenship = our_origin.possible_citizenships[1]
if(!(pref.religion in our_origin.possible_religions))
to_client_chat(SPAN_WARNING("Your previous religion is invalid for this origin! Resetting."))
pref.religion = our_origin.possible_religions[1]
if(!(pref.accent in our_origin.possible_accents))
to_client_chat(SPAN_WARNING("Your previous accent is invalid for this origin! Resetting."))
pref.accent = our_origin.possible_accents[1]
pref.economic_status = sanitize_inlist(pref.economic_status, ECONOMIC_POSITIONS, initial(pref.economic_status))
/datum/category_item/player_setup_item/origin/content(var/mob/user)
@@ -114,10 +119,11 @@
if(OR.important_information)
dat += "<br><i>- <font color=red>[OR.important_information]</font></i>"
dat += "<hr>"
dat += "<b>Economic Status:</b> <a href='byond://?src=[REF(src)];economic_status=1'>[pref.economic_status]</a><br/>"
dat += "<b>Citizenship:</b> <a href='byond://?src=[REF(src)];citizenship=1'>[pref.citizenship]</a><br/>"
dat += "<b>Religion:</b> <a href='byond://?src=[REF(src)];religion=1'>[pref.religion]</a><br/>"
dat += "<b>Accent:</b> <a href='byond://?src=[REF(src)];accent=1'>[pref.accent]</a><br/>"
dat += "<b>Economic Status:</b> <a href='?src=[REF(src)];economic_status=1'>[pref.economic_status]</a><br/>"
dat += "<b>Citizenship:</b> <a href='?src=[REF(src)];citizenship=1'>[pref.citizenship]</a><br/>"
dat += "<b>Religion:</b> <a href='?src=[REF(src)];religion=1'>[pref.religion]</a><br/>"
dat += "<b>Accent:</b> <a href='?src=[REF(src)];accent=1'>[pref.accent]</a><br/>"
. = dat.Join()
/datum/category_item/player_setup_item/origin/OnTopic(href, href_list, user)
@@ -131,7 +137,7 @@
var/result = tgui_input_list(user, "Choose your character's culture.", "Culture", options)
var/singleton/origin_item/culture/chosen_culture = options[result]
if(chosen_culture)
show_window(chosen_culture, "set_culture_data", user)
show_origin_window(chosen_culture, "set_culture_data", user)
return TOPIC_HANDLED
if(href_list["open_origin_menu"])
@@ -144,7 +150,7 @@
var/result = tgui_input_list(user, "Choose your character's origin.", "Origins", options)
var/singleton/origin_item/origin/chosen_origin = options[result]
if(chosen_origin)
show_window(chosen_origin, "set_origin_data", user)
show_origin_window(chosen_origin, "set_origin_data", user)
return TOPIC_HANDLED
if(href_list["set_culture_data"])
@@ -207,7 +213,7 @@
sanitize_character()
return TOPIC_REFRESH
/datum/category_item/player_setup_item/origin/proc/show_window(var/singleton/origin_item/OI, var/topic_data, var/mob/user)
/datum/category_item/player_setup_item/origin/proc/show_origin_window(var/singleton/origin_item/OI, var/topic_data, var/mob/user)
var/datum/browser/origin_win = new(user, topic_data, "Origins Selection")
var/dat = "<html><center><b>[OI.name]</center></b>"
dat += "<hr>[OI.desc]<br>"
@@ -28,30 +28,35 @@
sort_order = 2
category_item_type = /datum/category_item/player_setup_item/origin
/datum/category_group/player_setup_category/skill_preferences
name = "Skills"
sort_order = 3
category_item_type = /datum/category_item/player_setup_item/skills
/datum/category_group/player_setup_category/occupation_preferences
name = "Occupation"
sort_order = 3
sort_order = 4
category_item_type = /datum/category_item/player_setup_item/occupation
/datum/category_group/player_setup_category/appearance_preferences
name = "Roles"
sort_order = 4
sort_order = 5
category_item_type = /datum/category_item/player_setup_item/antagonism
/datum/category_group/player_setup_category/loadout_preferences
name = "Loadout"
sort_order = 5
sort_order = 6
category_item_type = /datum/category_item/player_setup_item/loadout
/datum/category_group/player_setup_category/global_preferences
name = "Global"
sort_order = 6
sort_order = 7
category_item_type = /datum/category_item/player_setup_item/player_global
sql_role = SQL_PREFERENCES
/datum/category_group/player_setup_category/other_preferences
name = "Other"
sort_order = 7
sort_order = 8
category_item_type = /datum/category_item/player_setup_item/other
/****************************
@@ -0,0 +1,336 @@
/datum/category_item/player_setup_item/skills
name = "Skills"
sort_order = 1
/datum/category_item/player_setup_item/skills/load_character(var/savefile/S)
S["skills"] >> pref.skills
S["education"] >> pref.education
/datum/category_item/player_setup_item/skills/save_character(var/savefile/S)
S["skills"] << pref.skills
S["education"] << pref.education
/datum/category_item/player_setup_item/skills/gather_load_query()
return list(
"ss13_characters" = list(
"vars" = list(
"education",
"skills"
),
"args" = list("id")
)
)
/datum/category_item/player_setup_item/skills/gather_load_parameters()
return list("id" = pref.current_character)
/datum/category_item/player_setup_item/skills/gather_save_query()
return list(
"ss13_characters" = list(
"education",
"skills",
"id" = 1,
"ckey" = 1
)
)
/datum/category_item/player_setup_item/skills/gather_save_parameters()
var/list/sanitized_skills = list()
for(var/S in pref.skills)
var/singleton/skill/skill = GET_SINGLETON(S)
if(!istype(skill))
continue
var/skill_val = pref.skills[S]
if(skill_val <= SKILL_LEVEL_UNFAMILIAR)
continue
sanitized_skills["[skill.type]"] = skill_val
return list(
"education" = pref.education,
"skills" = json_encode(sanitized_skills),
"id" = pref.current_character,
"ckey" = PREF_CLIENT_CKEY
)
/datum/category_item/player_setup_item/skills/load_character_special(savefile/S)
if(!pref.skills)
pref.skills = "{}"
var/before = pref.skills
var/loaded_skills
try
if(istext(pref.skills))
loaded_skills = json_decode(pref.skills)
else
loaded_skills = pref.skills
catch (var/exception/e)
log_debug("SKILLS: Caught [e]. Initial value: [before]")
loaded_skills = list()
pref.skills = list()
for(var/key in SSskills.required_skills)
var/singleton/skill/skill = GET_SINGLETON(key)
if (istype(skill))
pref.skills[skill.type] = SKILL_LEVEL_UNFAMILIAR
for(var/key,value in loaded_skills)
if (!key)
continue
var/path = istext(key) ? text2path(key) : key
var/singleton/skill/skill = GET_SINGLETON(path)
if(istype(skill))
pref.skills[skill.type] = value
/datum/category_item/player_setup_item/skills/sanitize_character(var/sql_load = 0)
//todomatt
if(!istext(pref.education) || !ispath(text2path(pref.education), /singleton/education))
var/singleton/education/ED = find_suitable_education()
if(ED)
pref.education = "[ED.type]"
else
var/singleton/education/our_education = GET_SINGLETON(text2path(pref.education))
if(length(our_education.species_restriction))
if(pref.species in our_education.species_restriction)
var/singleton/education/ED = find_suitable_education()
if(ED)
pref.education = "[ED.type]"
if(length(our_education.minimum_character_age))
if(pref.species in our_education.minimum_character_age)
if(pref.age < our_education.minimum_character_age[pref.species])
var/singleton/education/ED = find_suitable_education()
if(ED)
pref.education = "[ED.type]"
// Skills HTML UI, along with a lot of other components here, lifted from Baystation 12. Credit goes to Afterthought12. Thank you for saving me from HTML hell!
/datum/category_item/player_setup_item/skills/content(var/mob/user)
if(!SSskills.initialized)
return "<center><large>Skills not initialized yet. Please wait a bit and reload this section.</large></center>"
var/list/dat = list()
dat += "<body>"
dat += "<style>.Selectable,.Current,.Unavailable,.Toohigh,.Forced{border: 1px solid #161616;padding: 1px 4px 1px 4px;margin: 0 2px 0 0}</style>"
dat += "<style>.Forced,a.Forced{background: #FF0000}</style>"
dat += "<style>.Selectable,a.Selectable{background: #40628a}</style>"
dat += "<style>.Current,a.Current{background: #2f943c}</style>"
dat += "<style>.Unavailable{background: #d09000}</style>"
var/singleton/education/ED = GET_SINGLETON(text2path(pref.education))
dat += "<center><b>Education:</b> <a href='?src=[REF(src)];open_education_menu=1'>[ED.name]</a></center><br/><hr>"
dat += "<table>"
var/singleton/education/education = GET_SINGLETON(text2path(pref.education))
for(var/category in SSskills.skill_tree)
var/singleton/skill_category/skill_category = category
dat += "<tr><th colspan = 4><b>[skill_category.name] ([calculate_remaining_skill_points(skill_category)] points remaining)</b>"
dat += "</th></tr>"
for(var/subcategory in SSskills.skill_tree[skill_category])
dat += "<tr><th colspan = 3><b>[subcategory]</b></th></tr>"
for(var/singleton/skill/skill in SSskills.skill_tree[skill_category][subcategory])
dat += get_skill_row(skill, education)
dat += "</table>"
. = JOINTEXT(dat)
/**
* Returns an HTML skill row.
*/
/datum/category_item/player_setup_item/skills/proc/get_skill_row(singleton/skill/skill, singleton/education/education)
var/list/dat = list()
dat += "<tr style='text-align:left;'>"
dat += "<th><a href='?src=[REF(src)];skillinfo=[skill.type]'>[skill.name]</a></th>"
var/level_from_pref = pref.skills[skill.type]
var/current_level = level_from_pref ? level_from_pref : SKILL_LEVEL_UNFAMILIAR
var/maximum_skill_level = get_maximum_skill_level(skill, education)
for(var/i = SKILL_LEVEL_UNFAMILIAR, i <= skill.maximum_level, i++)
dat += skill_to_button(skill, education, current_level, i, maximum_skill_level)
return JOINTEXT(dat)
/datum/category_item/player_setup_item/skills/proc/get_maximum_skill_level(singleton/skill/skill, singleton/education/education)
var/base_maximum_level = skill.get_maximum_level(education)
var/remaining_skill_points = calculate_remaining_skill_points(GET_SINGLETON(skill.category))
var/current_level = SKILL_LEVEL_UNFAMILIAR
if(skill.type in pref.skills)
current_level = pref.skills[skill.type]
var/current_cost = 0
if(!(skill.type in education.skills))
current_cost = skill.get_cost(current_level)
var/available_points = remaining_skill_points + current_cost
for(var/skill_level = base_maximum_level; skill_level >= SKILL_LEVEL_UNFAMILIAR; skill_level--)
if(skill.get_cost(skill_level) <= available_points)
return skill_level
return SKILL_LEVEL_UNFAMILIAR
/**
* Turns a skill into a dynamic button.
*/
/datum/category_item/player_setup_item/skills/proc/skill_to_button(singleton/skill/skill, singleton/education/education, current_level, selection_level, maximum_skill_level)
var/effective_level = selection_level
if(effective_level <= 0)
return "<th></th>"
var/level_name = skill.skill_level_map[effective_level]
var/cost = skill.get_cost(effective_level)
var/button_label = "[level_name] ([cost])"
var/given_skill = FALSE
// Prevent removal of skills given by education. These are meant to be minimum skills for jobs, after all.
if(skill.type in education.skills)
given_skill = TRUE
if((effective_level < current_level) && given_skill)
return "<th>[span("Forced", "[button_label]")]</th>"
else if((effective_level < current_level) && !given_skill)
return "<th>[add_link(skill, education, button_label, "'Current'", effective_level)]</th>"
else if(effective_level == current_level)
return "<th>[span("Current", "[button_label]")]</th>"
else if(effective_level <= maximum_skill_level)
return "<th>[add_link(skill, education, button_label, "'Selectable'", effective_level)]</th>"
else
return "<th>[span("Toohigh", "[button_label]")]</th>"
/**
* Returns a button to set a skill in the skill UI.
*/
/datum/category_item/player_setup_item/skills/proc/add_link(singleton/skill/skill, singleton/education/education, text, style, value)
if(skill.get_maximum_level(education) >= value)
return "<a class=[style] href='?src=[REF(src)];setskill=[skill.type];newvalue=[value]'>[text]</a>"
return text
/**
* Returns the currently remaining skill points in a given category.
*/
/datum/category_item/player_setup_item/skills/proc/calculate_remaining_skill_points(singleton/skill_category/skill_category)
if(!istype(skill_category))
crash_with("Invalid skill category [skill_category] fed to calculate_remaining_skill_points!")
var/skill_points_remaining = skill_category.calculate_skill_points(GLOB.all_species[pref.species], pref.age, GET_SINGLETON(text2path(pref.culture)), GET_SINGLETON(text2path(pref.origin)))
var/current_points_used = get_used_skill_points_per_category(skill_category, GET_SINGLETON(text2path(pref.education)))
return skill_points_remaining - current_points_used
/**
* Returns the amount of used skill points in a certain skill category, ignoring skills given by education.
*/
/datum/category_item/player_setup_item/skills/proc/get_used_skill_points_per_category(singleton/skill_category/skill_category, singleton/education/education)
if(!istype(skill_category))
crash_with("Invalid skill category [skill_category] fed to get_used_skill_points_per_category!")
if(!istype(education))
crash_with("Invalid education [education] fed to get_used_skill_points_per_category!")
. = 0
for(var/skill_type in pref.skills)
var/singleton/skill/skill = GET_SINGLETON(skill_type)
if(skill.category != skill_category.type)
continue
if(skill.type in education.skills)
continue
. += skill.get_cost(pref.skills[skill.type])
/datum/category_item/player_setup_item/skills/OnTopic(href, href_list, user)
if(href_list["skillinfo"])
var/singleton/skill/skill_to_show = GET_SINGLETON(text2path(href_list["skillinfo"]))
if(!skill_to_show)
log_debug("SKILLS: Invalid skill selected for [user]: [skill_to_show]")
return
var/datum/browser/skill_window = new(user, "skill_info", "Skill Information")
var/dat = "<html><center><b>[skill_to_show.name]</center></b>"
dat += "<hr>[skill_to_show.description]<br>"
if(skill_to_show.uneducated_skill_cap)
dat += "Without the relevant education, you may only reach the <b>[skill_to_show.skill_level_map[skill_to_show.uneducated_skill_cap]]</b> level.<br>"
dat += "<hr>"
var/skill_level = (skill_to_show.type in pref.skills) ? pref.skills[skill_to_show.type] : SKILL_LEVEL_UNFAMILIAR
dat += "Your current level in this skill is [SPAN_BOLD(skill_to_show.skill_level_map[skill_level])].<br>"
dat += "[skill_to_show.skill_level_descriptions[skill_level]]"
dat += "</html>"
skill_window.set_content(dat)
skill_window.open()
else if(href_list["setskill"])
var/singleton/skill/new_skill = GET_SINGLETON(text2path(href_list["setskill"]))
if(!new_skill)
log_debug("SKILLS: Invalid skill selected for [user]: [new_skill]")
return
var/new_skill_value = text2num(href_list["newvalue"])
pref.skills[new_skill.type] = text2num(new_skill_value)
return TOPIC_REFRESH
else if(href_list["open_education_menu"])
var/list/options = list()
var/list/singleton/education/education_list = GET_SINGLETON_SUBTYPE_MAP(/singleton/education)
for(var/singleton_type in education_list)
var/singleton/education/ED = education_list[singleton_type]
if(length(ED.species_restriction))
if(pref.species in ED.species_restriction)
continue
if(length(ED.minimum_character_age))
if(pref.species in ED.minimum_character_age)
if(pref.age < ED.minimum_character_age[pref.species])
continue
options[ED.name] = ED
var/result = tgui_input_list(user, "Choose your character's education.", "Education", options)
var/singleton/education/chosen_education = options[result]
if(chosen_education)
show_education_window(chosen_education, "set_education_data", user)
else if(href_list["set_education_data"])
user << browse(null, "window=set_education_data")
var/new_education = html_decode(href_list["set_education_data"])
pref.education = new_education
pref.skills = list() // reset skills because we have to give them new minimums
to_chat(user, SPAN_WARNING("Your skills have been reset as you changed your education."))
var/singleton/education/education = GET_SINGLETON(text2path(new_education))
if(istype(education))
for(var/skill in education.skills)
var/singleton/skill/new_skill = GET_SINGLETON(skill)
pref.skills[new_skill.type] = education.skills[new_skill.type]
to_chat(user, SPAN_NOTICE("Added the [new_skill.name] skill at level [new_skill.skill_level_map[education.skills[new_skill.type]]]."))
sanitize_character()
return TOPIC_REFRESH
return ..()
/**
* Opens a window showing details of an education.
*/
/datum/category_item/player_setup_item/skills/proc/show_education_window(var/singleton/education/ED, var/topic_data, var/mob/user)
var/datum/browser/education_win = new(user, topic_data, "Education Selection")
var/dat = "<html><center><b>[ED.name]</center></b>"
dat += "<hr>[ED.description]<hr>"
dat += "This education gives you the following skills: "
var/list/skills_to_show = list()
for(var/skill in ED.skills)
var/singleton/skill/S = GET_SINGLETON(skill)
skills_to_show += "[S.name] ([SPAN_DANGER(S.skill_level_map[ED.skills[S.type]])])"
dat += "<b>[english_list(skills_to_show)]</b>.<br>"
dat += "<br><center>\[<a href='?src=[REF(src)];[topic_data]=[html_encode(ED.type)]'>Select</a>\]</center>"
dat += "</html>"
education_win.set_content(dat)
education_win.open()
/**
* Finds and returns the first suitable education for the pref datum.
*/
/datum/category_item/player_setup_item/skills/proc/find_suitable_education()
var/list/singleton/education/education_list = GET_SINGLETON_SUBTYPE_MAP(/singleton/education)
for(var/singleton_type in education_list)
var/singleton/education/ED = education_list[singleton_type]
if(length(ED.species_restriction))
if(pref.species in ED.species_restriction)
continue
if(length(ED.minimum_character_age))
if(pref.species in ED.minimum_character_age)
if(pref.age < ED.minimum_character_age[pref.species])
continue
return ED
+22 -8
View File
@@ -97,16 +97,28 @@ GLOBAL_LIST_EMPTY_TYPED(preferences_datums, /datum/preferences)
var/machine_ownership_status = IPC_OWNERSHIP_COMPANY
var/hidden_shell_status = FALSE
//Some faction information.
var/home_system = "Unset" //System of birth.
var/citizenship = "None" //Current home system.
var/faction = "None" //Antag faction/general associated faction.
var/religion = "None" //Religious association.
var/accent = "None" //Character accent.
/// Character citizenship.
var/citizenship = "None"
/// Antag faction/general associated faction.
var/faction = "None"
/// Religious association.
var/religion = "None"
/// Character accent.
var/accent = "None"
/// The character's culture singleton.
var/culture
/// The character's origin singleton.
var/origin
/// The character's education singleton.
var/education
/// The character's skills list. JSON.
var/list/skills = list()
/// The character's current spent skill points. Assoc list of SKILL_CATEGORY define to number of remaining skill points.
var/list/skill_points_remaining
/// The character's psionics. JSON.
var/list/psionics = list()
var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
@@ -476,7 +488,6 @@ GLOBAL_LIST_EMPTY_TYPED(preferences_datums, /datum/preferences)
character.set_culture(GET_SINGLETON(text2path(culture)))
character.set_origin(GET_SINGLETON(text2path(origin)))
// Destroy/cyborgize organs & setup body markings
character.sync_organ_prefs_to_mob(src)
@@ -517,6 +528,10 @@ GLOBAL_LIST_EMPTY_TYPED(preferences_datums, /datum/preferences)
if(istype(P) && (P.ability_flags & PSI_FLAG_CANON))
P.apply(character)
for(var/skill_type in skills)
var/singleton/skill/skill = GET_SINGLETON(skill_type)
skill.on_spawn(character, skills[skill.type])
if(icon_updates)
character.force_update_limbs()
character.update_mutations(0)
@@ -651,7 +666,6 @@ GLOBAL_LIST_EMPTY_TYPED(preferences_datums, /datum/preferences)
b_eyes = 0
species = SPECIES_HUMAN
home_system = "Unset"
citizenship = "None"
faction = "None"
religion = "None"
+7 -5
View File
@@ -1,4 +1,4 @@
/mob/living/heavy_vehicle/proc/can_move(var/mob/user)
/mob/living/heavy_vehicle/proc/can_move(var/mob/user, delay_modifier)
if(world.time < next_mecha_move)
return
@@ -28,10 +28,11 @@
to_chat(user, SPAN_WARNING("The power indicator flashes briefly."))
return
next_mecha_move = world.time + (incorporeal_move ? legs.move_delay / 2 : legs.move_delay) + (legs.damaged_delay * (legs.total_damage / legs.max_damage) * legs.damaged_delay_slope)
next_mecha_move = delay_modifier + world.time + (incorporeal_move ? legs.move_delay / 2 : legs.move_delay) + (legs.damaged_delay * (legs.total_damage / legs.max_damage) * legs.damaged_delay_slope)
return TRUE
/mob/living/heavy_vehicle/proc/can_turn(var/mob/user)
/mob/living/heavy_vehicle/proc/can_turn(var/mob/user, delay_modifier)
if(world.time < next_mecha_turn)
return
@@ -61,10 +62,11 @@
to_chat(user, SPAN_WARNING("The power indicator flashes briefly."))
return
next_mecha_turn = world.time + legs.turn_delay + (legs.damaged_delay * (legs.total_damage / legs.max_damage) * legs.damaged_delay_slope)
next_mecha_turn = delay_modifier + world.time + legs.turn_delay + (legs.damaged_delay * (legs.total_damage / legs.max_damage) * legs.damaged_delay_slope)
return TRUE
/mob/living/heavy_vehicle/proc/can_strafe(var/mob/user)
/mob/living/heavy_vehicle/proc/can_strafe(var/mob/user, delay_modifier)
if (world.time < next_mecha_move)
return
+18 -9
View File
@@ -299,22 +299,25 @@
/mob/living/heavy_vehicle/relaymove(mob/living/user, direction, var/turn_only = FALSE)
. = ..()
var/delay_modifier = 0
SEND_SIGNAL(user, COMSIG_MECH_MOVE_WASD, &direction, &delay_modifier)
if(hallucination >= EMP_MOVE_DISRUPT && prob(30))
direction = pick(GLOB.cardinals)
// Convert keyboard inputs to Battletech-style controls.
switch (direction)
if (NORTH) // "Throttle Forwards"
throttle_move(user, dir, FALSE)
throttle_move(user, dir, FALSE, delay_modifier)
if (SOUTH) // "Throttle Reverse"
throttle_move(user, angle2dir(dir2angle(dir) + 180), TRUE)
throttle_move(user, angle2dir(dir2angle(dir) + 180), TRUE, delay_modifier)
if (EAST) // "Turn Right"
rotate_by_angle(user, angle2dir(dir2angle(dir) + 90))
rotate_by_angle(user, angle2dir(dir2angle(dir) + 90), delay_modifier)
if (WEST) // "Turn Left"
rotate_by_angle(user, angle2dir(dir2angle(dir) + 270))
rotate_by_angle(user, angle2dir(dir2angle(dir) + 270), delay_modifier)
/mob/living/heavy_vehicle/proc/throttle_move(mob/living/user, direction, reverse)
if (!legs || !can_move(user))
/mob/living/heavy_vehicle/proc/throttle_move(mob/living/user, direction, reverse, delay_modifier)
if (!legs || !can_move(user, delay_modifier))
return
// Get the tile in the direction.
@@ -335,7 +338,13 @@
Move(target_loc, direction, 0, FALSE)
/mob/living/heavy_vehicle/proc/strafe_move(mob/user, direction)
if (!legs || !can_strafe(user))
if (!legs)
return
var/delay_modifier = 0
SEND_SIGNAL(user, COMSIG_MECH_MOVE_STRAFE, &direction, &delay_modifier)
if (!can_strafe(user, delay_modifier))
return
// Get the tile in the direction.
@@ -352,8 +361,8 @@
else
Move(target_loc, direction, 0, FALSE)
/mob/living/heavy_vehicle/proc/rotate_by_angle(mob/living/user, direction)
if (!legs || !can_turn(user))
/mob/living/heavy_vehicle/proc/rotate_by_angle(mob/living/user, direction, delay_modifier)
if (!legs || !can_turn(user, delay_modifier))
return
use_cell_power(legs.power_use * CELLRATE)
+7
View File
@@ -263,6 +263,13 @@
/// `var/remote` can be set to TRUE to have proc adjust where messages and hud elements are presented
/// If `remote` is TRUE, messages and other hud elements are called on the exosuit itself to prevent wierdness, and errors are handled in `handle_hear_say()`
/mob/living/heavy_vehicle/proc/toggle_power(var/mob/user, var/remote = FALSE)
var/cancelled = FALSE
var/delay = 0
SEND_SIGNAL(user, COMSIG_MECH_TOGGLE_POWER, &cancelled, &delay)
if (cancelled || delay && !do_after(user, delay))
to_chat(user, SPAN_WARNING("There's just too many buttons! You fail to find the power switch!"))
return
// if remotely called, send these messages to the exosuit, not the person calling this proc
var/reciever = user
if(remote)
+23 -6
View File
@@ -59,6 +59,9 @@
var/force_layer
var/hydrotray_only
/// The amount of time it takes to harvest this plant.
var/harvest_time = 2 SECONDS
/datum/seed/proc/setup_traits()
/datum/seed/New()
@@ -773,13 +776,28 @@
/// Place the plant products at the feet of the user.
/datum/seed/proc/harvest(var/mob/user,var/yield_mod,var/harvest_sample,var/force_amount,var/stunted_status = FALSE)
if(!user)
if(!istype(user))
return
var/total_yield = 0
var/cancelled = FALSE
var/doafter = harvest_time
// Check if any components on the user wish to modify the harvest.
SEND_SIGNAL(user, COMSIG_PLANT_HARVESTER, src, &total_yield, &cancelled, &doafter)
// And check if any components on the plant wish to modify the harvest.
SEND_SIGNAL(src, COMSIG_PLANT_HARVESTED, user, &total_yield, &cancelled, &doafter)
if (cancelled)
return
user.visible_message(SPAN_WARNING("[user] starts harvesting \the [display_name]"))
if (doafter > 0 && !do_after(user, doafter, src))
to_chat(user, SPAN_DANGER("You were interrupted while trying to harvest \the [display_name]"))
return
if(!force_amount && GET_SEED_TRAIT(src, TRAIT_YIELD) == 0 && !harvest_sample)
if(istype(user)) to_chat(user, SPAN_DANGER("You fail to harvest anything useful."))
to_chat(user, SPAN_DANGER("You fail to harvest anything useful."))
else
if(istype(user)) to_chat(user, "You [harvest_sample ? "take a sample" : "harvest"] from the [display_name].")
to_chat(user, "You [harvest_sample ? "take a sample" : "harvest"] from the [display_name].")
//This may be a new line. Update the global if it is.
if(name == "new line" || !(name in SSplants.seeds))
@@ -793,16 +811,15 @@
seeds.update_seed()
return
var/total_yield = 0
if(!isnull(force_amount))
total_yield = force_amount
else
if(GET_SEED_TRAIT(src, TRAIT_YIELD) > -1)
if(isnull(yield_mod) || yield_mod < 1)
yield_mod = 0
total_yield = GET_SEED_TRAIT(src, TRAIT_YIELD)
total_yield += GET_SEED_TRAIT(src, TRAIT_YIELD)
else
total_yield = GET_SEED_TRAIT(src, TRAIT_YIELD) + rand(yield_mod)
total_yield += GET_SEED_TRAIT(src, TRAIT_YIELD) + rand(yield_mod)
total_yield = max(1,total_yield)
// If the plant is stunted, you get half the yield.
+80 -80
View File
@@ -9,37 +9,37 @@
// If is_brittle() returns true, these are only good for a single strike.
recipes += new /datum/stack_recipe_list("generic crafts",
list(
new /datum/stack_recipe("[display_name] baseball bat", /obj/item/material/twohanded/baseballbat, 10, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] sword hilt", /obj/item/material/sword_hilt, 10, time = 100, one_per_turf = 0, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] sword blade", /obj/item/material/sword_blade, 15, time = 100, one_per_turf = 0, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] ring", /obj/item/clothing/ring/material, 1, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] armor plating", /obj/item/material/armor_plating, 3, time = 20, on_floor = 1, supplied_material = "[name]")
new /datum/stack_recipe("[display_name] baseball bat", /obj/item/material/twohanded/baseballbat, 10, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("[display_name] sword hilt", /obj/item/material/sword_hilt, 10, time = 100, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("[display_name] sword blade", /obj/item/material/sword_blade, 15, time = 100, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("[display_name] ring", /obj/item/clothing/ring/material, 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("[display_name] armor plating", /obj/item/material/armor_plating, 3, time = 20, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED))
))
if(integrity >= 50)
recipes += new /datum/stack_recipe_list("generic construction",
list(
new /datum/stack_recipe("[display_name] door", /obj/structure/simple_door, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] blocker", /obj/structure/blocker, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] railing", /obj/structure/railing, BUILD_AMT, time = 25, one_per_turf = FALSE, on_floor = TRUE, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] stool", /obj/structure/bed/stool, BUILD_AMT, one_per_turf = 1, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] chair", /obj/structure/bed/stool/chair, BUILD_AMT, one_per_turf = 1, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] bed", /obj/structure/bed, BUILD_AMT, one_per_turf = 1, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] lock", /obj/item/material/lock_construct, 1, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("[display_name] urn", /obj/item/material/urn, 10, time = 30, one_per_turf = FALSE, on_floor = 1, supplied_material = "[name]")
new /datum/stack_recipe("[display_name] door", /obj/structure/simple_door, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("[display_name] blocker", /obj/structure/blocker, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("[display_name] railing", /obj/structure/railing, BUILD_AMT, time = 25, one_per_turf = FALSE, on_floor = TRUE, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("[display_name] stool", /obj/structure/bed/stool, BUILD_AMT, one_per_turf = 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("[display_name] chair", /obj/structure/bed/stool/chair, BUILD_AMT, one_per_turf = 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("[display_name] bed", /obj/structure/bed, BUILD_AMT, one_per_turf = 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("[display_name] lock", /obj/item/material/lock_construct, 1, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("[display_name] urn", /obj/item/material/urn, 10, time = 30, one_per_turf = FALSE, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR))
))
var/list/hardness_craftables = list()
if(hardness >= 10)
hardness_craftables += new /datum/stack_recipe("[display_name] ashtray", /obj/item/material/ashtray, 2, one_per_turf = 1, on_floor = 1, supplied_material = "[name]")
hardness_craftables += new /datum/stack_recipe("[display_name] ashtray", /obj/item/material/ashtray, 2, one_per_turf = 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR))
if(hardness > 50)
hardness_craftables += new /datum/stack_recipe("[display_name] fork", /obj/item/material/kitchen/utensil/fork/plastic, 1, on_floor = 1, supplied_material = "[name]")
hardness_craftables += new /datum/stack_recipe("[display_name] spoon", /obj/item/material/kitchen/utensil/spoon/plastic, 1, on_floor = 1, supplied_material = "[name]")
hardness_craftables += new /datum/stack_recipe("[display_name] knife", /obj/item/material/kitchen/utensil/knife/plastic, 1, on_floor = 1, supplied_material = "[name]")
hardness_craftables += new /datum/stack_recipe("[display_name] blade", /obj/item/material/butterflyblade, 6, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]")
hardness_craftables += new /datum/stack_recipe("[display_name] spearhead", /obj/item/material/spearhead, 6, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]")
hardness_craftables += new /datum/stack_recipe("[display_name] drill head", /obj/item/material/drill_head, 6, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]")
hardness_craftables += new /datum/stack_recipe("[display_name] fork", /obj/item/material/kitchen/utensil/fork/plastic, 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR))
hardness_craftables += new /datum/stack_recipe("[display_name] spoon", /obj/item/material/kitchen/utensil/spoon/plastic, 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR))
hardness_craftables += new /datum/stack_recipe("[display_name] knife", /obj/item/material/kitchen/utensil/knife/plastic, 1, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR))
hardness_craftables += new /datum/stack_recipe("[display_name] blade", /obj/item/material/butterflyblade, 6, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED))
hardness_craftables += new /datum/stack_recipe("[display_name] spearhead", /obj/item/material/spearhead, 6, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED))
hardness_craftables += new /datum/stack_recipe("[display_name] drill head", /obj/item/material/drill_head, 6, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED))
if(length(hardness_craftables))
recipes += new /datum/stack_recipe_list("generic miscellaneous", hardness_craftables)
@@ -48,91 +48,91 @@
..()
recipes += new /datum/stack_recipe_list("construction recipes",
list(
new /datum/stack_recipe("regular floor tile", /obj/item/stack/tile/floor, 1, 4, 20),
new /datum/stack_recipe("full regular floor tile", /obj/item/stack/tile/mono, 1, 4, 20),
new /datum/stack_recipe("metal rod", /obj/item/stack/rods, 1, 2, 60),
new /datum/stack_recipe("steel barricade", /obj/structure/barricade/metal, BUILD_AMT, time = 10 SECONDS, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("barbed wire", /obj/item/stack/barbed_wire, 1, 2, 20),
new /datum/stack_recipe("wall girders", /obj/structure/girder, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("steel window frame", /obj/structure/window_frame/unanchored, BUILD_AMT, time = 25, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("computer frame", /obj/structure/computerframe, BUILD_AMT, time = 25, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("machine blueprint", /obj/machinery/constructable_frame/machine_frame, 2, time = 25, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("light fixture frame", /obj/item/frame/light, 2),
new /datum/stack_recipe("floor light fixture frame", /obj/item/floor_frame/light, 2),
new /datum/stack_recipe("small light fixture frame", /obj/item/frame/light/small, 1),
new /datum/stack_recipe("small floor light fixture frame", /obj/item/floor_frame/light/small, 1),
new /datum/stack_recipe("spotlight fixture frame", /obj/item/frame/light/spot, 3),
new /datum/stack_recipe("apc frame", /obj/item/frame/apc, 2),
new /datum/stack_recipe("air alarm frame", /obj/item/frame/air_alarm, 2),
new /datum/stack_recipe("fire alarm frame", /obj/item/frame/fire_alarm, 2),
new /datum/stack_recipe("fuel port", /obj/item/fuel_port, 2, time = 2 SECONDS)
new /datum/stack_recipe("regular floor tile", /obj/item/stack/tile/floor, 1, 4, 20, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("full regular floor tile", /obj/item/stack/tile/mono, 1, 4, 20, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("metal rod", /obj/item/stack/rods, 1, 2, 60, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("steel barricade", /obj/structure/barricade/metal, BUILD_AMT, time = 10 SECONDS, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("barbed wire", /obj/item/stack/barbed_wire, 1, 2, 20, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("wall girders", /obj/structure/girder, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("steel window frame", /obj/structure/window_frame/unanchored, BUILD_AMT, time = 25, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("computer frame", /obj/structure/computerframe, BUILD_AMT, time = 10 SECONDS, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("machine blueprint", /obj/machinery/constructable_frame/machine_frame, 2, time = 10 SECONDS, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("light fixture frame", /obj/item/frame/light, 2, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("floor light fixture frame", /obj/item/floor_frame/light, 2, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("small light fixture frame", /obj/item/frame/light/small, 1, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("small floor light fixture frame", /obj/item/floor_frame/light/small, 1, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("spotlight fixture frame", /obj/item/frame/light/spot, 3, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("apc frame", /obj/item/frame/apc, 2, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("air alarm frame", /obj/item/frame/air_alarm, 2, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("fire alarm frame", /obj/item/frame/fire_alarm, 2, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("fuel port", /obj/item/fuel_port, 2, time = 2 SECONDS, required_skills_hard = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED))
))
recipes += new /datum/stack_recipe_list("miscellaneous construction",
list(
new /datum/stack_recipe("key", /obj/item/key, 1, time = 10, one_per_turf = 0, on_floor = 1),
new /datum/stack_recipe("custodial cart", /obj/structure/cart/storage/janitorialcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("engineering cart", /obj/structure/cart/storage/engineeringcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("parcel cart", /obj/structure/cart/storage/parcelcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("steel closet", /obj/structure/closet, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("canister", /obj/machinery/portable_atmospherics/canister, 10, time = 15, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("target stake", /obj/structure/target_stake, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("shooting target", /obj/item/target, 5, time = 10, one_per_turf = 0, on_floor = 1),
new /datum/stack_recipe("dark office chair", /obj/structure/bed/stool/chair/office/dark, BUILD_AMT, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("light office chair", /obj/structure/bed/stool/chair/office/light, BUILD_AMT, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("steel chair (fancy)", /obj/structure/bed/stool/chair/fancy, BUILD_AMT, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("sofa (middle)", /obj/structure/bed/stool/chair/sofa, BUILD_AMT, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("sofa (left)", /obj/structure/bed/stool/chair/sofa/left, BUILD_AMT, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("sofa (right)", /obj/structure/bed/stool/chair/sofa/right, BUILD_AMT, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("sofa (corner, concave)", /obj/structure/bed/stool/chair/sofa/corner/concave, BUILD_AMT, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("sofa (corner, convex)", /obj/structure/bed/stool/chair/sofa/corner/convex, BUILD_AMT, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("key", /obj/item/key, 1, time = 10, one_per_turf = 0, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("custodial cart", /obj/structure/cart/storage/janitorialcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("engineering cart", /obj/structure/cart/storage/engineeringcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("parcel cart", /obj/structure/cart/storage/parcelcart, BUILD_AMT, time = 120, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("steel closet", /obj/structure/closet, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("canister", /obj/machinery/portable_atmospherics/canister, 10, time = 15, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("target stake", /obj/structure/target_stake, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("shooting target", /obj/item/target, 5, time = 10, one_per_turf = 0, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("dark office chair", /obj/structure/bed/stool/chair/office/dark, BUILD_AMT, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("light office chair", /obj/structure/bed/stool/chair/office/light, BUILD_AMT, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("steel chair (fancy)", /obj/structure/bed/stool/chair/fancy, BUILD_AMT, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("sofa (middle)", /obj/structure/bed/stool/chair/sofa, BUILD_AMT, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("sofa (left)", /obj/structure/bed/stool/chair/sofa/left, BUILD_AMT, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("sofa (right)", /obj/structure/bed/stool/chair/sofa/right, BUILD_AMT, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("sofa (corner, concave)", /obj/structure/bed/stool/chair/sofa/corner/concave, BUILD_AMT, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("sofa (corner, convex)", /obj/structure/bed/stool/chair/sofa/corner/convex, BUILD_AMT, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("steel crate", /obj/structure/closet/crate, BUILD_AMT, time = 50, one_per_turf = 1)
))
recipes += new /datum/stack_recipe_list("airlock assemblies",
list(
new /datum/stack_recipe("generic airlock assembly", /obj/structure/door_assembly/door_assembly_generic, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("maintenance airlock assembly", /obj/structure/door_assembly/door_assembly_mai, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("external airlock assembly", /obj/structure/door_assembly/door_assembly_ext, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("airtight hatch assembly", /obj/structure/door_assembly/door_assembly_hatch, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("maintenance hatch assembly", /obj/structure/door_assembly/door_assembly_mhatch, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("high security airlock assembly", /obj/structure/door_assembly/door_assembly_highsecurity, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("emergency shutter", /obj/structure/firedoor_assembly, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("multi-tile airlock assembly", /obj/structure/door_assembly/multi_tile, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1)
new /datum/stack_recipe("generic airlock assembly", /obj/structure/door_assembly/door_assembly_generic, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("maintenance airlock assembly", /obj/structure/door_assembly/door_assembly_mai, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("external airlock assembly", /obj/structure/door_assembly/door_assembly_ext, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("airtight hatch assembly", /obj/structure/door_assembly/door_assembly_hatch, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("maintenance hatch assembly", /obj/structure/door_assembly/door_assembly_mhatch, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("high security airlock assembly", /obj/structure/door_assembly/door_assembly_highsecurity, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1), required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED),
new /datum/stack_recipe("emergency shutter", /obj/structure/firedoor_assembly, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("multi-tile airlock assembly", /obj/structure/door_assembly/multi_tile, BUILD_AMT, time = 50, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED))
))
recipes += new /datum/stack_recipe_list("turret frames",
list(
new /datum/stack_recipe("light turret frame", /obj/machinery/porta_turret_construct, 5, time = 25, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("dark turret frame", /obj/machinery/porta_turret_construct/dark, 5, time = 25, one_per_turf = 1, on_floor = 1)
new /datum/stack_recipe("light turret frame", /obj/machinery/porta_turret_construct, 5, time = 25, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("dark turret frame", /obj/machinery/porta_turret_construct/dark, 5, time = 25, one_per_turf = 1, on_floor = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED))
))
recipes += new /datum/stack_recipe_list("modular computers",
list(
new /datum/stack_recipe("modular console frame", /obj/item/modular_computer/console, 20, time = 25, one_per_turf = TRUE),
new /datum/stack_recipe("modular laptop frame", /obj/item/modular_computer/laptop, 10, time = 25),
new /datum/stack_recipe("modular tablet frame", /obj/item/modular_computer/handheld, 5, time = 25)
new /datum/stack_recipe("modular console frame", /obj/item/modular_computer/console, 20, time = 25, one_per_turf = TRUE, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("modular laptop frame", /obj/item/modular_computer/laptop, 10, time = 25, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("modular tablet frame", /obj/item/modular_computer/handheld, 5, time = 25, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED))
))
recipes += new /datum/stack_recipe_list("[display_name] weaponry",
list(
new /datum/stack_recipe("makeshift magazine (5.56mm)", /obj/item/ammo_magazine/a556/makeshift/empty, 5, time = 20),
new /datum/stack_recipe("grenade casing", /obj/item/grenade/chem_grenade),
new /datum/stack_recipe("firearm receiver", /obj/item/receivergun, 15, time = 25, one_per_turf = 0, on_floor = 0),
new /datum/stack_recipe("shield fittings", /obj/item/material/shieldbits, 10, time = 25),
new /datum/stack_recipe("cannon frame", /obj/item/cannonframe, 10, time = 15, one_per_turf = 0, on_floor = 0),
new /datum/stack_recipe("large trap foundation", /obj/item/large_trap_foundation, 4, time = 40)
new /datum/stack_recipe("makeshift magazine (5.56mm)", /obj/item/ammo_magazine/a556/makeshift/empty, 5, time = 20, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("grenade casing", /obj/item/grenade/chem_grenade, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("firearm receiver", /obj/item/receivergun, 15, time = 25, one_per_turf = 0, on_floor = 0, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("shield fittings", /obj/item/material/shieldbits, 10, time = 25, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_UNFAMILIAR)),
new /datum/stack_recipe("cannon frame", /obj/item/cannonframe, 10, time = 15, one_per_turf = 0, on_floor = 0, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("large trap foundation", /obj/item/large_trap_foundation, 4, time = 40, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR))
))
/material/plasteel/generate_recipes()
..()
recipes += new /datum/stack_recipe_list("[display_name] recipes",
list(
new /datum/stack_recipe("AI core", /obj/structure/AIcore, BUILD_AMT, time = 50, one_per_turf = 1),
new /datum/stack_recipe("plateel barricade", /obj/structure/barricade/plasteel, BUILD_AMT, time = 12 SECONDS, one_per_turf = 1),
new /datum/stack_recipe("knife grip", /obj/item/material/butterflyhandle, 4, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]"),
new /datum/stack_recipe("dark floor tile", /obj/item/stack/tile/floor_dark, 1, 4, 20),
new /datum/stack_recipe("full dark floor tile", /obj/item/stack/tile/floor_dark/full, 1, 4, 20)
new /datum/stack_recipe("AI core", /obj/structure/AIcore, BUILD_AMT, time = 50, one_per_turf = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("plateel barricade", /obj/structure/barricade/plasteel, BUILD_AMT, time = 12 SECONDS, one_per_turf = 1, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)),
new /datum/stack_recipe("knife grip", /obj/item/material/butterflyhandle, 4, time = 20, one_per_turf = 0, on_floor = 1, supplied_material = "[name]", required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("dark floor tile", /obj/item/stack/tile/floor_dark, 1, 4, 20, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("full dark floor tile", /obj/item/stack/tile/floor_dark/full, 1, 4, 20, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR))
))
/material/plastic/generate_recipes()
@@ -163,9 +163,9 @@
new /datum/stack_recipe("wood circlet", /obj/item/woodcirclet, 1),
new /datum/stack_recipe("wood floor tile", /obj/item/stack/tile/wood, 1, 4, 20),
new /datum/stack_recipe("wooden chair", /obj/structure/bed/stool/chair/wood, BUILD_AMT, time = 10, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("crossbow frame", /obj/item/crossbowframe, 5, time = 25, one_per_turf = 0, on_floor = 0),
new /datum/stack_recipe("crossbow frame", /obj/item/crossbowframe, 5, time = 25, one_per_turf = 0, on_floor = 0, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("coffin", /obj/structure/closet/crate/coffin, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1),
new /datum/stack_recipe("rifle stock", /obj/item/stock, 10, time = 25, one_per_turf = 0, on_floor = 0),
new /datum/stack_recipe("rifle stock", /obj/item/stock, 10, time = 25, one_per_turf = 0, on_floor = 0, required_skills_soft = alist(MECHANICAL_ENGINEERING_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)),
new /datum/stack_recipe("beehive assembly", /obj/item/beehive_assembly, 4),
new /datum/stack_recipe("beehive frame", /obj/item/honey_frame, 1),
new /datum/stack_recipe("book shelf", /obj/structure/bookcase, BUILD_AMT, time = 15, one_per_turf = 1, on_floor = 1),
@@ -242,37 +242,31 @@ INITIALIZE_IMMEDIATE(/mob/abstract/new_player)
/mob/abstract/new_player/proc/IsJobAvailable(rank)
var/datum/job/job = SSjobs.GetJob(rank)
if (!job)
return FALSE
if (!job.is_position_available())
return FALSE
if (jobban_isbanned(src,rank))
// Check if the job is open in-round.
if (!job || !job.is_position_available() \
/* check for job bans */\
|| jobban_isbanned(src,rank) \
/* check for species blacklists */\
|| (job.blacklisted_species && (GLOB.all_species[client.prefs.species].name in job.blacklisted_species)) \
/* check for citizenship restrictions */\
|| job.blacklisted_citizenship && (astype(SSrecords.citizenships[client.prefs.citizenship], /datum/citizenship).name in job.blacklisted_citizenship))
return FALSE
if(job.blacklisted_species) // check for restricted species
var/datum/species/S = GLOB.all_species[client.prefs.species]
if(S.name in job.blacklisted_species)
return FALSE
if(job.blacklisted_citizenship)
var/datum/citizenship/C = SSrecords.citizenships[client.prefs.citizenship]
if(C.name in job.blacklisted_citizenship)
return FALSE
// Checks for faction requirements.
var/datum/faction/faction = SSjobs.name_factions[client.prefs.faction] || SSjobs.default_faction
var/list/faction_allowed_roles = unpacklist(faction.allowed_role_types)
if (!(job.type in faction_allowed_roles))
if (!(job.type in faction_allowed_roles) \
|| !faction.can_select(client.prefs,src) \
|| !(client.prefs.GetPlayerAltTitle(job) in client.prefs.GetValidTitles(job)))
return FALSE
if(!faction.can_select(client.prefs,src))
return FALSE
if(!(client.prefs.GetPlayerAltTitle(job) in client.prefs.GetValidTitles(job))) // does age/species check for us!
return FALSE
// Checks for skill requirements.
for (var/key,value in job.skill_requirements)
if (key && client.prefs.skills[key] < value)
return FALSE
return TRUE
/mob/abstract/new_player/proc/AttemptLateSpawn(rank,var/spawning_at)
if(src != usr)
return 0
@@ -175,6 +175,16 @@
var/hit_zone = H.zone_sel.selecting
var/obj/item/organ/external/affecting = get_organ(hit_zone)
// Check both "attacker" and "defender" for component based responses to unarmed attacks.
var/attacker_skill_level = SKILL_LEVEL_UNFAMILIAR // Lacking any component responses, treat the attacker as 'rank 1'
var/defender_skill_level = SKILL_LEVEL_UNFAMILIAR // Lacking any component responses, treat the defender as 'rank 1'
var/miss_chance = 15 // Base chance for melee attacks targeting body parts other than the torso to fail.
var/block_chance = 20 // Chance to block incoming attacks when in Harm or Grab intent.
SEND_SIGNAL(H, COMSIG_UNARMED_HARM_ATTACKER, src, &attacker_skill_level, &miss_chance, &rand_damage, &block_chance)
SEND_SIGNAL(src, COMSIG_UNARMED_HARM_DEFENDER, H, &defender_skill_level, &miss_chance, &rand_damage, &block_chance)
if (miss_chance < 1)
accurate = TRUE
if(!affecting || affecting.is_stump())
to_chat(M, SPAN_DANGER("They are missing that limb!"))
return 1
@@ -186,7 +196,7 @@
accurate = 1
if(I_HURT, I_GRAB)
// We're in a fighting stance, there's a chance we block
if(src.canmove && src!=H && prob(20))
if(src.canmove && src!=H && block_chance >= 1 && prob(block_chance))
block = 1
if (M.grabbed_by.len)
@@ -227,9 +237,10 @@
*/
if(prob(80))
hit_zone = ran_zone(hit_zone)
if(prob(15) && hit_zone != BP_CHEST) // Missed!
if(prob(miss_chance) && hit_zone != BP_CHEST) // Missed!
if(!src.lying)
attack_message = "[H] attempted to strike [src], but missed!"
attack_message = "[H] attempted to strike [src], [attacker_skill_level - defender_skill_level <= -1 ? "but [src] [pick("dodged", "ducked")] out of the way!" : "but missed!"]"
else
attack_message = "[H] attempted to strike [src], but [src.get_pronoun("he")] rolled out of the way!"
src.set_dir(pick(GLOB.cardinals))
@@ -308,17 +319,24 @@
to_chat(M, SPAN_NOTICE("You don't want to risk hurting [src]!"))
return FALSE
var/disarm_cost
var/obj/item/organ/internal/machine/power_core/cell = M.internal_organs_by_name[BP_CELL]
var/obj/item/cell/potato
if(cell)
potato = cell.cell
var/attacker_skill_level = SKILL_LEVEL_UNFAMILIAR
var/defender_skill_level = SKILL_LEVEL_UNFAMILIAR
var/disarm_cost = 0
var/push_chance = 25
var/disarm_chance = 25
SEND_SIGNAL(H, COMSIG_UNARMED_DISARM_ATTACKER, src, &attacker_skill_level, &disarm_cost, &push_chance, &disarm_chance)
SEND_SIGNAL(src, COMSIG_UNARMED_DISARM_DEFENDER, H, &defender_skill_level, &disarm_cost, &push_chance, &disarm_chance)
var/skill_difference = attacker_skill_level - defender_skill_level
if(isipc(M))
var/obj/item/cell/potato = astype(M.internal_organs_by_name[BP_CELL], /obj/item/organ/internal/machine/power_core)?.cell
disarm_cost = potato.maxcharge / 24
if(potato.charge < disarm_cost)
to_chat(M, SPAN_DANGER("You don't have enough charge to disarm someone!"))
return FALSE
// Skill difference will be negative if the opponent is stronger than us.
if(skill_difference < 0)
disarm_cost += -(skill_difference * 50)
potato.use(disarm_cost)
else
if(M.max_stamina > 0)
@@ -330,6 +348,9 @@
if(M.stamina <= disarm_cost)
to_chat(M, SPAN_DANGER("You're too tired to disarm someone!"))
return FALSE
// Skill difference will be negative if the opponent is stronger than us.
if(skill_difference < 0)
disarm_cost = max(0, disarm_cost - (skill_difference * 5))
M.stamina = clamp(M.stamina - disarm_cost, 0, M.max_stamina) // attempting to knock something out of someone's hands, or pushing them over, is exhausting!
else if(M.max_stamina <= 0)
disarm_cost = M.max_nutrition / 6
@@ -353,14 +374,18 @@
//See if they have any weapons to retaliate with
if(src.a_intent != I_HELP)
for(var/obj/item/W in holding)
if(W && prob(holding[W]))
if(!W)
continue
var/misfire_chance = holding[W] + -(skill_difference * 10)
if(W && prob(misfire_chance))
if(istype(W, /obj/item/grab))
var/obj/item/grab/G = W
if(G.affecting && G.affecting != M)
visible_message(SPAN_WARNING("[src] repositions \the [G.affecting] to block \the [M]'s disarm attempt!"), SPAN_NOTICE("You reposition \the [G.affecting] to block \the [M]'s disarm attempt!"))
G.attack_hand(M)
return
if(istype(W,/obj/item/gun))
if(istype(W, /obj/item/gun))
var/list/turfs = list()
for(var/turf/T in view())
turfs += T
@@ -370,7 +395,7 @@
return W.afterattack(target,src)
else
if(M.Adjacent(src))
visible_message(SPAN_DANGER("[src] retaliates against [M]'s disarm attempt with [W]!"))
visible_message(SPAN_DANGER("[src] retaliates against [M]'s [attacker_skill_level == SKILL_LEVEL_UNFAMILIAR ? "inexperienced shoving" : "disarm attempt"] with [W]!"))
return M.attackby(W,src)
var/randn = rand(1, 100)
@@ -402,7 +427,7 @@
forceMove(GET_TURF_ABOVE(current_turf)) //We use GET_TURF_ABOVE so people can't cheese it by turning their sprite.
return
if(randn <= 25)
if(randn <= push_chance)
if(H.gloves && istype(H.gloves,/obj/item/clothing/gloves/force))
apply_effect(6, WEAKEN)
playsound(loc, 'sound/weapons/push_connect.ogg', 50, 1, -1)
@@ -423,8 +448,8 @@
playsound(loc, 'sound/weapons/push.ogg', 50, 1, -1)
return
if(randn <= 60)
if(H.gloves && istype(H.gloves,/obj/item/clothing/gloves/force))
if(randn <= disarm_chance)
if(H.gloves && istype(H.gloves, /obj/item/clothing/gloves/force))
playsound(loc, 'sound/weapons/push_connect.ogg', 50, 1, -1)
visible_message(SPAN_DANGER("[M] shoves, sending [src] flying!"))
step_away(src,M,15)
@@ -454,12 +479,14 @@
//No return here is intentional, as it will then try to disarm other items, and/or play a failed disarm message
playsound(loc, SFX_PUNCH_MISS, 25, 1, -1)
visible_message(SPAN_DANGER("[M] attempted to disarm [src]!"))
visible_message(SPAN_DANGER("[M] [attacker_skill_level == SKILL_LEVEL_UNFAMILIAR ? "[pick("inexpertly", "clumsily")] disarm" : "disarm"] attempted to disarm [src]!"))
return
/mob/living/carbon/human/proc/cpr(mob/living/carbon/human/H, var/starting = FALSE, var/cpr_mode)
var/obj/item/main_hand = H.get_active_hand()
var/obj/item/off_hand = H.get_inactive_hand()
var/datum/component/skill/medicine/medicine_component = H.GetComponent(MEDICINE_SKILL_COMPONENT)
var/medicine_skill = medicine_component ? medicine_component.skill_level : SKILL_LEVEL_TRAINED
if(istype(main_hand) || istype(off_hand))
cpr = FALSE
to_chat(H, SPAN_NOTICE("You cannot perform CPR with anything in your hands."))
@@ -478,7 +505,12 @@
if(!cpr_mode)
cpr = FALSE
return
to_chat(H, SPAN_NOTICE("You begin performing [cpr_mode] on \the [src]."))
var/cpr_attempt_message = SPAN_DANGER("You have no idea how to perform CPR on \the [src]... but you're going to try your best!")
if(medicine_skill > SKILL_LEVEL_UNFAMILIAR)
cpr_attempt_message = SPAN_NOTICE("You begin performing [cpr_mode] on \the [src].")
to_chat(H, cpr_attempt_message)
H.do_attack_animation(src, null, image('icons/mob/screen/generic.dmi', src, "cpr", src.layer + 1))
var/starting_pixel_y = pixel_y
@@ -490,23 +522,31 @@
cpr = FALSE //If it cancelled, cancel it. Simple.
if(cpr_mode == "Full CPR")
cpr_compressions(H)
cpr_ventilation(H)
cpr_compressions(H, medicine_skill)
cpr_ventilation(H, medicine_skill)
if(cpr_mode == "Compressions")
cpr_compressions(H)
cpr_compressions(H, medicine_skill)
if(cpr_mode == "Mouth-to-Mouth")
cpr_ventilation(H)
cpr_ventilation(H, medicine_skill)
cpr(H, FALSE, cpr_mode) //Again.
/mob/living/carbon/human/proc/cpr_compressions(mob/living/carbon/human/H)
/mob/living/carbon/human/proc/cpr_compressions(mob/living/carbon/human/H, medicine_skill)
if(is_asystole())
if(prob(5 * rand(2, 3)))
var/break_probability = 5 * rand(2,3)
if(medicine_skill < SKILL_LEVEL_FAMILIAR)
break_probability *= 3
else if(medicine_skill < SKILL_LEVEL_TRAINED)
break_probability *= 1.5
if(prob(break_probability))
var/obj/item/organ/external/chest = get_organ(BP_CHEST)
if(chest)
chest.fracture()
if(medicine_skill < SKILL_LEVEL_FAMILIAR)
to_chat(H, FONT_HUGE(SPAN_DANGER("Something crunches under your hands...!")))
var/obj/item/organ/internal/heart/heart = internal_organs_by_name[BP_HEART]
if(heart)
@@ -515,7 +555,7 @@
if(stat != DEAD && prob(10 * rand(0.5, 1)))
resuscitate()
/mob/living/carbon/human/proc/cpr_ventilation(mob/living/carbon/human/H)
/mob/living/carbon/human/proc/cpr_ventilation(mob/living/carbon/human/H, medicine_skill)
if(!H.check_has_mouth())
to_chat(H, SPAN_WARNING("You don't have a mouth, you cannot do mouth-to-mouth resuscitation!"))
return
@@ -537,6 +577,12 @@
if(L)
var/datum/gas_mixture/breath = H.get_breath_from_environment()
var/fail = L.handle_breath(breath, 1)
if(medicine_skill == SKILL_LEVEL_UNFAMILIAR)
fail = prob(80)
if(fail)
to_chat(H, SPAN_DANGER("No... that wasn't how you do it!"))
if(!fail)
if(!L.is_bruised() || (L.is_bruised() && L.rescued))
losebreath = 0
@@ -449,6 +449,13 @@
/// Which species-unique robolimb types can this species take?
var/list/valid_prosthetics
/// Modifiers for the available skill points for this species. Assoc list of SKILL_CATEGORY to number.
var/list/skill_points_modifiers = list(
SKILL_CATEGORY_EVERYDAY = 1,
SKILL_CATEGORY_OCCUPATIONAL = 1,
SKILL_CATEGORY_COMBAT = 1
)
//Sleeping stuff
/// Does this species sleep standing up?
var/sleeps_upright = FALSE
@@ -461,6 +468,9 @@
/// The default lighting alpha of this species. Override to set innate NVGs.
var/default_lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
/// Controls whether this species spawns with a Morale Component.
var/has_morale = TRUE
/datum/species/proc/get_eyes(var/mob/living/carbon/human/H)
return
@@ -591,6 +601,9 @@
if(natural_armor)
H.AddComponent(natural_armor_type, natural_armor)
if(has_morale)
H.LoadComponent(MORALE_COMPONENT)
/datum/species/proc/tap(var/mob/living/carbon/human/H,var/mob/living/target)
if(H.on_fire)
target.fire_stacks += 1
@@ -1080,5 +1093,18 @@
* This proc handles the species temperature regulation. By default, it just adds `passive_temp_gain` to the human's bodytemperature.
* Can be overridden for more complex calculations.
*/
/datum/species/proc/handle_temperature_regulation(mob/living/carbon/human/human, seconds_per_tick)
human.bodytemperature += passive_temp_gain * seconds_per_tick
/**
* Gets a modifier for a skill category based on the character age or other species things.
* Must return a list with all three skill categories to a modifier (example: list(SKILL_CATEGORY_EVERYDAY = 1.5) )
*/
/datum/species/proc/modify_skill_points(singleton/skill_category/skill_category, age)
var/list/skill_age_modifiers = list(
SKILL_CATEGORY_EVERYDAY = 1,
SKILL_CATEGORY_OCCUPATIONAL = 1,
SKILL_CATEGORY_COMBAT = 1
)
return skill_age_modifiers
@@ -65,6 +65,13 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
return ..()
/obj/machinery/computer/ship/attack_hand(mob/user)
// Snowflake case for checking player characters for a Pilot Spacecraft Skill.
// Only player characters will have the component. Which will both always be present on them, and will only enable its own return logic if it exists.
// NPCs, Ghostroles, and Offship Antags that don't generate skills are unaffected by this check by intentional design so that we don't have to account for them.
if (user.GetComponent(PILOT_SPACECRAFT_SKILL_COMPONENT)?.skill_level == SKILL_LEVEL_UNFAMILIAR)
to_chat(user, SPAN_WARNING("There's just so many buttons... You have no idea where to even begin."))
return
if(use_check_and_message(user))
return
if(!emagged && !allowed(user))
@@ -43,6 +43,11 @@
. = data
/obj/machinery/computer/fusion/ui_interact(mob/user, datum/tgui/ui)
var/cancelled = FALSE
SEND_SIGNAL(user, COMSIG_USE_REACTOR_COMPUTER, &cancelled)
if (cancelled)
return
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, ui_template, name)
@@ -105,6 +105,15 @@
/obj/machinery/power/emitter/attack_hand(mob/user)
add_fingerprint(user)
var/max_engineering_skill = \
max(astype(user.GetComponent(REACTOR_SYSTEMS_SKILL_COMPONENT), SKILL_COMPONENT)?.skill_level, \
astype(user.GetComponent(ELECTRICAL_ENGINEERING_SKILL_COMPONENT), SKILL_COMPONENT)?.skill_level)
if(max_engineering_skill <= SKILL_LEVEL_TRAINED)
to_chat(user, SPAN_WARNING("You try to find the switch on \the [src]... How do you even turn this thing on?"))
if(!do_after(user, 3 SECONDS + 1.5 SECONDS * (SKILL_LEVEL_TRAINED - max_engineering_skill)))
return
to_chat(user, SPAN_NOTICE("You finally find the switch!"))
activate(user)
/obj/machinery/power/emitter/proc/activate(mob/user)
+11 -2
View File
@@ -292,6 +292,11 @@ ABSTRACT_TYPE(/obj/item/gun)
return
/obj/item/gun/proc/toggle_firing_mode(var/mob/user, var/list/message_mobs)
var/cancelled = FALSE
SEND_SIGNAL(user, COMSIG_GUN_TOGGLE_FIRING_MODE, src, &cancelled)
if (cancelled)
return
var/datum/firemode/new_mode = switch_firemodes(user)
if(new_mode)
playsound(user, safetyoff_sound, 25)
@@ -433,7 +438,7 @@ ABSTRACT_TYPE(/obj/item/gun)
return TRUE
/obj/item/gun/proc/Fire(atom/target, mob/living/user, clickparams, pointblank=0, reflex=0, accuracy_decrease=0, is_offhand=0)
/obj/item/gun/proc/Fire(atom/target, mob/living/user, clickparams, pointblank = 0, reflex = 0, var/accuracy_decrease = 0, is_offhand = 0)
if(!fire_checks(target,user,clickparams,pointblank,reflex))
return FALSE
@@ -443,6 +448,10 @@ ABSTRACT_TYPE(/obj/item/gun)
var/decreased_accuracy = SG.w_class - SG.offhand_accuracy
addtimer(CALLBACK(SG, PROC_REF(Fire), target, user, clickparams, pointblank, reflex, decreased_accuracy, TRUE), 1)
/// The amount of extra degrees of firing arc the gun will have from the effects of a signal raised on the user.
var/dispersion_increase = 0
SEND_SIGNAL(user, COMSIG_BEFORE_GUN_FIRE, &accuracy_decrease, &dispersion_increase)
//actually attempt to shoot
var/turf/targloc = get_turf(target) //cache this in case target gets deleted during shooting, e.g. if it was a securitron that got destroyed.
for(var/i in 1 to burst)
@@ -452,7 +461,7 @@ ABSTRACT_TYPE(/obj/item/gun)
break
var/acc = burst_accuracy[min(i, burst_accuracy.len)] - accuracy_decrease
var/disp = dispersion[min(i, dispersion.len)]
var/disp = dispersion[min(i, dispersion.len)] + dispersion_increase
process_accuracy(projectile, user, target, acc, disp)
if(pointblank)
+3 -2
View File
@@ -248,6 +248,7 @@
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) //puts a limit on how fast people can eat/drink things
self_feed_message(user)
reagents.trans_to_mob(user, min(10,amount_per_transfer_from_this), CHEM_INGEST) //A sane limiter. So you don't go drinking 300u all at once.
SEND_SIGNAL(src, COMSIG_CONTAINER_DRANK, user)
feed_sound(user)
return TRUE
else
@@ -350,8 +351,8 @@
var/trans = reagents.trans_to(target, amount_per_transfer_from_this)
to_chat(user, SPAN_NOTICE("You transfer [trans] units of the solution to [target]."))
on_pour()
on_pour(target)
return 1
/obj/item/reagent_containers/proc/on_pour()
/obj/item/reagent_containers/proc/on_pour(atom/target)
playsound(src, SFX_POUR, 25, 1)
@@ -368,6 +368,9 @@ If you add a drink with an empty icon sprite, ensure it is in the same folder, e
var/twisted = FALSE
var/obj/item/shaker_top/top
var/obj/item/reagent_containers/food/drinks/shaker_cup/cap
var/drink_quality = null
var/drink_moodlet_value = null
var/moodlet_value_per_bartending_rank = 5
/obj/item/reagent_containers/food/drinks/shaker/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
@@ -429,6 +432,16 @@ If you add a drink with an empty icon sprite, ensure it is in the same folder, e
if(last_shake <= world.time - 10) //Spam limiter.
last_shake = world.time
playsound(src.loc, SFX_SHAKER_SHAKING, 50, 1)
/**
------------ Bartender Skill Handling ------------
*/
var/datum/component/skill/bartending/bar_skill = user.GetComponent(BARTENDING_SKILL_COMPONENT)
if (bar_skill)
drink_moodlet_value = moodlet_value_per_bartending_rank * bar_skill.skill_level
if (isnull(drink_quality))
drink_quality = rand(1, 20) + 3 * bar_skill.skill_level
src.add_fingerprint(user)
return
@@ -464,7 +477,15 @@ If you add a drink with an empty icon sprite, ensure it is in the same folder, e
return TRUE
return ..()
/obj/item/reagent_containers/food/drinks/shaker/on_pour()
/obj/item/reagent_containers/food/drinks/shaker/on_pour(atom/target)
if (!isnull(drink_moodlet_value))
target.LoadComponent(/datum/component/drink_moodlet_provider, drink_moodlet_value, FALSE, drink_quality)
// Bartender has finished pouring a drink, prepare the shaker to make the next one.
if (!reagents.total_volume)
drink_quality = null
drink_moodlet_value = null
if(!top)
playsound(src, 'sound/effects/pour_big.ogg', 50, 1)
return
+14
View File
@@ -15,6 +15,11 @@
/// For hotwiring, how many cycles are needed. This decreases by 1 each cycle and triggers at 0
var/hotwire_progress = 8
/// Skill needed to use this computer.
var/required_skill = PILOT_SPACECRAFT_SKILL_COMPONENT
/// Skill level needed to use this computer, see required_skill
var/required_skill_level = SKILL_LEVEL_FAMILIAR
/obj/machinery/computer/shuttle_control/feedback_hints(mob/user, distance, is_adjacent)
. += ..()
if(initial(hotwire_progress) != hotwire_progress)
@@ -89,6 +94,15 @@
return ..()
/obj/machinery/computer/shuttle_control/attack_hand(mob/user)
// Snowflake case for checking player characters for a Pilot Spacecraft Skill.
// Only player characters will have the component. Which will both always be present on them, and will only enable its own return logic if it exists.
// NPCs, Ghostroles, and Offship Antags that don't generate skills are unaffected by this check by intentional design so that we don't have to account for them.
if (required_skill)
var/datum/component/skill/skill_check = user.GetComponent(astype(required_skill, SKILL_COMPONENT))
if (skill_check && skill_check.skill_level < required_skill_level)
to_chat(user, SPAN_WARNING("There's just so many buttons... You have no idea where to even begin."))
return
if(use_check_and_message(user))
return
if(!emagged && !allowed(user))
@@ -6,6 +6,7 @@
density = FALSE
req_access = null
ui_template = "ShuttleControlConsoleMultiLift"
required_skill = null
/obj/machinery/computer/shuttle_control/multi/lift/wall
icon_state = "lift_wall"
+4
View File
@@ -14,6 +14,7 @@
min_duration = 30
max_duration = 40
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/glue_bone/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -50,6 +51,7 @@
min_duration = 30
max_duration = 50
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/set_bone/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -90,6 +92,7 @@
min_duration = 40
max_duration = 50
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/mend_skull/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -127,6 +130,7 @@
min_duration = 30
max_duration = 40
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/finish_bone/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
+4
View File
@@ -7,6 +7,7 @@
priority = 2
can_infect = TRUE
blood_level = 1
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/open_encased/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -73,6 +74,7 @@
min_duration = 20
max_duration = 30
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/open_encased/retract/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -127,6 +129,7 @@
min_duration = 20
max_duration = 30
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/open_encased/close/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -185,6 +188,7 @@
min_duration = 20
max_duration = 30
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/open_encased/mend/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
+2
View File
@@ -12,6 +12,7 @@
min_duration = 70
max_duration = 90
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/generic/cut_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_NORMAL
@@ -42,6 +43,7 @@
min_duration = 70
max_duration = 90
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/robotics/face/synthskin/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.face == FACE_NORMAL && target.get_species() == SPECIES_IPC_SHELL
+8
View File
@@ -8,6 +8,7 @@
name = "Retract Facial Incisions"
priority = 2
can_infect = FALSE
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -25,6 +26,7 @@
min_duration = 70
max_duration = 90
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/generic/prepare_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_CUT_OPEN
@@ -57,6 +59,7 @@
min_duration = 30
max_duration = 70
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/generic/alter_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_RETRACTED
@@ -104,6 +107,7 @@
min_duration = 50
max_duration = 80
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/face/cauterize/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.face > FACE_NORMAL
@@ -147,6 +151,7 @@
min_duration = 70
max_duration = 90
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/robotics/face/synthskinopen/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.face == FACE_NORMAL && target.get_species() == SPECIES_IPC_SHELL
@@ -175,6 +180,7 @@
min_duration = 70
max_duration = 90
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/robotics/face/prepare_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_CUT_OPEN
@@ -204,6 +210,7 @@
min_duration = 30
max_duration = 70
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/robotics/face/alter_synthface/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target_zone == BP_MOUTH && target.op_stage.face == FACE_RETRACTED
@@ -249,6 +256,7 @@
min_duration = 50
max_duration = 80
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/robotics/face/seal_face/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
return ..() && target.op_stage.face > FACE_NORMAL
+14
View File
@@ -30,6 +30,8 @@
priority = 2
min_duration = 50
max_duration = 70
// Basic procedure any novice can do
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/generic/cut_with_laser/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -73,6 +75,8 @@
priority = 2
min_duration = 60
max_duration = 80
// Automated tool that requires no skill
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_UNFAMILIAR)
/singleton/surgery_step/generic/incision_manager/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -117,6 +121,8 @@
min_duration = 60
max_duration = 80
// Basic surgery any novice surgeon can do
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/generic/cut_open/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -161,6 +167,8 @@
min_duration = 80
max_duration = 100
// Basic surgery any novice surgeon can do
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/generic/cut_open_vaurca/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -205,6 +213,8 @@
min_duration = 20
max_duration = 30
// Basic surgery any novice surgeon can do
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/generic/clamp_bleeders/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -244,6 +254,8 @@
min_duration = 20
max_duration = 30
// Basic surgery any novice surgeon can do
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/generic/retract_skin/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -307,6 +319,8 @@
min_duration = 40
max_duration = 70
// Basic surgery any novice surgeon can do
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/generic/cauterize/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
+5
View File
@@ -6,6 +6,7 @@
/singleton/surgery_step/cavity
priority = 1
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -49,6 +50,7 @@
min_duration = 50
max_duration = 70
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/make_space/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -81,6 +83,7 @@
min_duration = 50
max_duration = 70
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/close_space/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -108,6 +111,7 @@
min_duration = 60
max_duration = 80
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/place_item/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -159,6 +163,7 @@
min_duration = 60
max_duration = 80
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/implant_removal/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
+4
View File
@@ -6,6 +6,7 @@
/singleton/surgery_step/limb
priority = 3 // Must be higher than /singleton/surgery_step/internal
can_infect = FALSE
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/limb/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -27,6 +28,7 @@
min_duration = 40
max_duration = 60
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/limb/attach/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/E = tool
@@ -60,6 +62,7 @@
min_duration = 80
max_duration = 100
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/limb/connect/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -98,6 +101,7 @@
min_duration = 60
max_duration = 80
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/limb/mechanize/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
+8
View File
@@ -3,6 +3,7 @@
priority = 2
can_infect = TRUE
blood_level = 1
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/internal/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -28,6 +29,7 @@
min_duration = 50
max_duration = 70
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/internal/fix_organ/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -116,6 +118,7 @@
min_duration = 50
max_duration = 70
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/internal/fix_organ_robotic/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -187,6 +190,7 @@
min_duration = 70
max_duration = 90
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/internal/detach_organ/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -253,6 +257,7 @@
min_duration = 40
max_duration = 60
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/internal/remove_organ/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -312,6 +317,7 @@
min_duration = 40
max_duration = 60
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/internal/replace_organ/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -417,6 +423,7 @@
min_duration = 80
max_duration = 100
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/internal/attach_organ/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -471,6 +478,7 @@
min_duration = 80
max_duration = 100
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/internal/prepare/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
+6
View File
@@ -16,6 +16,7 @@
min_duration = 40
max_duration = 60
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/fix_vein/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -65,6 +66,7 @@
min_duration = 80
max_duration = 130
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_PROFESSIONAL)
/singleton/surgery_step/internal/fix_dead_tissue/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -130,6 +132,7 @@
min_duration = 80
max_duration = 90
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_PROFESSIONAL)
/singleton/surgery_step/treat_necrosis/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -196,6 +199,7 @@
min_duration = 50
max_duration = 70
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/fix_tendon/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -237,6 +241,7 @@
min_duration = 100
max_duration = 160
skill_requirements = alist(ROBOTICS_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/hardsuit/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
@@ -274,6 +279,7 @@
min_duration = 90
max_duration = 140
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_FAMILIAR)
/singleton/surgery_step/amputate/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())

Some files were not shown because too many files have changed in this diff Show More