code:
/datum/attribute
	var/name = null
	var/desc = ""

/datum/attribute/proc/description_from_level(level)
	return "FUCK"


code:
/datum/attribute_modifier
	/// Whether or not this is a variable modifier. Variable modifiers can NOT be ever auto-cached. ONLY CHECKED VIA INITIAL(), EFFECTIVELY READ ONLY (and for very good reason)
	var/variable = FALSE

	/// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID.
	var/id

	/// Higher ones override lower priorities. This is NOT used for ID, ID must be unique, if it isn't unique the newer one overwrites automatically if overriding.
	var/priority = 0
	var/flags = NONE

	/// Associative list, attribute path = modification
	var/list/attribute_list

	/// Other modification datums this conflicts with.
	var/conflicts_with

/datum/attribute_modifier/New()
	. = ..()
	if(!id)
		id = "[type]" //We turn the path into a string.

/// Grabs a STATIC MODIFIER datum from cache. YOU MUST NEVER EDIT THESE DATUMS, OR IT WILL AFFECT ANYTHING ELSE USING IT TOO!
/proc/get_cached_attribute_modifier(modtype)
	if(!ispath(modtype, /datum/attribute_modifier))
		CRASH("[modtype] is not a attribute modification typepath.")
	var/datum/attribute_modifier/attribute_mod = modtype
	if(initial(attribute_mod.variable))
		CRASH("[modtype] is a variable modifier, and can never be cached.")
	attribute_mod = GLOB.attribute_modification_cache[modtype]
	if(!attribute_mod)
		attribute_mod = GLOB.attribute_modification_cache[modtype] = new modtype
	return attribute_mod

///Add an attribute modifier to a holder. If a variable subtype is passed in as the first argument, it will make a new datum. If ID conflicts, it will overwrite the old ID.
/datum/attribute_holder/proc/add_attribute_modifier(datum/attribute_modifier/type_or_datum, update = TRUE)
	if(ispath(type_or_datum))
		if(!initial(type_or_datum.variable))
			type_or_datum = get_cached_attribute_modifier(type_or_datum)
		else
			type_or_datum = new type_or_datum
	var/datum/attribute_modifier/existing = LAZYACCESS(attribute_modification, type_or_datum.id)
	if(existing)
		if(existing == type_or_datum) //same thing don't need to touch
			return TRUE
		remove_attribute_modifier(existing, FALSE)
	if(length(attribute_modification))
		BINARY_INSERT(type_or_datum.id, attribute_modification, /datum/attribute_modifier, type_or_datum, priority, COMPARE_VALUE)
	LAZYSET(attribute_modification, type_or_datum.id, type_or_datum)
	if(update)
		update_attributes()
	return TRUE

/// Remove an attribute modifier from a holder, whether static or variable.
/datum/attribute_holder/proc/remove_attribute_modifier(datum/attribute_modifier/type_id_datum, update = TRUE)
	var/key
	if(ispath(type_id_datum))
		key = initial(type_id_datum.id) || "[type_id_datum]" //id if set, path set to string if not.
	else if(!istext(type_id_datum)) //if it isn't text it has to be a datum, as it isn't a type.
		key = type_id_datum.id
	else //assume it's an id
		key = type_id_datum
	if(!LAZYACCESS(attribute_modification, key))
		return FALSE
	LAZYREMOVE(attribute_modification, key)
	if(update)
		update_attributes()
	return TRUE

/*! Used for variable modification like hunger/health loss/etc, works somewhat like the old list-based modification adds. Returns the modifier datum if successful
	How this SHOULD work is:
	1. Ensures type_id_datum one way or another refers to a /variable datum. This makes sure it can't be cached. This includes if it's already in the modification list.
	2. Instantiate a new datum if type_id_datum isn't already instantiated + in the list, using the type. Obviously, wouldn't work for ID only.
	3. Add the datum if necessary using the regular add proc
	4. If any of the rest of the args are not null (see: multiplicative slowdown), modify the datum
	5. Update if necessary
*/
/datum/attribute_holder/proc/add_or_update_variable_attribute_modifier(datum/attribute_modifier/type_id_datum, update = TRUE, list/new_modification)
	var/modified = FALSE
	var/inject = FALSE
	var/datum/attribute_modifier/final
	if(istext(type_id_datum))
		final = LAZYACCESS(attribute_modification, type_id_datum)
		if(!final)
			CRASH("Couldn't find existing modification when provided a text ID.")
	else if(ispath(type_id_datum))
		if(!initial(type_id_datum.variable))
			CRASH("Not a variable modifier")
		final = LAZYACCESS(attribute_modification, initial(type_id_datum.id) || "[type_id_datum]")
		if(!final)
			final = new type_id_datum
			inject = TRUE
			modified = TRUE
	else
		if(!initial(type_id_datum.variable))
			CRASH("Not a variable modifier")
		final = type_id_datum
		if(!LAZYACCESS(attribute_modification, final.id))
			inject = TRUE
			modified = TRUE
	if(islist(new_modification))
		final.attribute_list = new_modification
		modified = TRUE
	if(inject)
		add_attribute_modifier(final, FALSE)
	if(update && modified)
		update_attributes()
	return final

///Is there an attribute modifier for this holder
/datum/attribute_holder/proc/has_attribute_modifier(datum/attribute_modifier/datum_type_id)
	var/key
	if(ispath(datum_type_id))
		key = initial(datum_type_id.id) || "[datum_type_id]"
	else if(istext(datum_type_id))
		key = datum_type_id
	else
		key = datum_type_id.id
	return LAZYACCESS(attribute_modification, key)

/// Go through the list of actionspeed modifiers and calculate a final actionspeed. ANY ADD/REMOVE DONE IN UPDATE_actionspeed MUST HAVE THE UPDATE ARGUMENT SET AS FALSE!
/datum/attribute_holder/proc/update_attributes()
	. = raw_attribute_list.Copy()
	var/list/conflict_tracker = list()
	for(var/key in get_attribute_modification())
		var/datum/attribute_modifier/M = attribute_modification[key]
		var/conflict = M.conflicts_with
		if(conflict)
			// Conflicting modifiers prioritize the higher priority modifier
			if(conflict_tracker[conflict] < M.priority)
				conflict_tracker[conflict] = M.priority
			else
				continue
		for(var/attribute_path in .)
			if(ispath(attribute_path, /datum/attribute/skill))
				.[attribute_path] = clamp(.[attribute_path] + M.attribute_list[attribute_path], skill_min, skill_max)
			else
				.[attribute_path] = clamp(.[attribute_path] + M.attribute_list[attribute_path], attribute_min, attribute_max)
	attribute_list = .
	parent?.hud_used?.stat_viewer?.update_stats()
	if(iscarbon(parent))
		var/mob/living/carbon/carbon_parent = parent
		carbon_parent.update_maximum_carry_weight()

/// Get the attribute modifiers list of the holder
/datum/attribute_holder/proc/get_attribute_modification()
	. = LAZYCOPY(attribute_modification)
	for(var/id in attribute_mod_immunities)
		. -= id

/// Checks if an attribute modifier is valid and not missing any data
/proc/attribute_mod_data_null_check(datum/attribute_modifier/M) //Determines if a data list is not meaningful and should be discarded.
	. = LAZYLEN(M.attribute_list)

/// Ignores specific attribute mods - Accepts a list of attribute mods
/datum/attribute_holder/proc/add_attribute_mod_immunities(source, mod_type, update = TRUE)
	if(islist(mod_type))
		for(var/listed_type in mod_type)
			if(ispath(listed_type))
				listed_type = "[mod_type]" //Path2String
			LAZYADDASSOCLIST(attribute_mod_immunities, listed_type, source)
	else
		if(ispath(mod_type))
			mod_type = "[mod_type]" //Path2String
		LAZYADDASSOCLIST(attribute_mod_immunities, mod_type, source)
	if(update)
		update_attributes()

///Unignores specific attribute mods - Accepts a list of attribute mods
/datum/attribute_holder/proc/remove_attribute_mod_immunities(source, mod_type, update = TRUE)
	if(islist(mod_type))
		for(var/listed_type in mod_type)
			if(ispath(listed_type))
				listed_type = "[listed_type]" //Path2String
			LAZYREMOVEASSOC(attribute_mod_immunities, listed_type, source)
	else
		if(ispath(mod_type))
			mod_type = "[mod_type]" //Path2String
		LAZYREMOVEASSOC(attribute_mod_immunities, mod_type, source)
	if(update)
		update_attributes()

repo_name: AnalWerewolf/sundown

license: mit


code:
// Combat skills
/datum/attribute/skill/melee
	name = "Melee Combat"
	desc = "Proficiency at close quarters combat, both armed and unarmed."
	category = SKILL_CATEGORY_COMBAT

/datum/attribute/skill/ranged
	name = "Ranged Combat"
	desc = "Proficiency at using firearms, crossbows and bows."
	category = SKILL_CATEGORY_COMBAT

/datum/attribute/skill/throwing
	name = "Throwing"
	desc = "Proficiency at throwing items, regardless of their purpose."
	category = SKILL_CATEGORY_COMBAT

/datum/attribute/skill/tracking
	name = "Tracking"
	desc = "Proficiency at analyzing the clues and tracks of your enemies."
	category = SKILL_CATEGORY_COMBAT

// Skullduggery skills
/datum/attribute/skill/pickpocket
	name = "Pickpocketing"
	desc = "Ability to steal without being noticed."
	category = SKILL_CATEGORY_SKULLDUGGERY

/datum/attribute/skill/lockpicking
	name = "Lockpicking"
	desc = "Ability at breaking mechanical locks open."
	category = SKILL_CATEGORY_SKULLDUGGERY

// Medical skills
/datum/attribute/skill/medicine
	name = "Medicine"
	desc = "Proficiency at diagnosis and treatment of physical ailments, and handling of medical instruments."
	category = SKILL_CATEGORY_MEDICAL

/datum/attribute/skill/surgery
	name = "Surgery"
	desc = "Knowledge in humanoid anatomy, as well as surgical procedures and tools."
	category = SKILL_CATEGORY_MEDICAL

// Engineering skills
/datum/attribute/skill/masonry
	name = "Masonry"
	desc = "Ability to create infrastructure, structure and furniture out of various materials."
	category = SKILL_CATEGORY_ENGINEERING

/datum/attribute/skill/smithing
	name = "Smithing"
	desc = "Ability to create weapons, armor and items out of metal."
	category = SKILL_CATEGORY_ENGINEERING

/datum/attribute/skill/electronics
	name = "Electronics"
	desc = "Ability at handling, hacking and repairing electrical machinery and wiring."
	category = SKILL_CATEGORY_ENGINEERING

/datum/attribute/skill/climbing
	name = "Climbing"
	desc = "Ability at acrobatic maneuvers and escalating walls."
	category = SKILL_CATEGORY_ENGINEERING

// Research skills
/datum/attribute/skill/science
	name = "Science"
	desc = "Comprehension, research, experimentation and creation of complex technology."
	category = SKILL_CATEGORY_RESEARCH

/datum/attribute/skill/chemistry
	name = "Chemistry"
	desc = "Capability at handling chemicals and chemical reactions."
	category = SKILL_CATEGORY_RESEARCH

// Domestic skills
/datum/attribute/skill/culinary
	name = "Culinary"
	desc = "Ability at cooking food."
	category = SKILL_CATEGORY_DOMESTIC

/datum/attribute/skill/agriculture
	name = "Agriculture"
	desc = "Ability at planting and harvesting produce."
	category = SKILL_CATEGORY_DOMESTIC

/datum/attribute/skill/cleaning
	name = "Cleaning"
	desc = "Ability at performing janitorial tasks."
	category = SKILL_CATEGORY_DOMESTIC

// Dumb meme skills
/datum/attribute/skill/gaming
	name = "Gaming"
	desc = "Ability at getting kill streaks in Fortnight."
	category = SKILL_CATEGORY_DUMB


code:
/datum/attribute/stat/strength
	name = "Strength"
	shorthand = "STR"
	desc = "Strength measures how strong your blows are, as well as your capacity at carrying weight. \
		Favorite attribute of the warrior."

/datum/attribute/stat/dexterity
	name = "Dexterity"
	shorthand = "DEX"
	desc = "Dexterity measures your reflexes and balance, making you better at landing blows as well as avoiding them. \
		Favorite attribute of the thief."

/datum/attribute/stat/endurance
	name = "Endurance"
	shorthand = "END"
	desc = "Endurance measures your capability at handling pain, disease and other physical hardships. \
		Favorite attribute of the masochist."

/datum/attribute/stat/intelligence
	name = "Intellect"
	shorthand = "INT"
	desc = "Intellect measures your abilities to perform complex tasks and retain information. \
		Favorite attribute of the scholar."

//i had to
/datum/attribute/stat/intelligence/description_from_level(level)
	switch(CEILING(level, 1))
		if(-INFINITY to 6)
			return "crippling"
		if(7)
			return "poor"
		if(8,9)
			return "below average"
		if(10)
			return "average"
		if(11,12)
			return "above average"
		if(13,14)
			return "gifted"
		if(15,16)
			return "amazing"
		if(17,18)
			return "incredible"
		if(19,20)
			return "legendary"
		if(21 to INFINITY)
			return "divine"
		else
			return "invalid"


code:
/datum/diceroll_modifier
	/// Whether or not this is a variable modifier. Variable modifiers can NOT be ever auto-cached. ONLY CHECKED VIA INITIAL(), EFFECTIVELY READ ONLY (and for very good reason)
	var/variable = FALSE

	/// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID.
	var/id

	/// Higher ones override lower priorities. This is NOT used for ID, ID must be unique, if it isn't unique the newer one overwrites automatically if overriding.
	var/priority = 0
	var/flags = NONE

	/// How much we add to the REQUIREMENT of dicerolls, not the dice
	var/modification

	/// Other modification datums this conflicts with.
	var/conflicts_with

/datum/diceroll_modifier/New()
	. = ..()
	if(!id)
		id = "[type]" //We turn the path into a string.

/// Grabs a STATIC MODIFIER datum from cache. YOU MUST NEVER EDIT THESE DATUMS, OR IT WILL AFFECT ANYTHING ELSE USING IT TOO!
/proc/get_cached_diceroll_modifier(modtype)
	if(!ispath(modtype, /datum/diceroll_modifier))
		CRASH("[modtype] is not a attribute modification typepath.")
	var/datum/diceroll_modifier/diceroll_mod = modtype
	if(initial(diceroll_mod.variable))
		CRASH("[modtype] is a variable modifier, and can never be cached.")
	diceroll_mod = GLOB.diceroll_modification_cache[modtype]
	if(!diceroll_mod)
		diceroll_mod = GLOB.diceroll_modification_cache[modtype] = new modtype
	return diceroll_mod

