code:
//Separates fixeye into it's own thing instead of tacking it on combat mode
/datum/component/fixeye
	var/fixeye_flags
	var/facedir
	var/hud_loc
	var/atom/movable/screen/fixeye/hud_icon

//Does stuff
/datum/component/fixeye/Initialize(hud_loc = ui_fixeye)
	if(!isliving(parent))
		return COMPONENT_INCOMPATIBLE
	src.hud_loc = hud_loc

/datum/component/fixeye/Destroy(force, silent)
	. = ..()
	QDEL_NULL(hud_icon)

/datum/component/fixeye/RegisterWithParent()
	var/mob/living/source = parent
	RegisterSignal(source, COMSIG_FIXEYE_TOGGLE, .proc/user_toggle_fixeye)
	RegisterSignal(source, COMSIG_FIXEYE_DISABLE, .proc/safe_disable_fixeye)
	RegisterSignal(source, COMSIG_FIXEYE_ENABLE, .proc/safe_enable_fixeye)
	RegisterSignal(source, COMSIG_LIVING_DEATH, .proc/on_death)
	RegisterSignal(source, COMSIG_MOB_LOGOUT, .proc/on_logout)
	RegisterSignal(source, COMSIG_MOB_HUD_CREATED, .proc/on_mob_hud_created)
	RegisterSignal(source, COMSIG_FIXEYE_CHECK, .proc/check_flags)
	if(source.client)
		on_mob_hud_created(source)

/datum/component/fixeye/UnregisterFromParent()
	var/mob/source = parent
	UnregisterSignal(source, list(COMSIG_FIXEYE_TOGGLE, COMSIG_FIXEYE_DISABLE, COMSIG_FIXEYE_ENABLE, COMSIG_FIXEYE_CHECK))
	UnregisterSignal(source, list(COMSIG_MOB_LOGOUT, COMSIG_MOB_HUD_CREATED))
	UnregisterSignal(source, COMSIG_LIVING_DEATH)
	source.client?.screen -= hud_icon
	source.hud_used?.fixeye = null
	source.hud_used?.infodisplay -= hud_icon
	QDEL_NULL(hud_icon)

///Creates the hud screen object.
/datum/component/fixeye/proc/on_mob_hud_created(mob/source)
	SIGNAL_HANDLER
	hud_icon = new
	hud_icon.hud = source.hud_used
	hud_icon.screen_loc = hud_loc
	source.hud_used?.fixeye = hud_icon
	source.hud_used?.infodisplay += hud_icon
	hud_icon.update_appearance()
	source.client?.screen |= hud_icon

//Toggles intentionally between on and off
/datum/component/fixeye/proc/user_toggle_fixeye(mob/living/source, silent = TRUE, forced = FALSE)
	SIGNAL_HANDLER

	if(CHECK_BITFIELD(fixeye_flags, FIXEYE_TOGGLED))
		safe_disable_fixeye(source, silent, forced)
	else if(source.stat == CONSCIOUS)
		safe_enable_fixeye(source, silent, forced)

//Intentionally toggling on
/datum/component/fixeye/proc/safe_enable_fixeye(mob/living/source, silent = FALSE, forced = FALSE)
	SIGNAL_HANDLER
	if(CHECK_BITFIELD(fixeye_flags, FIXEYE_TOGGLED) && CHECK_BITFIELD(fixeye_flags, FIXEYE_ACTIVE))
		return TRUE
	fixeye_flags |= FIXEYE_TOGGLED
	enable_fixeye(source, silent, forced)
	return TRUE

//Handles toggling on itself
/datum/component/fixeye/proc/enable_fixeye(mob/living/source, silent = TRUE, forced = TRUE)
	if(CHECK_BITFIELD(fixeye_flags, FIXEYE_ACTIVE))
		return
	fixeye_flags |= FIXEYE_ACTIVE
	fixeye_flags &= ~FIXEYE_INACTIVE
	SEND_SIGNAL(source, COMSIG_LIVING_FIXEYE_ENABLED, forced)
	if(!silent)
		source.playsound_local(source, 'sound/misc/ui_togglecombat.ogg', 25, FALSE, pressure_affected = FALSE)
	facedir = source.dir
	RegisterSignal(source, COMSIG_ATOM_PRE_DIR_CHANGE, .proc/on_dir_change)
	RegisterSignal(source, COMSIG_MOB_CLIENT_MOVED, .proc/on_client_move)
	RegisterSignal(source, COMSIG_MOB_CLICKON, .proc/on_clickon)
	if(hud_icon)
		hud_icon.fixed_eye = TRUE
		hud_icon.update_appearance()

//Intentionally toggling off
/datum/component/fixeye/proc/safe_disable_fixeye(mob/living/source, silent = FALSE, forced = FALSE)
	SIGNAL_HANDLER
	if(!CHECK_BITFIELD(fixeye_flags, FIXEYE_TOGGLED) && !CHECK_BITFIELD(fixeye_flags, FIXEYE_ACTIVE))
		return TRUE
	fixeye_flags &= ~FIXEYE_TOGGLED
	disable_fixeye(source, silent, forced)
	return TRUE

//Handles toggling off itself
/datum/component/fixeye/proc/disable_fixeye(mob/living/source, silent = TRUE, forced = TRUE)
	if(!CHECK_BITFIELD(fixeye_flags, FIXEYE_ACTIVE))
		return
	fixeye_flags &= ~FIXEYE_ACTIVE
	fixeye_flags |= FIXEYE_INACTIVE
	facedir = null
	SEND_SIGNAL(source, COMSIG_LIVING_FIXEYE_DISABLED, forced)
	if(!silent)
		source.playsound_local(source, 'sound/misc/ui_toggleoffcombat.ogg', 25, FALSE, pressure_affected = FALSE)
	UnregisterSignal(source, list(COMSIG_ATOM_PRE_DIR_CHANGE, COMSIG_MOB_CLIENT_MOVED, COMSIG_MOB_CLICKON))
	if(hud_icon)
		hud_icon.fixed_eye = FALSE
		hud_icon.update_appearance()

//Returns a field of flags that are contained in both the second arg and our bitfield variable.
/datum/component/fixeye/proc/check_flags(mob/living/source, flags)
	SIGNAL_HANDLER
	return CHECK_BITFIELD(fixeye_flags, flags)

//Disables fixeye upon death.
/datum/component/fixeye/proc/on_death(mob/living/source)
	SIGNAL_HANDLER
	safe_disable_fixeye(source)

//Disables fixeye upon logout
/datum/component/fixeye/proc/on_logout(mob/living/source)
	SIGNAL_HANDLER
	safe_disable_fixeye(source)

//Added movement delay if moving backward
/datum/component/fixeye/proc/on_client_move(mob/living/source, client/client, direction, newloc, oldloc, added_delay)
	SIGNAL_HANDLER
	if(oldloc != newloc && (direction & REVERSE_DIR(source.dir)))
		client.move_delay += added_delay*0.5

//Keep that fucking face right onwards
/datum/component/fixeye/proc/on_dir_change(mob/living/source, dir, newdir)
	SIGNAL_HANDLER
	return COMPONENT_DENY_DIR_CHANGE

//Handles dir change when clicking
/datum/component/fixeye/proc/on_clickon(mob/living/source, atom/A, params)
	SIGNAL_HANDLER
	var/list/modifiers = params2list(params)
	if(LAZYACCESS(modifiers, RIGHT_CLICK) || LAZYACCESS(modifiers, MIDDLE_CLICK) || LAZYACCESS(modifiers, SHIFT_CLICK))
		return

	//This is stupid but it works
	UnregisterSignal(source, COMSIG_ATOM_PRE_DIR_CHANGE)
	source.setDir(get_dir(source, A))
	RegisterSignal(source, COMSIG_ATOM_PRE_DIR_CHANGE, .proc/on_dir_change)


code:
/datum/component/edible/CanConsume(mob/living/eater, mob/living/feeder)
	. = ..()
	if(iscarbon(feeder))
		if(feeder.zone_selected != BODY_ZONE_PRECISE_MOUTH)
			return FALSE
	if(iscarbon(eater))
		var/mob/living/carbon/consumer_softproducts = eater
		var/obj/item/bodypart/jaw = consumer_softproducts.get_bodypart_nostump(BODY_ZONE_PRECISE_MOUTH)
		if(!jaw)
			return FALSE
		if(jaw.bodypart_disabled)
			return FALSE
		if(jaw.limb_efficiency <= 0)
			return FALSE

/datum/component/edible/TakeBite(mob/living/eater, mob/living/feeder)
	var/atom/owner = parent

	if(!owner.reagents)
		stack_trace("[eater] failed to bite [owner], because [owner] had no reagents.")
		return FALSE
	if(eater.satiety > -200)
		eater.satiety -= junkiness
	playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
	if(owner.reagents.total_volume)
		var/jaw_efficiency = 100
		if(iscarbon(eater))
			var/obj/item/bodypart/jaw = eater.get_bodypart_nostump(BODY_ZONE_PRECISE_MOUTH)
			jaw_efficiency = jaw?.limb_efficiency
		if(jaw_efficiency <= LIMB_EFFICIENCY_DISABLING)
			to_chat(feeder, span_warning("Their jaw is far too inefficient to take a bite."))
			return
		SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder, bitecount, bite_consumption)
		var/fraction = min(bite_consumption / owner.reagents.total_volume, 1)
		owner.reagents.trans_to(eater, CEILING(bite_consumption * (jaw_efficiency/LIMB_EFFICIENCY_OPTIMAL), 1), transfered_by = feeder, methods = INGEST)
		bitecount++
		if(!owner.reagents.total_volume)
			On_Consume(eater, feeder)
		checkLiked(fraction, eater)

		//Invoke our after eat callback if it is valid
		if(after_eat)
			after_eat.Invoke(eater, feeder, bitecount)

		return TRUE

repo_name: AnalWerewolf/sundown

license: mit


code:
/datum/component/forensics
	//i hate this i hate this i HATE this FUCK
	var/list/shit_DNA //assoc dna = bloodtype
	var/list/cum_DNA //assoc dna = bloodtype
	var/list/femcum_DNA //assoc dna = bloodtype

/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL.
	shit_DNA = LAZY_LISTS_OR(shit_DNA, F.shit_DNA)
	cum_DNA = LAZY_LISTS_OR(cum_DNA, F.cum_DNA)
	femcum_DNA = LAZY_LISTS_OR(femcum_DNA, F.femcum_DNA)
	check_shit()
	check_cum()
	check_femcum()
	return ..()

/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers, new_shit_DNA, new_cum_DNA, new_femcum_DNA)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	fingerprints = new_fingerprints
	hiddenprints = new_hiddenprints
	blood_DNA = new_blood_DNA
	fibers = new_fibers
	shit_DNA = new_shit_DNA
	cum_DNA = new_cum_DNA
	femcum_DNA = new_femcum_DNA
	check_blood()
	check_shit()
	check_cum()
	check_femcum()

/datum/component/forensics/RegisterWithParent()
	check_blood()
	check_shit()
	check_cum()
	check_femcum()
	RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act)

/datum/component/forensics/add_blood_DNA(list/dna) //list(dna_enzymes = type)
	if(!length(dna))
		return
	. = length(blood_DNA)
	if(!.)
		var/atom/parent_atom = parent
		parent_atom.adjust_germ_level(INFECTION_LEVEL_ONE)
	LAZYINITLIST(blood_DNA)
	for(var/i in dna)
		blood_DNA[i] = dna[i]
	check_blood()
	return TRUE

/datum/component/forensics/wipe_blood_DNA()
	blood_DNA = null
	wipe_shit_DNA()
	wipe_cum_DNA()
	wipe_femcum_DNA()
	return TRUE

/datum/component/forensics/proc/add_shit_DNA(list/dna) //list(dna_enzymes = type)
	if(!length(dna))
		return
	. = length(blood_DNA)
	if(!.)
		var/atom/parent_atom = parent
		parent_atom.adjust_germ_level(INFECTION_LEVEL_ONE)
	LAZYINITLIST(shit_DNA)
	for(var/i in dna)
		shit_DNA[i] = dna[i]
	check_shit()
	return TRUE

/datum/component/forensics/proc/wipe_shit_DNA()
	shit_DNA = null
	return TRUE

/datum/component/forensics/proc/check_shit()
	if(!isitem(parent))
		return
	if(!length(shit_DNA))
		return
	parent.AddElement(/datum/element/decal/shit)

/datum/component/forensics/proc/add_cum_DNA(list/dna) //list(dna_enzymes = type)
	if(!length(dna))
		return
	. = length(blood_DNA)
	if(!.)
		var/atom/parent_atom = parent
		parent_atom.adjust_germ_level(INFECTION_LEVEL_ONE)
	LAZYINITLIST(cum_DNA)
	for(var/i in dna)
		cum_DNA[i] = dna[i]
	check_cum()
	return TRUE

/datum/component/forensics/proc/wipe_cum_DNA()
	cum_DNA = null
	return TRUE

/datum/component/forensics/proc/check_cum()
	if(!isitem(parent))
		return
	if(!length(shit_DNA))
		return
	parent.AddElement(/datum/element/decal/cum)

/datum/component/forensics/proc/add_femcum_DNA(list/dna) //list(dna_enzymes = type)
	if(!length(dna))
		return
	LAZYINITLIST(cum_DNA)
	for(var/i in dna)
		cum_DNA[i] = dna[i]
	check_femcum()
	return TRUE

/datum/component/forensics/proc/wipe_femcum_DNA()
	femcum_DNA = null
	return TRUE

/datum/component/forensics/proc/check_femcum()
	if(!isitem(parent))
		return
	if(!length(femcum_DNA))
		return
	parent.AddElement(/datum/element/decal/femcum)


code:
/datum/component/fraggot

/datum/component/fraggot/Initialize()
	if(!isliving(parent))
		return COMPONENT_INCOMPATIBLE

