diff --git a/code/__DEFINES/lighting.dm b/code/__DEFINES/lighting.dm
index 819abd032fc7..96197f578e25 100644
--- a/code/__DEFINES/lighting.dm
+++ b/code/__DEFINES/lighting.dm
@@ -73,6 +73,7 @@
#define LIGHT_RANGE_FIRE 3 //How many tiles standard fires glow.
#define LIGHTING_PLANE_ALPHA_VISIBLE 255
+#define LIGHTING_PLANE_ALPHA_NV_TRAIT 250
#define LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE 192
#define LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE 128 //For lighting alpha, small amounts lead to big changes. even at 128 its hard to figure out what is dark and what is light, at 64 you almost can't even tell.
#define LIGHTING_PLANE_ALPHA_INVISIBLE 0
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index ad3f3d0d62cb..240d75f39b48 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -216,6 +216,10 @@
#define REAGENTS_METABOLISM 0.4 //How many units of reagent are consumed per tick, by default.
#define REAGENTS_EFFECT_MULTIPLIER (REAGENTS_METABOLISM / 0.4) // By defining the effect multiplier this way, it'll exactly adjust all effects according to how they originally were with the 0.4 metabolism
+// Roundstart trait system
+
+#define MAX_TRAITS 6 //The maximum amount of traits one character can have at roundstart
+
// AI Toggles
#define AI_CAMERA_LUMINOSITY 5
#define AI_VOX // Comment out if you don't want VOX to be enabled and have players download the voice sounds.
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index d5db08be9b36..3e654f041e31 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -56,13 +56,14 @@
#define INIT_ORDER_RESEARCH 14
#define INIT_ORDER_EVENTS 13
#define INIT_ORDER_JOBS 12
-#define INIT_ORDER_TICKER 11
-#define INIT_ORDER_MAPPING 10
-#define INIT_ORDER_ATOMS 9
-#define INIT_ORDER_NETWORKS 8
-#define INIT_ORDER_LANGUAGE 7
-#define INIT_ORDER_MACHINES 6
-#define INIT_ORDER_CIRCUIT 5
+#define INIT_ORDER_TRAITS 11
+#define INIT_ORDER_TICKER 10
+#define INIT_ORDER_MAPPING 9
+#define INIT_ORDER_ATOMS 8
+#define INIT_ORDER_NETWORKS 7
+#define INIT_ORDER_LANGUAGE 6
+#define INIT_ORDER_MACHINES 5
+#define INIT_ORDER_CIRCUIT 4
#define INIT_ORDER_TIMER 1
#define INIT_ORDER_DEFAULT 0
#define INIT_ORDER_AIR -1
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 182cba58171c..d575793e6fb0 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -39,6 +39,19 @@
#define TRAIT_ANTIMAGIC "anti_magic"
#define TRAIT_HOLY "holy"
+#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
+#define TRAIT_AGEUSIA "ageusia"
+#define TRAIT_HEAVY_SLEEPER "heavy_sleeper"
+#define TRAIT_NIGHT_VISION "night_vision"
+#define TRAIT_LIGHT_STEP "light_step"
+#define TRAIT_SPIRITUAL "spiritual"
+#define TRAIT_VORACIOUS "voracious"
+#define TRAIT_SELF_AWARE "self_aware"
+#define TRAIT_FREERUNNING "freerunning"
+#define TRAIT_SKITTISH "skittish"
+#define TRAIT_POOR_AIM "poor_aim"
+#define TRAIT_PROSOPAGNOSIA "prosopagnosia"
+
// common trait sources
#define TRAIT_GENERIC "generic"
#define EYE_DAMAGE "eye_damage"
@@ -47,6 +60,7 @@
#define MAGIC_TRAIT "magic"
#define TRAUMA_TRAIT "trauma"
#define SPECIES_TRAIT "species"
+#define ROUNDSTART_TRAIT "roundstart" //cannot be removed without admin intervention
// unique trait sources, still defines
#define STATUE_MUTE "statue"
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index c5ea390922de..a9b016fd4e19 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -308,7 +308,7 @@
inv.hud = src
inv_slots[inv.slot_id] = inv
inv.update_icon()
-
+
update_locked_slots()
/datum/hud/human/update_locked_slots()
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 6f8e630ffb95..9ad962b21dfe 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -273,6 +273,8 @@
/datum/config_entry/flag/ic_printing
+/datum/config_entry/flag/roundstart_traits
+
/datum/config_entry/flag/enable_night_shifts
/datum/config_entry/flag/randomize_shift_time
diff --git a/code/controllers/subsystem/processing/traits.dm b/code/controllers/subsystem/processing/traits.dm
new file mode 100644
index 000000000000..9d536c813102
--- /dev/null
+++ b/code/controllers/subsystem/processing/traits.dm
@@ -0,0 +1,36 @@
+//Used to process and handle roundstart trait datums
+//Trait datums are separate from trait strings:
+// - Trait strings are used for faster checking in code
+// - Trait datums are stored and hold different effects, as well as being a vector for applying trait string
+PROCESSING_SUBSYSTEM_DEF(traits)
+ name = "Traits"
+ init_order = INIT_ORDER_TRAITS
+ flags = SS_BACKGROUND
+ wait = 10
+ runlevels = RUNLEVEL_GAME
+
+ var/list/traits = list() //Assoc. list of all roundstart trait datums; "name" = /path/
+ var/list/trait_points = list() //Assoc. list of trait names and their "point cost"; positive numbers are good traits, and negative ones are bad
+ var/list/trait_objects = list() //A list of all trait objects in the game, since some may process
+
+/datum/controller/subsystem/processing/traits/Initialize(timeofday)
+ if(!traits.len)
+ SetupTraits()
+ ..()
+
+/datum/controller/subsystem/processing/traits/proc/SetupTraits()
+ for(var/V in subtypesof(/datum/trait))
+ var/datum/trait/T = V
+ traits[initial(T.name)] = T
+ trait_points[initial(T.name)] = initial(T.value)
+
+/datum/controller/subsystem/processing/traits/proc/AssignTraits(mob/living/user, client/cli)
+ if(!isnewplayer(user))
+ GenerateTraits(cli)
+ for(var/V in cli.prefs.character_traits)
+ user.add_trait_datum(V)
+
+/datum/controller/subsystem/processing/traits/proc/GenerateTraits(client/user)
+ if(user.prefs.character_traits.len)
+ return
+ user.prefs.character_traits = user.prefs.all_traits
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
index 707fce9aef3f..9193651ee2ac 100644
--- a/code/datums/components/caltrop.dm
+++ b/code/datums/components/caltrop.dm
@@ -46,6 +46,8 @@
return
var/damage = rand(min_damage, max_damage)
+ if(H.has_trait(TRAIT_LIGHT_STEP))
+ damage *= 0.75
H.apply_damage(damage, BRUTE, picked_def_zone)
if(cooldown < world.time - 10) //cooldown to avoid message spam.
diff --git a/code/datums/traits/_trait.dm b/code/datums/traits/_trait.dm
new file mode 100644
index 000000000000..a6073b5008ea
--- /dev/null
+++ b/code/datums/traits/_trait.dm
@@ -0,0 +1,108 @@
+//every trait in this folder should be coded around being applied on spawn
+//these are NOT "mob traits" like GOTTAGOFAST, but exist as a medium to apply them and other different effects
+/datum/trait
+ var/name = "Test Trait"
+ var/desc = "This is a test trait."
+ var/value = 0
+ var/human_only = TRUE
+ var/gain_text
+ var/lose_text
+ var/medical_record_text //This text will appear on medical records for the trait. Not yet implemented
+ var/mob_trait //if applicable, apply and remove this mob trait
+ var/mob/living/trait_holder
+
+/datum/trait/New(mob/living/trait_mob)
+ ..()
+ if(!trait_mob || (human_only && !ishuman(trait_mob)) || trait_mob.has_trait_datum(type))
+ qdel(src)
+ trait_holder = trait_mob
+ SStraits.trait_objects += src
+ to_chat(trait_holder, gain_text)
+ trait_holder.roundstart_traits += src
+ if(mob_trait)
+ trait_holder.add_trait(mob_trait, ROUNDSTART_TRAIT)
+ START_PROCESSING(SStraits, src)
+ add()
+ if(!SSticker.HasRoundStarted()) //on roundstart or on latejoin; latejoin code is in new_player.dm
+ on_spawn()
+ addtimer(CALLBACK(src, .proc/post_add), 30)
+
+/datum/trait/Destroy()
+ STOP_PROCESSING(SStraits, src)
+ remove()
+ if(trait_holder)
+ to_chat(trait_holder, lose_text)
+ trait_holder.roundstart_traits -= src
+ if(mob_trait)
+ trait_holder.remove_trait(mob_trait, ROUNDSTART_TRAIT, TRUE)
+ SStraits.trait_objects -= src
+ return ..()
+
+/datum/trait/proc/add() //special "on add" effects
+/datum/trait/proc/on_spawn() //these should only trigger when the character is being created for the first time, i.e. roundstart/latejoin
+/datum/trait/proc/remove() //special "on remove" effects
+/datum/trait/proc/on_process() //process() has some special checks, so this is the actual process
+/datum/trait/proc/post_add() //for text, disclaimers etc. given after you spawn in with the trait
+
+/datum/trait/process()
+ if(QDELETED(trait_holder))
+ qdel(src)
+ return
+ on_process()
+
+/mob/living/proc/get_trait_string(medical) //helper string. gets a string of all the traits the mob has
+ var/list/dat = list()
+ if(!medical)
+ for(var/V in roundstart_traits)
+ var/datum/trait/T = V
+ dat += T.name
+ if(!dat.len)
+ return "None"
+ return dat.Join(", ")
+ else
+ for(var/V in roundstart_traits)
+ var/datum/trait/T = V
+ dat += T.medical_record_text
+ if(!dat.len)
+ return "None"
+ return dat.Join("
")
+
+/*
+
+Commented version of Nearsighted to help you add your own traits
+Use this as a guideline
+
+/datum/trait/nearsighted
+ name = "Nearsighted"
+ ///The trait's name
+
+ desc = "You are nearsighted without prescription glasses, but spawn with a pair."
+ ///Short description, shows next to name in the trait panel
+
+ value = -1
+ ///If this is above 0, it's a positive trait; if it's not, it's a negative one; if it's 0, it's a neutral
+
+ mob_trait = TRAIT_NEARSIGHT
+ ///This define is in __DEFINES/traits.dm and is the actual "trait" that the game tracks
+ ///You'll need to use "has_trait(X, sources)" checks around the code to check this; for instance, the Ageusia trait is checked in taste code
+ ///If you need help finding where to put it, the declaration finder on GitHub is the best way to locate it
+
+ gain_text = "Things far away from you start looking blurry."
+ lose_text = "You start seeing faraway things normally again."
+ medical_record_text = "Subject has permanent nearsightedness."
+ ///These three are self-explanatory
+
+/datum/trait/nearsighted/on_spawn()
+ var/mob/living/carbon/human/H = trait_holder
+ var/obj/item/clothing/glasses/regular/glasses = new(get_turf(H))
+ H.put_in_hands(glasses)
+ H.equip_to_slot(glasses, slot_glasses)
+ H.regenerate_icons()
+
+//This whole proc is called automatically
+//It spawns a set of prescription glasses on the user, then attempts to put it into their hands, then attempts to make them equip it.
+//This means that if they fail to equip it, they glasses spawn in their hands, and if they fail to be put into the hands, they spawn on the ground
+//Hooray for fallbacks!
+//If you don't need any special effects like spawning glasses, then you don't need an add()
+
+*/
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
new file mode 100644
index 000000000000..538146b873aa
--- /dev/null
+++ b/code/datums/traits/good.dm
@@ -0,0 +1,83 @@
+//predominantly positive traits
+//this file is named weirdly so that positive traits are listed above negative ones
+
+/datum/trait/alcohol_tolerance
+ name = "Alcohol Tolerance"
+ desc = "You become drunk more slowly and suffer fewer drawbacks from alcohol."
+ value = 1
+ mob_trait = TRAIT_ALCOHOL_TOLERANCE
+ gain_text = "You feel like you could drink a whole keg!"
+ lose_text = "You don't feel as resistant to alcohol anymore. Somehow."
+
+
+
+/datum/trait/freerunning
+ name = "Freerunning"
+ desc = "You're great at quick moves! You can climb tables more quickly."
+ value = 2
+ mob_trait = TRAIT_FREERUNNING
+ gain_text = "You feel lithe on your feet!"
+ lose_text = "You feel clumsy again."
+
+
+
+/datum/trait/light_step
+ name = "Light Step"
+ desc = "You walk with a gentle step, making stepping on sharp objects quieter and less painful."
+ value = 1
+ mob_trait = TRAIT_LIGHT_STEP
+ gain_text = "You walk with a little more lithenessk."
+ lose_text = "You start tromping around like a barbarian."
+
+
+
+/datum/trait/night_vision
+ name = "Night Vision"
+ desc = "You can see slightly more clearly in full darkness than most people."
+ value = 1
+ mob_trait = TRAIT_NIGHT_VISION
+ gain_text = "The shadows seem a little less dark."
+ lose_text = "Everything seems a little darker."
+
+/datum/trait/night_vision/on_spawn()
+ var/mob/living/carbon/human/H = trait_holder
+ var/obj/item/organ/eyes/eyes = H.getorgan(/obj/item/organ/eyes)
+ if(!eyes || eyes.lighting_alpha)
+ return
+ eyes.Insert(H) //refresh their eyesight and vision
+
+
+
+/datum/trait/selfaware
+ name = "Self-Aware"
+ desc = "You know your body well, and can accurately assess the extent of your wounds."
+ value = 2
+ mob_trait = TRAIT_SELF_AWARE
+
+
+
+/datum/trait/skittish
+ name = "Skittish"
+ desc = "You can conceal yourself in danger. Ctrl-shift-click a closed locker to jump into it, as long as you have access."
+ value = 2
+ mob_trait = TRAIT_SKITTISH
+
+
+
+/datum/trait/spiritual
+ name = "Spiritual"
+ desc = "You're in tune with the gods, and your prayers may be more likely to be heard. Or not."
+ value = 1
+ mob_trait = TRAIT_SPIRITUAL
+ gain_text = "You feel a little more faithful to the gods today."
+ lose_text = "You feel less faithful in the gods."
+
+
+
+/datum/trait/voracious
+ name = "Voracious"
+ desc = "Nothing gets between you and your food. You eat twice as fast as everyone else!"
+ value = 1
+ mob_trait = TRAIT_VORACIOUS
+ gain_text = "You feel HONGRY."
+ lose_text = "You no longer feel HONGRY."
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
new file mode 100644
index 000000000000..9fd966eeadbf
--- /dev/null
+++ b/code/datums/traits/negative.dm
@@ -0,0 +1,156 @@
+//predominantly negative traits
+
+
+
+/datum/trait/heavy_sleeper
+ name = "Heavy Sleeper"
+ desc = "You sleep like a rock! Whenever you're put to sleep, you sleep for a little bit longer."
+ value = -1
+ mob_trait = TRAIT_HEAVY_SLEEPER
+ gain_text = "You feel sleepy."
+ lose_text = "You feel awake again."
+ medical_record_text = "Patient has abnormal sleep study results and is difficult to wake up."
+
+
+
+/datum/trait/nearsighted //t. errorage
+ name = "Nearsighted"
+ desc = "You are nearsighted without prescription glasses, but spawn with a pair."
+ value = -1
+ gain_text = "Things far away from you start looking blurry."
+ lose_text = "You start seeing faraway things normally again."
+ medical_record_text = "Patient requires prescription glasses in order to counteract nearsightedness."
+
+/datum/trait/nearsighted/add()
+ trait_holder.become_nearsighted(ROUNDSTART_TRAIT)
+
+/datum/trait/nearsighted/on_spawn()
+ var/mob/living/carbon/human/H = trait_holder
+ var/obj/item/clothing/glasses/regular/glasses = new(get_turf(H))
+ H.put_in_hands(glasses)
+ H.equip_to_slot(glasses, slot_glasses)
+ H.regenerate_icons() //this is to remove the inhand icon, which persists even if it's not in their hands
+
+
+
+/datum/trait/nonviolent
+ name = "Pacifist"
+ desc = "The thought of violence makes you sick. So much so, in fact, that you can't hurt anyone."
+ value = -2
+ mob_trait = TRAIT_PACIFISM
+ gain_text = "You feel repulsed by the thought of violence!"
+ lose_text = "You think you can defend yourself again."
+ medical_record_text = "Patient is unusually pacifistic and cannot bring themselves to cause physical harm."
+
+/datum/trait/nonviolent/on_process()
+ if(trait_holder.mind && trait_holder.mind.antag_datums.len)
+ to_chat(trait_holder, "Your antagonistic nature has caused you to renounce your pacifism.")
+ qdel(src)
+
+
+
+/datum/trait/poor_aim
+ name = "Poor Aim"
+ desc = "You're terrible with guns and can't line up a straight shot to save your life. Dual-wielding is right out."
+ value = -1
+ mob_trait = TRAIT_POOR_AIM
+ medical_record_text = "Patient possesses a strong tremor in both hands."
+
+
+
+/datum/trait/prosopagnosia
+ name = "Prosopagnosia"
+ desc = "You have a mental disorder that prevents you from being able to recognize faces at all."
+ value = -1
+ mob_trait = TRAIT_PROSOPAGNOSIA
+ medical_record_text = "Patient suffers from prosopagnosia and cannot recognize faces."
+
+
+
+/datum/trait/prosthetic_limb
+ name = "Prosthetic Limb"
+ desc = "An accident caused you to lose one of your limbs. Because of this, you now have a random prosthetic!"
+ value = -1
+ var/slot_string = "limb"
+
+/datum/trait/prosthetic_limb/on_spawn()
+ var/limb_slot = pick("l_arm", "r_arm", "l_leg", "r_leg")
+ var/mob/living/carbon/human/H = trait_holder
+ var/obj/item/bodypart/old_part = H.get_bodypart(limb_slot)
+ var/obj/item/bodypart/prosthetic
+ switch(limb_slot)
+ if("l_arm")
+ prosthetic = new/obj/item/bodypart/l_arm/robot/surplus(trait_holder)
+ slot_string = "left arm"
+ if("r_arm")
+ prosthetic = new/obj/item/bodypart/r_arm/robot/surplus(trait_holder)
+ slot_string = "right arm"
+ if("l_leg")
+ prosthetic = new/obj/item/bodypart/l_leg/robot/surplus(trait_holder)
+ slot_string = "left leg"
+ if("r_leg")
+ prosthetic = new/obj/item/bodypart/r_leg/robot/surplus(trait_holder)
+ slot_string = "right leg"
+ prosthetic.replace_limb(H)
+ qdel(old_part)
+ H.regenerate_icons()
+
+/datum/trait/prosthetic_limb/post_add()
+ to_chat(trait_holder, "Your [slot_string] has been replaced with a surplus prosthetic. It is fragile and will easily come apart under duress. Additionally, \
+ you need to use a welding tool and cables to repair it, instead of bruise packs and ointment.")
+
+
+
+/datum/trait/insanity
+ name = "Reality Dissociation Syndrome"
+ desc = "You suffer from a severe disorder that causes very vivid hallucinations. Mindbreaker toxin can suppress its effects, and you are immune to mindbreaker's hallucinogenic properties. This is not a license to grief."
+ value = -2
+ //no mob trait because it's handled uniquely
+ gain_text = "..."
+ lose_text = "You feel in tune with the world again."
+ medical_record_text = "Patient suffers from acute Reality Dissociation Syndrome and experiences vivid hallucinations."
+
+/datum/trait/insanity/on_process()
+ if(trait_holder.reagents.has_reagent("mindbreaker"))
+ trait_holder.hallucination = 0
+ return
+ if(prob(1)) //we'll all be mad soon enough
+ madness()
+
+/datum/trait/insanity/proc/madness(mad_fools)
+ set waitfor = FALSE
+ if(!mad_fools)
+ mad_fools = prob(20)
+ if(mad_fools)
+ var/hallucination_type = pick(subtypesof(/datum/hallucination/rds))
+ new hallucination_type (trait_holder, FALSE)
+ else
+ trait_holder.hallucination += rand(10, 50)
+
+/datum/trait/insanity/post_add() //I don't /think/ we'll need this but for newbies who think "roleplay as insane" = "license to kill" it's probably a good thing to have
+ if(!trait_holder.mind || trait_holder.mind.special_role)
+ return
+ to_chat(trait_holder, "Please note that your dissociation syndrome does NOT give you the right to attack people or otherwise cause any interference to \
+ the round. You are not an antagonist, and the rules will treat you the same as other crewmembers.")
+
+
+
+/datum/trait/social_anxiety
+ name = "Social Anxiety"
+ desc = "Talking to people is very difficult for you, and you often stutter or even lock up."
+ value = -1
+ gain_text = "You start worrying about what you're saying."
+ lose_text = "You feel easier about talking again." //if only it were that easy!
+ medical_record_text = "Patient is usually anxious in social encounters and prefers to avoid them."
+ var/dumb_thing = TRUE
+
+/datum/trait/social_anxiety/on_process()
+ var/mob/living/carbon/human/H = trait_holder
+ if(prob(5))
+ H.stuttering = max(3, H.stuttering)
+ else if(prob(1) && !H.silent)
+ to_chat(H, "You retreat into yourself. You really don't feel up to talking.")
+ H.silent = max(10, H.silent)
+ else if(prob(0.5) && dumb_thing)
+ to_chat(H, "You think of a dumb thing you said a long time ago and scream internally.")
+ dumb_thing = FALSE //only once per life
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
new file mode 100644
index 000000000000..140b751fd809
--- /dev/null
+++ b/code/datums/traits/neutral.dm
@@ -0,0 +1,33 @@
+//traits with no real impact that can be taken freely
+//MAKE SURE THESE DO NOT MAJORLY IMPACT GAMEPLAY. those should be positive or negative traits.
+
+/datum/trait/no_taste
+ name = "Ageusia"
+ desc = "You can't taste anything! Toxic food will still poison you."
+ value = 0
+ mob_trait = TRAIT_AGEUSIA
+ gain_text = "You can't taste anything!"
+ lose_text = "You can taste again!"
+ medical_record_text = "Patient suffers from ageusia and is incapable of tasting food or reagents."
+
+
+
+/datum/trait/deviant_tastes
+ name = "Deviant Tastes"
+ desc = "You dislike food that most people enjoy, and find delicious what they don't."
+ value = 0
+ gain_text = "You start craving something that tastes strange."
+ lose_text = "You feel like eating normal food again."
+
+/datum/trait/deviant_tastes/add()
+ var/mob/living/carbon/human/H = trait_holder
+ var/datum/species/species = H.dna.species
+ var/liked = species.liked_food
+ species.liked_food = species.disliked_food
+ species.disliked_food = liked
+
+/datum/trait/deviant_tastes/remove()
+ var/mob/living/carbon/human/H = trait_holder
+ var/datum/species/species = H.dna.species
+ species.liked_food = initial(species.liked_food)
+ species.disliked_food = initial(species.disliked_food)
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 39ab21d72bb1..05264aaa5a2b 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -172,6 +172,8 @@ GAS ANALYZER
trauma_desc += B.scan_desc
trauma_text += trauma_desc
to_chat(user, "\tCerebral traumas detected: subjects appears to be suffering from [english_list(trauma_text)].")
+ if(C.roundstart_traits.len)
+ to_chat(user, "\tSubject has the following physiological traits: [C.get_trait_string()].")
if(advanced)
to_chat(user, "\tBrain Activity Level: [(200 - M.getBrainLoss())/2]%.")
if (M.radiation)
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index 2e9fcae7bdf8..fba0c1b1a2f4 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -305,5 +305,8 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
/obj/item/shard/Crossed(mob/living/L)
if(istype(L) && has_gravity(loc))
- playsound(loc, 'sound/effects/glass_step.ogg', 50, 1)
+ if(L.has_trait(TRAIT_LIGHT_STEP))
+ playsound(loc, 'sound/effects/glass_step.ogg', 30, 1)
+ else
+ playsound(loc, 'sound/effects/glass_step.ogg', 50, 1)
. = ..()
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 39ad436b05e6..cd265969ebbf 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -74,6 +74,8 @@
adjusted_climb_time *= 2
if(isalien(user))
adjusted_climb_time *= 0.25 //aliens are terrifyingly fast
+ if(user.has_trait(TRAIT_FREERUNNING)) //do you have any idea how fast I am???
+ adjusted_climb_time *= 0.8
structureclimber = user
if(do_mob(user, user, adjusted_climb_time))
if(src.loc) //Checking if structure has been destroyed
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 52fe7b9bf96d..8332f1b338e7 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -397,7 +397,14 @@
else
togglelock(user)
-/obj/structure/closet/proc/togglelock(mob/living/user)
+/obj/structure/closet/CtrlShiftClick(mob/living/user)
+ if(!user.has_trait(TRAIT_SKITTISH))
+ return ..()
+ if(!user.canUseTopic(src) || !isturf(user.loc))
+ return
+ dive_into(user)
+
+/obj/structure/closet/proc/togglelock(mob/living/user, silent)
if(secure && !broken)
if(allowed(user))
if(iscarbon(user))
@@ -406,7 +413,7 @@
user.visible_message("[user] [locked ? null : "un"]locks [src].",
"You [locked ? null : "un"]lock [src].")
update_icon()
- else
+ else if(!silent)
to_chat(user, "Access Denied")
else if(secure && broken)
to_chat(user, "\The [src] is broken!")
@@ -456,3 +463,23 @@
/obj/structure/closet/return_temperature()
return
+
+/obj/structure/closet/proc/dive_into(mob/living/user)
+ var/turf/T1 = get_turf(user)
+ var/turf/T2 = get_turf(src)
+ if(!open() && !opened)
+ togglelock(user, TRUE)
+ if(!open())
+ to_chat(user, "It won't budge!")
+ return
+ step_towards(user, T2)
+ T1 = get_turf(user)
+ if(T1 == T2)
+ user.resting = TRUE //so people can jump into crates without slamming the lid on their head
+ if(!close())
+ to_chat(user, "You can't get [src] to close!")
+ user.resting = FALSE
+ return
+ user.resting = FALSE
+ togglelock(user)
+ T1.visible_message("[user] dives into [src]!")
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index 9f5af20b672c..aaed0f229426 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -32,6 +32,12 @@
font_color = "red"
prayer_type = "CULTIST PRAYER"
deity = "Nar-Sie"
+ else if(isliving(usr))
+ var/mob/living/L = usr
+ if(L.has_trait(TRAIT_SPIRITUAL))
+ cross.icon_state = "holylight"
+ font_color = "blue"
+ prayer_type = "SPIRITUAL PRAYER"
msg = "[icon2html(cross, GLOB.admins)][prayer_type][deity ? " (to [deity])" : ""]: [ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]: [msg]"
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index a3d9f9b8dc1a..3e9e4cd27c6b 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -76,6 +76,13 @@ GLOBAL_LIST_EMPTY(preferences_datums)
//Mob preview
var/icon/preview_icon = null
+ //Trait list
+ var/list/positive_traits = list()
+ var/list/negative_traits = list()
+ var/list/neutral_traits = list()
+ var/list/all_traits = list()
+ var/list/character_traits = list()
+
//Jobs, uses bitflags
var/job_civilian_high = 0
var/job_civilian_med = 0
@@ -173,6 +180,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "
| "
if(jobban_isbanned(user, "appearance"))
@@ -761,6 +772,65 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return job_engsec_low
return 0
+/datum/preferences/proc/SetTraits(mob/user)
+ if(!SStraits)
+ to_chat(user, "The trait subsystem is still initializing! Try again in a minute.")
+ return
+
+ var/list/dat = list()
+ if(!SStraits.traits.len)
+ dat += "The trait subsystem hasn't finished initializing, please hold..."
+ dat += " " + + else + dat += " " + dat += " Left-click to add or remove traits. You need one negative trait for every positive trait. "
+ dat += "\ + Traits are applied at roundstart and cannot normally be removed. " + dat += " "*/ + dat += " \ + Trait balance remaining: [GetTraitBalance()] " + for(var/V in SStraits.traits) + var/datum/trait/T = SStraits.traits[V] + var/trait_name = initial(T.name) + var/has_trait + var/trait_cost = initial(T.value) * -1 + for(var/_V in all_traits) + if(_V == trait_name) + has_trait = TRUE + if(has_trait) + trait_cost *= -1 //invert it back, since we'd be regaining this amount + if(trait_cost > 0) + trait_cost = "+[trait_cost]" + var/font_color = "#AAAAFF" + if(initial(T.value) != 0) + font_color = initial(T.value) > 0 ? "#AAFFAA" : "#FFAAAA" + if(has_trait) + dat += "[trait_name] - [initial(T.desc)] \ + [has_trait ? "Lose" : "Take"] ([trait_cost] pts.) " + else + dat += "[trait_name] - [initial(T.desc)] \ + [has_trait ? "Lose" : "Take"] ([trait_cost] pts.) " + dat += " Trait Preferences ", 900, 600) //no reason not to reuse the occupation window, as it's cleaner that way
+ popup.set_window_options("can_close=0")
+ popup.set_content(dat.Join())
+ popup.open(0)
+ return
+
+/datum/preferences/proc/GetTraitBalance()
+ var/bal = 0
+ for(var/V in all_traits)
+ var/datum/trait/T = SStraits.traits[V]
+ bal -= initial(T.value)
+ return bal
+
/datum/preferences/proc/process_link(mob/user, list/href_list)
if(href_list["jobbancheck"])
var/job = sanitizeSQL(href_list["jobbancheck"])
@@ -808,6 +878,64 @@ GLOBAL_LIST_EMPTY(preferences_datums)
SetChoices(user)
return 1
+ else if(href_list["preference"] == "trait")
+ if(SSticker.HasRoundStarted() && !isnewplayer(user))
+ to_chat(user, "The round has already started. Please wait until next round to set up your traits!")
+ return
+ switch(href_list["task"])
+ if("close")
+ user << browse(null, "window=mob_occupation")
+ ShowChoices(user)
+ if("update")
+ var/trait = href_list["trait"]
+ var/value = SStraits.trait_points[trait]
+ if(value == 0)
+ if(trait in neutral_traits)
+ neutral_traits -= trait
+ all_traits -= trait
+ else
+ if(all_traits.len >= MAX_TRAITS)
+ to_chat(user, "You can't have more than [MAX_TRAITS] traits!")
+ return
+ neutral_traits += trait
+ all_traits += trait
+ else
+ var/balance = GetTraitBalance()
+ if(trait in positive_traits)
+ positive_traits -= trait
+ all_traits -= trait
+ else if(trait in negative_traits)
+ if(balance + value < 0)
+ to_chat(user, "Refunding this would cause you to go below your balance!")
+ return
+ negative_traits -= trait
+ all_traits -= trait
+ else if(value > 0)
+ if(all_traits.len >= MAX_TRAITS)
+ to_chat(user, "You can't have more than [MAX_TRAITS] traits!")
+ return
+ if(balance - value < 0)
+ to_chat(user, "You don't have enough balance to gain this trait!")
+ return
+ positive_traits += trait
+ all_traits += trait
+ else
+ if(all_traits.len >= MAX_TRAITS)
+ to_chat(user, "You can't have more than [MAX_TRAITS] traits!")
+ return
+ negative_traits += trait
+ all_traits += trait
+ SetTraits(user)
+ if("reset")
+ all_traits = list()
+ positive_traits = list()
+ negative_traits = list()
+ neutral_traits = list()
+ SetTraits(user)
+ else
+ SetTraits(user)
+ return TRUE
+
switch(href_list["task"])
if("random")
switch(href_list["preference"])
@@ -1331,3 +1459,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
character.update_body()
character.update_hair()
character.update_body_parts()
+
+ if(CONFIG_GET(flag/roundstart_traits))
+ SStraits.AssignTraits(character, parent)
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 756a5577a67c..09cbc60aa98c 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -321,6 +321,12 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["job_engsec_med"] >> job_engsec_med
S["job_engsec_low"] >> job_engsec_low
+ //Traits
+ S["all_traits"] >> all_traits
+ S["positive_traits"] >> positive_traits
+ S["negative_traits"] >> negative_traits
+ S["neutral_traits"] >> neutral_traits
+
//try to fix any outdated data if necessary
if(needs_update >= 0)
update_character(needs_update, S) //needs_update == savefile_version if we need an update (positive integer)
@@ -376,6 +382,11 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
job_engsec_med = sanitize_integer(job_engsec_med, 0, 65535, initial(job_engsec_med))
job_engsec_low = sanitize_integer(job_engsec_low, 0, 65535, initial(job_engsec_low))
+ all_traits = SANITIZE_LIST(all_traits)
+ positive_traits = SANITIZE_LIST(positive_traits)
+ negative_traits = SANITIZE_LIST(negative_traits)
+ neutral_traits = SANITIZE_LIST(neutral_traits)
+
return 1
/datum/preferences/proc/save_character()
@@ -439,6 +450,12 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["job_engsec_med"] , job_engsec_med)
WRITE_FILE(S["job_engsec_low"] , job_engsec_low)
+ //Traits
+ WRITE_FILE(S["all_traits"] , all_traits)
+ WRITE_FILE(S["positive_traits"] , positive_traits)
+ WRITE_FILE(S["negative_traits"] , negative_traits)
+ WRITE_FILE(S["neutral_traits"] , neutral_traits)
+
return 1
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index f334368a7a6c..c912a9b4e867 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -1197,3 +1197,28 @@ GLOBAL_LIST_INIT(hallucinations_major, list(
H.preparePixelProjectile(target, start)
H.fire()
qdel(src)
+
+//Reality Dissociation Syndrome hallucinations only trigger in special cases and have no cost
+/datum/hallucination/rds
+ cost = 0
+
+/datum/hallucination/rds/fourth_wall/New(mob/living/carbon/C, forced = TRUE)
+ ..()
+ to_chat(C, "[pick("Leave the server" , "Close the game window")] [pick("immediately", "right now")].")
+
+/datum/hallucination/rds/supermatter/New(mob/living/carbon/C, forced = TRUE)
+ ..()
+ SEND_SOUND(C, 'sound/magic/charge.ogg')
+ to_chat(C, "You feel reality distort for a moment...")
+
+/datum/hallucination/rds/narsie/New(mob/living/carbon/C, forced = TRUE)
+ C.playsound_local(C, 'sound/creatures/narsie_rises.ogg', 50, FALSE, pressure_affected = FALSE)
+ to_chat(C, "NAR-SIE HAS RISEN")
+
+/datum/hallucination/rds/ark/New(mob/living/carbon/C, forced = TRUE)
+ set waitfor = FALSE
+ ..()
+ C.playsound_local(C, 'sound/machines/clockcult/ark_deathrattle.ogg', 50, FALSE, pressure_affected = FALSE)
+ C.playsound_local(C, 'sound/effects/clockcult_gateway_disrupted.ogg', 50, FALSE, pressure_affected = FALSE)
+ sleep(27)
+ C.playsound_local(C, 'sound/effects/explosion_distant.ogg', 50, FALSE, pressure_affected = FALSE)
diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm
index 637c6c279d57..bbc56f9fe43e 100644
--- a/code/modules/food_and_drinks/drinks/drinks.dm
+++ b/code/modules/food_and_drinks/drinks/drinks.dm
@@ -21,7 +21,7 @@
else
gulp_size = max(round(reagents.total_volume / 5), 5)
-/obj/item/reagent_containers/food/drinks/attack(mob/M, mob/user, def_zone)
+/obj/item/reagent_containers/food/drinks/attack(mob/living/M, mob/user, def_zone)
if(!reagents || !reagents.total_volume)
to_chat(user, "[src] is empty!")
@@ -36,6 +36,8 @@
if(M == user)
to_chat(M, "You swallow a gulp of [src].")
+ if(M.has_trait(TRAIT_VORACIOUS))
+ M.changeNext_move(CLICK_CD_MELEE * 0.5) //chug! chug! chug!
else
M.visible_message("[user] attempts to feed the contents of [src] to [M].", "[user] attempts to feed the contents of [src] to [M].")
diff --git a/code/modules/food_and_drinks/food.dm b/code/modules/food_and_drinks/food.dm
index 5e05e85a28cc..67e8a6e91379 100644
--- a/code/modules/food_and_drinks/food.dm
+++ b/code/modules/food_and_drinks/food.dm
@@ -19,13 +19,18 @@
if(last_check_time + 50 < world.time)
if(ishuman(M))
var/mob/living/carbon/human/H = M
- if(foodtype & H.dna.species.toxic_food)
- to_chat(H,"What the hell was that thing?!")
- H.adjust_disgust(25 + 30 * fraction)
- else if(foodtype & H.dna.species.disliked_food)
- to_chat(H,"That didn't taste very good...")
- H.adjust_disgust(11 + 15 * fraction)
- else if(foodtype & H.dna.species.liked_food)
- to_chat(H,"I love this taste!")
- H.adjust_disgust(-5 + -2.5 * fraction)
+ if(!H.has_trait(TRAIT_AGEUSIA))
+ if(foodtype & H.dna.species.toxic_food)
+ to_chat(H,"What the hell was that thing?!")
+ H.adjust_disgust(25 + 30 * fraction)
+ else if(foodtype & H.dna.species.disliked_food)
+ to_chat(H,"That didn't taste very good...")
+ H.adjust_disgust(11 + 15 * fraction)
+ else if(foodtype & H.dna.species.liked_food)
+ to_chat(H,"I love this taste!")
+ H.adjust_disgust(-5 + -2.5 * fraction)
+ else
+ if(foodtype & H.dna.species.toxic_food)
+ to_chat(H, "You don't feel so good...")
+ H.adjust_disgust(25 + 30 * fraction)
last_check_time = world.time
diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm
index b46165cb80a5..c8b9a2bd175e 100644
--- a/code/modules/food_and_drinks/food/snacks.dm
+++ b/code/modules/food_and_drinks/food/snacks.dm
@@ -50,7 +50,7 @@
return
-/obj/item/reagent_containers/food/snacks/attack(mob/M, mob/user, def_zone)
+/obj/item/reagent_containers/food/snacks/attack(mob/living/M, mob/user, def_zone)
if(user.a_intent == INTENT_HARM)
return ..()
if(!eatverb)
@@ -82,6 +82,8 @@
else if(fullness > (600 * (1 + M.overeatduration / 2000))) // The more you eat - the more you can eat
to_chat(M, "You cannot force any more of \the [src] to go down your throat!")
return 0
+ if(M.has_trait(TRAIT_VORACIOUS))
+ M.changeNext_move(CLICK_CD_MELEE * 0.5) //nom nom nom
else
if(!isbrain(M)) //If you're feeding it to someone else.
if(fullness <= (600 * (1 + M.overeatduration / 1000)))
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index a1e0ed44ab54..7b2d8d15db4b 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -373,6 +373,10 @@
if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5)
SSticker.mode.make_antag_chance(humanc)
+ for(var/V in character.roundstart_traits)
+ var/datum/trait/T = V
+ T.on_spawn() //so latejoins still get their correct traits
+
log_manifest(character.mind.key,character.mind,character,latejoin = TRUE)
/mob/dead/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 574b2087e342..33ff87070ab7 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -6,8 +6,14 @@
var/t_him = p_them()
var/t_has = p_have()
var/t_is = p_are()
+ var/obscure_name
- var/msg = "*---------*\nThis is [name]!\n"
+ if(isliving(user))
+ var/mob/living/L = user
+ if(L.has_trait(TRAIT_PROSOPAGNOSIA))
+ obscure_name = TRUE
+
+ var/msg = "*---------*\nThis is [!obscure_name ? name : "Unknown"]!\n"
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
@@ -259,6 +265,7 @@
if(digitalcamo)
msg += "[t_He] [t_is] moving [t_his] body in an unnatural and blatantly inhuman manner.\n"
+ var/traitstring = get_trait_string()
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/cyberimp/eyes/hud/CIH = H.getorgan(/obj/item/organ/cyberimp/eyes/hud)
@@ -286,6 +293,10 @@
R = find_record("name", perpname, GLOB.data_core.medical)
if(R)
msg += "\[Medical evaluation\]" + if(traitstring) + msg += "Detected physiological traits: " + msg += "[traitstring] " + if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(CIH, /obj/item/organ/cyberimp/eyes/hud/security)) @@ -302,6 +313,8 @@ msg += "\[Add crime\] " msg += "\[View comment log\] " msg += "\[Add comment\]\n" + else if(isobserver(user) && traitstring) + msg += "Traits: [traitstring] " msg += "*---------*" to_chat(user, msg) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index e0be6314d39c..c7d2541b0810 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -655,24 +655,33 @@ if(prob(30)) burndamage += rand(30,40) - if(brutedamage > 0) - status = "bruised" - if(brutedamage > 20) - status = "battered" - if(brutedamage > 40) - status = "mangled" - if(brutedamage > 0 && burndamage > 0) - status += " and " - if(burndamage > 40) - status += "peeling away" + if(has_trait(TRAIT_SELF_AWARE)) + status = "[brutedamage] brute damage and [burndamage] burn damage" + if(!brutedamage && !burndamage) + status = "no damage" - else if(burndamage > 10) - status += "blistered" - else if(burndamage > 0) - status += "numb" - if(status == "") - status = "OK" - to_chat(src, "\t Your [LB.name] is [status].") + else + if(brutedamage > 0) + status = "bruised" + if(brutedamage > 20) + status = "battered" + if(brutedamage > 40) + status = "mangled" + if(brutedamage > 0 && burndamage > 0) + status += " and " + if(burndamage > 40) + status += "peeling away" + + else if(burndamage > 10) + status += "blistered" + else if(burndamage > 0) + status += "numb" + if(status == "") + status = "OK" + var/no_damage + if(status == "OK" || status == "no damage") + no_damage = TRUE + to_chat(src, "\t Your [LB.name] [has_trait(TRAIT_SELF_AWARE) ? "has" : "is"] [status].") for(var/obj/item/I in LB.embedded_objects) to_chat(src, "\t There is \a [I] embedded in your [LB.name]!") @@ -687,6 +696,23 @@ to_chat(src, "You're completely exhausted.") else to_chat(src, "You feel fatigued.") + if(has_trait(TRAIT_SELF_AWARE)) + if(toxloss) + if(toxloss > 10) + to_chat(src, "You feel sick.") + else if(toxloss > 20) + to_chat(src, "You feel nauseous.") + else if(toxloss > 40) + to_chat(src, "You feel very unwell!") + if(oxyloss) + if(oxyloss > 10) + to_chat(src, "You feel lightheaded.") + else if(oxyloss > 20) + to_chat(src, "Your thinking is clouded and distant.") + else if(oxyloss > 30) + to_chat(src, "You're choking!") + if(roundstart_traits.len) + to_chat(src, "You have these traits: [get_trait_string()].") else if(wear_suit) wear_suit.add_fingerprint(M) diff --git a/code/modules/mob/living/carbon/human/status_procs.dm b/code/modules/mob/living/carbon/human/status_procs.dm index cf3d676e9026..844545a748bc 100644 --- a/code/modules/mob/living/carbon/human/status_procs.dm +++ b/code/modules/mob/living/carbon/human/status_procs.dm @@ -9,6 +9,13 @@ /mob/living/carbon/human/Unconscious(amount, updating = 1, ignore_canunconscious = 0) amount = dna.species.spec_stun(src,amount) + if(has_trait(TRAIT_HEAVY_SLEEPER)) + amount *= rand(1.25, 1.3) + return ..() + +/mob/living/carbon/human/Sleeping(amount, updating = 1, ignore_sleepimmune = 0) + if(has_trait(TRAIT_HEAVY_SLEEPER)) + amount *= rand(1.25, 1.3) return ..() /mob/living/carbon/human/cure_husk(list/sources) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 8ba5449b8ec7..f5c00b874ade 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -34,6 +34,8 @@ var/list/status_traits = list() + var/list/roundstart_traits = list() + var/list/surgeries = list() //a list of surgery datums. generally empty, they're added when the player wants them. var/now_pushing = null //used by living/Collide() and living/PushAM() to prevent potential infinite loop. diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index 593f535d8164..3979bb9b8420 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -146,10 +146,23 @@ else status_traits[trait] |= list(source) -/mob/living/proc/remove_trait(trait, list/sources) +/mob/living/proc/add_trait_datum(trait) //separate proc due to the way these ones are handled + if(has_trait(trait)) + return + if(!SStraits || !SStraits.traits[trait]) + return + var/datum/trait/T = SStraits.traits[trait] + new T (src) + return TRUE + +/mob/living/proc/remove_trait(trait, list/sources, force) + if(!status_traits[trait]) return + if(locate(ROUNDSTART_TRAIT) in status_traits[trait] && !force) //mob traits applied through roundstart cannot normally be removed + return + if(!sources) // No defined source cures the trait entirely. status_traits -= trait return @@ -167,6 +180,12 @@ if(!LAZYLEN(status_traits[trait])) status_traits -= trait +/mob/living/proc/remove_trait_datum(trait) + var/datum/trait/T = roundstart_traits[trait] + if(T) + qdel(T) + return TRUE + /mob/living/proc/has_trait(trait, list/sources) if(!status_traits[trait]) return FALSE @@ -182,6 +201,9 @@ else if(LAZYLEN(status_traits[trait])) return TRUE +/mob/living/proc/has_trait_datum(trait) + return roundstart_traits[trait] + /mob/living/proc/remove_all_traits() status_traits = list() diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm index c66168cee47f..b2a4a867bc2a 100644 --- a/code/modules/mob/living/taste.dm +++ b/code/modules/mob/living/taste.dm @@ -9,7 +9,7 @@ /mob/living/carbon/get_taste_sensitivity() var/obj/item/organ/tongue/tongue = getorganslot(ORGAN_SLOT_TONGUE) - if(istype(tongue)) + if(istype(tongue) && !has_trait(TRAIT_AGEUSIA)) . = tongue.taste_sensitivity else . = 101 // can't taste anything without a tongue diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 1a0e75627763..a1de4db47724 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -246,6 +246,8 @@ var/rand_spr = rand() if(spread) randomized_gun_spread = rand(0,spread) + if(user.has_trait(TRAIT_POOR_AIM)) //nice shootin' tex + bonus_spread += 25 var/randomized_bonus_spread = rand(0, bonus_spread) if(burst_size > 1) diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 53b98b2d4aca..ebf423cbf5c8 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -37,9 +37,12 @@ All effects don't start immediately, but rather get worse over time; the rate is if(ishuman(M)) var/mob/living/carbon/human/H = M if(H.drunkenness < volume * boozepwr * ALCOHOL_THRESHOLD_MODIFIER) - H.drunkenness = max((H.drunkenness + (sqrt(volume) * boozepwr * ALCOHOL_RATE)), 0) //Volume, power, and server alcohol rate effect how quickly one gets drunk + var/booze_power = boozepwr + if(H.has_trait(TRAIT_ALCOHOL_TOLERANCE)) //we're an accomplished drinker + booze_power *= 0.7 + H.drunkenness = max((H.drunkenness + (sqrt(volume) * booze_power * ALCOHOL_RATE)), 0) //Volume, power, and server alcohol rate effect how quickly one gets drunk var/obj/item/organ/liver/L = H.getorganslot(ORGAN_SLOT_LIVER) - H.applyLiverDamage((max(sqrt(volume) * boozepwr * L.alcohol_tolerance, 0))/4) + H.applyLiverDamage((max(sqrt(volume) * booze_power * L.alcohol_tolerance, 0))/4) return ..() || . /datum/reagent/consumable/ethanol/reaction_obj(obj/O, reac_volume) @@ -117,7 +120,8 @@ All effects don't start immediately, but rather get worse over time; the rate is M.dizziness = max(0,M.dizziness-5) M.drowsyness = max(0,M.drowsyness-3) M.AdjustSleeping(-40, FALSE) - M.Jitter(5) + if(!M.has_trait(TRAIT_ALCOHOL_TOLERANCE)) + M.Jitter(5) ..() . = 1 @@ -150,7 +154,8 @@ All effects don't start immediately, but rather get worse over time; the rate is M.drowsyness = max(0,M.drowsyness-7) M.AdjustSleeping(-40) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) - M.Jitter(5) + if(!M.has_trait(TRAIT_ALCOHOL_TOLERANCE)) + M.Jitter(5) return ..() /datum/reagent/consumable/ethanol/vodka @@ -305,7 +310,7 @@ All effects don't start immediately, but rather get worse over time; the rate is shot_glass_icon_state = "shotglassgreen" /datum/reagent/consumable/ethanol/absinthe/on_mob_life(mob/living/M) - if(prob(10)) + if(prob(10) && !M.has_trait(TRAIT_ALCOHOL_TOLERANCE)) M.hallucination += 4 //Reference to the urban myth ..() @@ -556,7 +561,10 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "Heavy, hot and strong. Just like the Iron fist of the LAW." /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_life(mob/living/M) - M.Stun(40, 0) + if(M.has_trait(TRAIT_ALCOHOL_TOLERANCE)) + M.Stun(30, 0) //this realistically does nothing to prevent chainstunning but will cause them to recover faster once it's out of their system + else + M.Stun(40, 0) return ..() /datum/reagent/consumable/ethanol/irish_cream @@ -585,7 +593,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/manly_dorf/on_mob_add(mob/living/M) if(ishuman(M)) var/mob/living/carbon/human/H = M - if(H.dna.check_mutation(DWARFISM)) + if(H.dna.check_mutation(DWARFISM) || H.has_trait(TRAIT_ALCOHOL_TOLERANCE)) to_chat(H, "Now THAT is MANLY!") boozepwr = 5 //We've had worse in the mines dorf_mode = TRUE @@ -1148,8 +1156,9 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/atomicbomb/on_mob_life(mob/living/M) M.set_drugginess(50) - M.confused = max(M.confused+2,0) - M.Dizzy(10) + if(!M.has_trait(TRAIT_ALCOHOL_TOLERANCE)) + M.confused = max(M.confused+2,0) + M.Dizzy(10) if (!M.slurring) M.slurring = 1 M.slurring += 3 diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index cf030fcc6675..2eed6c33d5d2 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -182,7 +182,7 @@ /datum/reagent/toxin/mindbreaker name = "Mindbreaker Toxin" id = "mindbreaker" - description = "A powerful hallucinogen. Not a thing to be messed with." + description = "A powerful hallucinogen. Not a thing to be messed with. For some mental patients. it counteracts their symptoms and anchors them to reality." color = "#B31008" // rgb: 139, 166, 233 toxpwr = 0 taste_description = "sourness" diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm index d9bcbc09d41e..350ff3316e9c 100644 --- a/code/modules/surgery/organs/eyes.dm +++ b/code/modules/surgery/organs/eyes.dm @@ -26,6 +26,8 @@ HMN.regenerate_icons() else eye_color = HMN.eye_color + if(HMN.has_trait(TRAIT_NIGHT_VISION) && !lighting_alpha) + lighting_alpha = LIGHTING_PLANE_ALPHA_NV_TRAIT M.update_tint() owner.update_sight() diff --git a/config/game_options.txt b/config/game_options.txt index 7b53f1459fa1..cc9d7a809e09 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -503,6 +503,9 @@ MICE_ROUNDSTART 10 ## Determines if players are allowed to print integrated circuits, uncomment to allow. #IC_PRINTING +## Uncomment to allow roundstart trait selection in the character setup menu. +ROUNDSTART_TRAITS + ## Enable night shifts ## ENABLE_NIGHT_SHIFTS diff --git a/tgstation.dme b/tgstation.dme index dd6f18b753c2..536fae949e93 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -249,6 +249,7 @@ #include "code\controllers\subsystem\processing\obj.dm" #include "code\controllers\subsystem\processing\processing.dm" #include "code\controllers\subsystem\processing\projectiles.dm" +#include "code\controllers\subsystem\processing\traits.dm" #include "code\datums\action.dm" #include "code\datums\ai_laws.dm" #include "code\datums\armor.dm" @@ -406,6 +407,10 @@ #include "code\datums\status_effects\gas.dm" #include "code\datums\status_effects\neutral.dm" #include "code\datums\status_effects\status_effect.dm" +#include "code\datums\traits\_trait.dm" +#include "code\datums\traits\good.dm" +#include "code\datums\traits\negative.dm" +#include "code\datums\traits\neutral.dm" #include "code\datums\weather\weather.dm" #include "code\datums\weather\weather_types\acid_rain.dm" #include "code\datums\weather\weather_types\advanced_darkness.dm" |