///Add a action speed modifier to a holder. If a variable subtype is passed in as the first argument, it will make a new datum. If ID conflicts, it will overwrite the old ID.
/datum/attribute_holder/proc/add_diceroll_modifier(datum/diceroll_modifier/type_or_datum, update = TRUE)
	if(ispath(type_or_datum))
		if(!initial(type_or_datum.variable))
			type_or_datum = get_cached_diceroll_modifier(type_or_datum)
		else
			type_or_datum = new type_or_datum
	var/datum/diceroll_modifier/existing = LAZYACCESS(diceroll_modification, type_or_datum.id)
	if(existing)
		if(existing == type_or_datum) //same thing don't need to touch
			return TRUE
		remove_diceroll_modifier(existing, FALSE)
	if(length(diceroll_modification))
		BINARY_INSERT(type_or_datum.id, diceroll_modification, /datum/diceroll_modifier, type_or_datum, priority, COMPARE_VALUE)
	LAZYSET(diceroll_modification, type_or_datum.id, type_or_datum)
	if(update)
		update_diceroll()
	return TRUE

/// Remove an attribute modifier from a holder, whether static or variable.
/datum/attribute_holder/proc/remove_diceroll_modifier(datum/diceroll_modifier/type_id_datum, update = TRUE)
	var/key
	if(ispath(type_id_datum))
		key = initial(type_id_datum.id) || "[type_id_datum]" //id if set, path set to string if not.
	else if(!istext(type_id_datum)) //if it isn't text it has to be a datum, as it isn't a type.
		key = type_id_datum.id
	else //assume it's an id
		key = type_id_datum
	if(!LAZYACCESS(diceroll_modification, key))
		return FALSE
	LAZYREMOVE(diceroll_modification, key)
	if(update)
		update_diceroll()
	return TRUE

/*! Used for variable modification like hunger/health loss/etc, works somewhat like the old list-based modification adds. Returns the modifier datum if successful
	How this SHOULD work is:
	1. Ensures type_id_datum one way or another refers to a /variable datum. This makes sure it can't be cached. This includes if it's already in the modification list.
	2. Instantiate a new datum if type_id_datum isn't already instantiated + in the list, using the type. Obviously, wouldn't work for ID only.
	3. Add the datum if necessary using the regular add proc
	4. If any of the rest of the args are not null (see: multiplicative slowdown), modify the datum
	5. Update if necessary
*/
/datum/attribute_holder/proc/add_or_update_variable_diceroll_modifier(datum/diceroll_modifier/type_id_datum, update = TRUE, list/new_modification)
	var/modified = FALSE
	var/inject = FALSE
	var/datum/diceroll_modifier/final
	if(istext(type_id_datum))
		final = LAZYACCESS(diceroll_modification, type_id_datum)
		if(!final)
			CRASH("Couldn't find existing modification when provided a text ID.")
	else if(ispath(type_id_datum))
		if(!initial(type_id_datum.variable))
			CRASH("Not a variable modifier")
		final = LAZYACCESS(diceroll_modification, initial(type_id_datum.id) || "[type_id_datum]")
		if(!final)
			final = new type_id_datum
			inject = TRUE
			modified = TRUE
	else
		if(!initial(type_id_datum.variable))
			CRASH("Not a variable modifier")
		final = type_id_datum
		if(!LAZYACCESS(diceroll_modification, final.id))
			inject = TRUE
			modified = TRUE
	if(isnum(new_modification))
		final.modification = new_modification
		modified = TRUE
	if(inject)
		add_diceroll_modifier(final, FALSE)
	if(update && modified)
		update_diceroll()
	return final

///Is there an attribute modifier for this holder
/datum/attribute_holder/proc/has_diceroll_modifier(datum/diceroll_modifier/datum_type_id)
	var/key
	if(ispath(datum_type_id))
		key = initial(datum_type_id.id) || "[datum_type_id]"
	else if(istext(datum_type_id))
		key = datum_type_id
	else
		key = datum_type_id.id
	return LAZYACCESS(diceroll_modification, key)

/// Go through the list of actionspeed modifiers and calculate a final actionspeed. ANY ADD/REMOVE DONE IN UPDATE_actionspeed MUST HAVE THE UPDATE ARGUMENT SET AS FALSE!
/datum/attribute_holder/proc/update_diceroll()
	. = 0
	var/list/conflict_tracker = list()
	for(var/key in get_diceroll_modification())
		var/datum/diceroll_modifier/M = diceroll_modification[key]
		var/conflict = M.conflicts_with
		if(conflict)
			// Conflicting modifiers prioritize the higher priority modifier
			if(conflict_tracker[conflict] < M.priority)
				conflict_tracker[conflict] = M.priority
			else
				continue
		. += M.modification
	cached_diceroll_modifier = .

/// Get the attribute modifiers list of the holder
/datum/attribute_holder/proc/get_diceroll_modification()
	. = LAZYCOPY(diceroll_modification)
	for(var/id in diceroll_mod_immunities)
		. -= id

/// Checks if an attribute modifier is valid and not missing any data
/proc/diceroll_mod_data_null_check(datum/diceroll_modifier/M) //Determines if a data list is not meaningful and should be discarded.
	. = TRUE
	if(M.modification)
		return FALSE

/// Ignores specific attribute mods - Accepts a list of attribute mods
/datum/attribute_holder/proc/add_diceroll_mod_immunities(source, mod_type, update = TRUE)
	if(islist(mod_type))
		for(var/listed_type in mod_type)
			if(ispath(listed_type))
				listed_type = "[mod_type]" //Path2String
			LAZYADDASSOCLIST(diceroll_mod_immunities, listed_type, source)
	else
		if(ispath(mod_type))
			mod_type = "[mod_type]" //Path2String
		LAZYADDASSOCLIST(diceroll_mod_immunities, mod_type, source)
	if(update)
		update_diceroll()

///Unignores specific attribute mods - Accepts a list of attribute mods
/datum/attribute_holder/proc/remove_diceroll_mod_immunities(source, mod_type, update = TRUE)
	if(islist(mod_type))
		for(var/listed_type in mod_type)
			if(ispath(listed_type))
				listed_type = "[listed_type]" //Path2String
			LAZYREMOVEASSOC(diceroll_mod_immunities, listed_type, source)
	else
		if(ispath(mod_type))
			mod_type = "[mod_type]" //Path2String
		LAZYREMOVEASSOC(diceroll_mod_immunities, mod_type, source)
	if(update)
		update_diceroll()


code:
// This is stupid and inefficient and should be defines, but i don't want to type every argument out
/mob/proc/diceroll(requirement = 0, crit = 10, dice_num = 1, dice_sides = 20, count_modifiers = TRUE)
	return attributes ? attributes.diceroll(requirement, crit, dice_num, dice_sides, count_modifiers) : DICE_FAILURE

/mob/proc/attribute_probability(modifier = ATTRIBUTE_MIDDLING, base_prob = 50, delta_value = ATTRIBUTE_MIDDLING, increment = 5)
	return attributes ? attributes.attribute_probability(modifier, base_prob, delta_value, increment) : base_prob


code:
/datum/attribute_holder
	/// Mob we are attached to
	var/mob/parent
	/// Default value for non-skill attributes
	var/attribute_default = ATTRIBUTE_DEFAULT
	/// Minimum value for non-skill attributes
	var/attribute_min = ATTRIBUTE_MIN
	/// Maximum value for non-skill attributes
	var/attribute_max = ATTRIBUTE_MAX
	/// Default value for non-specified skills
	var/skill_default = SKILL_DEFAULT
	/// Minimum value for skill attributes
	var/skill_min = SKILL_MIN
	/// Maximum value for skill attributes
	var/skill_max = SKILL_MAX
	/// Attribute list, counting modifiers
	var/list/attribute_list = list()
	/// Associative list, attribute path = value
	var/list/raw_attribute_list = list()
	/// List of attribute modifiers applying to this holder
	var/list/attribute_modification //Lazy list, see attribute_modifier.dm
	/// Attribute modifier immunities
	var/list/attribute_mod_immunities //Lazy list, see attribute_modifier.dm
	/// Diceroll modifier to be added on top of diceroll
	var/cached_diceroll_modifier = 0
	/// List of diceroll modifiers applying to this holder
	var/list/diceroll_modification //Lazy list, see attribute_modifier.dm
	/// Diceroll modifier immunities
	var/list/diceroll_mod_immunities //Lazy list, see attribute_modifier.dm

/datum/attribute_holder/New(mob/new_parent)
	. = ..()
	if(new_parent)
		set_parent(new_parent)
	for(var/attribute in GLOB.all_attributes)
		if(!raw_attribute_list[attribute])
			if(ispath(attribute, /datum/attribute/skill))
				raw_attribute_list[attribute] = skill_default
			else
				raw_attribute_list[attribute] = attribute_default
		else
			if(ispath(attribute, /datum/attribute/skill))
				raw_attribute_list[attribute] = clamp(raw_attribute_list[attribute], skill_min, skill_max)
			else
				raw_attribute_list[attribute] = clamp(raw_attribute_list[attribute], attribute_min, attribute_max)
	update_attributes()

/datum/attribute_holder/Destroy()
	. = ..()
	set_parent(null)
	attribute_list?.Cut()
	raw_attribute_list?.Cut()
	attribute_mod_immunities?.Cut()
	for(var/thing in attribute_modification)
		remove_attribute_modifier(thing, FALSE) //they lazyremove themselves

/**
 * Sets a mob as our owner.
 */
/datum/attribute_holder/proc/set_parent(mob/new_parent)
	if(new_parent)
		parent = new_parent
		new_parent.attributes = src
	else
		parent.attributes = null
		parent = null
	update_attributes()

/**
 * Adds up attributes from a sheet
 */
/datum/attribute_holder/proc/add_sheet(datum/attribute_holder/sheet/to_add)
	if(ispath(to_add, /datum/attribute_holder/sheet))
		if(GLOB.attribute_sheets[to_add])
			to_add = GLOB.attribute_sheets[to_add]
		else
			to_add = GLOB.attribute_sheets[to_add] = new to_add()
	else if(!istype(to_add))
		return
	add_holder(to_add)

/**
 * Adds up another holder's attributes
 */
/datum/attribute_holder/proc/add_holder(datum/attribute_holder/to_add)
	for(var/thing in to_add.raw_attribute_list)
		if(ispath(thing, /datum/attribute/skill))
			raw_attribute_list[thing] = clamp(raw_attribute_list[thing] + to_add.raw_attribute_list[thing], skill_min, skill_max)
		else
			raw_attribute_list[thing] = clamp(raw_attribute_list[thing] + to_add.raw_attribute_list[thing], attribute_min, attribute_max)
	to_add.on_add(src)
	update_attributes()

/**
 * Stuff we do when another holder adds us
 */
/datum/attribute_holder/proc/on_add(datum/attribute_holder/plagiarist)
	return

/**
 * Copies attributes from a sheet
 */
/datum/attribute_holder/proc/copy_sheet(datum/attribute_holder/sheet/to_copy)
	if(ispath(to_copy, /datum/attribute_holder/sheet))
		if(GLOB.attribute_sheets[to_copy])
			to_copy = GLOB.attribute_sheets[to_copy]
		else
			to_copy = GLOB.attribute_sheets[to_copy] = new to_copy()
	else if(!istype(to_copy))
		return
	copy_holder(to_copy)

/**
 * Copies another holder's raw attributes
 */
/datum/attribute_holder/proc/copy_holder(datum/attribute_holder/to_copy)
	raw_attribute_list = to_copy.raw_attribute_list.Copy()
	to_copy.on_copy(src)
	update_attributes()

/**
 * Stuff we do when another holder copies us
 */
/datum/attribute_holder/proc/on_copy(datum/attribute_holder/plagiarist)
	return

/**
 * Returns a probability value from an attribute, to be used in prob()
 * You should use the diceroll proc instead, but yeah this works too.
 */
/datum/attribute_holder/proc/attribute_probability(modifier = ATTRIBUTE_MIDDLING, base_prob = 50, delta_value = ATTRIBUTE_MIDDLING, increment = 5)
	return (base_prob + (modifier - delta_value) * increment)

/**
 * DICE ROLL
 * Add this to the action and specify what will happen in each outcome.
 * Modifier should be the get_value() of an attribute.
 *
 * Important! you should not use more than one stat in proc but if you really want to,
 * you should multiply amount of dices and crit according to how many of them you added to the formula.
 *
 * If you don't care about crits, just count them as being the same as normal successes/failures.
 */
/datum/attribute_holder/proc/diceroll(requirement = 0, crit = 10, dice_num = 1, dice_sides = 20, count_modifiers = TRUE)
	//Get our dice result
	var/dice = roll(dice_num, dice_sides)

	//Get the necessary number to pass the roll
	var/requirement_sum = requirement
	if(count_modifiers)
		//diceroll modifiers assume you use 1d20, so we gotta account for that
		var/final_modifier = (cached_diceroll_modifier/20)*dice_num*dice_sides
		if(final_modifier >= 0)
			final_modifier = CEILING(final_modifier, 1)
		else
			final_modifier = FLOOR(final_modifier, 1)
		requirement_sum += final_modifier

	//Return whether it was a failure or a success
	if(dice <= requirement_sum)
		if(dice <= requirement_sum - crit)
			return DICE_CRIT_SUCCESS
		else
			return DICE_SUCCESS
	else
		//Why do we not use >=? We can never get 0 on a dice roll, but we can get 20, this is compensating that fact
		if(dice > requirement_sum + crit)
			return DICE_CRIT_FAILURE
		else
			return DICE_FAILURE

/datum/attribute_holder/proc/print_skills(mob/user, show_all = FALSE)
	var/list/output = list()
	output += "
"
	var/list/skills_by_category = list()
	for(var/thing in attribute_list)
		if(ispath(thing,  /datum/attribute/skill))
			var/datum/attribute/skill/skill = GLOB.all_skills[thing]
			//Only gather the skills we actually have, unless we want to be shown all
			if(show_all || (attribute_list[thing] >= 1))
				if(skills_by_category[skill.category])
					skills_by_category[skill.category] += skill
				else
					skills_by_category[skill.category] = list(skill)

	for(var/category in skills_by_category)
		if(skills_by_category.Find(category) == 1)
			output += span_notice("[category]")
		else
			output += span_notice("\n[category]")
		for(var/thing in skills_by_category[category])
			var/datum/attribute/skill/skill = thing
			var/total_style = "class='info'"
			if(attribute_list[skill.type] > raw_attribute_list[skill.type])
				total_style =  "class='green'"
			else if(attribute_list[skill.type] < raw_attribute_list[skill.type])
				total_style = "class='red'"
			output += "\n\
				• [capitalize_like_old_man(skill.name)]: \
				[capitalize(skill.description_from_level(attribute_list[skill.type]))] \
				([attribute_list[skill.type]]/[raw_attribute_list[skill.type]]).\
				"
	if(!length(skills_by_category))
		output += span_info("I am genuinely, absolutely and completely useless.")
	output += "