/datum/component/fraggot/RegisterWithParent()
	. = ..()
	var/mob/living/our_fraggot = parent
	our_fraggot.attributes?.add_diceroll_modifier(/datum/diceroll_modifier/fraggot)
	for(var/mob/living/carbon/human/human in (GLOB.mob_living_list - our_fraggot))
		SEND_SIGNAL(human, COMSIG_ADD_MOOD_EVENT, "[our_fraggot.real_name]", /datum/mood_event/fraggot, our_fraggot)
	RegisterSignal(our_fraggot, COMSIG_PARENT_EXAMINE, .proc/)
	RegisterSignal(our_fraggot, COMSIG_LIVING_DEATH, .proc/fraggot_died)
	RegisterSignal(our_fraggot, COMSIG_PARENT_PREQDELETED, .proc/fraggot_deleted)
	ADD_TRAIT(our_fraggot, TRAIT_FRAGGOT, "fraggot")
	START_PROCESSING(SSfraggots, src)

/datum/component/fraggot/UnregisterFromParent()
	. = ..()
	var/mob/living/our_fraggot = parent
	STOP_PROCESSING(SSfraggots, src)
	UnregisterSignal(our_fraggot, COMSIG_LIVING_DEATH)
	UnregisterSignal(our_fraggot, COMSIG_PARENT_PREQDELETED)
	UnregisterSignal(our_fraggot, COMSIG_PARENT_EXAMINE)
	REMOVE_TRAIT(our_fraggot, TRAIT_FRAGGOT, "fraggot")
	for(var/mob/living/carbon/human/human in (GLOB.mob_living_list - our_fraggot))
		SEND_SIGNAL(human, COMSIG_CLEAR_MOOD_EVENT, "[our_fraggot.real_name]")

/datum/component/fraggot/proc/fraggot_died(mob/living/our_fraggot)
	if(!QDELETED(our_fraggot))
		our_fraggot.gib()

/datum/component/fraggot/proc/fraggot_deleted(mob/living/our_fraggot)
	for(var/mob/living/carbon/human/human in (GLOB.mob_living_list - our_fraggot))
		SEND_SIGNAL(human, COMSIG_CLEAR_MOOD_EVENT, "[our_fraggot.real_name]")

/datum/component/fraggot/proc/fraggot_examine(mob/living/our_fraggot, mob/user, list/examine_text)
	examine_text += span_flashingdanger("[uppertext(our_fraggot.name)] IS A FRAGGOT! [uppertext(our_fraggot.p_they())] MUST BE KILLED!")
	if(user.client)
		SEND_SOUND(user.client, sound('modular_septic/sound/effects/yomai.ogg', FALSE, CHANNEL_LOBBYMUSIC, 100))

/datum/component/fraggot/process(delta_time)
	var/mob/living/our_fraggot = parent
	if(our_fraggot.client)
		var/message = pick("BIG CHUNGUS LOLOLOLOLLLLOLOLOLO!!!!", \
						"UGANDA KNUCKLES LOLOLOLOL!", \
						"REDDIT!!!!!!", \
						"EPSTEIN DIDENT KILL HIMSELF!!!!", \
						"GAMING!!!!", \
						"NIGGERS!!!!!", \
						"KIKES!!!!", \
						"FAGGOTS!!!", \
						"Okay, WHO TOOK A SHIT IN MY CUM SOCK? The cum sock and poop sock are CLEARLY LABELED. I should know, because I embroidered them myself after the last incident. If you quinkers could take TWO SECONDS of your time to make sure you have the right sock before dropping a MASSIVE FUDGE DRAGON inside of it, I wouldn't be sitting here jacking off into a pile of SHIT right now. Seriously, this is a mess. I've got squishy chunks oozing out the hole, the fabric is soaking through with juices, my phone is covered in shit, and the smell is god AWFUL. I'm not kidding guys. My mom is gonna be LIVID when she cleans this off my bedsheets. It's everywhere and I'm honestly disgusted. Fess up now and it will be easier for all of us, trust me.", \
						"The black community has suffered more than enough. We, as Funko POP owners, need to stand with them and DEMAND that Funko honors the lives of black victims of police brutality by making a George Floyd Funko POP. We need to tell the world to take a stand against anti-black racism, and there would be no better way to do so than buying a George Floyd Funko POP. Will you take a stand and join me?", \
						"So I ask you: If Mario is way out there, but the position used for floor detection is still in the box, can he still stand? The answer is yes. As far as the game sees it, Mario is in fact above land out here, because the game checks for floor detection back over there. \['There' is the actual course.\] So for all intensive purposes there is land over here! This is known as a parallel universe, or a PU for short.")
		to_chat(our_fraggot, span_flashinguserdanger(message))
		if(DT_PROB(0.25, delta_time))
			our_fraggot.client.bruh_moment()
	if(DT_PROB(1, delta_time))
		our_fraggot.agony_scream()
	if(DT_PROB(1, delta_time))
		our_fraggot.death_scream()
	if(ishuman(our_fraggot))
		var/mob/living/carbon/human/human_fraggot = our_fraggot
		if(DT_PROB(4, delta_time))
			human_fraggot.piss(FALSE)
		if(DT_PROB(4, delta_time))
			human_fraggot.shit(FALSE)
		human_fraggot.adjust_lust(250 * delta_time)


code:
/datum/component/grillable
	var/pollutant_type

/datum/component/grillable/Initialize(cook_result, required_cook_time, positive_result, use_large_steam_sprite, pollutant_type)
	if(!isitem(parent)) //Only items support grilling at the moment
		return COMPONENT_INCOMPATIBLE

	src.cook_result = cook_result
	src.required_cook_time = required_cook_time
	src.positive_result = positive_result
	src.use_large_steam_sprite = use_large_steam_sprite
	src.pollutant_type = pollutant_type

	RegisterSignal(parent, COMSIG_ITEM_GRILLED, .proc/OnGrill)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/OnExamine)

/datum/component/grillable/OnGrill(datum/source, atom/used_grill, delta_time = 1)
	. = COMPONENT_HANDLED_GRILLING

	if(pollutant_type)
		var/turf/parent_turf = get_turf(parent)
		parent_turf.pollute_turf(pollutant_type, 10)
	current_cook_time += delta_time * 10 //turn it into ds
	if(current_cook_time >= required_cook_time)
		FinishGrilling(used_grill)
	else if(!currently_grilling) //We havn't started grilling yet
		StartGrilling(used_grill)


code:
//Add this component to an atom to make them sexable, basically
/datum/component/interactable
	COOLDOWN_DECLARE(next_interaction)
	COOLDOWN_DECLARE(next_sexual_interaction)
	/// Last world.time we did an interaction
	var/last_interaction_as_user_time = 0
	/// Last world.time we received an interaction
	var/last_interaction_as_target_time = 0
	/// Does not need to be a weakref since interaction datums aren't supposed to be deleted
	var/datum/interaction/last_interaction_as_user
	/// Does not need to be a weakref since interaction datums aren't supposed to be deleted
	var/datum/interaction/last_interaction_as_target
	/// Used to clear var/last_interaction_as_user
	var/clear_user_interaction_timer
	/// Used to clear var/last_interaction_as_target
	var/clear_target_interaction_timer

/datum/component/interactable/Initialize()
	if(!isatom(parent) || isarea(parent))
		return COMPONENT_INCOMPATIBLE

/datum/component/interactable/Destroy(force, silent)
	. = ..()
	last_interaction_as_user = null
	last_interaction_as_target = null
	if(clear_user_interaction_timer)
		deltimer(clear_user_interaction_timer)
	if(clear_target_interaction_timer)
		deltimer(clear_target_interaction_timer)

/datum/component/interactable/RegisterWithParent()
	RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop)
	RegisterSignal(parent, COMSIG_INTERACTABLE_TRY_INTERACT, .proc/try_interact)
	RegisterSignal(parent, COMSIG_INTERACTABLE_COOLDOWN, .proc/on_cooldown)
	RegisterSignal(parent, COMSIG_INTERACTABLE_SEX_COOLDOWN, .proc/on_sex_cooldown)
	var/atom/atom_parent = parent
	atom_parent.verbs += /atom/proc/cmd_interact

/datum/component/interactable/UnregisterFromParent()
	UnregisterSignal(parent, COMSIG_MOUSEDROP_ONTO)
	UnregisterSignal(parent, COMSIG_INTERACTABLE_TRY_INTERACT)
	UnregisterSignal(parent, COMSIG_INTERACTABLE_COOLDOWN)
	UnregisterSignal(parent, COMSIG_INTERACTABLE_SEX_COOLDOWN)
	var/atom/atom_parent = parent
	atom_parent.verbs -= /atom/proc/cmd_interact

/datum/component/interactable/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "InteractionMenu")
		ui.set_autoupdate(TRUE)
		ui.open()

/datum/component/interactable/ui_state(mob/user)
	return GLOB.always_state

/datum/component/interactable/ui_status(mob/user, datum/ui_state/state)
	var/atom/atom_parent = parent
	if(get_dist(user, atom_parent) > DEFAULT_MESSAGE_RANGE)
		return UI_CLOSE
	return min(
		ui_status_only_living(user, atom_parent),
		max(
			ui_status_user_is_conscious_and_lying_down(user),
			ui_status_user_is_abled(user, atom_parent),
		),
	)

/datum/component/interactable/ui_data(mob/user)
	var/list/data = list()
	var/datum/component/interactable/user_interactable = user.GetComponent(/datum/component/interactable)
	var/atom/target = parent

	var/list/categories = list()
	for(var/datum/interaction/interaction in get_interactions_with(user_interactable))
		if(!categories[interaction.category])
			categories[interaction.category] = list("name" = interaction.category, "interactions" = list())
		var/list/this_interaction = list()
		this_interaction["name"] = interaction.name
		this_interaction["desc"] = interaction.desc
		this_interaction["button_icon"] = interaction.button_icon
		this_interaction["block_interact"] = FALSE
		if((interaction.interaction_flags & INTERACTION_RESPECT_COOLDOWN) && !COOLDOWN_FINISHED(src, next_interaction))
			this_interaction["block_interact"] = TRUE
		else if((interaction.interaction_flags & INTERACTION_RESPECT_SEX_COOLDOWN) && !COOLDOWN_FINISHED(src, next_sexual_interaction))
			this_interaction["block_interact"] = TRUE

		categories[interaction.category]["interactions"] += list(this_interaction)
	data["target_name"] = target.name
	data["categories"] = list()
	for(var/category in categories)
		data["categories"] += list(categories[category])
	if(user_interactable)
		data["block_interact"] = !COOLDOWN_FINISHED(user_interactable, next_interaction)
	return data

/datum/component/interactable/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
	. = ..()
	if(.)
		return
	if((action == "interact") && params["interaction"])
		var/datum/interaction/interact = GLOB.all_interaction_datums[params["interaction"]]
		if(interact)
			var/mob/living/user = usr
			var/datum/component/interactable/user_interactable = user.GetComponent(/datum/component/interactable)
			if(interact.allow_interaction(user_interactable, src) && interact.do_interaction(user_interactable, src))
				interact.after_interact(user_interactable, src)
			return TRUE

/datum/component/interactable/proc/mousedrop(atom/dropped_atom, atom/movable/receiver, mob/user, params)
	SIGNAL_HANDLER
	if((dropped_atom == parent) && (user == parent))
		if(SEND_SIGNAL(receiver, COMSIG_INTERACTABLE_TRY_INTERACT, user))
			return COMPONENT_NO_MOUSEDROP

/datum/component/interactable/proc/try_interact(atom/source, mob/living/user)
	SIGNAL_HANDLER

	. = TRUE
	var/mob/living/carbon/human/human_user = user
	//TK or distance check
	if((get_dist(parent, user) > 1) && !(istype(human_user) && human_user.dna.check_mutation(TK)))
		return FALSE
	var/datum/component/interactable/user_interactable = user.GetComponent(/datum/component/interactable)
	if(!user_interactable)
		return FALSE
	INVOKE_ASYNC(src, .proc/ui_interact, user)

/datum/component/interactable/proc/on_cooldown()
	SIGNAL_HANDLER

	return COOLDOWN_FINISHED(src, next_interaction)

/datum/component/interactable/proc/on_sex_cooldown()
	SIGNAL_HANDLER

	return COOLDOWN_FINISHED(src, next_sexual_interaction)

/datum/component/interactable/proc/get_interactions_with(datum/component/interactable/user)
	. = list()
	for(var/thing in GLOB.all_interaction_datums)
		var/datum/interaction/interact = GLOB.all_interaction_datums[thing]
		//we want to display interactions even if we are on cooldown
		if(interact.allow_interaction(user, src, TRUE, FALSE))
			. |= interact

/datum/component/interactable/proc/clear_user_interaction()
	last_interaction_as_user = null

/datum/component/interactable/proc/clear_target_interaction()
	last_interaction_as_target = null


code:
#define MINOR_INSANITY_PEN 5
#define MAJOR_INSANITY_PEN 10

/datum/component/mood/print_mood(mob/user)
	var/msg = "
"
	msg += span_notice("My thoughts")
	if(ishuman(user))
		var/mob/living/carbon/human/H = user
		msg += span_info("\nI remember my name, it is [H.real_name].")
		msg += span_info("\nI am, chronologically, [H.age] years old.")
		if(H.mind.assigned_role)
			msg += span_info("\nI'm [prefix_a_or_an(H.mind.assigned_role.title)] [lowertext(H.mind.assigned_role.title)] by trade.")
		for(var/thing in H.mind.antag_datums)
			var/datum/antagonist/antag = thing
			msg += span_info("\nI am also \a [lowertext(antag.name)].")
		if(!(H.dna.species.exotic_blood))
			msg += span_info("\nMy blood type is [H.dna.blood_type].")
		else
			var/datum/reagent/blood_reagent = H.dna.species.exotic_blood
			var/blood_name = initial(blood_reagent.name)
			msg += span_info("\nMy blood type is [blood_name].")
		switch(H.handed_flags)
			if(RIGHT_HANDED|LEFT_HANDED, AMBIDEXTROUS|RIGHT_HANDED|LEFT_HANDED, AMBIDEXTROUS)
				msg += span_info("\nI am ambidextrous.")
			if(LEFT_HANDED)
				msg += span_info("\nI am left-handed.")
			else
				msg += span_info("\nI am right-handed.")
		msg += span_info("\nMy gender is [lowertext(H.gender)].")
		msg += span_info("\nMy species is [lowertext(H.dna.species.name)].")
		if(length(H.quirks))
			msg += span_info("\nI am special: [H.get_quirk_string(FALSE, FALSE)].")
	msg += "