" //div infobox
	to_chat(user, jointext(output, ""))

/datum/attribute_holder/proc/print_stats(mob/user)
	var/list/output = list()
	output += "
"
	var/list/stats = list()
	for(var/thing in attribute_list)
		if(ispath(thing, /datum/attribute/stat))
			var/datum/attribute/attribute = GLOB.all_stats[thing]
			stats += attribute
	output += span_notice("Stats")
	for(var/thing in stats)
		var/datum/attribute/stat/stat = thing
		var/total_style = "class='info'"
		if(attribute_list[stat.type] > raw_attribute_list[stat.type])
			total_style =  "class='green'"
		else if(attribute_list[stat.type] < raw_attribute_list[stat.type])
			total_style = "class='red'"
		output += "\n\
			• [capitalize_like_old_man(stat.name)] ([stat.shorthand]): \
			[capitalize(stat.description_from_level(attribute_list[stat.type]))] \
			([attribute_list[stat.type]]/[raw_attribute_list[stat.type]]).\
			"
	output += "
" //div infobox
	to_chat(user, jointext(output, ""))


code:
// This subtype is added to a global list of attribute sheets, and should never be deleted once created
/datum/attribute_holder/sheet

/datum/attribute_holder/sheet/set_parent(mob/new_parent)
	return


code:
///job subtype so i can implement stat and skill variance
/datum/attribute_holder/sheet/job
	skill_default = 0
	attribute_default = 0
	var/list/attribute_variance = 0

/datum/attribute_holder/sheet/job/New(mob/new_parent)
	. = ..()
	if(isnum(attribute_variance))
		var/og_variance = attribute_variance
		attribute_variance = list()
		for(var/thing in raw_attribute_list)
			if(!ispath(thing, /datum/attribute/stat))
				continue
			attribute_variance[thing] = list(-og_variance, og_variance)
	else if(islist(attribute_variance) && length(attribute_variance) == 2 && isnum(attribute_variance[1]))
		var/list/og_variance = attribute_variance.Copy()
		attribute_variance = list()
		for(var/thing in raw_attribute_list)
			if(!ispath(thing, /datum/attribute/stat))
				continue
			attribute_variance[thing] = list(og_variance[1], og_variance[2])

/datum/attribute_holder/sheet/job/on_add(datum/attribute_holder/plagiarist)
	. = ..()
	if(attribute_variance)
		for(var/path in plagiarist.raw_attribute_list)
			if(length(attribute_variance[path]) < 2)
				continue
			var/random = rand(attribute_variance[path][1], attribute_variance[path][2])
			if(ispath(path, /datum/attribute/skill))
				plagiarist.raw_attribute_list[path] = clamp(plagiarist.raw_attribute_list[path] + random, plagiarist.skill_min, plagiarist.skill_max)
			else
				plagiarist.raw_attribute_list[path] = clamp(plagiarist.raw_attribute_list[path] + random, plagiarist.attribute_min, plagiarist.attribute_max)


code:
/datum/attribute_holder/sheet/birthsign
	skill_default = 0
	attribute_default = 0

/datum/attribute_holder/sheet/birthsign/warrior
	raw_attribute_list = list(STAT_STRENGTH = 1, \
							STAT_INTELLIGENCE = -1)

/datum/attribute_holder/sheet/birthsign/mage
	raw_attribute_list = list(STAT_STRENGTH = -1, \
							STAT_INTELLIGENCE = 1)


code:
//Assistant
/datum/attribute_holder/sheet/job/hobo
	attribute_variance = list(STAT_STRENGTH = list(-4, -2),
						STAT_ENDURANCE = list(-4, -2),
						STAT_DEXTERITY = list(-4, -2),
						STAT_INTELLIGENCE = list(-4, -2),
						SKILL_MELEE = list(-2, 3),
						SKILL_GAMING = list(-2, 6))
	raw_attribute_list = list(SKILL_MELEE = 2,
							SKILL_GAMING = 10)

//Atmos tech/Engineer
/datum/attribute_holder/sheet/job/engi
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
						STAT_ENDURANCE = list(-1, 2),
						STAT_DEXTERITY = list(-3, 0),
						STAT_INTELLIGENCE = list(-1, 3),
						SKILL_MELEE = list(-2, 2),
						SKILL_RANGED = list(-2, 4),
						SKILL_ELECTRONICS = list(-2, 4),
						SKILL_MASONRY = list(-2, 4),
						SKILL_SMITHING = list(-2, 2),
						SKILL_LOCKPICKING = list(-2, 6),
						SKILL_CLIMBING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 8,
							SKILL_RANGED = 4,
							SKILL_ELECTRONICS = 14,
							SKILL_MASONRY = 14,
							SKILL_SMITHING = 8,
							SKILL_LOCKPICKING = 12,
							SKILL_CLIMBING = 16)

//Bartender
/datum/attribute_holder/sheet/job/innkeeper
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
							STAT_ENDURANCE = list(1, 2),
							STAT_DEXTERITY = list(-2, 0),
							STAT_INTELLIGENCE = list(-2, 1),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 2),
							SKILL_CHEMISTRY = list(-2, 2),
							SKILL_COOKING = list(-4, 4),
							SKILL_CLEANING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 6,
							SKILL_RANGED = 6,
							SKILL_THROWING = 8,
							SKILL_CHEMISTRY = 10,
							SKILL_COOKING = 12,
							SKILL_CLEANING = 8)

//Botanist (Formerly Chuck's)
/datum/attribute_holder/sheet/job/farmer
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
							STAT_ENDURANCE = list(-1, 2),
							STAT_DEXTERITY = list(-3, 0),
							STAT_INTELLIGENCE = list(-2, 2),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_COOKING = list(-4, 4),
							SKILL_BOTANY = list(-4, 4),
							SKILL_CLEANING = list(-2, 2))
	raw_attribute_list = list(SKILL_MELEE = 8,
							SKILL_RANGED = 2,
							SKILL_COOKING = 12,
							SKILL_BOTANY = 16,
							SKILL_CLEANING = 10)

//Captain
/datum/attribute_holder/sheet/job/doge
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
							STAT_ENDURANCE = list(-1, 3),
							STAT_DEXTERITY = list(-1, 3),
							STAT_INTELLIGENCE = list(-3, 1),
							SKILL_MELEE = list(-4, 4),
							SKILL_RANGED = list(-4, 4),
							SKILL_THROWING = list(-4, 2),
							SKILL_CLEANING = list(-2, 2),
							SKILL_MEDICINE = list(-3, 3),
							SKILL_LOCKPICKING = list(-2, 2),
							SKILL_CLIMBING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 14,
							SKILL_RANGED = 14,
							SKILL_THROWING = 4,
							SKILL_CLEANING = 10,
							SKILL_MEDICINE = 10,
							SKILL_LOCKPICKING = 8,
							SKILL_CLIMBING = 16)

//Cargo tech
/datum/attribute_holder/sheet/job/freighter
	attribute_variance = list(STAT_STRENGTH = list(-1, 2),
							STAT_ENDURANCE = list(-1, 2),
							STAT_DEXTERITY = list(-3, 1),
							STAT_INTELLIGENCE = list(-2, 1),
							SKILL_MELEE = list(-2, 4),
							SKILL_RANGED = list(-2, 2),
							SKILL_CLEANING = list(-2, 2),
							SKILL_MASONRY = list(-2, 2),
							SKILL_SCIENCE = list(-2, 2),
							SKILL_PICKPOCKET = list(-2, 4),
							SKILL_LOCKPICKING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 6,
							SKILL_RANGED = 10,
							SKILL_CLEANING = 8,
							SKILL_MASONRY = 6,
							SKILL_SCIENCE = 2,
							SKILL_PICKPOCKET = 8,
							SKILL_LOCKPICKING = 8)

//Chaplain
/datum/attribute_holder/sheet/job/chaplain
	attribute_variance = list(STAT_STRENGTH = list(-2, 2),
							STAT_ENDURANCE = list(-2, 2),
							STAT_DEXTERITY = list(-1, 3),
							STAT_INTELLIGENCE = list(0, 3),
							SKILL_MELEE = list(-2, 4),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 2),
							SKILL_MEDICINE = list(-2, 2),
							SKILL_COOKING = list(-2, 2),
							SKILL_CLEANING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 6,
							SKILL_RANGED = 4,
							SKILL_THROWING = 8,
							SKILL_MEDICINE = 10,
							SKILL_COOKING = 8,
							SKILL_CLEANING = 8)

//Chemist
/datum/attribute_holder/sheet/job/apothecary
	attribute_variance = list(STAT_STRENGTH = list(-2, 1),
							STAT_ENDURANCE = list(-2, 2),
							STAT_DEXTERITY = list(-1, 2),
							STAT_INTELLIGENCE = list(0, 3),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 4),
							SKILL_CHEMISTRY = list(-4, 4),
							SKILL_MEDICINE = list(-2, 2),
							SKILL_SCIENCE = list(-2, 2))
	raw_attribute_list = list(SKILL_MELEE = 6,
							SKILL_RANGED = 2,
							SKILL_THROWING = 6,
							SKILL_CHEMISTRY = 16,
							SKILL_MEDICINE = 10,
							SKILL_SCIENCE = 10)

//Chief engineer
/datum/attribute_holder/sheet/job/chief_engi
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
						STAT_ENDURANCE = list(-1, 3),
						STAT_DEXTERITY = list(-2, 1),
						STAT_INTELLIGENCE = list(-1, 4),
						SKILL_MELEE = list(-2, 2),
						SKILL_RANGED = list(-2, 2),
						SKILL_ELECTRONICS = list(-2, 4),
						SKILL_MASONRY = list(-2, 4),
						SKILL_SMITHING = list(-2, 4),
						SKILL_LOCKPICKING = list(-2, 4),
						SKILL_CLIMBING = list(0, 4))
	raw_attribute_list = list(SKILL_MELEE = 8,
							SKILL_RANGED = 8,
							SKILL_ELECTRONICS = 16,
							SKILL_MASONRY = 16,
							SKILL_SMITHING = 8,
							SKILL_LOCKPICKING = 16,
							SKILL_CLIMBING = 16)

//CMO
/datum/attribute_holder/sheet/job/master_practitioner
	attribute_variance = list(STAT_STRENGTH = list(-2, 1),
							STAT_ENDURANCE = list(-2, 1),
							STAT_DEXTERITY = list(-2, 4),
							STAT_INTELLIGENCE = list(-1, 4),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 4),
							SKILL_CHEMISTRY = list(-2, 4),
							SKILL_MEDICINE = list(-2, 4),
							SKILL_SURGERY = list(-2, 4),
							SKILL_SCIENCE = list(-2, 2))
	raw_attribute_list = list(SKILL_MELEE = 8,
							SKILL_RANGED = 8,
							SKILL_THROWING = 4,
							SKILL_CHEMISTRY = 10,
							SKILL_MEDICINE = 16,
							SKILL_SURGERY = 16,
							SKILL_SCIENCE = 10)

//Clown and mime
/datum/attribute_holder/sheet/job/jester
	attribute_variance = list(STAT_STRENGTH = list(-6, 6),
							STAT_ENDURANCE = list(-6, 6),
							STAT_DEXTERITY = list(-6, 6),
							STAT_INTELLIGENCE = list(-6, 6),
							SKILL_MELEE = list(-6, 6),
							SKILL_PICKPOCKET = list(-6, 6),
							SKILL_THROWING = list(-4, 2),
							SKILL_GAMING = list(0, 4))
	raw_attribute_list = list(SKILL_MELEE = 6,
							SKILL_PICKPOCKET = 6,
							SKILL_THROWING = 12,
							SKILL_GAMING = 16)

//Chef
/datum/attribute_holder/sheet/job/chef
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
							STAT_ENDURANCE = list(-1, 2),
							STAT_DEXTERITY = list(-3, 0),
							STAT_INTELLIGENCE = list(-2, 2),
							SKILL_MELEE = list(-2, 2),
							SKILL_COOKING = list(-2, 4),
							SKILL_BOTANY = list(-2, 4),
							SKILL_CLEANING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 10,
							SKILL_COOKING = 16,
							SKILL_BOTANY = 10,
							SKILL_CLEANING = 10)

//Detective
/datum/attribute_holder/sheet/job/sheriff
	attribute_variance = list(STAT_STRENGTH = list(-2, 1),
						STAT_ENDURANCE = list(-2, 0),
						STAT_DEXTERITY = list(-1, 3),
						STAT_INTELLIGENCE = list(-1, 2),
						SKILL_MELEE = list(-2, 2),
						SKILL_RANGED = list(-2, 4),
						SKILL_TRACKING = list(-2, 4),
						SKILL_LOCKPICKING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 10,
							SKILL_RANGED = 12,
							SKILL_TRACKING = 16,
							SKILL_LOCKPICKING = 10)

//Geneticist / Medical Doctor
/datum/attribute_holder/sheet/job/practitioner
	attribute_variance = list(STAT_STRENGTH = list(-1, 1),
							STAT_ENDURANCE = list(-1, 2),
							STAT_DEXTERITY = list(1, 3),
							STAT_INTELLIGENCE = list(-1, 3),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-4, 4),
							SKILL_CHEMISTRY = list(-1, 2),
							SKILL_MEDICINE = list(-2, 2),
							SKILL_SURGERY = list(-2, 2),
							SKILL_SCIENCE = list(-2, 2))
	raw_attribute_list = list(SKILL_MELEE = 4,
							SKILL_RANGED = 2,
							SKILL_THROWING = 6,
							SKILL_CHEMISTRY = 7,
							SKILL_MEDICINE = 12,
							SKILL_SURGERY = 12,
							SKILL_SCIENCE = 4)

//Head of personnel
/datum/attribute_holder/sheet/job/gatekeeper
	attribute_variance = list(STAT_STRENGTH = list(-1, 2),
							STAT_ENDURANCE = list(-2, 1),
							STAT_DEXTERITY = list(-1, 2),
							STAT_INTELLIGENCE = list(-2, 2),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 2),
							SKILL_PICKPOCKET = list(-2, 2),
							SKILL_LOCKPICKING = list(-2, 2),
							SKILL_SCIENCE = list(-4, 2),
							SKILL_CLIMBING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 10,
							SKILL_RANGED = 10,
							SKILL_THROWING = 2,
							SKILL_PICKPOCKET = 8,
							SKILL_LOCKPICKING = 8,
							SKILL_SCIENCE = 4,
							SKILL_CLIMBING = 6)

//Head of security
/datum/attribute_holder/sheet/job/head_ordinator
	attribute_variance = list(STAT_STRENGTH = list(-1, 5),
						STAT_ENDURANCE = list(-2, 5),
						STAT_DEXTERITY = list(-2, 4),
						STAT_INTELLIGENCE = list(-3, -1),
						SKILL_MELEE = list(-2, 2),
						SKILL_RANGED = list(-4, 4),
						SKILL_THROWING = list(-2, 4),
						SKILL_TRACKING = list(-2, 4),
						SKILL_CLIMBING = list(-2, 2))
	raw_attribute_list = list(SKILL_MELEE = 16,
							SKILL_RANGED = 16,
							SKILL_THROWING = 12,
							SKILL_TRACKING = 12,
							SKILL_CLIMBING = 8)

//Janitor
/datum/attribute_holder/sheet/job/janitor
	attribute_variance = list(STAT_STRENGTH = list(-1, 2),
						STAT_ENDURANCE = list(-1, 3),
						STAT_DEXTERITY = list(-2, 1),
						STAT_INTELLIGENCE = list(-3, 2),
						SKILL_CLEANING = list(-4, 4),
						SKILL_MASONRY = list(-4, 2),
						SKILL_MELEE = list(-2, 0),
						SKILL_RANGED = list(-2, 0),
						SKILL_THROWING = list(-2, 2))
	raw_attribute_list = list(SKILL_CLEANING = 16,
							SKILL_MASONRY = 6,
							SKILL_MELEE = 6,
							SKILL_RANGED = 6,
							SKILL_THROWING = 6,
							SKILL_CLIMBING = 6)

//Paramedic
/datum/attribute_holder/sheet/job/paramedic
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
							STAT_ENDURANCE = list(-1, 3),
							STAT_DEXTERITY = list(-2, 1),
							STAT_INTELLIGENCE = list(-2, 1),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 2),
							SKILL_CHEMISTRY = list(-4, 4),
							SKILL_MEDICINE = list(-2, 2),
							SKILL_SURGERY = list(-2, 2))
	raw_attribute_list = list(SKILL_MELEE = 8,
							SKILL_RANGED = 8,
							SKILL_THROWING = 6,
							SKILL_CHEMISTRY = 6,
							SKILL_MEDICINE = 12,
							SKILL_SURGERY = 8)

//Quartermaster
/datum/attribute_holder/sheet/job/merchant
	attribute_variance = list(STAT_STRENGTH = list(-2, 1),
							STAT_ENDURANCE = list(-2, 1),
							STAT_DEXTERITY = list(-1, 3),
							STAT_INTELLIGENCE = list(-1, 3),
							SKILL_MELEE = list(-4, 4),
							SKILL_RANGED = list(-4, 4),
							SKILL_THROWING = list(-4, 2),
							SKILL_CLEANING = list(-6, 2),
							SKILL_PICKPOCKET = list(-2, 2),
							SKILL_LOCKPICKING = list(-2, 2))
	raw_attribute_list = list(SKILL_MELEE = 8,
							SKILL_RANGED = 10,
							SKILL_THROWING = 4,
							SKILL_CLEANING = 8,
							SKILL_PICKPOCKET = 8,
							SKILL_LOCKPICKING = 10)

//Research Director
/datum/attribute_holder/sheet/job/technocrat
	attribute_variance = list(STAT_STRENGTH = list(-2, 1),
							STAT_ENDURANCE = list(-2, 1),
							STAT_DEXTERITY = list(1, 3),
							STAT_INTELLIGENCE = list(1, 5),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 2),
							SKILL_CHEMISTRY = list(-2, 4),
							SKILL_MEDICINE = list(-2, 4),
							SKILL_SURGERY = list(-2, 2),
							SKILL_SCIENCE = list(-2, 4),
							SKILL_CLIMBING = list(-4, 4),
							SKILL_ELECTRONICS = list(-4, 4))
	raw_attribute_list = list(SKILL_MELEE = 6,
							SKILL_RANGED = 10,
							SKILL_THROWING = 6,
							SKILL_CHEMISTRY = 10,
							SKILL_MEDICINE = 6,
							SKILL_SURGERY = 6,
							SKILL_SCIENCE = 16,
							SKILL_ELECTRONICS = 14,
							SKILL_CLIMBING = 10)
//Technomancer
/datum/attribute_holder/sheet/job/technologist
	attribute_variance = list(STAT_STRENGTH = list(-2, 1),
							STAT_ENDURANCE = list(-2, 1),
							STAT_DEXTERITY = list(0, 2),
							STAT_INTELLIGENCE = list(0, 2),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 2),
							SKILL_CHEMISTRY = list(-2, 4),
							SKILL_MEDICINE = list(-2, 4),
							SKILL_SURGERY = list(-2, 2),
							SKILL_SCIENCE = list(-2, 4),
							SKILL_CLIMBING = list(-4, 4))
	raw_attribute_list = list(SKILL_MELEE = 6,
							SKILL_RANGED = 10,
							SKILL_THROWING = 6,
							SKILL_CHEMISTRY = 12,
							SKILL_MEDICINE = 6,
							SKILL_SURGERY = 6,
							SKILL_SCIENCE = 16,
							SKILL_CLIMBING = 16)

//Machinist/Roboticist
/datum/attribute_holder/sheet/job/machinist
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
							STAT_ENDURANCE = list(-1, 3),
							STAT_DEXTERITY = list(-3, 1),
							STAT_INTELLIGENCE = list(-1, 2),
							SKILL_MELEE = list(-2, 2),
							SKILL_RANGED = list(-2, 2),
							SKILL_THROWING = list(-2, 2),
							SKILL_CHEMISTRY = list(-2, 4),
							SKILL_MEDICINE = list(-2, 4),
							SKILL_SURGERY = list(-2, 2),
							SKILL_SCIENCE = list(-2, 4),
							SKILL_CLIMBING = list(-2, 4),
							SKILL_ELECTRONICS = list(-2, 6))
	raw_attribute_list = list(SKILL_MELEE = 6,
							SKILL_RANGED = 6,
							SKILL_THROWING = 6,
							SKILL_CHEMISTRY = 2,
							SKILL_MEDICINE = 4,
							SKILL_SURGERY = 10,
							SKILL_SCIENCE = 10,
							SKILL_CLIMBING = 10,
							SKILL_ELECTRONICS = 12)

//Security officer
/datum/attribute_holder/sheet/job/ordinator
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
						STAT_ENDURANCE = list(-1, 3),
						STAT_DEXTERITY = list(-2, 2),
						STAT_INTELLIGENCE = list(-3, 0),
						SKILL_MELEE = list(-4, 2),
						SKILL_RANGED = list(-4, 2),
						SKILL_THROWING = list(-4, 2),
						SKILL_TRACKING = list(-2, 2),
						SKILL_CLIMBING = list(-2, 2))
	raw_attribute_list = list(SKILL_MELEE = 14,
							SKILL_RANGED = 14,
							SKILL_THROWING = 12,
							SKILL_TRACKING = 8,
							SKILL_CLIMBING = 4)

//Miner
/datum/attribute_holder/sheet/job/miner
	attribute_variance = list(STAT_STRENGTH = list(-1, 3),
						STAT_ENDURANCE = list(-1, 3),
						STAT_DEXTERITY = list(-2, 2),
						STAT_INTELLIGENCE = list(-3, 0),
						SKILL_MELEE = list(-4, 2),
						SKILL_RANGED = list(-4, 2),
						SKILL_THROWING = list(-4, 3),
						SKILL_TRACKING = list(-2, 2),
						SKILL_CLIMBING = list(-2, 4))
	raw_attribute_list = list(SKILL_MELEE = 14,
							SKILL_RANGED = 14,
							SKILL_THROWING = 12,
							SKILL_TRACKING = 2,
							SKILL_CLIMBING = 10)


code:
//Woman moment
/datum/attribute_holder/sheet/woman
	raw_attribute_list = list(STAT_STRENGTH = -1, \
							STAT_DEXTERITY = 1)
	attribute_default = 0
	skill_default = 0


code:
/datum/attribute_modifier/combat_style
	variable = TRUE


code:
/datum/attribute_modifier/nondominant_hand
	attribute_list = list(STAT_DEXTERITY = -1)


code:
/datum/diceroll_modifier/fraggot
	modification = -40


code:
/datum/diceroll_modifier/mood
	variable = TRUE


code:
/datum/attribute_modifier/lobotomy
	attribute_list = list(STAT_INTELLIGENCE = -1)

/datum/attribute_modifier/lobotomite
	attribute_list = list(STAT_INTELLIGENCE = -2)


code:
/datum/attribute/skill
	/// Skill category we fall under - DO NOT FORGET TO SET THIS, IT BREAKS SHIT
	var/category = SKILL_CATEGORY_GENERAL

/datum/attribute/skill/description_from_level(level)
	switch(CEILING(level, 1))
		if(-INFINITY to 2)
			return "unsalvageable"
		if(3,4)
			return "worthless"
		if(5,6)
			return "incompetent"
		if(7,8)
			return "novice"
		if(9,10)
			return "unskilled"
		if(11,12)
			return "competent"
		if(13,14)
			return "adept"
		if(15,16)
			return "versed"
		if(17,18)
			return "expert"
		if(19,20)
			return "master"
		if(21,22)
			return "legendary"
		if(23 to INFINITY)
			return "mythic"
		else
			return "invalid"


code:
/datum/attribute/stat
	/// Used when space is too valuable to write a full word
	var/shorthand = "FK"

/datum/attribute/stat/description_from_level(level)
	switch(CEILING(level, 1))
		if(-INFINITY to 6)
			return "crippling"
		if(7)
			return "poor"
		if(8,9)
			return "below average"
		if(10)
			return "average"
		if(11,12)
			return "above average"
		if(13,14)
			return "gifted"
		if(15,16)
			return "amazing"
		if(17,18)
			return "incredible"
		if(19,20)
			return "legendary"
		if(21 to INFINITY)
			return "mythic"
		else
			return "invalid"


code:
/datum/augment_item
	/// Name obviously, make sure that NO NAME gets repeated in the same slot!
	var/name
	/// Description of the loadout augment, automatically set by New() if null
	var/description
	/// How many quirk points does it cost?
	var/value = 0
	/// Category in which the augment belongs to - check "_DEFINES/augment.dm"
	var/category = AUGMENT_CATEGORY_NONE
	/// Slot in which the augment belongs to, MAKE SURE THE SAME SLOT IS ONLY IN ONE CATEGORY
	var/slot = AUGMENT_SLOT_NONE
	/// Which biotypes are allowed to receive the augment
	var/allowed_biotypes = MOB_ORGANIC
	/// Hardcoded styles can be chosen from - SHOULD ONLY BE USED ON LIMBS1
	var/uses_robotic_styles = FALSE
	/// Typepath to the augment being used
	var/path

/datum/augment_item/New()
	. = ..()
	if(!description && path)
		var/atom/atom_path = path
		description = initial(atom_path.desc)

/datum/augment_item/proc/apply_to_human(mob/living/carbon/human/human, character_setup = FALSE, datum/preferences/prefs)
	return


code:
/datum/augment_item/implant
	category = AUGMENT_CATEGORY_IMPLANTS

/datum/augment_item/implant/apply_to_human(mob/living/carbon/human/human, character_setup = FALSE, datum/preferences/prefs)
	if(character_setup)
		return
	var/obj/item/organ/new_organ = new path()
	new_organ.Insert(human, TRUE, FALSE)

//BRAIN IMPLANTS
/datum/augment_item/implant/brain
	slot = AUGMENT_SLOT_BRAIN_IMPLANT

//CHEST IMPLANTS
/datum/augment_item/implant/chest
	slot = AUGMENT_SLOT_CHEST_IMPLANT

//LEFT ARM IMPLANTS
/datum/augment_item/implant/l_arm
	slot = AUGMENT_SLOT_LEFT_ARM_IMPLANT

//RIGHT ARM IMPLANTS
/datum/augment_item/implant/r_arm
	slot = AUGMENT_SLOT_RIGHT_ARM_IMPLANT

//EYES IMPLANTS
/datum/augment_item/implant/eyes
	slot = AUGMENT_SLOT_EYE_IMPLANT

//MOUTH IMPLANTS
/datum/augment_item/implant/mouth
	slot = AUGMENT_SLOT_MOUTH_IMPLANT

/datum/augment_item/implant/mouth/breathing_tube
	name = "Breathing Tube"
	value = 3
	path = /obj/item/organ/cyberimp/mouth/breathing_tube


code:
/datum/augment_item/limb
	category = AUGMENT_CATEGORY_LIMBS
	allowed_biotypes = MOB_ORGANIC|MOB_ROBOTIC
	uses_robotic_styles = TRUE

/datum/augment_item/limb/apply_to_human(mob/living/carbon/human/human, character_setup = FALSE, datum/preferences/prefs)
	var/obj/item/bodypart/new_bodypart = new path()
	new_bodypart.advanced_rendering = FALSE
	if(uses_robotic_styles && prefs.augment_styles[slot])
		new_bodypart.render_icon = GLOB.robotic_styles_list[prefs.augment_styles[slot]]
	var/obj/item/bodypart/old_bodypart = human.get_bodypart(new_bodypart.body_zone)
	var/list/oldorgans = list()
	oldorgans |= human.getorganslotlistzone(ORGAN_SLOT_BONE, old_bodypart.body_zone)
	oldorgans |= human.getorganslotlistzone(ORGAN_SLOT_TENDON, old_bodypart.body_zone)
	oldorgans |= human.getorganslotlistzone(ORGAN_SLOT_ARTERY, old_bodypart.body_zone)
	oldorgans |= human.getorganslotlistzone(ORGAN_SLOT_NERVE, old_bodypart.body_zone)
	for(var/obj/item/organ/organ as anything in oldorgans)
		organ.Remove(human, TRUE)
		qdel(organ)
	new_bodypart.replace_limb(human, special = TRUE, ignore_children = TRUE)
	qdel(old_bodypart)

/datum/augment_item/limb/missing
	uses_robotic_styles = FALSE