"
	msg += span_notice("My current mood:") //Short term
	var/left_symbols = get_signs_from_number(mood_level - 5, 1)
	var/right_symbols = get_signs_from_number(mood_level - 5, 0)
	if(HAS_TRAIT(user.mind, TRAIT_CAPITALIST_MOOD))
		switch(mood_level)
			if(1)
				msg += span_boldwarning("\n[left_symbols]The great depression![right_symbols]")
			if(2)
				msg += span_boldwarning("\n[left_symbols]Absolute market crash.[right_symbols]")
			if(3)
				msg += span_boldwarning("\n[left_symbols]This is terrible for the economy.[right_symbols]")
			if(4)
				msg += span_boldwarning("\n[left_symbols]My stocks are dipping.[right_symbols]")
			if(5)
				msg += span_nicegreen("\n[left_symbols]Quiet day trading.[right_symbols]")
			if(6)
				msg += span_nicegreen("\n[left_symbols]My stocks are rising.[right_symbols]")
			if(7)
				msg += span_nicegreen("\n[left_symbols]This is great for the economy.[right_symbols]")
			if(8)
				msg += span_nicegreen("\n[left_symbols]CEO mindset.[right_symbols]")
			if(9)
				msg += span_nicegreen("\n[left_symbols]I feel like a crypto millionaire![right_symbols]")
			else
				msg += span_nicegreen("\n[left_symbols]I'm alright.[right_symbols]")
	else
		switch(mood_level)
			if(1)
				msg += span_boldwarning("\n[left_symbols]This day is ruined![right_symbols]")
			if(2)
				msg += span_boldwarning("\n[left_symbols]I feel awful.[right_symbols]")
			if(3)
				msg += span_boldwarning("\n[left_symbols]I feel quite upset.[right_symbols]")
			if(4)
				msg += span_boldwarning("\n[left_symbols]I feel a bit sad.[right_symbols]")
			if(5)
				msg += span_nicegreen("\n[left_symbols]I'm alright.[right_symbols]")
			if(6)
				msg += span_nicegreen("\n[left_symbols]I feel pretty okay.[right_symbols]")
			if(7)
				msg += span_nicegreen("\n[left_symbols]I feel quite good.[right_symbols]")
			if(8)
				msg += span_nicegreen("\n[left_symbols]I feel amazing.[right_symbols]")
			if(9)
				msg += span_nicegreen("\n[left_symbols]This day is great![right_symbols]")
			else
				msg += span_nicegreen("\n[left_symbols]I'm alright.[right_symbols]")
	msg += span_notice("\nMoodlets:")//All moodlets
	if(LAZYLEN(mood_events))
		for(var/i in mood_events)
			var/datum/mood_event/event = mood_events[i]
			left_symbols = get_signs_from_number(event.mood_change, 1)
			right_symbols = get_signs_from_number(event.mood_change, 0)
			var/event_desc = replacetext(event.description, "\n", "")
			msg += "\n[left_symbols][event_desc][right_symbols]"
	else
		msg += span_nicegreen("\nI don't have much of a reaction to anything right now.")
	var/mob/living/living_user = user
	if(istype(living_user))
		var/list/additional_info = list()
		if(living_user.getStaminaLoss())
			if(living_user.getStaminaLoss() >= 35)
				additional_info += span_info("\nI'm exhausted.")
			else
				additional_info += span_info("\nI feel tired.")
		if(living_user.losebreath)
			additional_info += span_danger("\nI can't breathe!")
		if(HAS_TRAIT(living_user, TRAIT_SELF_AWARE))
			var/toxloss = living_user.getToxLoss()
			if(toxloss)
				if(toxloss >= 10)
					additional_info += span_danger("\nI feel nauseous.")
				else if(toxloss >= 20)
					additional_info += span_danger("\nI feel sick.")
				else if(toxloss >= 40)
					additional_info += span_danger("\nI feel very unwell!")
			var/oxyloss = living_user.getOxyLoss()
			if(oxyloss)
				if(oxyloss >= 10)
					additional_info += span_danger("\nI feel lightheaded.")
				else if(oxyloss >= 20)
					additional_info += span_danger("\nI need more air.")
				else if(oxyloss >= 30)
					additional_info += span_danger("\nI'm choking!")
		if(LAZYLEN(additional_info))
			msg += "

"
			msg += span_notice("
Additional info:")
			msg += jointext(additional_info, "")
	msg += "
" //div infobox
	to_chat(user || parent, msg)

/datum/component/mood/update_mood()
	var/old_mood = mood_level
	. = ..()
	if(mood_level < old_mood)
		to_chat(parent, span_danger("My mood worsens."))
	else if(mood_level > old_mood)
		to_chat(parent, span_nicegreen("My mood improves."))
	var/mob/living/living_parent = parent
	if(istype(living_parent))
		if((old_mood != mood_level) && (living_parent.attributes))
			var/mood_malus = -min(1, mood_level - 5)
			living_parent.attributes.add_or_update_variable_diceroll_modifier(/datum/diceroll_modifier/mood, TRUE, mood_malus)
		if(living_parent.hud_used?.sadness)
			switch(mood_level)
				if(4)
					living_parent.hud_used.sadness.alpha = 32
				if(3)
					living_parent.hud_used.sadness.alpha = 64
				if(2)
					living_parent.hud_used.sadness.alpha = 128
				if(1)
					living_parent.hud_used.sadness.alpha = 160
				else
					living_parent.hud_used.sadness.alpha = 0

/datum/component/mood/setSanity(amount, minimum=SANITY_INSANE, maximum=SANITY_GREAT, override = FALSE)
	// If we're out of the acceptable minimum-maximum range move back towards it in steps of 0.7
	// If the new amount would move towards the acceptable range faster then use it instead
	if(amount < minimum)
		amount += clamp(minimum - amount, 0, 0.7)
	if((!override && HAS_TRAIT(parent, TRAIT_UNSTABLE)) || amount > maximum)
		amount = min(sanity, amount)
	if(amount == sanity) //Prevents stuff from flicking around.
		return
	sanity = amount
	var/mob/living/master = parent
	switch(sanity)
		if(SANITY_INSANE to SANITY_CRAZY)
			setInsanityEffect(MAJOR_INSANITY_PEN)
			master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/insane)
			master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity)
			sanity_level = 6
		if(SANITY_CRAZY to SANITY_UNSTABLE)
			setInsanityEffect(MINOR_INSANITY_PEN)
			master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/crazy)
			master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity)
			sanity_level = 5
		if(SANITY_UNSTABLE to SANITY_DISTURBED)
			setInsanityEffect(0)
			master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/disturbed)
			master.add_actionspeed_modifier(/datum/actionspeed_modifier/low_sanity)
			sanity_level = 4
		if(SANITY_DISTURBED to SANITY_NEUTRAL)
			setInsanityEffect(0)
			master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
			master.remove_actionspeed_modifier(ACTIONSPEED_ID_SANITY)
			sanity_level = 3
		if(SANITY_NEUTRAL+1 to SANITY_GREAT+1) //shitty hack but +1 to prevent it from responding to super small differences
			setInsanityEffect(0)
			master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
			master.add_actionspeed_modifier(/datum/actionspeed_modifier/high_sanity)
			sanity_level = 2
		if(SANITY_GREAT+1 to INFINITY)
			setInsanityEffect(0)
			master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
			master.add_actionspeed_modifier(/datum/actionspeed_modifier/high_sanity)
			sanity_level = 1
	update_mood_icon()

/datum/component/mood/HandleNutrition()
	var/mob/living/L = parent
	if(ishuman(L))
		var/mob/living/carbon/human/H = L
		if(locate(/obj/item/organ/stomach/ethereal) in H.internal_organs)
			handle_charge(H)
			return
	if(!HAS_TRAIT(L, TRAIT_NOHUNGER))
		switch(L.nutrition)
			if(NUTRITION_LEVEL_FULL to INFINITY)
				if (!HAS_TRAIT(L, TRAIT_VORACIOUS))
					add_event(null, "nutrition", /datum/mood_event/fat)
				else
					add_event(null, "nutrition", /datum/mood_event/wellfed) // round and full
			if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL)
				add_event(null, "nutrition", /datum/mood_event/wellfed)
			if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED)
				add_event(null, "nutrition", /datum/mood_event/fed)
			if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED)
				clear_event(null, "nutrition")
			if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY)
				add_event(null, "nutrition", /datum/mood_event/hungry)
			if(0 to NUTRITION_LEVEL_STARVING)
				add_event(null, "nutrition", /datum/mood_event/starving)
	if(!HAS_TRAIT(L, TRAIT_NOTHIRST))
		switch(L.hydration)
			if(HYDRATION_LEVEL_THIRSTY to INFINITY)
				clear_event(null, "hydration")
			if(HYDRATION_LEVEL_DEHYDRATED to HYDRATION_LEVEL_THIRSTY)
				add_event(null, "hydration", /datum/mood_event/thirsty)
			if(0 to HYDRATION_LEVEL_DEHYDRATED)
				add_event(null, "hydration", /datum/mood_event/dehydrated)

/datum/component/mood/proc/handle_charge(mob/living/carbon/human/H)
	var/obj/item/organ/stomach/ethereal/stomach = (locate(/obj/item/organ/stomach/ethereal) in H.internal_organs)
	switch(stomach.crystal_charge)
		if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER)
			add_event(null, "charge", /datum/mood_event/decharged)
		if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL)
			add_event(null, "charge", /datum/mood_event/lowpower)
		if(ETHEREAL_CHARGE_NORMAL to ETHEREAL_CHARGE_ALMOSTFULL)
			clear_event(null, "charge")
		if(ETHEREAL_CHARGE_ALMOSTFULL to ETHEREAL_CHARGE_FULL)
			add_event(null, "charge", /datum/mood_event/charged)
		if(ETHEREAL_CHARGE_FULL to ETHEREAL_CHARGE_OVERLOAD)
			add_event(null, "charge", /datum/mood_event/overcharged)
		if(ETHEREAL_CHARGE_OVERLOAD to ETHEREAL_CHARGE_DANGEROUS)
			add_event(null, "charge", /datum/mood_event/supercharged)

#undef MINOR_INSANITY_PEN
#undef MAJOR_INSANITY_PEN


code:
#define CLOUD_POSITION_DAMAGE 1
#define CLOUD_POSITION_W_BONUS 2
#define CLOUD_POSITION_BW_BONUS 3
/datum/component/pellet_cloud
	var/embed_count = 0
	var/through_count = 0
	var/suppressed = SUPPRESSED_NONE

/datum/component/pellet_cloud/create_casing_pellets(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro)
	shooter = user
	var/turf/target_loc = get_turf(target)

	// things like mouth executions and gunpoints can multiply the damage and wounds of projectiles, so this makes sure those effects are applied to each pellet instead of just one
	var/original_damage = shell.loaded_projectile.damage
	var/original_wb = shell.loaded_projectile.wound_bonus
	var/original_bwb = shell.loaded_projectile.bare_wound_bonus

	for(var/i in 1 to num_pellets)
		shell.ready_proj(target, user, null, zone_override, fired_from)
		// Follow the leader, we don't care about the diceroll result of peasants
		if(i == 1)
			zone_override = shell.loaded_projectile.def_zone
			suppressed = shell.loaded_projectile.suppressed
		shell.loaded_projectile.suppressed = SUPPRESSED_VERY
		if(distro)
			if(randomspread)
				spread = round((rand() - 0.5) * distro)
			else //Smart spread
				spread = round((i / num_pellets - 0.5) * distro)

		RegisterSignal(shell.loaded_projectile, COMSIG_PELLET_CLOUD_EMBEDDED, .proc/projectile_embedded)
		RegisterSignal(shell.loaded_projectile, COMSIG_PELLET_CLOUD_WENT_THROUGH, .proc/projectile_went_through)
		RegisterSignal(shell.loaded_projectile, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit)
		RegisterSignal(shell.loaded_projectile, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range)
		shell.loaded_projectile.damage = original_damage
		shell.loaded_projectile.wound_bonus = original_wb
		shell.loaded_projectile.bare_wound_bonus = original_bwb
		pellets += shell.loaded_projectile
		var/turf/current_loc = get_turf(user)
		if (!istype(target_loc) || !istype(current_loc) || !(shell.loaded_projectile))
			return
		INVOKE_ASYNC(shell, /obj/item/ammo_casing.proc/throw_proj, target, target_loc, shooter, params, spread)

		if(i != num_pellets)
			shell.newshot()