/datum/augment_item/limb/missing/apply_to_human(mob/living/carbon/human/human, character_setup = FALSE, datum/preferences/prefs)
	var/obj/item/bodypart/BP = path
	var/obj/item/bodypart/oldBP = human.get_bodypart(initial(BP.body_zone))
	if(oldBP)
		oldBP.drop_limb(FALSE, FALSE, FALSE, TRUE)

//RIGHT EYESOCKET
/datum/augment_item/limb/r_eyesocket
	slot = AUGMENT_SLOT_R_EYESOCKET

/datum/augment_item/limb/missing/r_eyesocket
	name = "Amputated Right Orbit"
	description = "You have no right eyesocket."
	slot = AUGMENT_SLOT_R_EYESOCKET
	path = /obj/item/bodypart/r_eyesocket
	value = -4

//LEFT EYESOCKET
/datum/augment_item/limb/l_eyesocket
	slot = AUGMENT_SLOT_L_EYESOCKET

/datum/augment_item/limb/missing/l_eyesocket
	name = "Amputated Left Orbit"
	description = "You have no left eyesocket."
	slot = AUGMENT_SLOT_L_EYESOCKET
	path = /obj/item/bodypart/l_eyesocket
	value = -4

//HEADS
/datum/augment_item/limb/head
	slot = AUGMENT_SLOT_HEAD

//FACES
/datum/augment_item/limb/face
	slot = AUGMENT_SLOT_FACE

/datum/augment_item/limb/missing/face
	name = "Amputated Face"
	description = "You have no face."
	slot = AUGMENT_SLOT_FACE
	path = /obj/item/bodypart/face
	value = -4

//JAWS
/datum/augment_item/limb/jaw
	slot = AUGMENT_SLOT_MOUTH

/datum/augment_item/limb/missing/jaw
	name = "Amputated Jaw"
	description = "You have no jaw."
	slot = AUGMENT_SLOT_MOUTH
	path = /obj/item/bodypart/mouth
	value = -4

//CHESTS
/datum/augment_item/limb/chest
	slot = AUGMENT_SLOT_CHEST

//GROINS
/datum/augment_item/limb/groin
	slot = AUGMENT_SLOT_GROIN

/datum/augment_item/limb/missing/groin
	name = "Amputated Lower Body"
	description = "You have no lower body."
	slot = AUGMENT_SLOT_GROIN
	path = /obj/item/bodypart/groin
	value = -4

//LEFT ARMS
/datum/augment_item/limb/l_arm
	slot = AUGMENT_SLOT_L_ARM

/datum/augment_item/limb/missing/l_arm
	name = "Amputated Left Arm"
	description = "You have no left arm."
	slot = AUGMENT_SLOT_L_ARM
	path = /obj/item/bodypart/l_arm
	value = -4

/datum/augment_item/limb/l_arm/surplus
	name = "Surplus Robotic Left Arm"
	path = /obj/item/bodypart/l_arm/robot/surplus
	value = -2

/datum/augment_item/limb/l_arm/robotic
	name = "Robotic Left Arm"
	path = /obj/item/bodypart/l_arm/robot/weak

//LEFT HANDS
/datum/augment_item/limb/l_hand
	slot = AUGMENT_SLOT_L_HAND

/datum/augment_item/limb/missing/l_hand
	name = "Amputated Left Hand"
	description = "You have no left hand."
	slot = AUGMENT_SLOT_L_HAND
	path = /obj/item/bodypart/l_hand
	value = -4

/datum/augment_item/limb/l_hand/surplus
	name = "Surplus Robotic Left Hand"
	path = /obj/item/bodypart/l_hand/robot/surplus
	value = -2

/datum/augment_item/limb/l_hand/robotic
	name = "Robotic Left Hand"
	path = /obj/item/bodypart/l_hand/robot/weak

//RIGHT ARMS
/datum/augment_item/limb/r_arm
	slot = AUGMENT_SLOT_R_ARM

/datum/augment_item/limb/missing/r_arm
	name = "Amputated Right Arm"
	description = "You have no right arm."
	slot = AUGMENT_SLOT_R_ARM
	path = /obj/item/bodypart/r_arm
	value = -4

/datum/augment_item/limb/r_arm/surplus
	name = "Surplus Robotic Right Arm"
	path = /obj/item/bodypart/r_arm/robot/surplus
	value = -2

/datum/augment_item/limb/r_arm/robotic
	name = "Robotic Right Arm"
	path = /obj/item/bodypart/r_arm/robot/weak

//RIGHT HANDS
/datum/augment_item/limb/r_hand
	slot = AUGMENT_SLOT_R_HAND

/datum/augment_item/limb/missing/r_hand
	name = "Amputated Right Hand"
	description = "You have no right hand."
	slot = AUGMENT_SLOT_R_HAND
	path = /obj/item/bodypart/r_hand
	value = -4

/datum/augment_item/limb/r_hand/surplus
	name = "Surplus Robotic Right Hand"
	path = /obj/item/bodypart/r_hand/robot/surplus
	value = -2

/datum/augment_item/limb/r_hand/robotic
	name = "Robotic Right Hand"
	path = /obj/item/bodypart/r_hand/robot/weak

//LEFT LEGS
/datum/augment_item/limb/l_leg
	slot = AUGMENT_SLOT_L_LEG

/datum/augment_item/limb/missing/l_leg
	name = "Amputated Left Leg"
	description = "You have no left leg."
	slot = AUGMENT_SLOT_L_LEG
	path = /obj/item/bodypart/l_leg
	value = -4

/datum/augment_item/limb/l_leg/surplus
	name = "Surplus Robotic Left Leg"
	path = /obj/item/bodypart/l_leg/robot/surplus
	value = -2

/datum/augment_item/limb/l_leg/robotic
	name = "Robotic Left Leg"
	path = /obj/item/bodypart/l_leg/robot/weak

//LEFT FEET
/datum/augment_item/limb/l_foot
	slot = AUGMENT_SLOT_L_FOOT

/datum/augment_item/limb/missing/l_foot
	name = "Amputated Left Foot"
	description = "You have no left foot."
	slot = AUGMENT_SLOT_L_FOOT
	path = /obj/item/bodypart/l_foot
	value = -4

/datum/augment_item/limb/l_foot/surplus
	name = "Surplus Robotic Left Foot"
	path = /obj/item/bodypart/l_foot/robot/surplus
	value = -2

/datum/augment_item/limb/l_foot/robotic
	name = "Robotic Left Foot"
	path = /obj/item/bodypart/l_foot/robot/weak

//RIGHT LEGS
/datum/augment_item/limb/r_leg
	slot = AUGMENT_SLOT_R_LEG

/datum/augment_item/limb/missing/r_leg
	name = "Amputated Right Leg"
	description = "You have no right leg."
	slot = AUGMENT_SLOT_R_LEG
	path = /obj/item/bodypart/r_leg
	value = -4

/datum/augment_item/limb/r_leg/surplus
	name = "Surplus Robotic Right Leg"
	path = /obj/item/bodypart/r_leg/robot/surplus
	value = -2

/datum/augment_item/limb/r_leg/robotic
	name = "Robotic Right Leg"
	path = /obj/item/bodypart/r_leg/robot/weak

//RIGHT FEET
/datum/augment_item/limb/r_foot
	slot = AUGMENT_SLOT_R_FOOT

/datum/augment_item/limb/missing/r_foot
	name = "Amputated Right Foot"
	description = "You have no right foot."
	slot = AUGMENT_SLOT_R_FOOT
	path = /obj/item/bodypart/r_foot
	value = -4

/datum/augment_item/limb/r_foot/surplus
	name = "Surplus Robotic Right Foot"
	path = /obj/item/bodypart/r_foot/robot/surplus
	value = -2

/datum/augment_item/limb/r_foot/robotic
	name = "Robotic Right Foot"
	path = /obj/item/bodypart/r_foot/robot/weak


code:
/datum/augment_item/organ
	category = AUGMENT_CATEGORY_ORGANS

/datum/augment_item/organ/missing

/datum/augment_item/organ/missing/apply_to_human(mob/living/carbon/human/human, character_setup = FALSE, datum/preferences/prefs)
	if(character_setup)
		return
	var/list/kill_organs = human.getorganlist(path)
	for(var/obj/item/organ/organ in kill_organs)
		organ.Remove(human, FALSE)
		qdel(organ)


code:
/datum/component/bloodysoles
	bloody_shoes = list(BLOOD_STATE_HUMAN = 0, \
					BLOOD_STATE_XENO = 0, \
					BLOOD_STATE_OIL = 0, \
					BLOOD_STATE_SHIT = 0, \
					BLOOD_STATE_CUM = 0, \
					BLOOD_STATE_FEMCUM = 0, \
					BLOOD_STATE_NOT_BLOODY = 0)

/datum/component/bloodysoles/share_blood(obj/effect/decal/cleanable/pool)
	last_blood_state = pool.blood_state

	// Share the blood between our boots and the blood pool
	var/total_bloodiness = pool.bloodiness + bloody_shoes[last_blood_state]

	// We can however be limited by how much blood we can hold
	var/new_our_bloodiness = min(BLOOD_ITEM_MAX, total_bloodiness / 2)

	bloody_shoes[last_blood_state] = new_our_bloodiness
	pool.bloodiness = total_bloodiness - new_our_bloodiness // Give the pool the remaining blood incase we were limited

	if(HAS_TRAIT(parent_atom, TRAIT_LIGHT_STEP)) //the character is agile enough to don't mess their clothing and hands just from one blood splatter at floor
		return TRUE

	if(last_blood_state == BLOOD_STATE_SHIT)
		parent_atom.add_shit_DNA(pool.return_shit_DNA())
	else if(last_blood_state == BLOOD_STATE_CUM)
		parent_atom.add_cum_DNA(pool.return_cum_DNA())
	else if(last_blood_state == BLOOD_STATE_FEMCUM)
		parent_atom.add_femcum_DNA(pool.return_femcum_DNA())
	else
		parent_atom.add_blood_DNA(pool.return_blood_DNA())
	update_icon()

/datum/component/bloodysoles/on_moved(datum/source, OldLoc, Dir, Forced)
	if(bloody_shoes[last_blood_state] == 0)
		return
	if(QDELETED(wielder) || is_obscured())
		return
	if(wielder.body_position == LYING_DOWN || !wielder.has_gravity(wielder.loc))
		return

	var/half_our_blood = bloody_shoes[last_blood_state] / 2

	// Add footprints in old loc if we have enough cream
	var/footprint_type = /obj/effect/decal/cleanable/blood/footprints
	switch(last_blood_state)
		if(BLOOD_STATE_SHIT)
			footprint_type = /obj/effect/decal/cleanable/blood/footprints/shit
		if(BLOOD_STATE_CUM)
			footprint_type = /obj/effect/decal/cleanable/blood/footprints/cum
		if(BLOOD_STATE_FEMCUM)
			footprint_type = /obj/effect/decal/cleanable/blood/footprints/femcum
	if(half_our_blood >= BLOOD_FOOTPRINTS_MIN)
		var/turf/oldLocTurf = get_turf(OldLoc)
		var/obj/effect/decal/cleanable/blood/footprints/oldLocFP = find_pool_by_blood_state(oldLocTurf, footprint_type)
		if(oldLocFP)
			// Footprints found in the tile we left, add us to it
			add_parent_to_footprint(oldLocFP)
			if (!(oldLocFP.exited_dirs & wielder.dir))
				oldLocFP.exited_dirs |= wielder.dir
				oldLocFP.update_appearance()
		else if(find_pool_by_blood_state(oldLocTurf))
			// No footprints in the tile we left, but there was some other blood pool there. Add exit footprints on it
			bloody_shoes[last_blood_state] -= half_our_blood
			update_icon()

			oldLocFP = new footprint_type(oldLocTurf)
			if(!QDELETED(oldLocFP)) ///prints merged
				oldLocFP.blood_state = last_blood_state
				oldLocFP.exited_dirs |= wielder.dir
				add_parent_to_footprint(oldLocFP)
				oldLocFP.bloodiness = half_our_blood
				switch(last_blood_state)
					if(BLOOD_STATE_SHIT)
						oldLocFP.add_shit_DNA(parent_atom.return_shit_DNA())
					if(BLOOD_STATE_CUM)
						oldLocFP.add_cum_DNA(parent_atom.return_cum_DNA())
					if(BLOOD_STATE_FEMCUM)
						oldLocFP.add_femcum_DNA(parent_atom.return_femcum_DNA())
					else
						oldLocFP.add_blood_DNA(parent_atom.return_blood_DNA())
				oldLocFP.update_appearance()

			half_our_blood = bloody_shoes[last_blood_state] / 2

	// If we picked up the blood on this tick in on_step_blood, don't make footprints at the same place
	if(last_pickup && last_pickup == world.time)
		return

	// Create new footprints
	if(half_our_blood >= BLOOD_FOOTPRINTS_MIN)
		bloody_shoes[last_blood_state] -= half_our_blood
		update_icon()

		var/obj/effect/decal/cleanable/blood/footprints/FP = new footprint_type(get_turf(parent_atom))
		if(!QDELETED(FP)) ///prints merged
			FP.blood_state = last_blood_state
			FP.entered_dirs |= wielder.dir
			add_parent_to_footprint(FP)
			FP.bloodiness = half_our_blood
			switch(last_blood_state)
				if(BLOOD_STATE_SHIT)
					FP.add_shit_DNA(parent_atom.return_shit_DNA())
				if(BLOOD_STATE_CUM)
					FP.add_cum_DNA(parent_atom.return_cum_DNA())
				if(BLOOD_STATE_FEMCUM)
					FP.add_femcum_DNA(parent_atom.return_femcum_DNA())
				else
					FP.add_blood_DNA(parent_atom.return_blood_DNA())
			FP.update_appearance()

/datum/component/bloodysoles/on_clean(datum/source, clean_types)
	if(!(clean_types & CLEAN_TYPE_BLOOD) || last_blood_state == BLOOD_STATE_NOT_BLOODY)
		return NONE

	bloody_shoes = list(BLOOD_STATE_HUMAN = 0, \
					BLOOD_STATE_XENO = 0, \
					BLOOD_STATE_OIL = 0, \
					BLOOD_STATE_SHIT = 0, \
					BLOOD_STATE_CUM = 0, \
					BLOOD_STATE_FEMCUM = 0, \
					BLOOD_STATE_NOT_BLOODY = 0)
	last_blood_state = BLOOD_STATE_NOT_BLOODY
	update_icon()
	return COMPONENT_CLEANED

/datum/component/bloodysoles/feet
	var/static/mutable_appearance/shitty_feet
	var/static/mutable_appearance/cummy_feet
	var/static/mutable_appearance/femcummy_feet

/datum/component/bloodysoles/feet/Initialize()
	if(!iscarbon(parent))
		return COMPONENT_INCOMPATIBLE
	parent_atom = parent
	wielder = parent

	if(!bloody_feet)
		bloody_feet = mutable_appearance('modular_septic/icons/effects/blood.dmi', "shoeblood", SHOES_LAYER)
	if(!shitty_feet)
		shitty_feet = mutable_appearance('modular_septic/icons/effects/blood.dmi', "shoeshit", SHOES_LAYER)
	if(!cummy_feet)
		cummy_feet = mutable_appearance('modular_septic/icons/effects/cum.dmi', "shoecum", SHOES_LAYER)
		cummy_feet.color = COLOR_WHITE_CUM
	if(!femcummy_feet)
		femcummy_feet = mutable_appearance('modular_septic/icons/effects/femcum.dmi', "shoefemcum", SHOES_LAYER)
		femcummy_feet.color = COLOR_WHITE_FEMCUM

	RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/on_clean)
	RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_moved)
	RegisterSignal(parent, COMSIG_STEP_ON_BLOOD, .proc/on_step_blood)
	RegisterSignal(parent, COMSIG_CARBON_UNEQUIP_SHOECOVER, .proc/unequip_shoecover)
	RegisterSignal(parent, COMSIG_CARBON_EQUIP_SHOECOVER, .proc/equip_shoecover)

/datum/component/bloodysoles/feet/update_icon()
	if(ishuman(wielder))
		var/mob/living/carbon/human/human = wielder
		if(NOBLOODOVERLAY in human.dna.species.species_traits)
			return
		switch(last_blood_state)
			if(BLOOD_STATE_HUMAN)
				if(bloody_shoes[BLOOD_STATE_HUMAN] > 0 && !is_obscured())
					human.remove_overlay(SHOES_LAYER)
					human.overlays_standing[SHOES_LAYER] = bloody_feet
					human.apply_overlay(SHOES_LAYER)
				else
					human.update_inv_shoes()
			if(BLOOD_STATE_SHIT)
				if(bloody_shoes[BLOOD_STATE_SHIT] > 0 && !is_obscured())
					human.remove_overlay(SHOES_LAYER)
					human.overlays_standing[SHOES_LAYER] = shitty_feet
					human.apply_overlay(SHOES_LAYER)
				else
					human.update_inv_shoes()
			if(BLOOD_STATE_CUM)
				if(bloody_shoes[BLOOD_STATE_CUM] > 0 && !is_obscured())
					human.remove_overlay(SHOES_LAYER)
					human.overlays_standing[SHOES_LAYER] = cummy_feet
					human.apply_overlay(SHOES_LAYER)
				else
					human.update_inv_shoes()
			if(BLOOD_STATE_FEMCUM)
				if(bloody_shoes[BLOOD_STATE_FEMCUM] > 0 && !is_obscured())
					human.remove_overlay(SHOES_LAYER)
					human.overlays_standing[SHOES_LAYER] = femcummy_feet
					human.apply_overlay(SHOES_LAYER)
				else
					human.update_inv_shoes()
			else
				human.update_inv_shoes()


code:
/datum/component/clinging
	///Atom our parent mob is clinging to
	var/atom/clinging_to
	///Clinging grabs our parent mob is holding
	var/list/obj/item/clinging_grab/grabs = list()
	///Used for do_after callback checks to cancel clings
	var/cling_valid = TRUE

/datum/component/clinging/Initialize(atom/clinging_to)
	if(!iscarbon(parent))
		return COMPONENT_INCOMPATIBLE
	src.clinging_to = clinging_to

/datum/component/clinging/RegisterWithParent()
	var/mob/living/carbon/carbon_parent = parent
	grabs += new /obj/item/clinging_grab()
	grabs += new /obj/item/clinging_grab()
	if(!carbon_parent.put_in_active_hand(grabs[1]) || !carbon_parent.put_in_inactive_hand(grabs[2]))
		qdel(src)
		return
	for(var/obj/item/clinging_grab/grab as anything in grabs)
		RegisterSignal(grab, COMSIG_PARENT_QDELETING, .proc/qdel_void)
		RegisterSignal(grab, COMSIG_PARENT_EXAMINE, .proc/grab_examine)
		RegisterSignal(grab, COMSIG_MOUSEDROP_ONTO, .proc/grab_mousedrop_onto)
	SEND_SIGNAL(carbon_parent, COMSIG_FIXEYE_DISABLE, TRUE, TRUE)
	RegisterSignal(carbon_parent, COMSIG_ATOM_PRE_DIR_CHANGE, .proc/deny_dir_change)
	RegisterSignal(carbon_parent, COMSIG_MOUSEDROP_ONTO, .proc/carbon_mousedrop_onto)
	RegisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED, .proc/parent_moved)
	ADD_TRAIT(carbon_parent, TRAIT_FORCED_STANDING, CLINGING_TRAIT)
	ADD_TRAIT(carbon_parent, TRAIT_IMMOBILIZED, CLINGING_TRAIT)
	ADD_TRAIT(carbon_parent, TRAIT_NO_FLOATING_ANIM, CLINGING_TRAIT)
	ADD_TRAIT(carbon_parent, TRAIT_MOVE_FLOATING, CLINGING_TRAIT)
	RegisterClinging()

/datum/component/clinging/Destroy(force, silent)
	UnregisterClinging()
	clinging_to = null
	for(var/obj/item/clinging_grab/grab as anything in grabs)
		UnregisterSignal(grab, COMSIG_PARENT_QDELETING)
		UnregisterSignal(grab, COMSIG_PARENT_EXAMINE)
		UnregisterSignal(grab, COMSIG_MOUSEDROP_ONTO)
		if(!QDELETED(grab))
			qdel(grab)
	grabs.Cut()
	if(parent)
		UnregisterSignal(parent, COMSIG_CLICK)
		UnregisterSignal(parent, COMSIG_ATOM_DIR_CHANGE)
		UnregisterSignal(parent, COMSIG_MOUSEDROP_ONTO)
		UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
		REMOVE_TRAIT(parent, TRAIT_FORCED_STANDING, CLINGING_TRAIT)
		REMOVE_TRAIT(parent, TRAIT_IMMOBILIZED, CLINGING_TRAIT)
		REMOVE_TRAIT(parent, TRAIT_MOVE_FLOATING, CLINGING_TRAIT)
		REMOVE_TRAIT(parent, TRAIT_NO_FLOATING_ANIM, CLINGING_TRAIT)
		var/mob/living/carbon/carbon_parent = parent
		var/turf/parent_turf = get_turf(carbon_parent)
		if(carbon_parent.can_zFall(parent_turf))
			parent_turf.zFall(carbon_parent)
	return ..()

/datum/component/clinging/proc/RegisterClinging()
	if(!clinging_to)
		return
	RegisterSignal(clinging_to, COMSIG_PARENT_QDELETING, .proc/qdel_void)

/datum/component/clinging/proc/UnregisterClinging()
	if(!clinging_to)
		return
	UnregisterSignal(clinging_to, COMSIG_PARENT_QDELETING)
	UnregisterSignal(clinging_to, COMSIG_CLICK)

/datum/component/clinging/proc/carbon_mousedrop_onto(mob/living/carbon/source, atom/over, mob/living/carbon/user)
	SIGNAL_HANDLER

	if(user != parent)
		return
	if(over == source)
		return
	if(DOING_INTERACTION_WITH_TARGET(user, over) || DOING_INTERACTION_WITH_TARGET(user, clinging_to))
		return COMPONENT_NO_MOUSEDROP
	var/turf/below_turf = SSmapping.get_turf_below(get_turf(user))
	//We're trying to move to an openspace adjacent to us
	if((over.z != user.z) && (over != below_turf))
		over = locate(over.x, over.y, user.z)
	//User to clinging = Go up
	if((clinging_to == over) || (get_turf(clinging_to) == over))
		. = COMPONENT_NO_MOUSEDROP
		INVOKE_ASYNC(src, .proc/try_going_up)
	//User to turf below user's turf = Go down
	else if(below_turf == over)
		. = COMPONENT_NO_MOUSEDROP
		INVOKE_ASYNC(src, .proc/try_going_down)
	//User to turf adjacent to user and clinger = Move to turf
	else if(isturf(over) && over.Adjacent(user) && over.Adjacent(clinging_to))
		. = COMPONENT_NO_MOUSEDROP
		INVOKE_ASYNC(src, .proc/try_going_sideways, over)

/datum/component/clinging/proc/grab_mousedrop_onto(atom/source, atom/over, mob/living/carbon/user)
	SIGNAL_HANDLER

	if(user != parent)
		return
	if(over == source)
		return
	if(DOING_INTERACTION_WITH_TARGET(user, over) || DOING_INTERACTION_WITH_TARGET(user, clinging_to))
		return COMPONENT_NO_MOUSEDROP
	var/turf/below_turf = SSmapping.get_turf_below(get_turf(user))
	//Grab to turf below user = Go down
	if((get_turf(user) == over) || (below_turf == over))
		. = COMPONENT_NO_MOUSEDROP
		INVOKE_ASYNC(src, .proc/try_going_down)
	//Grab to atom adjacent to user = Cling to the new atom
	else if(over.Adjacent(user))
		. = COMPONENT_NO_MOUSEDROP
		INVOKE_ASYNC(src, .proc/try_clinging_to, over)

/datum/component/clinging/proc/try_clinging_to(atom/over)
	var/mob/living/carbon/carbon_parent = parent
	if(SEND_SIGNAL(over, COMSIG_CLINGABLE_CHECK, carbon_parent))
		UnregisterClinging()
		UnregisterSignal(carbon_parent, COMSIG_ATOM_DIR_CHANGE)
		carbon_parent.face_atom(over)
		clinging_to = over
		RegisterClinging()
		RegisterSignal(carbon_parent, COMSIG_ATOM_DIR_CHANGE, .proc/deny_dir_change)
		to_chat(carbon_parent, span_notice("I cling onto [over]."))
	else
		to_chat(carbon_parent, span_notice("I can't cling to that."))

/datum/component/clinging/proc/try_going_sideways(atom/over)
	var/mob/living/carbon/carbon_parent = parent
	var/time = max(0, (35 - (GET_MOB_ATTRIBUTE_VALUE(carbon_parent, STAT_DEXTERITY) + GET_MOB_ATTRIBUTE_VALUE(carbon_parent, SKILL_CLIMBING)))/2)
	cling_valid = TRUE
	RegisterSignal(clinging_to, COMSIG_CLICK, .proc/cancel_cling)
	if(!do_after(carbon_parent, time, clinging_to, extra_checks = CALLBACK(src, .proc/did_not_cancel_cling)))
		UnregisterSignal(clinging_to, COMSIG_CLICK)
		to_chat(carbon_parent, span_warning("[fail_string(TRUE)]."))
		return
	UnregisterSignal(clinging_to, COMSIG_CLICK)
	UnregisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED)
	var/turf/over_turf = get_turf(over)
	var/dir = get_dir(carbon_parent, over_turf)
	if(carbon_parent.Move(over_turf, dir))
		to_chat(carbon_parent, span_notice("I shuffle myself to [over_turf]."))
	else
		to_chat(carbon_parent, span_warning("[fail_string(TRUE)]."))
	RegisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED, .proc/parent_moved)

/datum/component/clinging/proc/try_going_up()
	var/mob/living/carbon/carbon_parent = parent
	var/dir = get_dir(carbon_parent, clinging_to)
	var/turf/ceiling = get_step_multiz(carbon_parent, UP)
	var/atom/new_clinger
	if(istype(ceiling))
		new_clinger = get_step(ceiling, dir)
	else
		to_chat(carbon_parent, span_warning("I am already as high as i can go."))
		return
	if(!istype(new_clinger))
		new_clinger = null
	else if(!SEND_SIGNAL(new_clinger, COMSIG_CLINGABLE_CHECK, carbon_parent))
		//Turf is not clingable, but there could be something to grab onto in it
		var/turf/old_clinger = new_clinger
		new_clinger = null
		for(var/atom/clingable in old_clinger)
			if(SEND_SIGNAL(new_clinger, COMSIG_CLINGABLE_CHECK, carbon_parent))
				new_clinger = clingable
				break
	//We can't get there anyways
	if(!carbon_parent.canZMove(UP, ceiling))
		to_chat(carbon_parent, span_warning("I can't go up."))
		return
	if(!istype(new_clinger) || !(SEND_SIGNAL(new_clinger, COMSIG_CLINGABLE_CHECK, carbon_parent)))
		to_chat(carbon_parent, span_warning("I have nothing to latch onto above me."))
		return
	var/time = max(0, 50 - (GET_MOB_ATTRIBUTE_VALUE(carbon_parent, STAT_DEXTERITY) + GET_MOB_ATTRIBUTE_VALUE(carbon_parent, SKILL_CLIMBING)))
	cling_valid = TRUE
	RegisterSignal(clinging_to, COMSIG_CLICK, .proc/cancel_cling)
	if(!do_after(carbon_parent, time, clinging_to, extra_checks = CALLBACK(src, .proc/did_not_cancel_cling)))
		UnregisterSignal(clinging_to, COMSIG_CLICK)
		to_chat(span_warning("[fail_string(TRUE)]."))
		return
	UnregisterSignal(clinging_to, COMSIG_CLICK)
	var/turf/landing_spot
	if(new_clinger)
		landing_spot = get_turf(new_clinger)
	//don't move to open spaces lmao
	if(istype(landing_spot, /turf/open/openspace))
		landing_spot = null
	UnregisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED)
	if(!carbon_parent.zMove(UP, TRUE))
		RegisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED, .proc/parent_moved)
		return
	RegisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED, .proc/parent_moved)
	UnregisterClinging()
	clinging_to = new_clinger
	//(Probably) Open turf, try to move to it
	if(landing_spot && landing_spot.Adjacent(carbon_parent) && carbon_parent.Move(landing_spot, dir))
		carbon_parent.Move(clinging_to, dir)
		to_chat(carbon_parent, span_notice("I climb onto [clinging_to]."))
		qdel(src)
		return
	//Climg to (probably) closed turf instead
	else if(new_clinger?.Adjacent(carbon_parent))
		to_chat(carbon_parent, span_notice("I cling onto [clinging_to]."))
		RegisterClinging()
		return