/datum/component/pellet_cloud/pellet_hit(obj/projectile/projectile, atom/movable/firer, atom/target, Angle, hit_zone)
	pellets -= projectile
	terminated++
	hits++
	var/obj/item/bodypart/hit_part
	var/no_damage = FALSE
	if(iscarbon(target) && hit_zone)
		var/mob/living/carbon/hit_carbon = target
		hit_part = hit_carbon.get_bodypart(hit_zone)
		if(hit_part)
			target = hit_part
			if(projectile.wound_bonus != CANT_WOUND) // handle wounding
				// unfortunately, due to how pellet clouds handle finalizing only after every pellet is accounted for, that also means there might be a short delay in dealing wounds if one pellet goes wide
				// while buckshot may reach a target or miss it all in one tick, we also have to account for possible ricochets that may take a bit longer to hit the target
				if(isnull(wound_info_by_part[hit_part]))
					wound_info_by_part[hit_part] = list(0, 0, 0)
				// these account for decay
				wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] += projectile.damage
				wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] += projectile.wound_bonus
				wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] += projectile.bare_wound_bonus
				// actual wounding and damage will be handled aggregate, very wonky but works fine enough
				projectile.damage = 0.01
				projectile.wound_bonus = CANT_WOUND
	else if(isobj(target))
		var/obj/hit_object = target
		if(hit_object.damage_deflection > projectile.damage || !projectile.damage)
			no_damage = TRUE

	LAZYADDASSOC(targets_hit[target], "hits", 1)
	LAZYSET(targets_hit[target], "no damage", no_damage)
	if(targets_hit[target]["hits"] == 1)
		RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
	UnregisterSignal(projectile, list(COMSIG_PARENT_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
	if(terminated == num_pellets)
		finalize()

/datum/component/pellet_cloud/finalize()
	var/obj/projectile/projectile = projectile_type
	var/proj_name = initial(projectile.name)

	for(var/atom/target in targets_hit)
		var/num_hits = targets_hit[target]["hits"]
		var/did_damage = targets_hit[target]["no damage"]
		UnregisterSignal(target, COMSIG_PARENT_QDELETING)
		var/mob/living/carbon/carbon_target
		if(iscarbon(target))
			carbon_target = target

		SEND_SIGNAL(target, COMSIG_CARBON_CLEAR_WOUND_MESSAGE)
		var/obj/item/bodypart/hit_part
		if(isbodypart(target))
			hit_part = target
			target = hit_part.owner
			if(wound_info_by_part[hit_part] && (initial(projectile.damage_type) == BRUTE || initial(projectile.damage_type) == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds)
				var/damage_dealt = wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE]
				var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS]
				var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS]
				var/armor = hit_part.owner.run_armor_check(hit_part.body_zone, \
													initial(projectile.flag), \
													"", \
													"", \
													initial(projectile.armour_penetration), \
													"", \
													FALSE, \
													initial(projectile.weak_against_armour))
				wound_info_by_part -= hit_part
				hit_part.receive_damage((initial(projectile.damage_type) == BRUTE) ? damage_dealt : 0, \
										(initial(projectile.damage_type) == BURN) ? damage_dealt : 0, \
										(initial(projectile.damage_type) == STAMINA) ? damage_dealt : 0, \
										armor, TRUE, null, w_bonus, bw_bonus, initial(projectile.sharpness))

		//Wound message after we apply hurties, discounting the embed related stuff
		var/new_wound_message
		if(carbon_target)
			new_wound_message = carbon_target.wound_message

		var/wound_text = ""
		//This is gonna get wacky
		if(carbon_target)
			wound_text = new_wound_message
			if(embed_count > 1)
				wound_text += span_danger(" [embed_count] [proj_name]s embed!")
			else if(embed_count)
				wound_text += span_danger(" A [proj_name] embeds!")
			if(through_count > 1)
				wound_text += span_danger(" [embed_count] [proj_name]s go through!")
			else if(through_count)
				wound_text += span_danger(" A [proj_name] goes through!")

		if(ismob(target))
			if(num_hits > 1)
				if(suppressed < SUPPRESSED_QUIET)
					target.visible_message(span_danger("[target] is hit by [num_hits] [proj_name][plural_s(proj_name)][hit_part ? " in the [hit_part.name]" : ""]![wound_text]"), \
									vision_distance = COMBAT_MESSAGE_RANGE, \
									ignored_mobs = target)
				if(suppressed < SUPPRESSED_VERY)
					to_chat(target, span_userdanger("I'm hit by [num_hits] [proj_name]s[hit_part ? " on \the [hit_part.name]" : ""]![wound_text]"))
			else
				if(suppressed < SUPPRESSED_QUIET)
					target.visible_message(span_danger("[target] is hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]![wound_text]"), \
									vision_distance = COMBAT_MESSAGE_RANGE, \
									ignored_mobs = target)
				if(suppressed < SUPPRESSED_VERY)
					to_chat(target, span_userdanger("I'm hit by \a [proj_name][hit_part ? " on \the [hit_part.name]" : ""]![wound_text]"))
		else if(suppressed < SUPPRESSED_VERY)
			if(num_hits > 1)
				target.visible_message(span_danger("[target] is hit by [num_hits] [proj_name][did_damage ? ", which doesn't leave a mark" : ""]!"),
								vision_distance = COMBAT_MESSAGE_RANGE)
			else
				target.visible_message(span_danger("[target] is hit by \a [proj_name][did_damage ? ", which doesn't leave a mark" : ""]!"), \
								vision_distance = COMBAT_MESSAGE_RANGE)
		SEND_SIGNAL(target, COMSIG_CARBON_CLEAR_WOUND_MESSAGE)

	for(var/M in purple_hearts)
		var/mob/living/martyr = M
		if(martyr.stat == DEAD && martyr.client)
			martyr.client.give_award(/datum/award/achievement/misc/lookoutsir, martyr)
	UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED)
	if(queued_delete)
		qdel(parent)
	qdel(src)

/datum/component/pellet_cloud/proc/projectile_embedded(obj/projectile/source)
	embed_count++

/datum/component/pellet_cloud/proc/projectile_went_through(obj/projectile/source)
	through_count++

#undef CLOUD_POSITION_DAMAGE
#undef CLOUD_POSITION_W_BONUS
#undef CLOUD_POSITION_BW_BONUS


code:
/datum/component/temporary_pollution_emission
	dupe_mode = COMPONENT_DUPE_ALLOWED
	/// The type of the pollutant we're emitting
	var/pollutant_type
	/// The amount we emitt every process
	var/pollutant_amount
	/// When do we expire
	var/expiry_time

/datum/component/temporary_pollution_emission/Initialize(pollutant_type, pollutant_amount, expiry_time)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	src.pollutant_type = pollutant_type
	src.pollutant_amount = pollutant_amount
	src.expiry_time = world.time + expiry_time
	START_PROCESSING(SSobj, src)

/datum/component/temporary_pollution_emission/Destroy()
	STOP_PROCESSING(SSobj, src)
	return ..()

/datum/component/temporary_pollution_emission/process(delta_time = SSOBJ_DT)
	var/turf/my_turf = get_turf(parent)
	my_turf.pollute_turf(pollutant_type, pollutant_amount * delta_time)
	if(world.time >= expiry_time)
		qdel(src)


code:
//Organ storage component, used in surgeries
/datum/component/storage/concrete/organ
	rustle_sound = list('modular_septic/sound/gore/organ1.ogg', 'modular_septic/sound/gore/organ2.ogg')
	attack_hand_interact = FALSE
	max_items = 99 //this doesn't actually matter
	max_combined_w_class = 99 //this doesn't actually matter
	max_w_class = WEIGHT_CLASS_GIGANTIC //this doesn't actually matter
	allow_big_nesting = TRUE //this doesn't actually matter
	var/obj/item/bodypart/bodypart_affected

// Unregister signals we don't want
/datum/component/storage/concrete/organ/Initialize()
	if(!iscarbon(parent))
		return COMPONENT_INCOMPATIBLE
	. = ..()
	boxes?.icon = 'modular_septic/icons/hud/screen_organ.dmi'
	boxes?.icon_state = "organ_block"
	if(boxes)
		var/image/ribs = image(boxes.icon, boxes, "[boxes.icon_state]_overlay", boxes.layer+1)
		ribs.plane = ABOVE_HUD_PLANE
		boxes.add_overlay(ribs)
	closer?.icon = 'modular_septic/icons/hud/screen_organ.dmi'
	closer?.icon_state = "organ_close"
	UnregisterSignal(parent, list(COMSIG_MOUSEDROPPED_ONTO, COMSIG_CLICK_ALT, \
							COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_HAND_SECONDARY, \
							COMSIG_ITEM_PRE_ATTACK, COMSIG_ITEM_ATTACK_SELF, \
							COMSIG_ITEM_PICKUP, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_GHOST, \
							COMSIG_PARENT_ATTACKBY))
	var/mob/living/carbon/C = parent
	if(length(C.bodyparts) && isbodypart(C.bodyparts[1]))
		assign_bodypart(C.bodyparts[1])

//No real location
/datum/component/storage/concrete/organ/Destroy()
	for(var/atom/_A in contents())
		_A.mouse_opacity = initial(_A.mouse_opacity)
	return ..()

// Assign a bodypart to be affected
/datum/component/storage/concrete/organ/proc/assign_bodypart(obj/item/bodypart/new_bodypart)
	bodypart_affected = new_bodypart

// Gives all organs parent as stored_in
/datum/component/storage/concrete/organ/proc/update_insides()
	for(var/obj/item/I in contents())
		I.stored_in = parent

// Revert the opacity proper
/datum/component/storage/concrete/organ/Destroy()
	for(var/atom/_A in contents())
		_A.mouse_opacity = initial(_A.mouse_opacity)
	for(var/obj/item/I in contents())
		I.stored_in = null
		UnregisterSignal(I, COMSIG_CLICK)
	bodypart_affected = null
	return ..()

// Check if we are accessible
/datum/component/storage/concrete/organ/proc/is_accessible()
	. = FALSE
	if(bodypart_affected)
		var/how_open = bodypart_affected.how_open()
		if((bodypart_affected.is_encased() && CHECK_MULTIPLE_BITFIELDS(how_open, SURGERY_INCISED|SURGERY_RETRACTED|SURGERY_BROKEN)) || (!bodypart_affected.is_encased() && CHECK_MULTIPLE_BITFIELDS(how_open, SURGERY_INCISED|SURGERY_RETRACTED)))
			return TRUE
	else
		return TRUE

// Only visible when acessible
/datum/component/storage/concrete/organ/show_to_ghost(datum/source, mob/dead/observer/M)
	if(!is_accessible())
		return
	return ..()

// Only visible when accessible
/datum/component/storage/concrete/organ/user_show_to_mob(mob/M, force = FALSE)
	if(!is_accessible() && !force)
		return
	return ..()

// Can only insert organs or cavity implants depending on a few conditions
/datum/component/storage/concrete/organ/handle_item_insertion(obj/item/I, prevent_warning, mob/M, datum/component/storage/remote)
	var/datum/component/storage/concrete/master = master()
	var/atom/parent = src.parent
	var/moved = FALSE
	if(!istype(I))
		return FALSE
	if(M)
		if(!M.temporarilyRemoveItemFromInventory(I))
			return FALSE
		else
			moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever.
	if(I.pulledby)
		I.pulledby.stop_pulling()
	if(silent)
		prevent_warning = TRUE
	if(!_insert_physical_item(I))
		if(moved)
			if(M)
				if(!M.put_in_active_hand(I))
					I.forceMove(parent.drop_location())
			else
				I.forceMove(parent.drop_location())
		return FALSE
	I.on_enter_storage(master)
	I.item_flags |= IN_STORAGE
	refresh_mob_views()
	I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt
	if(M)
		if(M.client && M.active_storage != src)
			M.client.screen -= I
		if(M.observers && length(M.observers))
			for(var/i in M.observers)
				var/mob/dead/observe = i
				if(observe.client && observe.active_storage != src)
					observe.client.screen -= I
		if(!remote)
			parent.add_fingerprint(M)
			if(!prevent_warning)
				mob_item_insertion_feedback(usr, M, I)
	playsound(I, pick(rustle_sound), 50, 1, -5)
	update_insides()
	update_icon()
	return TRUE

// Don't play the default rustle sound PLEASE - Also, use the bodypart too to give better feedback
/datum/component/storage/concrete/organ/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE)
	if(silent && !override)
		return
	for(var/mob/viewing in fov_viewers(world.view, user))
		if(M == viewing)
			to_chat(usr, span_notice("You put [I] [insert_preposition]to [parent]'s [bodypart_affected]."))
		else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
			viewing.show_message(span_notice("[M] puts [I] [insert_preposition]to [parent]'s [bodypart_affected]."), MSG_VISUAL)
		else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance...
			viewing.show_message(span_notice("[M] puts [I] [insert_preposition]to [parent]'s [bodypart_affected]."), MSG_VISUAL)

// Return the proper organ list
/datum/component/storage/concrete/organ/contents()
	if(!bodypart_affected)
		var/mob/living/carbon/carbon_parent = parent
		var/list/return_list = list()
		if(LAZYLEN(carbon_parent.internal_organs))
			return_list |= carbon_parent.internal_organs
		return return_list
	else
		var/list/return_list = list()
		var/list/organs = bodypart_affected.get_organs()
		if(LAZYLEN(organs))
			return_list |= bodypart_affected.get_organs()
		if(LAZYLEN(bodypart_affected.cavity_items))
			return_list |= bodypart_affected.cavity_items
		return return_list

// No real location
/datum/component/storage/concrete/organ/hide_from(mob/M)
	if(!M.client)
		return TRUE
	M.client.screen -= boxes
	M.client.screen -= closer
	for(var/obj/item/thing in contents())
		M.client.screen -= thing
	if(M.active_storage == src)
		M.active_storage = null
	LAZYREMOVE(is_using, M)
	return TRUE

// No real location
/datum/component/storage/concrete/organ/show_to(mob/M)
	if(!M.client)
		return FALSE
	if(M.active_storage != src && (M.stat == CONSCIOUS))
		for(var/obj/item/I in contents())
			if(I.on_found(M))
				return FALSE
	if(M.active_storage)
		M.active_storage.hide_from(M)
	orient2hud()
	M.client.screen |= boxes
	M.client.screen |= closer
	for(var/obj/item/thing in contents())
		M.client.screen |= thing
	M.active_storage = src
	LAZYOR(is_using, M)
	return TRUE

// No real location
/datum/component/storage/concrete/organ/orient2hud()
	var/adjusted_contents = length(contents())

	//Numbered contents display
	var/list/datum/numbered_display/numbered_contents
	if(display_numerical_stacking)
		numbered_contents = _process_numerical_display()
		adjusted_contents = length(numbered_contents)

	var/rows = clamp(max_items, 1, screen_max_rows)
	var/columns = clamp(CEILING(adjusted_contents / rows, 1), 1, screen_max_columns)
	standard_orient_objs(rows, columns, numbered_contents)

//No real location
/datum/component/storage/concrete/organ/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents)
	boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y-rows+1]:[screen_pixel_y]"
	var/cx = screen_start_x
	var/cy = screen_start_y
	if(islist(numerical_display_contents))
		for(var/type in numerical_display_contents)
			var/datum/numbered_display/ND = numerical_display_contents[type]
			ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
			ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
			ND.sample_object.maptext = MAPTEXT("[(ND.number > 1)? "[ND.number]" : ""]")
			ND.sample_object.plane = ABOVE_HUD_PLANE
			cy--
			if(screen_start_y - cy >= rows)
				cy = screen_start_y
				cx++
				if(cx - screen_start_x >= cols)
					break
	else
		for(var/obj/O in contents())
			if(QDELETED(O))
				continue
			O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
			O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
			O.maptext = ""
			O.plane = ABOVE_HUD_PLANE
			cy--
			if(screen_start_y - cy >= rows)
				cy = screen_start_y
				cx++
				if(cx - screen_start_x >= cols)
					break
	closer.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y+1]:[screen_pixel_y]"

// No real location
/datum/component/storage/concrete/organ/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/living/M)
	if(!istype(I) || (I.item_flags & ABSTRACT) || !is_accessible(parent))
		return FALSE //Not an item
	if(I == parent)
		return FALSE //no paradoxes for you
	if(!IS_GRAB_INTENT(M, null))
		return FALSE //must be on grab intent
	var/organ_volume = bodypart_affected?.get_cavity_volume()
	var/obj/item/organ/O = I
	if(!istype(O) && bodypart_affected && ((organ_volume + I.w_class > bodypart_affected.max_cavity_volume) || (I.w_class > bodypart_affected.max_cavity_item_size)) )
		if(!stop_messages)
			to_chat(M, span_warning("\The [bodypart_affected ? bodypart_affected : "[parent]"] cannot hold [I]!"))
		return FALSE
	else if(istype(O) && bodypart_affected && !((bodypart_affected.body_zone in O.possible_zones) || (organ_volume + O.organ_volume > bodypart_affected.max_cavity_volume)) )
		if(!stop_messages)
			to_chat(M, span_warning("\The [bodypart_affected ? bodypart_affected : "[parent]"] cannot hold [O]!"))
		return FALSE
	var/atom/host = parent
	if(I in contents())
		return FALSE //Means the item is already in the storage
	if(is_type_in_typecache(I, cant_hold)) //Check for specific items which this container can't hold.
		if(!stop_messages)
			to_chat(M, span_warning("[host] cannot hold [I]!"))
		return FALSE
	if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry.
		to_chat(M, span_warning("\The [I] is stuck to your hand, you can't put it in \the [host]!"))
		return FALSE
	var/datum/component/storage/concrete/master = master()
	if(!istype(master))
		return FALSE
	return master.slave_can_insert_object(src, I, stop_messages, M)

// No real location
/datum/component/storage/concrete/organ/_insert_physical_item(obj/item/I, override)
	. = FALSE
	var/obj/item/organ/O = I
	if(istype(I, /obj/item/mmi))
		var/obj/item/mmi/meme = I
		if(meme.brain)
			O = meme.brain
			O.forceMove(parent)
			meme.brain = null
			qdel(meme)

	if(istype(O))
		if(!(O in contents()))
			var/mob/living/carbon/carbon_parent = parent
			O.forceMove(bodypart_affected)
			O.Insert(carbon_parent)
			update_insides()
	else
		if(!(I in contents()))
			I.forceMove(bodypart_affected)
			bodypart_affected.cavity_items += I
			update_insides()

	refresh_mob_views()
	return TRUE

// No real location
/datum/component/storage/concrete/organ/attackby(datum/source, obj/item/I, mob/M, params)
	. = TRUE //no afterattack
	if(iscyborg(M))
		return FALSE
	if(!can_be_inserted(I, FALSE, M))
		return FALSE
	handle_item_insertion(I, FALSE, M)

// No real location
/datum/component/storage/concrete/organ/remaining_space_items()
	return TRUE

//No real location
/datum/component/storage/concrete/organ/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE)
	if(!(AM in contents()))
		return FALSE
	return remove_from_storage(AM, new_loc)

// No real location
/datum/component/storage/concrete/organ/on_contents_del(datum/source, atom/A)
	if(A in contents())
		usr = null
		remove_from_storage(A, null)

// Handled by carbon parent
/datum/component/storage/concrete/organ/emp_act(datum/source, severity)
	return FALSE

// Deal with cavity items and organs appropriately
/datum/component/storage/concrete/organ/remove_from_storage(atom/movable/AM, atom/new_location)
	if(!is_accessible(parent))
		return FALSE
	. = ..()
	if(.)
		AM.mouse_opacity = initial(AM.mouse_opacity)
		var/obj/item/organ/O = AM
		if(!istype(O))
			var/obj/item/cavity_item = AM
			if(length(bodypart_affected?.cavity_items) && (cavity_item in bodypart_affected.cavity_items))
				cavity_item.stored_in = null
				bodypart_affected.cavity_items -= cavity_item
			if(istype(cavity_item))
				cavity_item.stored_in = null
			update_insides()
			return
		var/mob/living/carbon/carbon_parent = parent
		if(!carbon_parent.IsUnconscious() && (carbon_parent.get_chem_effect(CE_PAINKILLER) < 50))
			carbon_parent.death_scream()
			if(!CHECK_BITFIELD(O.organ_flags, ORGAN_CUT_AWAY))
				carbon_parent.custom_pain("MY [capitalize(O.name)] HURTS!", rand(30, 40))
		var/violent_bone_removal = FALSE
		if(!CHECK_BITFIELD(O.organ_flags, ORGAN_CUT_AWAY) && bodypart_affected)
			if(istype(O, /obj/item/organ/bone))
				violent_bone_removal = TRUE
			for(var/datum/injury/fucked in bodypart_affected.injuries)
				if(fucked.damage_type == WOUND_SLASH)
					fucked.unclamp_injury()
					fucked.open_injury(rand(10, 20))
			O.applyOrganDamage(rand(10, 20))
		O.stored_in = null
		O.Remove(O.owner, FALSE)
		O.organ_flags |= ORGAN_CUT_AWAY
		refresh_mob_views()
		playsound(O, pick(rustle_sound), 50, 1, -5)
		update_insides()
		//oof
		if(violent_bone_removal)
			if(bodypart_affected?.can_dismember())
				bodypart_affected.apply_dismember(WOUND_PIERCE, FALSE, FALSE)
			else
				bodypart_affected.receive_damage(50)
		return

//AAAAAAAA
/datum/component/storage/concrete/organ/mousedrop_onto(datum/source, atom/over_object, mob/M)
	var/mob/A = parent
	A.add_fingerprint(M)
	if(!over_object)
		return FALSE
	if(ismecha(M.loc)) // stops inventory actions in a mech
		return FALSE
	// this must come before the screen objects only block, dunno why it wasn't before
	var/mob/living/L = M
	var/mob/living/carbon/carbon_mob = parent
	if(istype(L) && IS_GRAB_INTENT(L, null))
		assign_bodypart(carbon_mob.get_bodypart(check_zone(L.zone_selected)))
	if(!istype(L) || !IS_GRAB_INTENT(L, null) || !is_accessible(L))
		return FALSE
	if(isliving(over_object) && (check_zone(L.zone_selected) == check_zone(bodypart_affected.body_zone)))
		update_insides()
		user_show_to_mob(M)
		return COMPONENT_NO_MOUSEDROP


code:
/datum/component/storage
	screen_max_columns = INFINITY
	screen_max_rows = 8
	screen_pixel_x = 0
	screen_pixel_y = 0
	screen_start_x = 1
	screen_start_y = 11
	var/maximum_depth = 1
	var/storage_flags = NONE

/datum/component/storage/Initialize(datum/component/storage/concrete/master)
	. = ..()
	if(!.)
		RegisterSignal(parent, COMSIG_STORAGE_CAN_USER_TAKE, .proc/can_user_take)

/datum/component/storage/orient2hud()
	var/atom/real_location = real_location()
	var/adjusted_contents = length(real_location.contents)

	//Numbered contents display
	var/list/datum/numbered_display/numbered_contents
	if(display_numerical_stacking)
		numbered_contents = _process_numerical_display()
		adjusted_contents = length(numbered_contents)

	var/rows = clamp(max_items, 1, screen_max_rows)
	var/columns = clamp(CEILING(adjusted_contents / rows, 1), 1, screen_max_columns)
	standard_orient_objs(rows, columns, numbered_contents)

/datum/component/storage/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents)
	boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y-rows+1]:[screen_pixel_y]"
	var/cx = screen_start_x
	var/cy = screen_start_y
	if(islist(numerical_display_contents))
		for(var/type in numerical_display_contents)
			var/datum/numbered_display/ND = numerical_display_contents[type]
			ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
			ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
			ND.sample_object.maptext = MAPTEXT("[(ND.number > 1)? "[ND.number]" : ""]")
			ND.sample_object.plane = ABOVE_HUD_PLANE
			cy--
			if(screen_start_y - cy >= rows)
				cy = screen_start_y
				cx++
				if(cx - screen_start_x >= cols)
					break
	else
		var/atom/real_location = real_location()
		for(var/obj/O in real_location)
			if(QDELETED(O))
				continue
			O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
			O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
			O.maptext = ""
			O.plane = ABOVE_HUD_PLANE
			cy--
			if(screen_start_y - cy >= rows)
				cy = screen_start_y
				cx++
				if(cx - screen_start_x >= cols)
					break
	closer.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y+1]:[screen_pixel_y]"

/datum/component/storage/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE, worn_check = FALSE)
	if((!force && !can_be_inserted(I, TRUE, M, worn_check)) || (I == parent))
		return FALSE
	return handle_item_insertion(I, silent, M)

/datum/component/storage/can_be_inserted(obj/item/I, stop_messages, mob/M, worn_check = FALSE)
	if(!istype(I) || (I.item_flags & ABSTRACT))
		return FALSE //Not an item
	if(I == parent)
		return FALSE //no paradoxes for you
	var/atom/real_location = real_location()
	var/atom/host = parent
	if(real_location == I.loc)
		return FALSE //Means the item is already in the storage item
	if(locked)
		if(M && !stop_messages)
			host.add_fingerprint(M)
			to_chat(M, span_warning("[host] seems to be locked!"))
		return FALSE
	if(worn_check && !worn_check(parent, M))
		host.add_fingerprint(M)
		return FALSE
	if(real_location.contents.len >= max_items)
		if(!stop_messages)
			to_chat(M, span_warning("[host] is full, make some space!"))
		return FALSE //Storage item is full
	if(length(can_hold))
		if(!is_type_in_typecache(I, can_hold))
			if(!stop_messages)
				to_chat(M, span_warning("[host] cannot hold [I]!"))
			return FALSE
	if(is_type_in_typecache(I, cant_hold) || HAS_TRAIT(I, TRAIT_NO_STORAGE_INSERT) || (can_hold_trait && !HAS_TRAIT(I, can_hold_trait))) //Items which this container can't hold.
		if(!stop_messages)
			to_chat(M, span_warning("[host] cannot hold [I]!"))
		return FALSE
	if(I.w_class > max_w_class && !is_type_in_typecache(I, exception_hold))
		if(!stop_messages)
			to_chat(M, span_warning("[I] is too big for [host]!"))
		return FALSE
	var/atom/recursive_loc = real_location?.loc
	var/depth = 0
	while(isatom(recursive_loc) && !isturf(recursive_loc) && !isarea(recursive_loc))
		depth += 1
		var/datum/component/storage/biggerfish = recursive_loc.GetComponent(/datum/component/storage)
		if(biggerfish && !istype(biggerfish, /datum/component/storage/concrete/organ))
			if(biggerfish.max_w_class < max_w_class) //return false if we are inside of another container, and that container has a smaller max_w_class than us (like if we're a bag in a box)
				if(!stop_messages)
					to_chat(M, span_warning("[I] can't fit in [host] while [recursive_loc] is in the way!"))
				return FALSE
			else if(worn_check && !biggerfish.worn_check(I, M, stop_messages))
				if(!stop_messages)
					to_chat(M, span_warning("[I] can't fit in [host] while [recursive_loc] is in the way!"))
				return FALSE
			else if(biggerfish.maximum_depth < depth)
				if(!stop_messages)
					to_chat(M, span_warning("[I] can't fit in [host] while [recursive_loc] is in the way!"))
				return FALSE
		recursive_loc = recursive_loc.loc
	var/sum_w_class = I.w_class
	for(var/obj/item/_I in real_location)
		sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it.
	if(sum_w_class > max_combined_w_class)
		if(!stop_messages)
			to_chat(M, span_warning("[I] won't fit in [host], make some space!"))
		return FALSE
	if(isitem(host))
		var/obj/item/IP = host
		var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage)
		if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting)
			if(!stop_messages)
				to_chat(M, span_warning("[IP] cannot hold [I] as it's a storage item of the same size!"))
			return FALSE //To prevent the stacking of same sized storage items.
	if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry.
		if(!stop_messages)
			to_chat(M, span_warning("\The [I] is stuck to your hand, you can't put it in \the [host]!"))
		return FALSE
	var/datum/component/storage/concrete/master = master()
	if(!istype(master))
		return FALSE
	return master.slave_can_insert_object(src, I, stop_messages, M)