/datum/component/clinging/proc/try_going_down()
	var/mob/living/carbon/carbon_parent = parent
	var/dir = get_dir(carbon_parent, clinging_to)
	var/turf/floor = get_step_multiz(carbon_parent, DOWN)
	var/atom/new_clinger
	if(istype(floor))
		new_clinger = get_step(floor, dir)
	else
		to_chat(carbon_parent, span_warning("I am already as low as i can go."))
		return
	if(!istype(new_clinger))
		new_clinger = null
	else if(!SEND_SIGNAL(new_clinger, COMSIG_CLINGABLE_CHECK, carbon_parent))
		//Turf is not clingable, but there could be something to grab onto in it
		var/turf/old_clinger = new_clinger
		new_clinger = null
		for(var/atom/clingable in old_clinger)
			if(SEND_SIGNAL(new_clinger, COMSIG_CLINGABLE_CHECK, carbon_parent))
				new_clinger = clingable
				break
	//We can't get there anyways
	if(!carbon_parent.canZMove(DOWN, floor))
		to_chat(carbon_parent, span_warning("I can't go down."))
		return
	if(!istype(new_clinger) || !(SEND_SIGNAL(new_clinger, COMSIG_CLINGABLE_CHECK, carbon_parent)))
		to_chat(carbon_parent, span_warning("I don't have much to hold onto..."))
	var/time = max(0, 45 - (GET_MOB_ATTRIBUTE_VALUE(carbon_parent, STAT_DEXTERITY) + GET_MOB_ATTRIBUTE_VALUE(carbon_parent, SKILL_CLIMBING)))
	cling_valid = TRUE
	RegisterSignal(clinging_to, COMSIG_CLICK, .proc/cancel_cling)
	if(!do_after(carbon_parent, time, clinging_to, extra_checks = CALLBACK(src, .proc/did_not_cancel_cling)))
		UnregisterSignal(clinging_to, COMSIG_CLICK)
		to_chat(span_warning("[fail_string(TRUE)]."))
		return
	var/turf/landing_spot
	//Remove floating trait temporarily to handle zfalling proper, if we aren't using a new clinger
	if(!new_clinger)
		REMOVE_TRAIT(parent, TRAIT_MOVE_FLOATING, CLINGING_TRAIT)
		REMOVE_TRAIT(parent, TRAIT_NO_FLOATING_ANIM, CLINGING_TRAIT)
	else
		landing_spot = get_turf(new_clinger)
	//Don't go on open spaces lmao
	if(istype(landing_spot, /turf/open/openspace))
		landing_spot = null
	//This proc will already do z fall logic if necessary
	UnregisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED)
	if(!carbon_parent.zMove(DOWN, TRUE))
		RegisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED, .proc/parent_moved)
		return
	RegisterSignal(carbon_parent, COMSIG_MOVABLE_MOVED, .proc/parent_moved)
	UnregisterClinging()
	clinging_to = new_clinger
	//(Probably) Open turf, try to move to it
	if(landing_spot && landing_spot.Adjacent(carbon_parent) && carbon_parent.Move(landing_spot, dir))
		to_chat(carbon_parent, span_notice("I land onto [landing_spot]."))
		qdel(src)
		return
	//Cling instead
	else if(new_clinger?.Adjacent(carbon_parent))
		to_chat(carbon_parent, span_notice("I cling onto [clinging_to]."))
		RegisterClinging()
		return

/datum/component/clinging/proc/cancel_cling()
	SIGNAL_HANDLER

	cling_valid = FALSE

/datum/component/clinging/proc/did_not_cancel_cling()
	return cling_valid

/datum/component/clinging/proc/deny_dir_change()
	SIGNAL_HANDLER

	. = COMPONENT_DENY_DIR_CHANGE

/datum/component/clinging/proc/qdel_void()
	SIGNAL_HANDLER

	qdel(src)

/datum/component/clinging/proc/parent_moved(atom/movable/mover, atom/oldloc, direction)
	SIGNAL_HANDLER

	to_chat(parent, span_warning("[fail_string(TRUE)]!"))
	qdel(src)

/datum/component/clinging/proc/grab_examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER

	examine_list += span_notice("Currently clinging to [clinging_to].")

/obj/item/clinging_grab
	name = "grab"
	icon = 'modular_septic/icons/hud/quake/grab.dmi'
	icon_state = "grab"
	base_icon_state = "grab"
	item_flags = DROPDEL | NOBLUDGEON | ABSTRACT | HAND_ITEM

/obj/item/clinging_grab/apply_outline(outline_color)
	return


code:
/datum/component/creamed
	var/cover_lips = "cream"

/datum/component/creamed/Initialize()
	if(!is_type_in_typecache(parent, return_creamable_list()))
		return COMPONENT_INCOMPATIBLE
	cream()

/datum/component/creamed/proc/return_creamable_list()
	return GLOB.creamable

/datum/component/creamed/proc/cream()
	SEND_SIGNAL(parent, COMSIG_MOB_CREAMED, type)
	creamface = mutable_appearance('icons/effects/creampie.dmi')
	if(ishuman(parent))
		var/mob/living/carbon/human/humie = parent
		if(LAZYACCESS(humie.dna.species.mutant_bodyparts, "snout"))
			creamface.icon_state = "creampie_lizard"
		else if(ismonkey(humie))
			creamface.icon_state = "creampie_monkey"
		else
			creamface.icon_state = "creampie_human"
		SEND_SIGNAL(humie, COMSIG_ADD_MOOD_EVENT, "creampie", /datum/mood_event/creampie)
	else if(iscorgi(parent))
		creamface.icon_state = "creampie_corgi"
	else if(isAI(parent))
		creamface.icon_state = "creampie_ai"

	var/atom/A = parent
	A.add_overlay(creamface)


code:
/datum/component/embedded
	var/datum/injury/injury

/datum/component/embedded/Initialize(obj/item/I,
			datum/thrownthing/throwingdatum,
			obj/item/bodypart/part,
			embed_chance = EMBED_CHANCE,
			fall_chance = EMBEDDED_ITEM_FALLOUT,
			pain_chance = EMBEDDED_PAIN_CHANCE,
			pain_mult = EMBEDDED_PAIN_MULTIPLIER,
			remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
			impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER,
			rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
			ignore_throwspeed_threshold = FALSE,
			jostle_chance = EMBEDDED_JOSTLE_CHANCE,
			jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER,
			pain_stam_pct = EMBEDDED_PAIN_STAM_PCT,
			datum/injury/supplied_injury,
			)
	if(!iscarbon(parent) || !isitem(I))
		return COMPONENT_INCOMPATIBLE

	if(part)
		src.limb = part
	src.embed_chance = embed_chance
	src.fall_chance = fall_chance
	src.pain_chance = pain_chance
	src.pain_mult = pain_mult
	src.remove_pain_mult = remove_pain_mult
	src.rip_time = rip_time
	src.impact_pain_mult = impact_pain_mult
	src.ignore_throwspeed_threshold = ignore_throwspeed_threshold
	src.jostle_chance = jostle_chance
	src.jostle_pain_mult = jostle_pain_mult
	src.pain_stam_pct = pain_stam_pct
	src.weapon = I
	if(supplied_injury)
		src.injury = supplied_injury
		LAZYADD(supplied_injury.embedded_objects, I)
		LAZYADD(supplied_injury.embedded_components, src)

	if(!weapon.isEmbedHarmless())
		harmful = TRUE

	weapon.embedded(parent, part)
	START_PROCESSING(SSdcs, src)
	var/mob/living/carbon/victim = parent
	LAZYADD(limb.embedded_objects, weapon)
	weapon.forceMove(victim)
	RegisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), .proc/weaponDeleted)
	victim.visible_message(span_danger("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] [victim]'s [limb.name]!"), \
						span_userdanger("[weapon] [harmful ? "embeds" : "sticks"] itself [harmful ? "in" : "to"] my [limb.name]!"))

	var/damage = weapon.throwforce
	if(harmful)
		weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody!
		damage += weapon.w_class * impact_pain_mult
		SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)

	if(damage > 0)
		var/armor = victim.run_armor_check(limb.body_zone, MELEE, "My armor has protected my [limb.name].", \
								"My armor has softened a hit to my [limb.name].", \
								I.armour_penetration, \
								weak_against_armour = I.weak_against_armour)
		if(!injury) //we hopefully dealt the initial damage by creating the supplied injury, no need to make the situation worse
			limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, blocked=armor, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness())

/datum/component/embedded/Destroy()
	var/obj/item/bodypart/old_limb =  limb
	if(injury)
		LAZYREMOVE(injury.embedded_components, src)
		LAZYREMOVE(injury.embedded_objects, weapon)
	injury = null
	. = ..()
	for(var/obj/item/grab/grab as anything in old_limb.grasped_by)
		grab.update_grab_mode()

/datum/component/embedded/ripOut(datum/source, obj/item/I, obj/item/bodypart/limb, mob/living/user)
	if(I != weapon || src.limb != limb)
		return
	INVOKE_ASYNC(src, .proc/complete_rip_out, source, I, limb, user)

/datum/component/embedded/complete_rip_out(mob/living/carbon/victim, obj/item/I, obj/item/bodypart/limb, mob/living/remover)
	var/time_taken = rip_time * weapon.w_class * (victim != remover ? 0.5 : 1)
	if(remover == victim)
		remover.visible_message(span_warning("[remover] attempts to remove [weapon] from [remover.p_their()] [limb.name]."), \
				span_userdanger("I attempt to remove \the [weapon] from my [limb.name]... (It will take [DisplayTimeText(time_taken)].)"))
	else
		remover.visible_message(span_warning("[remover] attempts to remove \the [weapon] from [victim]'s [limb.name]."), \
				span_userdanger("[remover] attempts to remove \the [weapon] from my [limb.name]!"), \
				ignored_mobs = remover)
		to_chat(remover, span_userdanger("I attempt to remove \the [weapon] from [victim]'s [limb.name]... (It will take [DisplayTimeText(time_taken)].)"))
	if(!do_mob(user = remover, target = victim, time = time_taken))
		return
	if(!weapon || !limb || weapon.loc != victim || !(weapon in limb.embedded_objects))
		qdel(src)
		return
	if(harmful)
		var/damage = weapon.w_class * remove_pain_mult
		if(injury)
			injury.open_injury(damage)
			limb.receive_damage(stamina=pain_stam_pct * damage)
		else
			limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, sharpness=SHARP_EDGED) //It hurts to rip it out, get surgery you dingus. unlike the others, this CAN wound + increase slash bloodflow
		victim.agony_scream()
	if(remover == victim)
		victim.visible_message(span_notice("[remover] successfully rips [weapon] [harmful ? "out" : "off"] of [remover.p_their()] [limb.name]!"), \
				span_userdanger("You successfully remove [weapon] from your [limb.name]."))
	else
		victim.visible_message(span_notice("[remover] successfully rips [weapon] [harmful ? "out" : "off"] of [limb.owner]'s [limb.name]!"), \
				span_userdanger("[remover] successfully removes \the [weapon] from my [limb.name]."), \
				ignored_mobs = remover)
		to_chat(remover, span_notice("I succesfully remove \the [weapon] from [victim]'s [limb.name]!"))
	safeRemove(remover)

/datum/component/embedded/safeRemove(mob/to_hands)
	var/mob/living/carbon/victim = parent
	LAZYREMOVE(limb.embedded_objects, weapon)
	if(injury)
		LAZYREMOVE(injury.embedded_objects, weapon)
		LAZYREMOVE(injury.embedded_components, src)
	UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) // have to do it here otherwise we trigger weaponDeleted()
	if(!weapon.unembedded(victim, limb))
		if(to_hands)
			INVOKE_ASYNC(to_hands, /mob.proc/put_in_hands, weapon)
		else
			weapon.forceMove(get_turf(victim))
	qdel(src)


code:
#define CENTERED_RENDER_SOURCE(img, atom, FoV) \
	atom.render_target = atom.render_target || ref(atom);\
	img.render_source = atom.render_target;\
	if(atom.icon){\
		var/_cached_sizes = FoV.width_n_height_offsets[atom.icon];\
		if(!_cached_sizes){\
			var/icon/_I = icon(atom.icon);\
			var/list/L = list();\
			L += (_I.Width() - world.icon_size)/2;\
			L += (_I.Height() - world.icon_size)/2;\
			_cached_sizes = FoV.width_n_height_offsets[atom.icon] = L\
		}\
		img.pixel_x = _cached_sizes[1];\
		img.pixel_y = _cached_sizes[2];\
		img.loc = atom\
	}

#define REGISTER_NESTED_LOCS(source, list, comsig, proc) \
	for(var/k in get_nested_locs(source)){\
		var/atom/_A = k;\
		RegisterSignal(_A, comsig, proc, override = TRUE);\
		list += _A\
	}

#define UNREGISTER_NESTED_LOCS(list, comsig, index) \
	for(var/k in list){\
		var/atom/_A = k;\
		UnregisterSignal(_A, comsig);\
		list -= _A\
	}

/**
  * Field of Vision component. Does totally what you probably think it does,
  * ergo preventing players from seeing what's behind them.
  */
/datum/component/field_of_vision
	can_transfer = TRUE
	/// Our screen object
	var/atom/movable/screen/fov_holder/fov_holder
	/// The current screen size this field of vision is meant to fit for.
	var/current_fov_size = list(15, 15)
	/// How much is the cone rotated clockwise, purely backend. Please use rotate_shadow_cone() if you must.
	var/angle = 0
	/// Used to scale the shadow cone when rotating it to fit over the edges of the screen.
	var/rot_scale = 1
	/// The inner angle of this cone, right hardset to 90, 180, or 270 degrees, until someone figures out a way to make it dynamic.
	var/shadow_angle = FOV_90_DEGREES
	/// The mask portion of the cone, placed on a * render target plane so while not visible it still applies the filter.
	var/image/shadow_mask
	/// The visual portion of the cone, placed on the highest layer of the wall plane
	var/image/visual_shadow
/**
  * An image whose render_source is kept up to date to prevent the mob (or the topmost movable holding it) from being hidden by the mask.
  * Will make it use vis_contents instead once a few byonds bugs with images and vis contents are fixed.
  */
	var/image/owner_mask
/**
  * A circle image used to somewhat uncover the adjacent portion of the shadow cone, making mobs and objects behind us somewhat visible.
  * The owner mask is still required for those mob going over the default 32x32 px size btw.
  */
	var/image/adj_mask
	/// A list of nested locations the mob is in, to ensure the above image works correctly.
	var/list/nested_locs = list()
/**
  * A static list of offsets based on icon width and height, because render sources are centered unlike most other visuals,
  * and that gives us some problems when the icon is larger or smaller than world.icon_size
  */
	var/static/list/width_n_height_offsets = list()
	var/has_adj_mask = FALSE

/datum/component/field_of_vision/Initialize(fov_type = FOV_90_DEGREES, _angle = 0, _has_adj_mask = FALSE)
	if(!isliving(parent))
		return COMPONENT_INCOMPATIBLE
	angle = _angle
	has_adj_mask = _has_adj_mask
	shadow_angle = fov_type