/datum/component/storage/proc/on_equipped(obj/item/source, mob/user, slot)
	SIGNAL_HANDLER

	var/atom/A = parent
	for(var/mob/living/L in can_see_contents())
		if(!L.CanReach(A))
			hide_from(L)
	if(!worn_check_aggressive(parent, user, TRUE))
		hide_from(user)
	update_actions()

/datum/component/storage/proc/worn_check(obj/item/I, mob/M, no_message = FALSE)
	. = TRUE
	if(!istype(I) || !istype(M) || !CHECK_BITFIELD(storage_flags, STORAGE_NO_WORN_ACCESS|STORAGE_NO_EQUIPPED_ACCESS))
		return TRUE

	if(storage_flags & STORAGE_NO_EQUIPPED_ACCESS && (I.item_flags & IN_INVENTORY))
		if(!no_message)
			to_chat(M, span_warning("[I] is too bulky! I need to set it down before I can access it's contents!"))
		return FALSE
	else if(storage_flags & STORAGE_NO_WORN_ACCESS && (I.item_flags & IN_INVENTORY) && !(I in M.held_items))
		if(!no_message)
			to_chat(M, span_warning("My arms aren't long enough to reach into [I] while wearing it!"))
		return FALSE

/datum/component/storage/proc/worn_check_aggressive(obj/item/I, mob/M, no_message = FALSE)
	. = TRUE
	if(!istype(I) || !istype(M) || !CHECK_BITFIELD(storage_flags, STORAGE_NO_WORN_ACCESS|STORAGE_NO_EQUIPPED_ACCESS))
		return TRUE

	if(storage_flags & STORAGE_NO_EQUIPPED_ACCESS)
		if(!no_message)
			to_chat(M, span_warning("[I] is too bulky! I need to set it down before I can access it's contents!"))
		return FALSE
	else if(storage_flags & STORAGE_NO_WORN_ACCESS && !(I in M.held_items))
		if(!no_message)
			to_chat(M, span_warning("My arms aren't long enough to reach into [I] while wearing it!"))
		return FALSE

/datum/component/storage/proc/can_user_take(obj/item/I, mob/user, no_message = FALSE)
	. = FALSE
	if(!worn_check(parent, user, no_message))
		return FALSE
	if(!istype(src, /datum/component/storage/concrete/organ))
		var/atom/real_location = real_location()
		var/atom/recursive_loc = real_location?.loc
		var/depth = 0
		while(isatom(recursive_loc) && !isturf(recursive_loc) && !isarea(recursive_loc))
			depth += 1
			var/datum/component/storage/biggerfish = recursive_loc.GetComponent(/datum/component/storage)
			if(biggerfish && !istype(biggerfish, /datum/component/storage/concrete/organ))
				if(!biggerfish.worn_check(biggerfish.parent, user, TRUE))
					if(!no_message)
						to_chat(user, span_warning("[recursive_loc] is in the way!"))
					return
				else if(biggerfish.maximum_depth < depth)
					if(!no_message)
						to_chat(user, span_warning("[recursive_loc] is in the way!"))
					return
			recursive_loc = recursive_loc.loc
	return TRUE

/datum/component/storage/proc/get_carry_weight()
	. = 0
	//we do need a typecheck here
	for(var/obj/item/thing in contents())
		. += thing.get_carry_weight()


code:
/datum/component/two_handed/RegisterWithParent()
	. = ..()
	RegisterSignal(parent, COMSIG_TWOHANDED_WIELD_CHECK, .proc/wield_check)

/datum/component/two_handed/UnregisterFromParent()
	. = ..()
	UnregisterSignal(parent, COMSIG_TWOHANDED_WIELD_CHECK)

/datum/component/two_handed/wield(mob/living/carbon/user)
	. = ..()
	if(!istype(user))
		return
	if(wielded)
		user.wield_ui_on()
	else
		user.wield_ui_off()

/datum/component/two_handed/unwield(mob/living/carbon/user, show_message, can_drop)
	. = ..()
	if(!istype(user))
		return
	if(wielded)
		user.wield_ui_on()
	else
		user.wield_ui_off()

/datum/component/two_handed/proc/wield_check()
	return wielded

/obj/item/offhand
	icon = 'modular_septic/icons/hud/quake/grab.dmi'
	icon_state = "offhand"
	base_icon_state = "offhand"

//Outline looks weird on offhand
/obj/item/offhand/apply_outline(outline_color)
	return


code:
/datum/cultural_info/birthsign
	var/patron_saint = ""
	var/favored_profession = "Any"

/datum/cultural_info/birthsign/get_extra_desc(more)
	. = ..()
	if(patron_saint)
		. += "
Patron Saint: [patron_saint]"
	if(favored_profession)
		. += "
Favored Profession: [favored_profession]"


code:
/datum/cultural_info/birthsign/starless
	name = "Starless"
	description = "You were born under the darkest of skies. You have no real defining characteristics."
	icon_state = "none"

/datum/cultural_info/birthsign/warrior
	name = "The Warrior"
	patron_saint = "St. Joan of Arc"
	favored_profession = "Ordinator"
	description = "Courageous, fierce, and morally inflexible - Those born under the sign of the warrior cannot ever \
		accept defeat in the face of injustice. Although easy to anger, they are rarely cruel; When they hurt someone, \
		it is never in their intention to do so - It is always a crime of passion. Friends of the warrior can always \
		count on them - Their loyalty is unwavering, and they will stop at nothing to defend those whom they love. \
		When overcome in battle, the warrior would rather die standing than live kneeling."
	icon_state = "warrior"
	attribute_sheet = /datum/attribute_holder/sheet/birthsign/warrior

/datum/cultural_info/birthsign/mage
	name = "The Mage"
	patron_saint = "St. Thomas of Aquino"
	favored_profession = "Technologist"
	description = "Wise beyond his years, the mage is always dedicated to improving his craft. Often introspective and \
		withdrawn, the mage will often lose himself in his research. Trying to anger him is a fool's errand, as his stoic \
		nature cares little for what he cannot control. The mage is exceptionally honest, and cannot bare the thought of \
		lying - Blunt to a fault, he is considered rude by his peers, even though his only intentions are to seek the truth \
		wherever it may be."
	icon_state = "mage"
	attribute_sheet = /datum/attribute_holder/sheet/birthsign/mage


code:
/datum/cultural_info
	/// Name of the cultural thing, be it place, faction, or culture
	var/name
	/// It's description
	var/description
	/// Icon
	var/icon = 'modular_septic/icons/ui_icons/preferences/culture.dmi'
	/// Icon state
	var/icon_state = "none"
	/// How many quirk points this costs
	var/value = 0
	/// It'll force people to know this language if they've picked this cultural thing
	var/list/required_langs
	/// This will allow people to pick extra languages
	var/list/additional_langs
	/// If we modify stats, we should use a sheet for it
	var/attribute_sheet

/datum/cultural_info/proc/get_extra_desc(more = FALSE)
	. = ""
	if(required_langs)
		var/list/required_lang = list()
		for(var/thing in required_langs)
			var/datum/language/lang_datum = thing
			required_lang += initial(lang_datum.name)
		. += "
Required Languages: [english_list(required_lang)]"
	if(!more)
		return
	if(additional_langs)
		var/list/additional_lang = list()
		for(var/thing in additional_langs)
			var/datum/language/lang_datum = thing
			additional_lang += initial(lang_datum.name)
		. += "
Optional Languages: [english_list(additional_lang)]"

/datum/cultural_info/proc/apply(mob/living/carbon/human/H)
	if(attribute_sheet && H.attributes)
		H.attributes.add_sheet(attribute_sheet)


code:
/datum/disease
	var/hopelessness = "Mild"


code:
/datum/dna
	features = MANDATORY_FEATURE_LIST
	///Mutant parts of the DNA's owner - This is for storing their original state for re-creating the character; They'll get changed on species mutation
	var/list/list/mutant_bodyparts = list()
	///Body markings of the DNA's owner - This is for storing their original state for re-creating the character; They'll get changed on species mutation
	var/list/list/body_markings = list()
	///Current body size, used for proper re-sizing and keeping track of that
	var/current_body_size = BODY_SIZE_NORMAL

/datum/dna/initialize_dna(newblood_type, skip_index = FALSE)
	if(newblood_type)
		blood_type = newblood_type
	unique_enzymes = generate_unique_enzymes()
	unique_identity = generate_unique_identity()
	if(!skip_index) //I hate this
		generate_dna_blocks()
	features = species.get_random_features()
	mutant_bodyparts = species.get_random_mutant_bodyparts(features)
	unique_features = generate_unique_features()

/datum/dna/proc/generate_unique_features()
	var/list/data = list()

	if(features["mcolor"])
		data += sanitize_hexcolor(features["mcolor"], DNA_BLOCK_SIZE, FALSE)
	else
		data += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters)
	if(features["mcolor2"])
		data += sanitize_hexcolor(features["mcolor2"], DNA_BLOCK_SIZE, FALSE)
	else
		data += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters)
	if(features["mcolor3"])
		data += sanitize_hexcolor(features["mcolor3"], DNA_BLOCK_SIZE, FALSE)
	else
		data += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters)
	if(features["ethcolor"])
		data += sanitize_hexcolor(features["ethcolor"], DNA_BLOCK_SIZE, FALSE)
	else
		data += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters)
	if(features["skin_color"])
		data += sanitize_hexcolor(features["skin_color"], DNA_BLOCK_SIZE, FALSE)
	else
		data += random_string(DNA_BLOCK_SIZE,GLOB.hex_characters)
	for(var/key in GLOB.genetic_accessories)
		if(LAZYACCESS(mutant_bodyparts, key) && ((LAZYACCESSASSOC(mutant_bodyparts, key, MUTANT_INDEX_NAME) in GLOB.genetic_accessories[key])) )
			var/list/accessories_for_key = GLOB.genetic_accessories[key]
			data += construct_block(accessories_for_key.Find(mutant_bodyparts[key][MUTANT_INDEX_NAME]), accessories_for_key.len)
			var/colors_to_randomize = DNA_BLOCKS_PER_FEATURE-1
			for(var/color in mutant_bodyparts[key][MUTANT_INDEX_COLOR])
				colors_to_randomize--
				data += sanitize_hexcolor(color, DNA_BLOCK_SIZE, FALSE)
			if(colors_to_randomize)
				data += random_string(DNA_BLOCK_SIZE*colors_to_randomize,GLOB.hex_characters)
		else
			data += random_string(DNA_BLOCK_SIZE*DNA_BLOCKS_PER_FEATURE,GLOB.hex_characters)
	for(var/zone in GLOB.marking_zones)
		if(body_markings[zone])
			data += construct_block(LAZYLEN(body_markings[zone])+1, MAXIMUM_MARKINGS_PER_LIMB+1)
			var/list/marking_list = GLOB.body_markings_per_limb[zone]
			var/markings_to_randomize = MAXIMUM_MARKINGS_PER_LIMB
			for(var/marking in body_markings[zone])
				markings_to_randomize--
				data += construct_block(marking_list.Find(marking), marking_list.len)
				data += sanitize_hexcolor(body_markings[zone][marking], DNA_BLOCK_SIZE, FALSE)
			if(markings_to_randomize)
				data += random_string(DNA_BLOCK_SIZE*markings_to_randomize*DNA_BLOCKS_PER_MARKING,GLOB.hex_characters)
		else
			data += construct_block(1, MAXIMUM_MARKINGS_PER_LIMB+1)
			data += random_string(DNA_BLOCK_SIZE*MAXIMUM_MARKINGS_PER_LIMB*DNA_BLOCKS_PER_MARKING,GLOB.hex_characters)
	return data.Join()

/datum/dna/proc/update_uf_block(blocknumber)
	if(!blocknumber)
		CRASH("UF block index is null")
	if(blocknumber<1 || blocknumber>DNA_FEATURE_BLOCKS)
		CRASH("UF block index out of bounds")
	if(!ishuman(holder))
		CRASH("Non-human mobs shouldn't have DNA")
	if(blocknumber <= DNA_MANDATORY_COLOR_BLOCKS)
		switch(blocknumber)
			if(DNA_MUTANT_COLOR_BLOCK)
				setblock(unique_features, blocknumber, sanitize_hexcolor(features["mcolor"], DNA_BLOCK_SIZE, FALSE))
			if(DNA_MUTANT_COLOR_2_BLOCK)
				setblock(unique_features, blocknumber, sanitize_hexcolor(features["mcolor2"], DNA_BLOCK_SIZE, FALSE))
			if(DNA_MUTANT_COLOR_3_BLOCK)
				setblock(unique_features, blocknumber, sanitize_hexcolor(features["mcolor3"], DNA_BLOCK_SIZE, FALSE))
			if(DNA_ETHEREAL_COLOR_BLOCK)
				setblock(unique_features, blocknumber, sanitize_hexcolor(features["ethcolor"], DNA_BLOCK_SIZE, FALSE))
			if(DNA_SKIN_COLOR_BLOCK)
				setblock(unique_features, blocknumber, sanitize_hexcolor(features["skin_color"], DNA_BLOCK_SIZE, FALSE))
	else if(blocknumber <= DNA_MANDATORY_COLOR_BLOCKS+(LAZYLEN(GLOB.genetic_accessories)*DNA_BLOCKS_PER_FEATURE))
		var/block_index = blocknumber - DNA_MANDATORY_COLOR_BLOCKS
		var/block_zero_index = block_index-1
		var/bodypart_index = (block_zero_index/DNA_BLOCKS_PER_FEATURE)+1
		var/color_index = block_zero_index%DNA_BLOCKS_PER_FEATURE
		var/key = GLOB.genetic_accessories[bodypart_index]
		if(LAZYACCESS(mutant_bodyparts, key))
			var/list/color_list = LAZYACCESSASSOC(mutant_bodyparts, key, MUTANT_INDEX_COLOR)
			if(color_index && color_index <= LAZYLEN(color_list))
				setblock(unique_features, blocknumber, sanitize_hexcolor(color_list[color_index], DNA_BLOCK_SIZE, FALSE))
			else
				var/list/accessories_for_key = GLOB.genetic_accessories[key]
				if(LAZYACCESSASSOC(mutant_bodyparts, key, MUTANT_INDEX_NAME) in accessories_for_key)
					setblock(unique_features, blocknumber, construct_block(mutant_bodyparts.Find(mutant_bodyparts[key][MUTANT_INDEX_NAME]), LAZYLEN(accessories_for_key)))
	else
		var/block_index = blocknumber - (DNA_MANDATORY_COLOR_BLOCKS+(LAZYLEN(GLOB.genetic_accessories)*DNA_BLOCKS_PER_FEATURE))
		var/block_zero_index = block_index-1
		var/zone_index = (block_zero_index/DNA_BLOCKS_PER_MARKING_ZONE)+1
		var/zone = GLOB.marking_zones[zone_index]
		if(blocknumber == GLOB.dna_body_marking_blocks[zone])
			var/markings = 0
			if(body_markings[zone])
				markings = LAZYLEN(body_markings[zone])
			setblock(unique_features, blocknumber, construct_block(markings+1, MAXIMUM_MARKINGS_PER_LIMB+1))
		else
			var/color_block = ((block_zero_index%DNA_BLOCKS_PER_MARKING_ZONE)+1)%DNA_BLOCKS_PER_MARKING
			var/marking_index = (((block_zero_index-1)%DNA_BLOCKS_PER_MARKING_ZONE)/DNA_BLOCKS_PER_MARKING)+1
			if(body_markings[zone] && marking_index <= body_markings[zone].len)
				var/marking = body_markings[zone][marking_index]
				if(color_block)
					setblock(unique_features, blocknumber, sanitize_hexcolor(body_markings[zone][marking], DNA_BLOCK_SIZE, FALSE))
				else
					var/list/marking_list = GLOB.body_markings_per_limb[zone]
					setblock(unique_features, blocknumber, construct_block(marking_list.Find(marking), marking_list.len))

/datum/dna/proc/update_body_size()
	if(!holder || current_body_size == features["body_size"])
		return
	var/change_multiplier = features["body_size"] / current_body_size
	//We update the translation to make sure our character doesn't go out of the southern bounds of the tile
	var/translate = ((change_multiplier-1) * 32)/2
	holder.transform = holder.transform.Scale(change_multiplier)
	holder.transform = holder.transform.Translate(0, translate)
	current_body_size = features["body_size"]

/mob/living/carbon/set_species(datum/species/mrace, icon_update = TRUE, datum/preferences/pref_load, list/override_features, \
							list/override_mutantparts, list/override_markings, retain_features = FALSE, retain_mutantparts = FALSE, retain_markings = FALSE)
	if(mrace && has_dna())
		var/datum/species/new_race
		if(ispath(mrace))
			new_race = new mrace
		else if(istype(mrace))
			new_race = mrace
		else
			return
		deathsound = new_race.deathsound
		dna.species.on_species_loss(src, new_race, pref_load)
		var/datum/species/old_species = dna.species
		dna.species = new_race

		//BODYPARTS AND FEATURES
		if(!retain_features)
			var/list/new_features = MANDATORY_FEATURE_LIST
			if(LAZYLEN(override_features))
				for(var/thing in override_features)
					if(override_features[thing])
						new_features[thing] = override_features[thing]
			else
				var/list/random_features = new_race.get_random_features()
				for(var/thing in random_features)
					if(random_features[thing])
						new_features[thing] = random_features[thing]
			dna.features = new_features
		if(!retain_mutantparts)
			dna.mutant_bodyparts = override_mutantparts || new_race.get_random_mutant_bodyparts(dna.features)
		else
			var/list/list/random_parts = new_race.get_random_mutant_bodyparts(dna.features)
			var/list/list/compiled_list = list()
			for(var/key in (random_parts|dna.mutant_bodyparts))
				if(dna.mutant_bodyparts[key])
					compiled_list[key] = dna.mutant_bodyparts[key].Copy()
				else
					compiled_list[key] = random_parts[key].Copy()
		if(!retain_markings)
			dna.body_markings = override_markings || new_race.get_random_body_markings(dna.features)

		assimilate_dna_to_species()
		build_all_organs_from_dna()

		dna.unique_features = dna.generate_unique_features()
		dna.update_body_size()

		dna.species.on_species_gain(src, old_species, pref_load)
		//END OF BODYPARTS AND FEATURES

		if(ishuman(src))
			qdel(language_holder)
			var/species_holder = initial(mrace.species_language_holder)
			language_holder = new species_holder(src, pref_load)
		update_atom_languages()

/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, datum/preferences/pref_load, list/override_features, \
							list/override_mutantparts, list/override_markings, retain_features = FALSE, retain_mutantparts = FALSE)
	. = ..()
	if(icon_update)
		update_body()
		update_hair()
		update_body_parts()
		update_mutations_overlay()// no lizard with human hulk overlay please.

/mob/living/carbon/proc/assimilate_dna_to_species()
	if(!has_dna())
		CRASH("[src] does not have DNA")
	dna.species.body_markings = LAZYCOPY(dna.body_markings)
	var/list/bodyparts_to_add = LAZYCOPY(dna.mutant_bodyparts)
	for(var/key in bodyparts_to_add)
		var/datum/sprite_accessory/accessory = LAZYACCESSASSOC(GLOB.sprite_accessories, key, bodyparts_to_add[key][MUTANT_INDEX_NAME])
		if(!accessory?.factual)
			bodyparts_to_add -= key
			continue
	dna.species.mutant_bodyparts = bodyparts_to_add

/mob/living/carbon/proc/build_all_organs_from_dna()
	for(var/obj/item/organ/organ as anything in internal_organs)
		if(organ.mutantpart_key)
			organ.build_from_dna(dna, organ.mutantpart_key)

/mob/living/carbon/updateappearance(icon_update=TRUE, mutcolor_update=FALSE, mutations_overlay_update=FALSE)
	if(!has_dna())
		return

	switch(deconstruct_block(getblock(dna.unique_identity, DNA_GENDER_BLOCK), 3))
		if(G_MALE)
			gender = MALE
		if(G_FEMALE)
			gender = FEMALE
		else
			gender = PLURAL

/mob/living/carbon/human/updateappearance(icon_update=TRUE, mutcolor_update=FALSE, mutations_overlay_update=FALSE)
	. = ..()
	var/structure = dna.unique_identity
	hair_color = sanitize_hexcolor(getblock(structure, DNA_HAIR_COLOR_BLOCK), DNA_BLOCK_SIZE, FALSE)
	facial_hair_color = sanitize_hexcolor(getblock(structure, DNA_FACIAL_HAIR_COLOR_BLOCK), DNA_BLOCK_SIZE, FALSE)
	skin_tone = GLOB.skin_tones[deconstruct_block(getblock(structure, DNA_SKIN_TONE_BLOCK), LAZYLEN(GLOB.skin_tones))]
	left_eye_color = sanitize_hexcolor(getblock(structure, DNA_LEFT_EYE_COLOR_BLOCK), DNA_BLOCK_SIZE, FALSE)
	right_eye_color = sanitize_hexcolor(getblock(structure, DNA_LEFT_EYE_COLOR_BLOCK), DNA_BLOCK_SIZE, FALSE)
	facial_hairstyle = GLOB.facial_hairstyles_list[deconstruct_block(getblock(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), LAZYLEN(GLOB.facial_hairstyles_list))]
	hairstyle = GLOB.hairstyles_list[deconstruct_block(getblock(structure, DNA_HAIRSTYLE_BLOCK), LAZYLEN(GLOB.hairstyles_list))]
	var/features = dna.unique_features
	if(dna.features["mcolor"])
		dna.features["mcolor"] = sanitize_hexcolor(getblock(features, DNA_MUTANT_COLOR_BLOCK), DNA_BLOCK_SIZE, FALSE)
	if(dna.features["mcolor2"])
		dna.features["mcolor2"] = sanitize_hexcolor(getblock(features, DNA_MUTANT_COLOR_2_BLOCK), DNA_BLOCK_SIZE, FALSE)
	if(dna.features["mcolor3"])
		dna.features["mcolor3"] = sanitize_hexcolor(getblock(features, DNA_MUTANT_COLOR_3_BLOCK), DNA_BLOCK_SIZE, FALSE)
	if(dna.features["ethcolor"])
		dna.features["ethcolor"] = sanitize_hexcolor(getblock(features, DNA_ETHEREAL_COLOR_BLOCK), DNA_BLOCK_SIZE, FALSE)
	if(dna.features["skin_color"])
		dna.features["skin_color"] = sanitize_hexcolor(getblock(features, DNA_SKIN_COLOR_BLOCK), DNA_BLOCK_SIZE, FALSE)
	for(var/key in GLOB.genetic_accessories)
		if(dna.mutant_bodyparts[key] && (dna.mutant_bodyparts[key][MUTANT_INDEX_NAME] in GLOB.genetic_accessories[key]))
			var/bodypart_block = GLOB.dna_mutant_bodypart_blocks[key]
			var/list/accessories_for_key = GLOB.sprite_accessories[key]
			var/list/possible_accessories = GLOB.genetic_accessories[key]
			var/accessory_name = GLOB.genetic_accessories[key][deconstruct_block(getblock(features, bodypart_block), possible_accessories.len)]
			dna.mutant_bodyparts[key][MUTANT_INDEX_NAME] = accessory_name
			var/datum/sprite_accessory/accessory_to_apply = accessories_for_key[accessory_name]
			if(accessory_to_apply.factual)
				switch(accessory_to_apply.color_src)
					if(USE_ONE_COLOR)
						dna.mutant_bodyparts[key][MUTANT_INDEX_COLOR] = list(sanitize_hexcolor(getblock(features,bodypart_block+1), DNA_BLOCK_SIZE, FALSE))
					if(USE_MATRIXED_COLORS)
						dna.mutant_bodyparts[key][MUTANT_INDEX_COLOR] = list()
						dna.mutant_bodyparts[key][MUTANT_INDEX_COLOR] += sanitize_hexcolor(getblock(features,bodypart_block+1), DNA_BLOCK_SIZE, FALSE)
						dna.mutant_bodyparts[key][MUTANT_INDEX_COLOR] += sanitize_hexcolor(getblock(features,bodypart_block+2), DNA_BLOCK_SIZE, FALSE)
						dna.mutant_bodyparts[key][MUTANT_INDEX_COLOR] += sanitize_hexcolor(getblock(features,bodypart_block+3), DNA_BLOCK_SIZE, FALSE)
	for(var/zone in GLOB.marking_zones)
		var/marking_count_block = GLOB.dna_body_marking_blocks[zone]
		var/first_marking_block = marking_count_block+1
		var/marking_count = deconstruct_block(getblock(features, marking_count_block), 4)-1
		if(!marking_count && dna.body_markings[zone])
			dna.body_markings -= zone
		if(marking_count)
			if(!dna.body_markings[zone])
				dna.body_markings[zone] = list()
			dna.body_markings[zone].len = marking_count
			for(var/i in 1 to marking_count)
				var/marking = GLOB.body_markings_per_limb[zone][deconstruct_block(getblock(features, first_marking_block+(i-1)*DNA_BLOCKS_PER_MARKING), LAZYLEN(GLOB.body_markings_per_limb[zone]))]
				dna.body_markings[zone][i] = marking
				dna.body_markings[zone][marking] = sanitize_hexcolor(getblock(features, first_marking_block+(i-1)*DNA_BLOCKS_PER_MARKING+1), DNA_BLOCK_SIZE, FALSE)

	assimilate_dna_to_species()
	build_all_organs_from_dna()

	if(icon_update)
		// We want 'update_body_parts()' to be called only if mutcolor_update is TRUE, so no 'update_body()' here.
		dna.species.handle_body(src)
		update_hair()
		if(mutcolor_update)
			update_body_parts()
		if(mutations_overlay_update)
			update_mutations_overlay()


code:
/datum/element/clingable
	id_arg_index = 3
	element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH

	///Skill used to cling to this
	var/clinging_skill = SKILL_CLIMBING
	///Skill level required to cling to this
	var/clinging_requirement = 3

/datum/element/clingable/Attach(datum/target, _clinging_skill, _clinging_requirement)
	. = ..()
	if(!ismovable(target) && !isturf(target))
		return ELEMENT_INCOMPATIBLE
	clinging_skill = _clinging_skill
	clinging_requirement = _clinging_requirement
	RegisterSignal(target, COMSIG_CLICK_MIDDLE, .proc/on_middle_click)
	RegisterSignal(target, COMSIG_CLINGABLE_CHECK, .proc/clingable_check)

/datum/element/clingable/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, COMSIG_CLICK_MIDDLE)
	UnregisterSignal(source, COMSIG_CLINGABLE_CHECK)

/datum/element/clingable/proc/clingable_check(atom/source, mob/user)
	if(GET_MOB_ATTRIBUTE_VALUE(user, clinging_skill) >= clinging_requirement)
		return TRUE