/datum/component/field_of_vision/RegisterWithParent()
	. = ..()
	var/mob/living/source = parent
	if(source.client)
		generate_fov_holder(source, angle, shadow_angle, register = TRUE)
	RegisterSignal(source, COMSIG_MOB_CLIENT_LOGIN, .proc/on_mob_login)
	RegisterSignal(source, COMSIG_MOB_LOGOUT, .proc/on_mob_logout)
	RegisterSignal(source, COMSIG_MOB_GET_VISIBLE_MESSAGE, .proc/on_visible_message)
	RegisterSignal(source, COMSIG_MOB_EXAMINATE, .proc/on_examinate)
	RegisterSignal(source, COMSIG_MOB_FOV_VIEW, .proc/on_fov_view)
	RegisterSignal(source, COMSIG_MOB_CLIENT_CHANGE_VIEW, .proc/on_change_view)
	RegisterSignal(source, COMSIG_MOB_RESET_PERSPECTIVE, .proc/on_reset_perspective)
	RegisterSignal(source, COMSIG_MOB_FOV_VIEWER, .proc/is_fov_viewer)

/datum/component/field_of_vision/UnregisterFromParent()
	. = ..()
	var/mob/living/source = parent
	if(!QDELETED(fov_holder))
		source.hud_used?.fov_holder = null
		source.client?.screen -= fov_holder
		qdel(fov_holder, TRUE) // Forced.
		fov_holder = null
		source.client?.images -= owner_mask
		source.client?.images -= adj_mask
		QDEL_NULL(owner_mask)
		QDEL_NULL(adj_mask)
	UnregisterSignal(source, COMSIG_FOV_HIDE)
	UnregisterSignal(source, COMSIG_FOV_SHOW)
	UnregisterSignal(source, COMSIG_LIVING_SET_BODY_POSITION)
	UnregisterSignal(source, COMSIG_MOVABLE_MOVED)
	UnregisterSignal(source, COMSIG_ATOM_POST_DIR_CHANGE)
	UnregisterSignal(source, COMSIG_ATOM_UPDATE_APPEARANCE)
	if(length(nested_locs))
		UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)

/**
  * Generates the holder and images (if not generated yet) and adds them to client.images.
  * Run when the component is registered to a player mob, or upon login.
  */
/datum/component/field_of_vision/proc/generate_fov_holder(mob/living/source, _angle = 0, _shadow_angle = FOV_90_DEGREES, register = TRUE, delete_holder = FALSE)
	if(delete_holder)
		current_fov_size = list(15, 15)
		source.client?.screen -= fov_holder
		QDEL_NULL(source.hud_used?.fov_holder)
		QDEL_NULL(fov_holder)
	if(_angle)
		angle = _angle
	if(_shadow_angle)
		shadow_angle = _shadow_angle
	if(QDELETED(fov_holder))
		fov_holder = new()
		fov_holder.hud = source.hud_used
		fov_holder.dir = source.dir
		fov_holder.screen_loc = ui_fov
		shadow_mask = image('modular_septic/icons/hud/fov_15x15.dmi', fov_holder, "[shadow_angle]", FIELD_OF_VISION_VISUAL_LAYER)
		shadow_mask.plane = FIELD_OF_VISION_MASK_PLANE
		fov_holder.add_overlay(shadow_mask)
		visual_shadow = image('modular_septic/icons/hud/fov_15x15.dmi', fov_holder, "[shadow_angle]_v", FIELD_OF_VISION_VISUAL_LAYER)
		visual_shadow.plane = FIELD_OF_VISION_VISUAL_PLANE
		fov_holder.add_overlay(visual_shadow)
		owner_mask = new()
		owner_mask.appearance_flags = RESET_TRANSFORM
		owner_mask.plane = FIELD_OF_VISION_BLOCKER_PLANE
		if(has_adj_mask)
			adj_mask = image('modular_septic/icons/hud/fov_15x15.dmi', fov_holder, "adj_mask", FIELD_OF_VISION_VISUAL_LAYER)
			adj_mask.appearance_flags = RESET_TRANSFORM
			adj_mask.plane = FIELD_OF_VISION_BLOCKER_PLANE
	if(shadow_angle != _shadow_angle)
		shadow_angle = _shadow_angle
	if(angle != _angle)
		rotate_shadow_cone(_angle)
	fov_holder.alpha = (source.body_position == LYING_DOWN ? 0 : 255)
	fov_holder.icon_state = "[shadow_angle]"
	if(register)
		RegisterSignal(source, COMSIG_FOV_HIDE, .proc/hide_fov, override = TRUE)
		RegisterSignal(source, COMSIG_FOV_SHOW, .proc/show_fov, override = TRUE)
		RegisterSignal(source, COMSIG_LIVING_SET_BODY_POSITION, .proc/update_body_position, override = TRUE)
		RegisterSignal(source, COMSIG_MOVABLE_MOVED, .proc/on_mob_moved, override = TRUE)
		RegisterSignal(source, COMSIG_ATOM_POST_DIR_CHANGE, .proc/on_dir_change, override = TRUE)
		if(iscyborg(source))
			RegisterSignal(source, COMSIG_ATOM_UPDATE_APPEARANCE, .proc/manual_centered_render_source, override = TRUE)
	var/atom/A = source
	if(source.loc && !isturf(source.loc) && register)
		REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
		A = nested_locs[nested_locs.len]
	CENTERED_RENDER_SOURCE(owner_mask, A, src)
	if(owner_mask)
		source.client?.images += owner_mask
	if(adj_mask)
		source.client?.images += adj_mask
	source.hud_used?.fov_holder = fov_holder
	source.hud_used?.show_hud(HUD_STYLE_STANDARD)
	if(source.client?.view && (source.client.view != "[current_fov_size[1]]x[current_fov_size[2]]") && fov_holder)
		//this is beyond fucking stupid i hate myself
		var/list/shit = getviewsize(source.client.view)
		resize_fov(current_fov_size, shit)

///Rotates the shadow cone to a certain degree. Backend shenanigans.
/datum/component/field_of_vision/proc/rotate_shadow_cone(new_angle)
	var/simple_degrees = SIMPLIFY_DEGREES(new_angle - angle)
	var/to_scale = cos(simple_degrees) * sin(simple_degrees)
	if(to_scale)
		var/old_rot_scale = rot_scale
		rot_scale = 1 + to_scale
		if(old_rot_scale != rot_scale)
			fov_holder.transform = visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Scale(rot_scale/old_rot_scale)
	fov_holder.transform = visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Turn(fov_holder.transform, simple_degrees)

/**
  * Resizes the shadow to match the current screen size.
  * Run when the client view size is changed, or if the player has a viewsize different than "15x15" on login/comp registration.
  */
/datum/component/field_of_vision/proc/resize_fov(list/old_view, list/new_view)
	//This is shit code, but scaling fucks up at 22x16 view, so we use 21x15 scaling
	if(new_view ~= list(22,16))
		new_view = list(21, 15)
	//Edges are still of the same length.
	if(old_view == new_view)
		return
	if(!fov_holder)
		return
	current_fov_size = new_view
	var/scale_x = (new_view[1]/old_view[1])
	var/scale_y = (new_view[2]/old_view[2])
	fov_holder.transform = fov_holder.transform.Scale(scale_x, scale_y)

/datum/component/field_of_vision/proc/on_mob_login(mob/living/source, client/client)
	SIGNAL_HANDLER
	generate_fov_holder(source, angle, delete_holder = TRUE)

/datum/component/field_of_vision/proc/on_mob_logout(mob/living/source)
	SIGNAL_HANDLER
	UnregisterSignal(source, COMSIG_FOV_HIDE)
	UnregisterSignal(source, COMSIG_FOV_SHOW)
	UnregisterSignal(source, COMSIG_LIVING_SET_BODY_POSITION)
	UnregisterSignal(source, COMSIG_MOVABLE_MOVED)
	UnregisterSignal(source, COMSIG_ATOM_POST_DIR_CHANGE)
	UnregisterSignal(source, COMSIG_ATOM_UPDATE_APPEARANCE)
	if(length(nested_locs))
		UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)

/datum/component/field_of_vision/proc/on_dir_change(mob/living/source, old_dir, new_dir)
	SIGNAL_HANDLER
	fov_holder.dir = new_dir

//Updates the alpha depending on whether or not we are lying
/datum/component/field_of_vision/proc/update_body_position(mob/living/source, new_value)
	SIGNAL_HANDLER
	if(source.body_position == LYING_DOWN)
		hide_fov(source)
	else
		show_fov(source)

///Hides the shadow, other visibility comsig procs will take it into account. Called when the mob dies.
/datum/component/field_of_vision/proc/hide_fov(mob/living/source)
	SIGNAL_HANDLER
	fov_holder?.alpha = 0
	owner_mask?.alpha = 0

/// Shows the shadow. Called when the mob is revived.
/datum/component/field_of_vision/proc/show_fov(mob/living/source)
	SIGNAL_HANDLER
	fov_holder?.alpha = 255
	owner_mask?.alpha = 255

/// Hides the shadow when looking through other items, shows it otherwise.
/datum/component/field_of_vision/proc/on_reset_perspective(mob/living/source, atom/target)
	SIGNAL_HANDLER
	if(source.client.eye == source || source.client.eye == source.loc)
		fov_holder?.alpha = 255
	else
		fov_holder?.alpha = 0

/// Called when the client view size is changed.
/datum/component/field_of_vision/proc/on_change_view(mob/living/source, client, list/old_view, list/view)
	SIGNAL_HANDLER
	if(fov_holder)
		resize_fov(old_view, view)

/**
  * Called when the owner mob moves around. Used to keep shadow located right behind us,
  * As well as modify the owner mask to match the topmost item.
  */
/datum/component/field_of_vision/proc/on_mob_moved(mob/living/source, atom/oldloc, dir, forced)
	SIGNAL_HANDLER
	if(!isturf(source.loc)) //Recalculate all nested locations.
		UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
		REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
		var/atom/movable/screen/topmost = nested_locs[LAZYLEN(nested_locs)]
		CENTERED_RENDER_SOURCE(owner_mask, topmost, src)
	else
		if(length(nested_locs))
			UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
			CENTERED_RENDER_SOURCE(owner_mask, source, src)

/// Pretty much like the above, but meant for other movables the mob is stored in (bodybags, boxes, mechs etc).
/datum/component/field_of_vision/proc/on_loc_moved(atom/movable/source, atom/oldloc, dir, forced)
	SIGNAL_HANDLER
	if(isturf(source.loc) && isturf(oldloc)) //This is the case of the topmost movable loc moving around the world, skip.
		return
	var/atom/movable/screen/prev_topmost = nested_locs[nested_locs.len]
	if(prev_topmost != source)
		UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, nested_locs.Find(source) + 1)
	REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
	var/atom/movable/screen/topmost = nested_locs[nested_locs.len]
	if(topmost != prev_topmost)
		CENTERED_RENDER_SOURCE(owner_mask, topmost, src)

/// A hacky comsig proc for things that somehow decide to change icon on the go. may make a change_icon_file() proc later but...
/datum/component/field_of_vision/proc/manual_centered_render_source(mob/living/source, old_icon)
	SIGNAL_HANDLER
	if(!isturf(source.loc))
		return
	CENTERED_RENDER_SOURCE(owner_mask, source, src)

#undef CENTERED_RENDER_SOURCE
#undef REGISTER_NESTED_LOCS
#undef UNREGISTER_NESTED_LOCS

/**
  * Byond doc is not entirely correct on the integrated arctan() proc.
  * When both x and y are negative, the output is also negative, cycling clockwise instead of counter-clockwise.
  * That's also why I am extensively using the SIMPLIFY_DEGREES macro here.
  *
  * Overall this is the main macro that calculates wheter a target is within the shadow cone angle or not.
  */
#define FOV_ANGLE_CHECK(mob, target, zero_x_y_statement, success_statement) \
	var/turf/T1 = get_turf(target);\
	var/turf/T2 = get_turf(mob);\
	if(!T1 || !T2){\
		zero_x_y_statement\
	}\
	var/_x = (T1.x - T2.x);\
	var/_y = (T1.y - T2.y);\
	if(ISINRANGE(_x, -1, 1) && ISINRANGE(_y, -1, 1)){\
		zero_x_y_statement\
	}\
	var/dir = (mob.dir & (EAST|WEST)) || mob.dir;\
	var/_degree = -angle;\
	var/real_shadow_angle = shadow_angle;\
	if(findtext(real_shadow_angle, "_")){\
		real_shadow_angle = copytext(real_shadow_angle, findtext(real_shadow_angle, "_") - 1);\
	}\
	var/_half = text2num(real_shadow_angle)/2;\
	switch(dir){\
		if(EAST){\
			_degree += 180;\
		}\
		if(NORTH){\
			_degree += 270;\
		}\
		if(SOUTH){\
			_degree += 90;\
		}\
	}\
	var/_min = SIMPLIFY_DEGREES(_degree - _half);\
	var/_max = SIMPLIFY_DEGREES(_degree + _half);\
	if((_min > _max) ? !ISINRANGE(SIMPLIFY_DEGREES(arctan(_x, _y)), _max, _min) : ISINRANGE(SIMPLIFY_DEGREES(arctan(_x, _y)), _min, _max)){\
		success_statement;\
	}

/datum/component/field_of_vision/proc/on_examinate(mob/living/source, atom/target)
	SIGNAL_HANDLER
	if(fov_holder?.alpha)
		FOV_ANGLE_CHECK(source, target, return, return COMPONENT_EXAMINATE_BLIND)

/datum/component/field_of_vision/proc/on_visible_message(mob/living/source, atom/target, message, range, list/ignored_mobs)
	SIGNAL_HANDLER
	if(fov_holder?.alpha)
		FOV_ANGLE_CHECK(source, target, return, return COMPONENT_VISIBLE_MESSAGE_BLIND)

/datum/component/field_of_vision/proc/on_fov_view(mob/living/source, list/atoms)
	SIGNAL_HANDLER
	if(!fov_holder?.alpha)
		return
	for(var/k in atoms)
		var/atom/A = k
		FOV_ANGLE_CHECK(source, A, continue, atoms -= A)

/datum/component/field_of_vision/proc/is_fov_viewer(mob/living/source, atom/center, depth, list/viewers_list)
	SIGNAL_HANDLER
	if(fov_holder?.alpha)
		FOV_ANGLE_CHECK(source, center, return, viewers_list -= source)

#undef FOV_ANGLE_CHECK