/datum/element/clingable/proc/on_middle_click(atom/source, mob/living/carbon/user)
	if(!source.Adjacent(user))
		return
	if(!istype(user) || !COOLDOWN_FINISHED(user, next_move))
		to_chat(src, click_fail_msg())
		return
	if(GET_MOB_ATTRIBUTE_VALUE(user, clinging_skill) < clinging_requirement)
		to_chat(user, span_warning("I don't know how to cling to that."))
		return
	else if(user.usable_hands < user.default_num_hands)
		to_chat(user, span_warning("I need [user.default_num_hands] hands to cling to [source]."))
		return
	else if(user.get_active_held_item() || user.get_inactive_held_item())
		to_chat(user, span_warning("I need all of my hands free."))
		return
	else if(user.body_position == LYING_DOWN)
		to_chat(user, span_warning("I need to stand up."))
		return
	else if(HAS_TRAIT(user, TRAIT_MOVE_VENTCRAWLING))
		to_chat(user, span_warning("Not while ventcrawling."))
		return
	source.add_fingerprint(user)
	user.changeNext_move(CLICK_CD_CLING)
	to_chat(user, span_notice("I cling onto [source]."))
	user.face_atom(source)
	user.AddComponent(/datum/component/clinging, source)
	return COMPONENT_CANCEL_CLICK_MIDDLE


code:
/datum/element/decal/blood/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
	var/obj/item/I = source
	if(!_icon)
		_icon = 'modular_septic/icons/effects/blood.dmi'
	if(!_icon_state)
		_icon_state = "itemblood"
	var/icon = I.icon
	var/icon_state = I.icon_state
	if(!icon || !icon_state)
		// It's something which takes on the look of other items, probably
		icon = I.icon
		icon_state = I.icon_state
	var/static/list/blood_splatter_appearances = list()
	//try to find a pre-processed blood-splatter. otherwise, make a new one
	var/index = "[REF(icon)]-[icon_state]"
	pic = blood_splatter_appearances[index]
	if(!pic)
		var/icon/blood_splatter_icon = icon(I.icon, I.icon_state, , 1)
		blood_splatter_icon.Blend("#ffffff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
		blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
		pic = mutable_appearance(blood_splatter_icon, I.icon_state)
		blood_splatter_appearances[index] = pic
	return TRUE

/datum/element/decal/blood/get_examine_name(datum/source, mob/user, list/override)
	var/atom/A = source
	override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
	override[EXAMINE_POSITION_BEFORE] = " blood-stained "
	return COMPONENT_EXNAME_CHANGED


code:
/datum/element/decal/cum

/datum/element/decal/cum/Attach(datum/target, _icon, _icon_state, _dir, _plane, _layer, _alpha, _color, _smoothing, _cleanable=CLEAN_TYPE_BLOOD, _description, mutable_appearance/_pic)
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE

	. = ..()
	RegisterSignal(target, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name, TRUE)

/datum/element/decal/cum/Detach(atom/source)
	UnregisterSignal(source, COMSIG_ATOM_GET_EXAMINE_NAME)
	return ..()

/datum/element/decal/cum/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
	var/obj/item/I = source
	if(!_icon)
		_icon = 'modular_septic/icons/effects/cum.dmi'
	if(!_icon_state)
		_icon_state = "itemcum"
	var/icon = I.icon
	var/icon_state = I.icon_state
	if(!icon || !icon_state)
		// It's something which takes on the look of other items, probably
		icon = I.icon
		icon_state = I.icon_state
	var/static/list/cum_splatter_appearances = list()
	//try to find a pre-processed blood-splatter. otherwise, make a new one
	var/index = "[REF(icon)]-[icon_state]"
	pic = cum_splatter_appearances[index]

	if(!pic)
		var/icon/cum_splatter_icon = icon(I.icon, I.icon_state, , 1)
		cum_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
		cum_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
		pic = mutable_appearance(cum_splatter_icon, I.icon_state)
		pic.color = COLOR_WHITE_CUM
		cum_splatter_appearances[index] = pic
	return TRUE

/datum/element/decal/cum/proc/get_examine_name(datum/source, mob/user, list/override)
	SIGNAL_HANDLER

	var/atom/A = source
	override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
	override[EXAMINE_POSITION_BEFORE] = " cum-stained "
	return COMPONENT_EXNAME_CHANGED


code:
/datum/element/decal/femcum

/datum/element/decal/femcum/Attach(datum/target, _icon, _icon_state, _dir, _plane, _layer, _alpha, _color, _smoothing, _cleanable=CLEAN_TYPE_BLOOD, _description, mutable_appearance/_pic)
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE
	. = ..()
	RegisterSignal(target, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name, TRUE)

/datum/element/decal/femcum/Detach(atom/source)
	UnregisterSignal(source, COMSIG_ATOM_GET_EXAMINE_NAME)
	return ..()

/datum/element/decal/femcum/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
	var/obj/item/I = source
	if(!_icon)
		_icon = 'modular_septic/icons/effects/femcum.dmi'
	if(!_icon_state)
		_icon_state = "itemfemcum"
	var/icon = I.icon
	var/icon_state = I.icon_state
	if(!icon || !icon_state)
		// It's something which takes on the look of other items, probably
		icon = I.icon
		icon_state = I.icon_state
	var/static/list/femcum_splatter_appearances = list()
	//try to find a pre-processed blood-splatter. otherwise, make a new one
	var/index = "[REF(icon)]-[icon_state]"
	pic = femcum_splatter_appearances[index]

	if(!pic)
		var/icon/femcum_splatter_icon = icon(I.icon, I.icon_state, , 1)
		femcum_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
		femcum_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
		pic = mutable_appearance(femcum_splatter_icon, I.icon_state)
		pic.color = COLOR_WHITE_FEMCUM
		femcum_splatter_appearances[index] = pic
	return TRUE

/datum/element/decal/femcum/proc/get_examine_name(datum/source, mob/user, list/override)
	SIGNAL_HANDLER

	var/atom/A = source
	override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
	override[EXAMINE_POSITION_BEFORE] = " squirt-stained "
	return COMPONENT_EXNAME_CHANGED


code:
/datum/element/decal/shit

/datum/element/decal/shit/Attach(datum/target, _icon, _icon_state, _dir, _plane, _layer, _alpha, _color, _smoothing, _cleanable=CLEAN_TYPE_BLOOD, _description, mutable_appearance/_pic)
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE

	. = ..()
	RegisterSignal(target, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name, TRUE)

/datum/element/decal/shit/Detach(atom/source)
	UnregisterSignal(source, COMSIG_ATOM_GET_EXAMINE_NAME)
	return ..()

/datum/element/decal/shit/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
	var/obj/item/I = source
	if(!_icon)
		_icon = 'modular_septic/icons/effects/shit.dmi'
	if(!_icon_state)
		_icon_state = "itempoo"
	var/icon = I.icon
	var/icon_state = I.icon_state
	if(!icon || !icon_state)
		// It's something which takes on the look of other items, probably
		icon = I.icon
		icon_state = I.icon_state
	var/static/list/shit_splatter_appearances = list()
	//try to find a pre-processed blood-splatter. otherwise, make a new one
	var/index = "[REF(icon)]-[icon_state]"
	pic = shit_splatter_appearances[index]

	if(!pic)
		var/icon/shit_splatter_icon = icon(I.icon, I.icon_state, , 1)
		shit_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
		shit_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
		pic = mutable_appearance(shit_splatter_icon, I.icon_state)
		shit_splatter_appearances[index] = pic
	return TRUE

/datum/element/decal/shit/proc/get_examine_name(datum/source, mob/user, list/override)
	SIGNAL_HANDLER

	var/atom/A = source
	override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
	override[EXAMINE_POSITION_BEFORE] = " shit-stained "
	return COMPONENT_EXNAME_CHANGED


code:
/datum/element/embed/proc/projectile_posthit(obj/projectile/projectile, atom/movable/firer, atom/hit, result, mode)
	SIGNAL_HANDLER

	if(!iscarbon(hit) || (result != BULLET_ACT_HIT) || (mode != PROJECTILE_PIERCE_NONE))
		return

	var/obj/item/payload = new payload_type(get_turf(hit))
	if(istype(payload, /obj/item/shrapnel/bullet))
		payload.name = projectile.name
	payload.embedding = projectile.embedding
	payload.updateEmbedding()
	var/mob/living/carbon/carbon_hit = hit
	var/obj/item/bodypart/limb = carbon_hit.get_bodypart(projectile.def_zone)
	//Oh no
	if(!limb)
		return

	// at this point we've created our shrapnel baby and set them up to embed in the target, we can now die in peace as they handle their embed try on their own
	if(payload.tryEmbed(limb))
		Detach(projectile)


code:
/datum/element/fireaxe_breaking
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2

	var/wield_needed

/datum/element/fireaxe_breaking/Attach(datum/target, wield_needed = TRUE)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE

	src.wield_needed = wield_needed
	RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, .proc/item_afterattack)

/datum/element/fireaxe_breaking/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, COMSIG_ITEM_AFTERATTACK)

/datum/element/fireaxe_breaking/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
	SIGNAL_HANDLER

	if(wield_needed && !SEND_SIGNAL(source, COMSIG_TWOHANDED_WIELD_CHECK))
		return
	if(proximity_flag)
		SEND_SIGNAL(target, COMSIG_FIREAXE_BRITTLE_BREAK, source)


code:
/datum/element/fireaxe_brittle

/datum/element/fireaxe_brittle/Attach(datum/target)
	. = ..()
	if(!isobj(target)) //we'll do walls one day, obj is fine for now
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_FIREAXE_BRITTLE_BREAK, .proc/do_break)

/datum/element/fireaxe_brittle/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, COMSIG_FIREAXE_BRITTLE_BREAK)

/datum/element/fireaxe_brittle/proc/do_break(obj/source, obj/item/weapon)
	SIGNAL_HANDLER

	source.atom_destruction(MELEE)


code:
/datum/element/multitool_emaggable

/datum/element/multitool_emaggable/Attach(datum/target)
	. = ..()
	if(!isatom(target))
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_CLICK_MIDDLE, .proc/try_hacking)

/datum/element/multitool_emaggable/Detach(datum/source, force)
	. = ..()
	UnregisterSignal(source, COMSIG_CLICK_MIDDLE)

/datum/element/multitool_emaggable/proc/try_hacking(atom/source, mob/user)
	var/mob/living/L = user
	if(!(L || L.mind || L.canUseTopic(source)))
		return FALSE
	var/obj/item/item = L.get_active_held_item()
	if(!item?.tool_behaviour == TOOL_MULTITOOL)
		return FALSE

	if(GET_MOB_ATTRIBUTE_VALUE_RAW(L, SKILL_ELECTRONICS) < 10)
		to_chat(L, span_warning("I don't know how to do this."))
		return
	var/electronic_level = GET_MOB_ATTRIBUTE_VALUE(L, SKILL_ELECTRONICS)

	source.audible_message(span_warning("\The [source] starts to beep sporadically!"))
	to_chat(L, span_notice("Okay, let's do this.."))
	if(!item.use_tool(source, L, 50))
		to_chat(L, span_warning("Gah, I can't focus like that!"))
		return
	if(L.diceroll(electronic_level) <= DICE_FAILURE)
		source.audible_message(span_warning("[source] pings successfully at defending the hack attempt!"))
		to_chat(L, fail_msg())
		return
	source.audible_message(span_warning("[source] starts to beep even more frantically!"))
	to_chat(L, span_notice("Almost there.."))
	if(!item.use_tool(source, L, 100))
		to_chat(L, span_warning("Gah, I can't focus like that!"))
		return
	if(L.diceroll(electronic_level) <= DICE_FAILURE)
		source.audible_message(span_warning("[source] pings successfully at defending the hack attempt!"))
		to_chat(L, span_warning("Damnit, I almost had it!"))
		return
	source.audible_message(span_warning("[source] beeps one last time..."))
	to_chat(L, span_notice("Success."))
	source.emag_act(L)
	return COMPONENT_CANCEL_CLICK_MIDDLE


code:
/**
 * Liquid Height element; for dynamically applying liquid blockages.
 *
 * Used for reinforced tables, sandbags, and the likes.
 */
/datum/element/liquids_height
	element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
	id_arg_index = 2

	///Height applied by this element
	var/height_applied

/datum/element/liquids_height/Attach(datum/target, height_applied)
	. = ..()
	if(!ismovable(target))
		return ELEMENT_INCOMPATIBLE

	src.height_applied = height_applied

	RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/on_target_move)
	var/atom/movable/movable_target = target
	if(isturf(movable_target.loc))
		var/turf/turf_loc = movable_target.loc
		turf_loc.liquid_height += height_applied
		turf_loc.reasses_liquids()

/datum/element/liquids_height/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
	var/atom/movable/movable_target = target
	if(isturf(movable_target.loc))
		var/turf/turf_loc = movable_target.loc
		turf_loc.liquid_height -= height_applied
		turf_loc.reasses_liquids()

/datum/element/liquids_height/proc/on_target_move(atom/movable/source, atom/old_loc, direction, forced = FALSE)
	if(isturf(old_loc))
		var/turf/old_turf = old_loc
		old_turf.liquid_height -= height_applied
		old_turf.reasses_liquids()
	if(isturf(source.loc))
		var/turf/new_turf = source.loc
		new_turf.liquid_height += height_applied
		new_turf.reasses_liquids()


code:
/**
 * This element allows for items to interact with liquids on turfs.
 */
/datum/element/liquids_interaction
	element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
	id_arg_index = 2
	///Callback interaction called when the turf has some liquids on it
	var/datum/callback/interaction_callback

/datum/element/liquids_interaction/Attach(datum/target, on_interaction_callback)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE
	if(!interaction_callback)
		interaction_callback = CALLBACK(target, on_interaction_callback)
	RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, .proc/on_afterattack) //The only signal allowing item -> turf interaction

/datum/element/liquids_interaction/Detach(mob/living/target)
	UnregisterSignal(target, COMSIG_ITEM_AFTERATTACK)

/datum/element/liquids_interaction/proc/on_afterattack(obj/item/source, atom/target, mob/user)
	SIGNAL_HANDLER

	if(!isturf(target))
		return
	var/turf/turf_target = target
	if(!turf_target.liquids)
		return
	if(interaction_callback.Invoke(source, target, user, turf_target.liquids))
		return COMPONENT_CANCEL_ATTACK_CHAIN