code:
/datum/dog_fashion
	var/name
	var/desc
	var/emote_see
	var/emote_hear
	var/speak
	var/speak_emote

	// This isn't applied to the dog, but stores the icon_state of the
	// sprite that the associated item uses
	var/icon_file
	var/obj_icon_state
	var/obj_alpha
	var/obj_color

/datum/dog_fashion/New(mob/M)
	name = replacetext(name, "REAL_NAME", M.real_name)
	desc = replacetext(desc, "NAME", name)

/datum/dog_fashion/proc/apply(mob/living/simple_animal/pet/dog/D)
	if(name)
		D.name = name
	if(desc)
		D.desc = desc
	if(emote_see)
		D.emote_see = string_list(emote_see)
	if(emote_hear)
		D.emote_hear = string_list(emote_hear)
	if(speak)
		D.speak = string_list(speak)
	if(speak_emote)
		D.speak_emote = string_list(speak_emote)

/datum/dog_fashion/proc/get_overlay(dir)
	if(icon_file && obj_icon_state)
		var/image/corgI = image(icon_file, obj_icon_state, dir = dir)
		corgI.alpha = obj_alpha
		corgI.color = obj_color
		return corgI

/datum/dog_fashion/head
	icon_file = 'icons/mob/corgi_head.dmi'

/datum/dog_fashion/back
	icon_file = 'icons/mob/corgi_back.dmi'

/datum/dog_fashion/head/helmet
	name = "Sergeant REAL_NAME"
	desc = "The ever-loyal, the ever-vigilant."

/datum/dog_fashion/head/chef
	name = "Sous chef REAL_NAME"
	desc = "Your food will be taste-tested. All of it."

/datum/dog_fashion/head/captain
	name = "Captain REAL_NAME"
	desc = "Probably better than the last captain."

/datum/dog_fashion/head/kitty
	name = "Runtime"
	emote_see = list("coughs up a furball", "stretches")
	emote_hear = list("purrs")
	speak = list("Purrr", "Meow!", "MAOOOOOW!", "HISSSSS", "MEEEEEEW")
	desc = "They're a cute little kitty-cat! ... wait ... what the hell?"

/datum/dog_fashion/head/rabbit
	name = "Hoppy"
	emote_see = list("twitches their nose", "hops around a bit")
	desc = "This is Hoppy. They're a corgi-...urmm... bunny rabbit."

/datum/dog_fashion/head/beret
	name = "Yann"
	desc = "Mon dieu! C'est un chien!"
	speak = list("le woof!", "le bark!", "JAPPE!!")
	emote_see = list("cowers in fear.", "surrenders.", "plays dead.","looks as though there is a wall in front of them.")

/datum/dog_fashion/head/detective
	name = "Detective REAL_NAME"
	desc = "NAME sees through your lies..."
	emote_see = list("investigates the area.","sniffs around for clues.","searches for scooby snacks.","takes a candycorn from the hat.")

/datum/dog_fashion/head/nurse
	name = "Nurse REAL_NAME"
	desc = "NAME needs 100cc of beef jerky... STAT!"

/datum/dog_fashion/head/pirate
	name = "Pirate-title Pirate-name"
	desc = "Yaarghh!! Thar' be a scurvy dog!"
	emote_see = list("hunts for treasure.","stares coldly...","gnashes their tiny corgi teeth!")
	emote_hear = list("growls ferociously!", "snarls.")
	speak = list("Arrrrgh!!","Grrrrrr!")

/datum/dog_fashion/head/pirate/New(mob/M)
	..()
	name = "[pick("Ol'","Scurvy","Black","Rum","Gammy","Bloody","Gangrene","Death","Long-John")] [pick("kibble","leg","beard","tooth","poop-deck","Threepwood","Le Chuck","corsair","Silver","Crusoe")]"

/datum/dog_fashion/head/ushanka
	name = "Communist-title Realname"
	desc = "A follower of Karl Barx."
	emote_see = list("contemplates the failings of the capitalist economic model.", "ponders the pros and cons of vanguardism.")

/datum/dog_fashion/head/ushanka/New(mob/M)
	..()
	name = "[pick("Comrade","Commissar","Glorious Leader")] [M.real_name]"

/datum/dog_fashion/head/warden
	name = "Officer REAL_NAME"
	emote_see = list("drools.","looks for donuts.")
	desc = "Stop right there criminal scum!"

/datum/dog_fashion/head/blue_wizard
	name = "Grandwizard REAL_NAME"
	speak = list("YAP", "Woof!", "Bark!", "AUUUUUU", "EI NATH!")

/datum/dog_fashion/head/red_wizard
	name = "Pyromancer REAL_NAME"
	speak = list("YAP", "Woof!", "Bark!", "AUUUUUU", "ONI SOMA!")

/datum/dog_fashion/head/cardborg
	name = "Borgi"
	speak = list("Ping!","Beep!","Woof!")
	emote_see = list("goes rogue.", "sniffs out non-humans.")
	desc = "Result of robotics budget cuts."

/datum/dog_fashion/head/ghost
	name = "\improper Ghost"
	speak = list("WoooOOOooo~","AUUUUUUUUUUUUUUUUUU")
	emote_see = list("stumbles around.", "shivers.")
	emote_hear = list("howls!","groans.")
	desc = "Spooky!"
	obj_icon_state = "sheet"

/datum/dog_fashion/head/santa
	name = "Santa's Corgi Helper"
	emote_hear = list("barks Christmas songs.", "yaps merrily!")
	emote_see = list("looks for presents.", "checks their list.")
	desc = "They're very fond of milk and cookies."

/datum/dog_fashion/head/cargo_tech
	name = "Corgi Tech REAL_NAME"
	desc = "The reason your yellow gloves have chew-marks."

/datum/dog_fashion/head/reindeer
	name = "REAL_NAME the red-nosed Corgi"
	emote_hear = list("lights the way!", "illuminates.", "yaps!")
	desc = "They have a very shiny nose."

/datum/dog_fashion/head/sombrero
	name = "Segnor REAL_NAME"
	desc = "You must respect Elder Dogname"

/datum/dog_fashion/head/sombrero/New(mob/M)
	..()
	desc = "You must respect Elder [M.real_name]."

/datum/dog_fashion/head/hop
	name = "Lieutenant REAL_NAME"
	desc = "Can actually be trusted to not run off on their own."

/datum/dog_fashion/head/deathsquad
	name = "Trooper REAL_NAME"
	desc = "That's not red paint. That's real corgi blood."

/datum/dog_fashion/head/clown
	name = "REAL_NAME the Clown"
	desc = "Honkman's best friend."
	speak = list("HONK!", "Honk!")
	emote_see = list("plays tricks.", "slips.")

/datum/dog_fashion/back/deathsquad
	name = "Trooper REAL_NAME"
	desc = "That's not red paint. That's real corgi blood."

/datum/dog_fashion/head/festive
	name = "Festive REAL_NAME"
	desc = "Ready to party!"
	obj_icon_state = "festive"

/datum/dog_fashion/head/pumpkin/unlit
	name = "Headless HoP-less REAL_NAME"
	desc = "A spooky dog spirit of a beloved pet who lost their owner."
	obj_icon_state = "pumpkin0"
	speak = list("BOO!", "AUUUUUUU", "RAAARGH!")
	emote_see = list("shambles around.", "yaps ominously.", "shivers.")
	emote_hear = list("howls at the Moon.", "yaps at the crows!")

/datum/dog_fashion/head/pumpkin/lit
	obj_icon_state = "pumpkin1"

/datum/dog_fashion/head/blumpkin/unlit
	name = "Hue-less Headless HoP-less REAL_NAME"
	desc = "An evil dog spirit of a beloved pet that haunts your treats pantries!"
	obj_icon_state = "blumpkin0"
	speak = list("BOO!", "AUUUUUUU", "RAAARGH!")
	emote_see = list("shambles around.", "yaps ominously.", "shivers.")
	emote_hear = list("howls at the Moon.", "yaps at the crows!", "growls eerily!")

/datum/dog_fashion/head/blumpkin/lit
	obj_icon_state = "blumpkin1"


code:
///We handle the unity part of plumbing. We track who is connected to who.
/datum/ductnet
	///Stuff that can supply chems
	var/list/suppliers = list()
	////Stuff that can take chems
	var/list/demanders = list()
	///All the ducts that make this network
	var/list/obj/machinery/duct/ducts = list()

	///Max reagents we can carry per tick
	var/capacity

///Add a duct to our network
/datum/ductnet/proc/add_duct(obj/machinery/duct/D)
	if(!D || (D in ducts))
		return
	ducts += D
	D.duct = src

///Remove a duct from our network and commit suicide, because this is probably easier than to check who that duct was connected to and what part of us was lost
/datum/ductnet/proc/remove_duct(obj/machinery/duct/ducting)
	destroy_network(FALSE)
	for(var/obj/machinery/duct/D in ducting.neighbours)
		addtimer(CALLBACK(D, /obj/machinery/duct/proc/attempt_connect)) //needs to happen after qdel
	qdel(src)

///add a plumbing object to either demanders or suppliers
/datum/ductnet/proc/add_plumber(datum/component/plumbing/P, dir)
	if(!P.can_add(src, dir))
		return FALSE
	P.ducts[num2text(dir)] = src
	if(dir & P.supply_connects)
		suppliers += P
	else if(dir & P.demand_connects)
		demanders += P
	return TRUE

///remove a plumber. we dont delete ourselves because ductnets dont persist through plumbing objects
/datum/ductnet/proc/remove_plumber(datum/component/plumbing/P)
	suppliers.Remove(P) //we're probably only in one of these, but Remove() is inherently sane so this is fine
	demanders.Remove(P)

	for(var/dir in P.ducts)
		if(P.ducts[dir] == src)
			P.ducts -= dir
	if(!ducts.len) //there were no ducts, so it was a direct connection. we destroy ourselves since a ductnet with only one plumber and no ducts is worthless
		destroy_network()

///we combine ductnets. this occurs when someone connects to seperate sets of fluid ducts
/datum/ductnet/proc/assimilate(datum/ductnet/D)
	ducts.Add(D.ducts)
	suppliers.Add(D.suppliers)
	demanders.Add(D.demanders)
	for(var/A in D.suppliers + D.demanders)
		var/datum/component/plumbing/P = A
		for(var/s in P.ducts)
			if(P.ducts[s] != D)
				continue
			P.ducts[s] = src  //all your ducts are belong to us
	for(var/A in D.ducts)
		var/obj/machinery/duct/M = A
		M.duct = src //forget your old master

	D.ducts.Cut() //clear this so the other network doesnt clear the ducts along with themselves (this took the life out of me)
	D.destroy_network()

///destroy the network and tell all our ducts and plumbers we are gone
/datum/ductnet/proc/destroy_network(delete=TRUE)
	for(var/A in suppliers + demanders)
		remove_plumber(A)
	for(var/A in ducts)
		var/obj/machinery/duct/D = A
		D.duct = null
	if(delete) //I don't want code to run with qdeleted objects because that can never be good, so keep this in-case the ductnet has some business left to attend to before commiting suicide
		qdel(src)

repo_name: AnalWerewolf/sundown

license: mit


code:
# Template file for your new element

See _element.dm for detailed explanations

```dm
/datum/element/myelement
	element_flags = ELEMENT_BESPOKE | ELEMENT_COMPLEX_DETACH | ELEMENT_DETACH | ELEMENT_NOTAREALFLAG    // code/__DEFINES/dcs/flags.dm
	//id_arg_index = 2                                                                                // Use with ELEMENT_BESPOKE
	var/list/myvar = list()

/datum/element/myelement/Attach(datum/target)
	if(!ismovable(target))
		return COMPONENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_MOVABLE_MOVED, myproc)
	to_chat(target, "Hey, you're in your element.")

/datum/element/myelement/Detach(datum/source)
	UnregisterSignal(source, COMSIG_MOVABLE_MOVED)
	to_chat(source, "You feel way out of your element.")

/datum/element/myelement/proc/myproc(datum/source)
	SIGNAL_HANDLER
	playsound(source, 'sound/effects/gong.ogg', 50, TRUE)
```


code:
/**
 * A holder for simple behaviour that can be attached to many different types
 *
 * Only one element of each type is instanced during game init.
 * Otherwise acts basically like a lightweight component.
 */
/datum/element
	/// Option flags for element behaviour
	var/element_flags = NONE
	/**
	  * The index of the first attach argument to consider for duplicate elements
	  *
	  * Is only used when flags contains [ELEMENT_BESPOKE]
	  *
	  * This is infinity so you must explicitly set this
	  */
	var/id_arg_index = INFINITY

/// Activates the functionality defined by the element on the given target datum
/datum/element/proc/Attach(datum/target)
	SHOULD_CALL_PARENT(TRUE)
	if(type == /datum/element)
		return ELEMENT_INCOMPATIBLE
	SEND_SIGNAL(target, COMSIG_ELEMENT_ATTACH, src)
	if(element_flags & ELEMENT_DETACH)
		RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/OnTargetDelete, override = TRUE)

/datum/element/proc/OnTargetDelete(datum/source, force)
	SIGNAL_HANDLER
	Detach(source)

/// Deactivates the functionality defines by the element on the given datum
/datum/element/proc/Detach(datum/source, ...)
	SIGNAL_HANDLER

	SEND_SIGNAL(source, COMSIG_ELEMENT_DETACH, src)
	SHOULD_CALL_PARENT(TRUE)
	UnregisterSignal(source, COMSIG_PARENT_QDELETING)

/datum/element/Destroy(force)
	if(!force)
		return QDEL_HINT_LETMELIVE
	SSdcs.elements_by_type -= type
	return ..()

//DATUM PROCS

/// Finds the singleton for the element type given and attaches it to src
/datum/proc/_AddElement(list/arguments)
	if(QDELING(src))
		CRASH("We just tried to add an element to a qdeleted datum, something is fucked")
	var/datum/element/ele = SSdcs.GetElement(arguments)
	arguments[1] = src
	if(ele.Attach(arglist(arguments)) == ELEMENT_INCOMPATIBLE)
		CRASH("Incompatible [arguments[1]] assigned to a [type]! args: [json_encode(args)]")

/**
 * Finds the singleton for the element type given and detaches it from src
 * You only need additional arguments beyond the type if you're using [ELEMENT_BESPOKE]
 */
/datum/proc/_RemoveElement(list/arguments)
	var/datum/element/ele = SSdcs.GetElement(arguments)
	if(ele.element_flags & ELEMENT_COMPLEX_DETACH)
		arguments[1] = src
		ele.Detach(arglist(arguments))
	else
		ele.Detach(src)


code:
/**
 * # animal variety element!
 *
 * bespoke element that picks an suffix to append onto the icon state from a list given on creation, among some pixel shifting stuff
 * basically you will see VARIETY in a batch of simplemobs.
 */
/datum/element/animal_variety
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2

	/// prefix for the icon state, this will always stay the same
	var/icon_prefix
	/// chosen sprite suffix, in element args it picks from a list on what this will be
	var/chosen_sprite_suffix
	/// boolean on whether the element should also mess with the pixel_x and pixel_y
	var/modify_pixels

/datum/element/animal_variety/Attach(datum/target, icon_prefix, chosen_sprite_suffix, modify_pixels = FALSE)
	. = ..()
	if(!isanimal(target))
		return ELEMENT_INCOMPATIBLE
	var/mob/living/simple_animal/animal = target
	src.icon_prefix = icon_prefix
	src.chosen_sprite_suffix = chosen_sprite_suffix
	src.modify_pixels = modify_pixels
	animal.icon_state = "[icon_prefix]_[chosen_sprite_suffix]"
	animal.icon_living = "[icon_prefix]_[chosen_sprite_suffix]"
	animal.icon_dead = "[icon_prefix]_[chosen_sprite_suffix]_dead"
	if(modify_pixels)
		animal.pixel_x = animal.base_pixel_x + rand(-6, 6)
		animal.pixel_y = animal.base_pixel_y + rand(0, 10)

/datum/element/animal_variety/Detach(datum/target)
	var/mob/living/simple_animal/animal = target
	animal.icon_state = initial(animal.icon_state)
	animal.icon_living = initial(animal.icon_living)
	animal.icon_dead = initial(animal.icon_living)
	animal.pixel_x = animal.base_pixel_x
	animal.pixel_y = animal.base_pixel_y
	. = ..()


code:
/datum/element/art
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	var/impressiveness = 0

/datum/element/art/Attach(datum/target, impress)
	. = ..()
	if(!isatom(target) || isarea(target))
		return ELEMENT_INCOMPATIBLE
	impressiveness = impress
	RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)

/datum/element/art/Detach(datum/target)
	UnregisterSignal(target, COMSIG_PARENT_EXAMINE)
	return ..()

/datum/element/art/proc/apply_moodlet(atom/source, mob/user, impress)
	SIGNAL_HANDLER

	var/msg
	switch(impress)
		if(GREAT_ART to INFINITY)
			SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
			msg = "What \a [pick("masterpiece", "chef-d'oeuvre")]. So [pick("trascended", "awe-inspiring", "bewitching", "impeccable")]!"
		if (GOOD_ART to GREAT_ART)
			SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artgood", /datum/mood_event/artgood)
			msg = "[source.p_theyre(TRUE)] a [pick("respectable", "commendable", "laudable")] art piece, easy on the keen eye."
		if (BAD_ART to GOOD_ART)
			SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artok", /datum/mood_event/artok)
			msg = "[source.p_theyre(TRUE)] fair to middling, enough to be called an \"art object\"."
		if (0 to BAD_ART)
			SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
			msg = "Wow, [source.p_they()] sucks."

	user.visible_message(span_notice("[user] stops and looks intently at [source]."), \
		span_notice("You appraise [source]... [msg]"))

/datum/element/art/proc/on_examine(atom/source, mob/user, list/examine_texts)
	SIGNAL_HANDLER

	if(!DOING_INTERACTION_WITH_TARGET(user, source))
		INVOKE_ASYNC(src, .proc/appraise, source, user) //Do not sleep the proc.

/datum/element/art/proc/appraise(atom/source, mob/user)
	to_chat(user, span_notice("You start appraising [source]..."))
	if(!do_after(user, 2 SECONDS, target = source))
		return
	var/mult = 1
	if(isobj(source))
		var/obj/art_piece = source
		mult = art_piece.get_integrity() / art_piece.max_integrity

	apply_moodlet(source, user, impressiveness * mult)

/datum/element/art/rev

/datum/element/art/rev/apply_moodlet(atom/source, mob/user, impress)
	var/msg
	if(user.mind?.has_antag_datum(/datum/antagonist/rev))
		SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
		msg = "What \a [pick("masterpiece", "chef-d'oeuvre")] [source.p_theyre()]. So [pick("subversive", "revolutionary", "unitizing", "egalitarian")]!"
	else
		SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
		msg = "Wow, [source.p_they()] sucks."

	user.visible_message(span_notice("[user] stops to inspect [source]."), \
		span_notice("You appraise [source], inspecting the fine craftsmanship of the proletariat... [msg]"))

/datum/element/art/commoner

/datum/element/art/commoner/apply_moodlet(atom/source, mob/user, impress)
	var/msg
	var/list/haters = list()
	for(var/hater_department_type as anything in list(/datum/job_department/security, /datum/job_department/command))
		var/datum/job_department/hater_department = SSjob.get_department_type(hater_department_type)
		for(var/datum/job/hater_job as anything in hater_department.department_jobs)
			haters += hater_job.title
	var/datum/job/quartermaster/fucking_quartermaster = SSjob.GetJobType(/datum/job/quartermaster)
	haters += fucking_quartermaster.title

	if(!(user.mind.assigned_role.title in haters))
		SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
		msg = "What \a [pick("masterpiece", "chef-d'oeuvre")] [source.p_theyre()]. So [pick("relatable", "down to earth", "true", "real")]!"
	else
		SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
		msg = "Wow, [source.p_they()] sucks."

	user.visible_message(span_notice("[user] stops to inspect [source]."), \
		span_notice("You appraise [source], inspecting the fine craftsmanship of the proletariat... [msg]"))


code:
	///Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage
	///Leaving something at 0 means it's off - has no maximum.

	///This damage is taken when atmos doesn't fit all the requirements above.

/**
 * ## atmos requirements element!
 *
 * bespoke element that deals damage to the attached mob when the atmos requirements aren't satisfied
 */
/datum/element/atmos_requirements
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	var/list/atmos_requirements
	var/unsuitable_atmos_damage

/datum/element/atmos_requirements/Attach(datum/target, list/atmos_requirements, unsuitable_atmos_damage)
	. = ..()
	if(!isliving(target))
		return ELEMENT_INCOMPATIBLE
	src.atmos_requirements = string_assoc_list(atmos_requirements)
	RegisterSignal(target, COMSIG_LIVING_HANDLE_BREATHING, .proc/on_non_stasis_life)

/datum/element/atmos_requirements/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_LIVING_HANDLE_BREATHING)

///signal called by the living mob's life() while non stasis
/datum/element/atmos_requirements/proc/on_non_stasis_life(mob/living/target, delta_time = SSMOBS_DT)
	SIGNAL_HANDLER
	if(is_breathable_atmos(target))
		target.clear_alert("not_enough_oxy")
		return
	target.adjustBruteLoss(unsuitable_atmos_damage * delta_time)
	target.throw_alert("not_enough_oxy", /atom/movable/screen/alert/not_enough_oxy)

/datum/element/atmos_requirements/proc/is_breathable_atmos(mob/living/target)
	if(target.pulledby && target.pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"])
		return FALSE

	if(!isopenturf(target.loc))
		return TRUE

	var/turf/open/open_turf = target.loc
	if(!open_turf.air && (atmos_requirements["min_oxy"] || atmos_requirements["min_tox"] || atmos_requirements["min_n2"] || atmos_requirements["min_co2"]))
		return FALSE

	var/open_turf_gases = open_turf.air.gases
	open_turf.air.assert_gases(arglist(GLOB.hardcoded_gases))

	var/plas = open_turf_gases[/datum/gas/plasma][MOLES]
	var/oxy = open_turf_gases[/datum/gas/oxygen][MOLES]
	var/n2  = open_turf_gases[/datum/gas/nitrogen][MOLES]
	var/co2 = open_turf_gases[/datum/gas/carbon_dioxide][MOLES]

	open_turf.air.garbage_collect()

	. = TRUE
	if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"])
		. = FALSE
	else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"])
		. = FALSE
	else if(atmos_requirements["min_plas"] && plas < atmos_requirements["min_plas"])
		. = FALSE
	else if(atmos_requirements["max_plas"] && plas > atmos_requirements["max_plas"])
		. = FALSE
	else if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"])
		. = FALSE
	else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"])
		. = FALSE
	else if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"])
		. = FALSE
	else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"])
		. = FALSE


code:
//This element facilitates reaction to atmos changes when a tile is inactive.
//It adds the object to a list on SSair to be processed for so long as the object wants to be processed
//And removes it as soon as the object is no longer interested
//Don't put it on things that tend to clump into one spot, you will cause lag spikes.
/datum/element/atmos_sensitive
	element_flags = ELEMENT_DETACH
	var/static/list/pass_on = list(COMSIG_TURF_EXPOSE = /atom/proc/check_atmos_process)

/datum/element/atmos_sensitive/Attach(datum/target, mapload)
	if(!isatom(target)) //How
		return ELEMENT_INCOMPATIBLE
	var/atom/to_track = target
	to_track.AddElement(/datum/element/connect_loc, pass_on)
	RegisterSignal(to_track, COMSIG_MOVABLE_MOVED, .proc/react_to_move)

	if(!mapload && isopenturf(to_track.loc))
		var/turf/open/new_open = to_track.loc
		to_track.check_atmos_process(new_open, new_open.air, new_open.air.temperature) //Make sure you're properly registered

	return ..()

/datum/element/atmos_sensitive/Detach(datum/source)
	var/atom/us = source
	us.RemoveElement(/datum/element/connect_loc, pass_on)
	if(us.flags_1 & ATMOS_IS_PROCESSING_1)
		us.atmos_end()
		SSair.atom_process -= us
		us.flags_1 &= ~ATMOS_IS_PROCESSING_1
	return ..()

/datum/element/atmos_sensitive/proc/react_to_move(datum/source, atom/movable/oldloc, direction, forced)
	SIGNAL_HANDLER
	var/atom/atom_source = source
	if(isopenturf(atom_source.loc))
		var/turf/open/new_open = atom_source.loc
		atom_source.check_atmos_process(new_open, new_open.air, new_open.air.temperature) //Make sure you're properly registered

/atom/proc/check_atmos_process(datum/source, datum/gas_mixture/air, exposed_temperature)
	SIGNAL_HANDLER
	if(should_atmos_process(air, exposed_temperature))
		if(flags_1 & ATMOS_IS_PROCESSING_1)
			return
		SSair.atom_process += src
		flags_1 |= ATMOS_IS_PROCESSING_1
	else if(flags_1 & ATMOS_IS_PROCESSING_1)
		atmos_end()
		SSair.atom_process -= src
		flags_1 &= ~ATMOS_IS_PROCESSING_1

/atom/proc/process_exposure()
	var/turf/open/spot = loc
	if(!istype(loc, /turf/open))
		//If you end up in a locker or a wall reconsider your life decisions
		atmos_end()
		SSair.atom_process -= src
		flags_1 &= ~ATMOS_IS_PROCESSING_1
		return
	if(!should_atmos_process(spot.air, spot.air.temperature)) //Things can change without a tile becoming active
		atmos_end()
		SSair.atom_process -= src
		flags_1 &= ~ATMOS_IS_PROCESSING_1
		return
	atmos_expose(spot.air, spot.air.temperature)

/turf/open/process_exposure()
	if(!should_atmos_process(air, air.temperature))
		atmos_end()
		SSair.atom_process -= src
		flags_1 &= ~ATMOS_IS_PROCESSING_1
		return
	atmos_expose(air, air.temperature)

///We use this proc to check if we should start processing an item, or continue processing it. Returns true/false as expected
/atom/proc/should_atmos_process(datum/gas_mixture/air, exposed_temperature)
	return FALSE

///This is your process() proc
/atom/proc/atmos_expose(datum/gas_mixture/air, exposed_temperature)
	return

///What to do when our requirements are no longer met
/atom/proc/atmos_end()
	return


code:
/**
 * When attached to a gun and the gun is successfully fired, this element creates a "backblast" of fire and pain, like you'd find in a rocket launcher or recoilless rifle
 *
 * The backblast is simulated by a number of fire plumes, or invisible incendiary rounds that will torch anything they come across for a short distance, as well as knocking
 * back nearby items.
 */
/datum/element/backblast
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2

	/// How many "pellets" of backblast we're shooting backwards, spread between the angle defined in angle_spread
	var/plumes
	/// Assuming we don't just have 1 plume, this is the total angle we'll cover with the plumes, split down the middle directly behind the angle we fired at
	var/angle_spread
	/// How far each plume of fire will fly, assuming it doesn't hit a mob
	var/range

/datum/element/backblast/Attach(datum/target, plumes = 4, angle_spread = 48, range = 6)
	. = ..()
	if(!isgun(target) || plumes < 1 || angle_spread < 1 || range < 1)
		return ELEMENT_INCOMPATIBLE

	src.plumes = plumes
	src.angle_spread = angle_spread
	src.range = range

	if(plumes == 1)
		RegisterSignal(target, COMSIG_GUN_FIRED, .proc/gun_fired_simple)
	else
		RegisterSignal(target, COMSIG_GUN_FIRED, .proc/gun_fired)

/datum/element/backblast/Detach(datum/source)
	if(source)
		UnregisterSignal(source, COMSIG_GUN_FIRED)
	return ..()

/// For firing multiple plumes behind us, we evenly spread out our projectiles based on the [angle_spread][/datum/element/backblast/var/angle_spread] and [number of plumes][/datum/element/backblast/var/plumes]
/datum/element/backblast/proc/gun_fired(obj/item/gun/weapon, mob/living/user, atom/target, params, zone_override)
	SIGNAL_HANDLER

	if(!weapon.chambered || HAS_TRAIT(user, TRAIT_PACIFISM))
		return

	var/backwards_angle = get_angle(target, user)
	var/starting_angle = SIMPLIFY_DEGREES(backwards_angle-(angle_spread * 0.5))
	var/iter_offset = angle_spread / plumes // how much we increment the angle for each plume

	for(var/i in 1 to plumes)
		var/this_angle = SIMPLIFY_DEGREES(starting_angle + ((i - 1) * iter_offset))
		var/turf/target_turf = get_turf_in_angle(this_angle, get_turf(user), 10)
		INVOKE_ASYNC(src, .proc/pew, target_turf, weapon, user)

/// If we're only firing one plume directly behind us, we don't need to bother with the loop or angles or anything
/datum/element/backblast/proc/gun_fired_simple(obj/item/gun/weapon, mob/living/user, atom/target, params, zone_override)
	SIGNAL_HANDLER

	if(!weapon.chambered || HAS_TRAIT(user, TRAIT_PACIFISM))
		return

	var/backwards_angle = get_angle(target, user)
	var/turf/target_turf = get_turf_in_angle(backwards_angle, get_turf(user), 10)
	INVOKE_ASYNC(src, .proc/pew, target_turf, weapon, user)

/// For firing an actual backblast pellet
/datum/element/backblast/proc/pew(turf/target_turf, obj/item/gun/weapon, mob/living/user)
	//Shooting Code:
	var/obj/projectile/bullet/incendiary/backblast/P = new (get_turf(user))
	P.original = target_turf
	P.range = range
	P.fired_from = weapon
	P.firer = user // don't hit ourself that would be really annoying
	P.impacted = list(user = TRUE) // don't hit the target we hit already with the flak
	P.preparePixelProjectile(target_turf, weapon)
	P.fire()


code:
/// Deals extra damage to mobs of a certain type or species.
/datum/element/bane
	element_flags = ELEMENT_DETACH|ELEMENT_BESPOKE
	id_arg_index = 2
	/// can be a mob or a species.
	var/target_type
	/// multiplier of the extra damage based on the force of the item.
	var/damage_multiplier
	/// Added after the above.
	var/added_damage
	/// If it requires combat mode on to deal the extra damage or not.
	var/requires_combat_mode

/datum/element/bane/Attach(datum/target, target_type, damage_multiplier=1, added_damage = 0, requires_combat_mode = TRUE)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE

	if(ispath(target_type, /mob/living))
		RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, .proc/mob_check)
	else if(ispath(target_type, /datum/species))
		RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, .proc/species_check)
	else
		return ELEMENT_INCOMPATIBLE

	src.target_type = target_type
	src.damage_multiplier = damage_multiplier
	src.added_damage = added_damage
	src.requires_combat_mode = requires_combat_mode

/datum/element/bane/Detach(datum/source)
	UnregisterSignal(source, COMSIG_ITEM_AFTERATTACK)
	return ..()

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

	if(!proximity_flag || !is_species(target, target_type))
		return
	activate(source, target, user)

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

	if(!proximity_flag || !istype(target, target_type))
		return
	activate(source, target, user)

/datum/element/bane/proc/activate(obj/item/source, mob/living/target, mob/living/attacker)
	if(requires_combat_mode && !attacker.combat_mode)
		return

	var/extra_damage = max(0, (source.force * damage_multiplier) + added_damage)
	target.apply_damage(extra_damage, source.damtype, attacker.zone_selected)
	SEND_SIGNAL(target, COMSIG_LIVING_BANED, source, attacker) // for extra effects when baned.


code:
/**
 * When attached to a basic mob, it gives it the ability to be hurt by cold/hot body temperatures
 */
/datum/element/basic_body_temp_sensitive
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2

	///Min body temp
	var/min_body_temp = 250
	///Max body temp
	var/max_body_temp = 350
	////Damage when below min temp
	var/cold_damage = 1
	///Damage when above max temp
	var/heat_damage = 1

/datum/element/basic_body_temp_sensitive/Attach(datum/target, min_body_temp, max_body_temp, cold_damage, heat_damage)
	. = ..()
	if(!isbasicmob(target))
		return ELEMENT_INCOMPATIBLE

	if(min_body_temp)
		src.min_body_temp = min_body_temp
	if(max_body_temp)
		src.max_body_temp = max_body_temp
	if(cold_damage)
		src.cold_damage = cold_damage
	if(heat_damage)
		src.heat_damage = heat_damage
	RegisterSignal(target, COMSIG_LIVING_LIFE, .proc/on_life)

/datum/element/basic_body_temp_sensitive/Detach(datum/source)
	if(source)
		UnregisterSignal(source, COMSIG_LIVING_LIFE)
	return ..()

/datum/element/basic_body_temp_sensitive/proc/on_life(datum/target, delta_time, times_fired)
	SIGNAL_HANDLER

	var/mob/living/basic/basic_mob = target
	var/gave_alert = FALSE

	if(basic_mob.bodytemperature < min_body_temp)
		basic_mob.adjust_health(cold_damage * delta_time)
		switch(cold_damage)
			if(1 to 5)
				basic_mob.throw_alert("temp", /atom/movable/screen/alert/cold, 1)
			if(5 to 10)
				basic_mob.throw_alert("temp", /atom/movable/screen/alert/cold, 2)
			if(10 to INFINITY)
				basic_mob.throw_alert("temp", /atom/movable/screen/alert/cold, 3)
		gave_alert = TRUE

	else if(basic_mob.bodytemperature > max_body_temp)
		basic_mob.adjust_health(heat_damage * delta_time)
		switch(heat_damage)
			if(1 to 5)
				basic_mob.throw_alert("temp", /atom/movable/screen/alert/hot, 1)
			if(5 to 10)
				basic_mob.throw_alert("temp", /atom/movable/screen/alert/hot, 2)
			if(10 to INFINITY)
				basic_mob.throw_alert("temp", /atom/movable/screen/alert/hot, 3)
		gave_alert = TRUE

	if(!gave_alert)
		basic_mob.clear_alert("temp")


code:
/**
 * Beauty element. It makes the indoor area the parent is in prettier or uglier depending on the beauty var value.
 * Clean and well decorated areas lead to positive moodlets for passerbies;
 * Shabbier, dirtier ones lead to negative moodlets EXCLUSIVE to characters with the snob quirk.
 */
/datum/element/beauty
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	var/beauty = 0
	/**
	  * Assoc list of atoms as keys and number of time the same element instance has been attached to them as assoc value.
	  * So things don't get odd with same-valued yet dissimilar beauty modifiers being added to the same atom.
	  */
	var/beauty_counter = list()

/datum/element/beauty/Attach(datum/target, beauty)
	. = ..()
	if(!isatom(target) || isarea(target))
		return ELEMENT_INCOMPATIBLE

	src.beauty = beauty

	if(!beauty_counter[target] && ismovable(target))
		var/atom/movable/mov_target = target
		mov_target.become_area_sensitive(BEAUTY_ELEMENT_TRAIT)
		RegisterSignal(mov_target, COMSIG_ENTER_AREA, .proc/enter_area)
		RegisterSignal(mov_target, COMSIG_EXIT_AREA, .proc/exit_area)

	beauty_counter[target]++

	var/area/current_area = get_area(target)
	if(current_area && !current_area.outdoors)
		current_area.totalbeauty += beauty
		current_area.update_beauty()

/datum/element/beauty/proc/enter_area(datum/source, area/new_area)
	SIGNAL_HANDLER

	if(new_area.outdoors)
		return
	new_area.totalbeauty += beauty * beauty_counter[source]
	new_area.update_beauty()

/datum/element/beauty/proc/exit_area(datum/source, area/old_area)
	SIGNAL_HANDLER

	if(old_area.outdoors)
		return
	old_area.totalbeauty -= beauty * beauty_counter[source]
	old_area.update_beauty()

/datum/element/beauty/Detach(datum/source)
	if(!beauty_counter[source])
		return ..()
	var/area/current_area = get_area(source)
	if(QDELETED(source))
		. = ..()
		UnregisterSignal(source, list(COMSIG_ENTER_AREA, COMSIG_EXIT_AREA))
		if(current_area)
			exit_area(source, current_area)
		beauty_counter -= source
		REMOVE_TRAIT(source, TRAIT_AREA_SENSITIVE, BEAUTY_ELEMENT_TRAIT)
	else //lower the 'counter' down by one, update the area, and call parent if it's reached zero.
		beauty_counter[source]--
		if(current_area && !current_area.outdoors)
			current_area.totalbeauty -= beauty
			current_area.update_beauty()
		if(!beauty_counter[source])
			. = ..()
			UnregisterSignal(source, list(COMSIG_ENTER_AREA, COMSIG_EXIT_AREA))
			beauty_counter -= source
			REMOVE_TRAIT(source, TRAIT_AREA_SENSITIVE, BEAUTY_ELEMENT_TRAIT)


code:
/// Tucking element, for things that can be tucked into bed.
/datum/element/bed_tuckable
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	/// our pixel_x offset - how much the item moves x when in bed (+x is closer to the pillow)
	var/x_offset = 0
	/// our pixel_y offset - how much the item move y when in bed (-y is closer to the middle)
	var/y_offset = 0
	/// our rotation degree - how much the item turns when in bed (+degrees turns it more parallel)
	var/rotation_degree = 0

/datum/element/bed_tuckable/Attach(obj/target, x = 0, y = 0, rotation = 0)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE

	x_offset = x
	y_offset = y
	rotation_degree = rotation
	RegisterSignal(target, COMSIG_ITEM_ATTACK_OBJ, .proc/tuck_into_bed)

/datum/element/bed_tuckable/Detach(obj/target)
	. = ..()
	UnregisterSignal(target, list(COMSIG_ITEM_ATTACK_OBJ, COMSIG_ITEM_PICKUP))

/**
 * Tuck our object into bed.
 *
 * tucked - the object being tucked
 * target_bed - the bed we're tucking them into
 * tucker - the guy doing the tucking
 */
/datum/element/bed_tuckable/proc/tuck_into_bed(obj/item/tucked, obj/structure/bed/target_bed, mob/living/tucker)
	SIGNAL_HANDLER

	if(!istype(target_bed))
		return

	if(!tucker.transferItemToLoc(tucked, target_bed.drop_location()))
		return

	to_chat(tucker, span_notice("You lay [tucked] out on [target_bed]."))
	tucked.pixel_x = x_offset
	tucked.pixel_y = y_offset
	if(rotation_degree)
		tucked.transform = turn(tucked.transform, rotation_degree)
		RegisterSignal(tucked, COMSIG_ITEM_PICKUP, .proc/untuck)

	return COMPONENT_NO_AFTERATTACK

/**
 * If we rotate our object, then we need to un-rotate it when it's picked up
 *
 * tucked - the object that is tucked
 */
/datum/element/bed_tuckable/proc/untuck(obj/item/tucked)
	SIGNAL_HANDLER

	tucked.transform = turn(tucked.transform, -rotation_degree)
	UnregisterSignal(tucked, COMSIG_ITEM_PICKUP)


code:
//blocks bluespace artillery beams that try to fly through
//look not all elements need to be fancy
/datum/element/bsa_blocker/Attach(datum/target)
	if(!isatom(target))
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_ATOM_BSA_BEAM, .proc/block_bsa)
	return ..()

/datum/element/bsa_blocker/proc/block_bsa()
	SIGNAL_HANDLER

	return COMSIG_ATOM_BLOCKS_BSA_BEAM


code:
//the default chemical transfer messages if they aren't set
#define DEFAULT_ATTACKER_MESSAGE span_notice("You transfer your chemicals to %VICTIM.")
#define DEFAULT_VICTIM_MESSAGE span_userdanger("Chemicals have been transferred into you from %ATTACKER!")

/**
 * ## chemical transfer element!
 *
 * Bespoke element that, on a certain chance to proc, transfers all chemicals from the person attacking to the victim being attacked
 * with whatever item has this element.
 *
 * attacker_message uses %VICTIM as whomever is getting attacked.
 * victim_message uses %ATTACKER for the same.
 */
/datum/element/chemical_transfer
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	///chance for the chemical transfer to proc.
	var/transfer_prob
	///message attacker gets when the chemical transfer procs
	var/attacker_message
	///message victim gets when the chemical transfer procs
	var/victim_message

/datum/element/chemical_transfer/Attach(datum/target, attacker_message = DEFAULT_ATTACKER_MESSAGE, victim_message = DEFAULT_VICTIM_MESSAGE, transfer_prob = 100)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE
	src.transfer_prob = transfer_prob
	src.attacker_message = attacker_message
	src.victim_message = victim_message
	RegisterSignal(target, COMSIG_ITEM_ATTACK, .proc/on_attack)
	RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)

/datum/element/chemical_transfer/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, list(COMSIG_ITEM_ATTACK, COMSIG_PARENT_EXAMINE))

///signal called on parent being examined
/datum/element/chemical_transfer/proc/on_examine(datum/target, mob/user, list/examine_list)
	SIGNAL_HANDLER
	var/probability_description
	switch(transfer_prob)
		if(1 to 25)
			probability_description = "rarely"
		if(26 to 66)
			probability_description = "sometimes"
		if(67 to 99)
			probability_description = "often"
		if(100)
			probability_description = "always"
	examine_list += span_notice("Attacking with [target] will [probability_description] transfer reagents inside of you to your victim.")

///signal called on parent being used to attack a victim
/datum/element/chemical_transfer/proc/on_attack(datum/target, mob/living/transfer_victim, mob/living/transfer_attacker)
	SIGNAL_HANDLER

	if(!istype(transfer_attacker) || !prob(transfer_prob))
		return
	var/built_attacker_message = replacetext(attacker_message, "%VICTIM", transfer_victim)
	var/built_victim_message = replacetext(attacker_message, "%ATTACKER", transfer_attacker)
	transfer_attacker.reagents?.trans_to(transfer_victim, transfer_attacker.reagents.total_volume, multiplier = 1, preserve_data = 1, no_react = 0, transfered_by = transfer_attacker)
	to_chat(transfer_attacker, built_attacker_message)
	to_chat(transfer_victim, built_victim_message)

#undef DEFAULT_ATTACKER_MESSAGE
#undef DEFAULT_VICTIM_MESSAGE


code:
/// Anything with this element will provide the reagents inside the
/// item to the user when it is equipped.
/datum/element/chewable
	element_flags = ELEMENT_DETACH | ELEMENT_BESPOKE
	id_arg_index = 2

	/// The amount to metabolize per second
	var/metabolization_amount = REAGENTS_METABOLISM

	/// A bitfield of valid slots. If this is not provided, then it will
	/// use the `slot_flags` of the item.
	var/slots_to_check

	/// The objects with this element that are currently being processed
	var/list/processing = list()

/datum/element/chewable/Attach(datum/target, metabolization_amount, slots_to_check)
	. = ..()
	if (!isitem(target))
		return ELEMENT_INCOMPATIBLE

	var/obj/item/target_item = target

	if (metabolization_amount)
		src.metabolization_amount = metabolization_amount

	src.slots_to_check = slots_to_check || target_item.slot_flags

	RegisterSignal(target, COMSIG_ITEM_DROPPED, .proc/on_dropped)
	RegisterSignal(target, COMSIG_ITEM_EQUIPPED, .proc/on_equipped)

/datum/element/chewable/Detach(datum/source, force)
	processing -= source
	UnregisterSignal(source, list(COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED))

/datum/element/chewable/process(delta_time)
	if (processing.len == 0)
		return PROCESS_KILL

	for (var/obj/item/item as anything in processing)
		var/mob/chewer = item.loc

		if (!istype(chewer) || !item.reagents?.total_volume)
			processing -= item
			continue

		handle_reagents(item, delta_time)

/datum/element/chewable/proc/handle_reagents(obj/item/item, delta_time)
	var/datum/reagents/reagents = item.reagents

	var/metabolism_amount = metabolization_amount * delta_time
	if (!reagents.trans_to(item.loc, metabolism_amount, methods = INGEST))
		reagents.remove_any(metabolism_amount)

/datum/element/chewable/proc/on_dropped(datum/source)
	SIGNAL_HANDLER
	processing -= source

/datum/element/chewable/proc/on_equipped(datum/source, mob/equipper, slot)
	SIGNAL_HANDLER

	if (slot & slots_to_check)
		processing += source
		START_PROCESSING(SSdcs, src)
	else
		processing -= source


code:
/datum/element/cleaning/Attach(datum/target)
	. = ..()
	if(!ismovable(target))
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/Clean)

/datum/element/cleaning/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_MOVABLE_MOVED)

/datum/element/cleaning/proc/Clean(datum/source)
	SIGNAL_HANDLER

	var/atom/movable/AM = source
	var/turf/tile = AM.loc
	if(!isturf(tile))
		return

	tile.wash(CLEAN_SCRUB)
	for(var/A in tile)
		// Clean small items that are lying on the ground
		if(isitem(A))
			var/obj/item/I = A
			if(I.w_class <= WEIGHT_CLASS_SMALL && !ismob(I.loc))
				I.wash(CLEAN_SCRUB)
		// Clean humans that are lying down
		else if(ishuman(A))
			var/mob/living/carbon/human/cleaned_human = A
			if(cleaned_human.body_position == LYING_DOWN)
				cleaned_human.wash(CLEAN_SCRUB)
				cleaned_human.regenerate_icons()
				to_chat(cleaned_human, span_danger("[AM] cleans your face!"))


code:
/datum/element/climbable
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	///Time it takes to climb onto the object
	var/climb_time = (2 SECONDS)
	///Stun duration for when you get onto the object
	var/climb_stun = (2 SECONDS)
	///Assoc list of object being climbed on - climbers.  This allows us to check who needs to be shoved off a climbable object when its clicked on.
	var/list/current_climbers

/datum/element/climbable/Attach(datum/target, climb_time, climb_stun)
	. = ..()

	if(!isatom(target) || isarea(target))
		return ELEMENT_INCOMPATIBLE
	if(climb_time)
		src.climb_time = climb_time
	if(climb_stun)
		src.climb_stun = climb_stun

	RegisterSignal(target, COMSIG_ATOM_ATTACK_HAND, .proc/attack_hand)
	RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	RegisterSignal(target, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive)
	RegisterSignal(target, COMSIG_ATOM_BUMPED, .proc/try_speedrun)
	ADD_TRAIT(target, TRAIT_CLIMBABLE, src)

/datum/element/climbable/Detach(datum/target)
	UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_PARENT_EXAMINE, COMSIG_MOUSEDROPPED_ONTO, COMSIG_ATOM_BUMPED))
	REMOVE_TRAIT(target, TRAIT_CLIMBABLE, src)
	return ..()

/datum/element/climbable/proc/on_examine(atom/source, mob/user, list/examine_texts)
	SIGNAL_HANDLER
	examine_texts += span_notice("[source] looks climbable.")

/datum/element/climbable/proc/can_climb(atom/source, mob/user)
	var/dir_step = get_dir(user, source.loc)
	//To jump over a railing you have to be standing next to it, not far behind it.
	if(source.flags_1 & ON_BORDER_1 && user.loc != source.loc && (dir_step & source.dir) == source.dir)
		return FALSE
	return TRUE

/datum/element/climbable/proc/attack_hand(atom/climbed_thing, mob/user)
	SIGNAL_HANDLER
	var/list/climbers = LAZYACCESS(current_climbers, climbed_thing)
	for(var/i in climbers)
		var/mob/living/structure_climber = i
		if(structure_climber == user)
			return
		user.changeNext_move(CLICK_CD_MELEE)
		user.do_attack_animation(climbed_thing)
		structure_climber.Paralyze(40)
		structure_climber.visible_message(span_warning("[structure_climber] is knocked off [climbed_thing]."), span_warning("You're knocked off [climbed_thing]!"), span_hear("You hear a cry from [structure_climber], followed by a slam."))

/datum/element/climbable/proc/climb_structure(atom/climbed_thing, mob/living/user, params)
	if(!can_climb(climbed_thing, user))
		return
	climbed_thing.add_fingerprint(user)
	user.visible_message(span_warning("[user] starts climbing onto [climbed_thing]."), \
								span_notice("You start climbing onto [climbed_thing]..."))
	var/adjusted_climb_time = climb_time
	var/adjusted_climb_stun = climb_stun
	if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //climbing takes twice as long without help from the hands.
		adjusted_climb_time *= 2
	if(isalien(user))
		adjusted_climb_time *= 0.25 //aliens are terrifyingly fast
	if(HAS_TRAIT(user, TRAIT_FREERUNNING)) //do you have any idea how fast I am???
		adjusted_climb_time *= 0.8
		adjusted_climb_stun *= 0.8
	LAZYADDASSOCLIST(current_climbers, climbed_thing, user)
	if(do_after(user, adjusted_climb_time, climbed_thing))
		if(QDELETED(climbed_thing)) //Checking if structure has been destroyed
			return
		if(do_climb(climbed_thing, user, params))
			user.visible_message(span_warning("[user] climbs onto [climbed_thing]."), \
								span_notice("You climb onto [climbed_thing]."))
			log_combat(user, climbed_thing, "climbed onto")
			if(adjusted_climb_stun)
				user.Stun(adjusted_climb_stun)
		else
			to_chat(user, span_warning("You fail to climb onto [climbed_thing]."))
	LAZYREMOVEASSOC(current_climbers, climbed_thing, user)

/datum/element/climbable/proc/do_climb(atom/climbed_thing, mob/living/user, params)
	if(!can_climb(climbed_thing, user))
		return
	climbed_thing.set_density(FALSE)
	var/dir_step = get_dir(user, climbed_thing.loc)
	var/same_loc = climbed_thing.loc == user.loc
	// on-border objects can be vaulted over and into the next turf.
	// The reverse dir check is for when normal behavior should apply instead (e.g. John Doe hops east of a railing facing west, ending on the same turf as it).
	if(climbed_thing.flags_1 & ON_BORDER_1 && (same_loc || !(dir_step & REVERSE_DIR(climbed_thing.dir))))
		//it can be vaulted over in two different cardinal directions. we choose one.
		if(ISDIAGONALDIR(climbed_thing.dir) && same_loc)
			if(params) //we check the icon x and y parameters of the click-drag to determine step_dir.
				var/list/modifiers = params2list(params)
				var/x_dist = (text2num(LAZYACCESS(modifiers, ICON_X)) - world.icon_size/2) * (climbed_thing.dir & WEST ? -1 : 1)
				var/y_dist = (text2num(LAZYACCESS(modifiers, ICON_Y)) - world.icon_size/2) * (climbed_thing.dir & SOUTH ? -1 : 1)
				dir_step = (x_dist >= y_dist ? (EAST|WEST) : (NORTH|SOUTH)) & climbed_thing.dir
			else //user is being moved by a forced_movement datum. dir_step will be the direction to the forced movement target.
				dir_step = get_dir(user, user.force_moving.target)
		else
			dir_step = get_dir(user, get_step(climbed_thing, climbed_thing.dir))
	. = step(user, dir_step)
	climbed_thing.set_density(TRUE)

///Handles climbing onto the atom when you click-drag
/datum/element/climbable/proc/mousedrop_receive(atom/receiver, atom/movable/dropped_atom, mob/user, params)
	SIGNAL_HANDLER
	if(user == dropped_atom && isliving(dropped_atom))
		var/mob/living/living_target = dropped_atom
		if(isanimal(living_target))
			var/mob/living/simple_animal/animal = dropped_atom
			if (!animal.dextrous)
				return
		if(living_target.mobility_flags & MOBILITY_MOVE)
			INVOKE_ASYNC(src, .proc/climb_structure, receiver, living_target, params)
			return

///Tries to climb onto the target if the forced movement of the mob allows it
/datum/element/climbable/proc/try_speedrun(datum/source, mob/bumpee)
	SIGNAL_HANDLER
	if(!istype(bumpee))
		return
	if(bumpee.force_moving?.allow_climbing)
		do_climb(source, bumpee)


code:
/// This element hooks a signal onto the loc the current object is on.
/// When the object moves, it will unhook the signal and rehook it to the new object.
/datum/element/connect_loc
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2

	/// An assoc list of signal -> procpath to register to the loc this object is on.
	var/list/connections

/datum/element/connect_loc/Attach(atom/movable/listener, list/connections)
	. = ..()
	if (!istype(listener))
		return ELEMENT_INCOMPATIBLE

	src.connections = connections

	RegisterSignal(listener, COMSIG_MOVABLE_MOVED, .proc/on_moved, override = TRUE)
	update_signals(listener)

/datum/element/connect_loc/Detach(atom/movable/listener)
	. = ..()
	unregister_signals(listener, listener.loc)
	UnregisterSignal(listener, COMSIG_MOVABLE_MOVED)

/datum/element/connect_loc/proc/update_signals(atom/movable/listener)
	var/atom/listener_loc = listener.loc
	if(isnull(listener_loc))
		return

	for (var/signal in connections)
		//override=TRUE because more than one connect_loc element instance tracked object can be on the same loc
		listener.RegisterSignal(listener_loc, signal, connections[signal], override=TRUE)

/datum/element/connect_loc/proc/unregister_signals(datum/listener, atom/old_loc)
	if(isnull(old_loc))
		return

	for (var/signal in connections)
		listener.UnregisterSignal(old_loc, signal)

/datum/element/connect_loc/proc/on_moved(atom/movable/listener, atom/old_loc)
	SIGNAL_HANDLER
	unregister_signals(listener, old_loc)
	update_signals(listener)


code:
/// Adds crack overlays to an object when integrity gets low
/datum/element/crackable
	element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
	id_arg_index = 2
	var/list/icon/crack_icons
	/// The level at which the object starts showing cracks, 1 being at full health and 0.5 being at half health
	var/crack_integrity = 1

/datum/element/crackable/Attach(datum/target, icon/crack_icon, list/crack_states, crack_integrity)
	. = ..()
	if(!isobj(target))
		return ELEMENT_INCOMPATIBLE
	src.crack_integrity = crack_integrity || src.crack_integrity
	if(!crack_icons) // This is the first attachment and we need to do first time setup
		crack_icons = list()
		for(var/state in crack_states)
			for(var/i in 1 to 36)
				var/icon/new_crack_icon = icon(crack_icon, state)
				new_crack_icon.Turn(i * 10)
				crack_icons += new_crack_icon
	RegisterSignal(target, COMSIG_ATOM_INTEGRITY_CHANGED, .proc/IntegrityChanged)

/datum/element/crackable/proc/IntegrityChanged(obj/source, old_value, new_value)
	SIGNAL_HANDLER
	if(new_value >= source.max_integrity * crack_integrity)
		return
	source.AddComponent(/datum/component/cracked, crack_icons, crack_integrity)


code:
/**
 * # curse announcement element!
 *
 * Bespoke element that sends a harrowing message when you first pick up an item, applying a spooky color outline and renaming the item.
 * For most items, it will announce when picked up. If the item can be equipped, though, it will only announce when the item is worn.
 * Possible improvements for the future: add an option to allow the cursed affix to be a prefix. right now only coded for suffixes
 */
/datum/element/curse_announcement
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	///message sent on announce
	var/announcement_message
	///color of the outline filter on announce
	var/filter_color
	///new name given to the item on announce
	var/new_name
	///optional fantasy component, used in building the name if provided
	var/datum/weakref/fantasy_component

/datum/element/curse_announcement/Attach(datum/target, announcement_message, filter_color, new_name, datum/component/fantasy/fantasy_component)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE
	var/obj/item/cursed_item = target
	src.announcement_message = announcement_message
	src.filter_color = filter_color
	src.new_name = new_name
	src.fantasy_component = WEAKREF(fantasy_component)
	if(cursed_item.slot_equipment_priority) //if it can equip somewhere, only go active when it is actually done
		RegisterSignal(cursed_item, COMSIG_ITEM_EQUIPPED, .proc/on_equipped)
	else
		RegisterSignal(cursed_item, COMSIG_ITEM_PICKUP, .proc/on_pickup)

/datum/element/curse_announcement/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_PICKUP))

/datum/element/curse_announcement/proc/on_equipped(obj/item/cursed_item, mob/equipper, slot)
	SIGNAL_HANDLER
	if(cursed_item.slot_flags & slot)
		announce(cursed_item, equipper)

/datum/element/curse_announcement/proc/on_pickup(obj/item/cursed_item, mob/grabber)
	SIGNAL_HANDLER
	announce(cursed_item, grabber)

/datum/element/curse_announcement/proc/announce(obj/item/cursed_item, mob/cursed)
	//this is from rpgloot, remove the quality suffix to format the name correctly
	var/quality_suffix_text
	var/datum/component/fantasy/perchance_to_dream = fantasy_component.resolve()
	if(perchance_to_dream)
		quality_suffix_text = " [perchance_to_dream.quality]"
		cursed_item.name = replacetext(cursed_item.name, quality_suffix_text,"")

	//modifications to the item so it looks cursed
	to_chat(cursed, span_userdanger("[announcement_message]"))
	cursed_item.add_filter("cursed_item", 9, list("type" = "outline", "color" = filter_color, "size" = 1))
	cursed_item.name = "[cursed_item][new_name]"

	//this is from rpgloot, readd the quality suffix
	if(quality_suffix_text)
		cursed_item.name += quality_suffix_text
	cursed_item.RemoveElement(/datum/element/curse_announcement)


code:
/**
 * # cursed element!
 *
 *Attaching this element to something will make it float, and get a special ai controller!
 */
/datum/element/cursed
	element_flags = ELEMENT_DETACH

/datum/element/cursed/Attach(datum/target, slot)
	. = ..()
	if(!isitem(target))
		return COMPONENT_INCOMPATIBLE
	var/obj/item/master = target
	var/datum/ai_controller/ai = new /datum/ai_controller/cursed(master)
	ai.blackboard[BB_TARGET_SLOT] = slot
	master.ai_controller = ai
	master.AddElement(/datum/element/movetype_handler)
	ADD_TRAIT(master, TRAIT_MOVE_FLYING, ELEMENT_TRAIT)

/datum/element/cursed/Detach(datum/source)
	. = ..()
	var/obj/item/master = source
	QDEL_NULL(master.ai_controller)
	REMOVE_TRAIT(master, TRAIT_MOVE_FLYING, ELEMENT_TRAIT)
	master.RemoveElement(/datum/element/movetype_handler)


code:
/**
 * ## death drops element!
 *
 * bespoke element that spawns loot when a mob is killed
 */
/datum/element/death_drops
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	///what items the target drops when killed
	var/list/loot

/datum/element/death_drops/Attach(datum/target, list/loot)
	. = ..()
	if(!isliving(target))
		return ELEMENT_INCOMPATIBLE
	if(!loot)
		stack_trace("death drops element added to [target] with NO LOOT")
	if(!src.loot)
		src.loot = loot.Copy()
	RegisterSignal(target, COMSIG_LIVING_DEATH, .proc/on_death)

/datum/element/death_drops/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_LIVING_DEATH)

///signal called by the stat of the target changing
/datum/element/death_drops/proc/on_death(mob/living/target, gibbed)
	SIGNAL_HANDLER
	for(var/thing_to_spawn in loot)
		if(loot[thing_to_spawn]) //If this is an assoc list, use the value of that to get the right amount
			for(var/index in 1 to loot[thing_to_spawn])
				new thing_to_spawn(target.drop_location())
		else
			new thing_to_spawn(target.drop_location())


code:
/datum/element/decal
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	/// Whether this decal can be cleaned.
	var/cleanable
	/// A description this decal appends to the target's examine message.
	var/description
	/// If true this was initialized with no set direction - will follow the parent dir.
	var/directional
	/// The base icon state that this decal was initialized with.
	var/base_icon_state
	/// What smoothing junction this was initialized with.
	var/smoothing
	/// The overlay applied by this decal to the target.
	var/mutable_appearance/pic

/// Remove old decals and apply new decals after rotation as necessary
/datum/controller/subsystem/processing/dcs/proc/rotate_decals(datum/source, old_dir, new_dir)
	SIGNAL_HANDLER

	if(old_dir == new_dir)
		return
	var/list/resulting_decals_params = list() // param lists
	var/list/old_decals = list() //instances

	if(!source.comp_lookup || !source.comp_lookup[COMSIG_ATOM_UPDATE_OVERLAYS])
		//should probably also unregister itself
		return

	if(length(source.comp_lookup[COMSIG_ATOM_UPDATE_OVERLAYS]))
		for(var/datum/element/decal/decal in source.comp_lookup[COMSIG_ATOM_UPDATE_OVERLAYS])
			old_decals += decal
			resulting_decals_params += list(decal.get_rotated_parameters(old_dir,new_dir))
	else
		var/datum/element/decal/decal = source.comp_lookup[COMSIG_ATOM_UPDATE_OVERLAYS]
		if(!istype(decal))
			return
		old_decals += decal
		resulting_decals_params += list(decal.get_rotated_parameters(old_dir,new_dir))

	//Instead we could generate ids and only remove duplicates to save on churn on four-corners symmetry ?
	for(var/datum/element/decal/decal in old_decals)
		decal.Detach(source)

	for(var/result in resulting_decals_params)
		source.AddElement(/datum/element/decal, result["icon"], result["icon_state"], result["dir"], result["plane"], result["layer"], result["alpha"], result["color"], result["smoothing"], result["cleanable"], result["desc"])

/datum/element/decal/proc/get_rotated_parameters(old_dir,new_dir)
	var/rotation = 0
	if(directional) //Even when the dirs are the same rotation is coming out as not 0 for some reason
		rotation = SIMPLIFY_DEGREES(dir2angle(new_dir)-dir2angle(old_dir))
		new_dir = turn(pic.dir,-rotation)
	return list(
		"icon" = pic.icon,
		"icon_state" = base_icon_state,
		"dir" = new_dir,
		"plane" = pic.plane,
		"layer" = pic.layer,
		"alpha" = pic.alpha,
		"color" = pic.color,
		"smoothing" = smoothing,
		"cleanable" = cleanable,
		"desc" = description
	)

/datum/element/decal/Attach(atom/target, _icon, _icon_state, _dir, _plane=FLOAT_PLANE, _layer=FLOAT_LAYER, _alpha=255, _color, _smoothing, _cleanable=FALSE, _description, mutable_appearance/_pic)
	. = ..()
	if(!isatom(target))
		return ELEMENT_INCOMPATIBLE
	if(_pic)
		pic = _pic
	else if(!generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, target))
		return ELEMENT_INCOMPATIBLE
	description = _description
	cleanable = _cleanable
	directional = _dir
	base_icon_state = _icon_state
	smoothing = _smoothing

	RegisterSignal(target,COMSIG_ATOM_UPDATE_OVERLAYS,.proc/apply_overlay, TRUE)
	if(target.flags_1 & INITIALIZED_1)
		target.update_appearance(UPDATE_OVERLAYS) //could use some queuing here now maybe.
	else
		RegisterSignal(target,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE,.proc/late_update_icon, TRUE)
	if(isitem(target))
		INVOKE_ASYNC(target, /obj/item/.proc/update_slot_icon, TRUE)
	if(_dir)
		SSdcs.RegisterSignal(target,COMSIG_ATOM_DIR_CHANGE, /datum/controller/subsystem/processing/dcs/proc/rotate_decals, TRUE)
	if(!isnull(_smoothing))
		RegisterSignal(target, COMSIG_ATOM_SMOOTHED_ICON, .proc/smooth_react, TRUE)
	if(_cleanable)
		RegisterSignal(target, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react, TRUE)
	if(_description)
		RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/examine,TRUE)

	RegisterSignal(target, COMSIG_TURF_ON_SHUTTLE_MOVE, .proc/shuttle_move_react,TRUE)

/**
 * ## generate_appearance
 *
 * If the decal was not given an appearance, it will generate one based on the other given arguments.
 * element won't be compatible if it cannot do either
 * all args are fed into creating an image, they are byond vars for images you'll recognize in the byond docs
 * (except source, source is the object whose appearance we're copying.)
 */
/datum/element/decal/proc/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
	if(!_icon || !_icon_state)
		return FALSE
	var/temp_image = image(_icon, null, isnull(_smoothing) ? _icon_state : "[_icon_state]-[_smoothing]", _layer, _dir)
	pic = new(temp_image)
	pic.plane = _plane
	pic.color = _color
	pic.alpha = _alpha
	return TRUE

/datum/element/decal/Detach(atom/source)
	UnregisterSignal(source, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE, COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_TURF_ON_SHUTTLE_MOVE, COMSIG_ATOM_SMOOTHED_ICON))
	SSdcs.UnregisterSignal(source, COMSIG_ATOM_DIR_CHANGE)
	source.update_appearance(UPDATE_OVERLAYS)
	if(isitem(source))
		INVOKE_ASYNC(source, /obj/item/.proc/update_slot_icon)
	SEND_SIGNAL(source, COMSIG_TURF_DECAL_DETACHED, description, cleanable, directional, pic)
	return ..()

/datum/element/decal/proc/late_update_icon(atom/source)
	SIGNAL_HANDLER

	if(source && istype(source))
		source.update_appearance(UPDATE_OVERLAYS)
		UnregisterSignal(source, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)

/datum/element/decal/proc/apply_overlay(atom/source, list/overlay_list)
	SIGNAL_HANDLER

	overlay_list += pic

/datum/element/decal/proc/clean_react(datum/source, clean_types)
	SIGNAL_HANDLER

	if(clean_types & cleanable)
		Detach(source)
		return COMPONENT_CLEANED
	return NONE

/datum/element/decal/proc/examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER

	examine_list += description

/datum/element/decal/proc/shuttle_move_react(datum/source, turf/new_turf)
	SIGNAL_HANDLER

	if(new_turf == source)
		return
	Detach(source)
	new_turf.AddElement(type, pic.icon, base_icon_state, directional, pic.plane, pic.layer, pic.alpha, pic.color, smoothing, cleanable, description)

/**
 * Reacts to the source atom smoothing.
 *
 * Arguments:
 * - [source][/atom]: The source of the signal and recently smoothed atom.
 */
/datum/element/decal/proc/smooth_react(atom/source)
	SIGNAL_HANDLER
	var/smoothing_junction = source.smoothing_junction
	if(smoothing_junction == smoothing)
		return NONE

	Detach(source)
	source.AddElement(type, pic.icon, base_icon_state, directional, pic.plane, pic.layer, pic.alpha, pic.color, smoothing_junction, cleanable, description)
	return NONE


code:
/datum/element/decal/blood

/datum/element/decal/blood/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/blood/Detach(atom/source)
	UnregisterSignal(source, COMSIG_ATOM_GET_EXAMINE_NAME)
	return ..()

/datum/element/decal/blood/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
	var/obj/item/I = source
	if(!_icon)
		_icon = '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("#fff", 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/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] = " blood-stained "
	return COMPONENT_EXNAME_CHANGED


code:
/**
 * Create /datum/component/aquarium_content with the preset path on the target right before being inserted into aquarium and deletes itself.
 * Used to save memory from aquarium properties on common objects/stacks that won't see aquarium in 99 out of 100 rounds.
 */
/datum/element/deferred_aquarium_content
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	var/aquarium_content_type

/datum/element/deferred_aquarium_content/Attach(datum/target, aquarium_content_type)
	. = ..()
	if(!ismovable(target))
		return ELEMENT_INCOMPATIBLE
	if(!aquarium_content_type)
		CRASH("Deferred aquarium content missing behaviour type.")
	src.aquarium_content_type = aquarium_content_type

	//If element is added to something already in aquarium, just create the component.
	var/atom/movable/movable_target = target
	if(istype(movable_target.loc, /obj/structure/aquarium))
		create_aquarium_component(movable_target)
	else //otherwise the component will be created when trying to insert the thing.
		RegisterSignal(target, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK, .proc/create_aquarium_component)

/datum/element/deferred_aquarium_content/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK)

/datum/element/deferred_aquarium_content/proc/create_aquarium_component(datum/source)
	SIGNAL_HANDLER

	source.AddComponent(/datum/component/aquarium_content, aquarium_content_type)
	Detach(source)


code:
/**
 * Attaches to an item, if that item is dropped on the floor delete it
 */
/datum/element/delete_on_drop
	element_flags = ELEMENT_DETACH
	var/list/myvar = list()

/datum/element/delete_on_drop/Attach(datum/target)
	. = ..()
	if(!isitem(target))
		return COMPONENT_INCOMPATIBLE
	RegisterSignal(target, list(COMSIG_ITEM_DROPPED, COMSIG_CASING_EJECTED), .proc/del_on_drop)

/datum/element/delete_on_drop/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, list(COMSIG_ITEM_DROPPED, COMSIG_CASING_EJECTED))

/datum/element/delete_on_drop/proc/del_on_drop(atom/source)
	SIGNAL_HANDLER
	if(isturf(source.loc))
		qdel(source)


code:
/datum/element/digitalcamo
	element_flags = ELEMENT_DETACH
	var/list/attached_mobs = list()

/datum/element/digitalcamo/New()
	. = ..()
	START_PROCESSING(SSdcs, src)

/datum/element/digitalcamo/Attach(datum/target)
	. = ..()
	if(!isliving(target) || (target in attached_mobs))
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	RegisterSignal(target, COMSIG_LIVING_CAN_TRACK, .proc/can_track)
	var/image/img = image(loc = target)
	img.override = TRUE
	attached_mobs[target] = img
	HideFromAIHuds(target)

/datum/element/digitalcamo/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, list(COMSIG_PARENT_EXAMINE, COMSIG_LIVING_CAN_TRACK))
	for(var/mob/living/silicon/ai/AI in GLOB.player_list)
		AI.client.images -= attached_mobs[target]
	attached_mobs -= target
	UnhideFromAIHuds(target)

/datum/element/digitalcamo/proc/HideFromAIHuds(mob/living/target)
	for(var/mob/living/silicon/ai/AI in GLOB.ai_list)
		var/datum/atom_hud/M = GLOB.huds[AI.med_hud]
		M.hide_single_atomhud_from(AI,target)
		var/datum/atom_hud/S = GLOB.huds[AI.sec_hud]
		S.hide_single_atomhud_from(AI,target)

/datum/element/digitalcamo/proc/UnhideFromAIHuds(mob/living/target)
	for(var/mob/living/silicon/ai/AI in GLOB.ai_list)
		var/datum/atom_hud/M = GLOB.huds[AI.med_hud]
		M.unhide_single_atomhud_from(AI,target)
		var/datum/atom_hud/S = GLOB.huds[AI.sec_hud]
		S.unhide_single_atomhud_from(AI,target)

/datum/element/digitalcamo/proc/on_examine(datum/source, mob/M)
	SIGNAL_HANDLER

	to_chat(M, "[source.p_their()] skin seems to be shifting and morphing like is moving around below it.")

/datum/element/digitalcamo/proc/can_track(datum/source)
	SIGNAL_HANDLER

	return COMPONENT_CANT_TRACK

/datum/element/digitalcamo/process()
	for(var/mob/living/silicon/ai/AI in GLOB.player_list)
		for(var/mob in attached_mobs)
			AI.client.images |= attached_mobs[mob]


code:
/**
 * drag_pickup element; for allowing things to be picked up by dragging.
 *
 * Used for paper bins.
 */
/datum/element/drag_pickup
	element_flags = ELEMENT_DETACH

/datum/element/drag_pickup/Attach(datum/target)
	if(!ismovable(target))
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_MOUSEDROP_ONTO, .proc/pick_up)
	return ..()

/datum/element/drag_pickup/Detach(datum/source)
	UnregisterSignal(source, COMSIG_MOUSEDROP_ONTO)
	return ..()

/datum/element/drag_pickup/proc/pick_up(atom/source, atom/over, mob/user)
	SIGNAL_HANDLER
	var/mob/living/picker = user
	if(!istype(picker) || picker.incapacitated() || !source.Adjacent(picker))
		return

	if(over == picker)
		INVOKE_ASYNC(picker, /mob/.proc/put_in_hands, source)
	else if(istype(over, /atom/movable/screen/inventory/hand))
		var/atom/movable/screen/inventory/hand/Selected_hand = over
		picker.putItemFromInventoryInHandIfPossible(source, Selected_hand.held_index)


code:
// If an item has this element, it can be dried on a drying rack.
/datum/element/dryable
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	/// The type of atom that is spawned by this element on drying.
	var/dry_result

/datum/element/dryable/Attach(datum/target, atom/dry_result)
	. = ..()
	if(!isatom(target))
		return ELEMENT_INCOMPATIBLE
	src.dry_result = dry_result

	RegisterSignal(target, COMSIG_ITEM_DRIED, .proc/finish_drying)
	ADD_TRAIT(target, TRAIT_DRYABLE, ELEMENT_TRAIT)

/datum/element/dryable/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_FOOD_CONSUMED)
	REMOVE_TRAIT(target, TRAIT_DRYABLE, ELEMENT_TRAIT)

/datum/element/dryable/proc/finish_drying(atom/source)
	SIGNAL_HANDLER
	var/atom/dried_atom = source
	if(dry_result == dried_atom.type)//if the dried type is the same as our currrent state, don't bother creating a whole new item, just re-color it.
		var/atom/movable/resulting_atom = dried_atom
		resulting_atom.add_atom_colour("#ad7257", FIXED_COLOUR_PRIORITY)
		ADD_TRAIT(resulting_atom, TRAIT_DRIED, ELEMENT_TRAIT)
		resulting_atom.forceMove(source.drop_location())
		return

	else if(istype(source, /obj/item/stack)) //Check if its a sheet
		var/obj/item/stack/itemstack = dried_atom
		for(var/i in 1 to itemstack.amount)
			var/atom/movable/resulting_atom = new dry_result(source.drop_location())
			ADD_TRAIT(resulting_atom, TRAIT_DRIED, ELEMENT_TRAIT)
		qdel(source)
		return
	else
		var/atom/movable/resulting_atom = new dry_result(source.drop_location())
		ADD_TRAIT(resulting_atom, TRAIT_DRIED, ELEMENT_TRAIT)
		qdel(source)


code:
/datum/element/earhealing
	element_flags = ELEMENT_DETACH
	var/list/user_by_item = list()

/datum/element/earhealing/New()
	START_PROCESSING(SSdcs, src)

/datum/element/earhealing/Attach(datum/target)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE

	RegisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED), .proc/equippedChanged)

/datum/element/earhealing/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
	user_by_item -= target

/datum/element/earhealing/proc/equippedChanged(datum/source, mob/living/carbon/user, slot)
	SIGNAL_HANDLER
	/* SEPTIC EDIT REMOVAL
	if(slot == ITEM_SLOT_EARS && istype(user))
	*/
	//SEPTIC EDIT BEGIN
	if(slot & ITEM_SLOT_EARS && istype(user))
	//SEPTIC EDIT END
		user_by_item[source] = user
	else
		user_by_item -= source

/datum/element/earhealing/process(delta_time)
	for(var/i in user_by_item)
		var/mob/living/carbon/user = user_by_item[i]
		var/obj/item/organ/ears/ears = user.getorganslot(ORGAN_SLOT_EARS)
		if(!ears || HAS_TRAIT_NOT_FROM(user, TRAIT_DEAF, EAR_DAMAGE))
			continue
		ears.deaf = max(ears.deaf - 0.25 * delta_time, (ears.damage < ears.maxHealth ? 0 : 1)) // Do not clear deafness if our ears are too damaged
		ears.damage = max(ears.damage - 0.025 * delta_time, 0)
		CHECK_TICK


code:
/*
 * An element for making tools fragile, giving them a chance of
 * "snapping into tiny pieces" after they've been used.
 */

/datum/element/easily_fragmented
	element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
	id_arg_index = 2

	var/break_chance

/datum/element/easily_fragmented/Attach(datum/target, break_chance)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE

	src.break_chance = break_chance

	RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, .proc/on_afterattack)

/datum/element/easily_fragmented/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_ITEM_AFTERATTACK)

/datum/element/easily_fragmented/proc/on_afterattack(datum/source, atom/target, mob/user, proximity_flag, click_parameters)
	SIGNAL_HANDLER

	var/obj/item/item = source

	if(prob(break_chance))
		user.visible_message(span_danger("[user]'s [item.name] snap[item.p_s()] into tiny pieces in [user.p_their()] hand."))
		item.deconstruct(disassembled = FALSE)


code:
/*
	The presence of this element allows an item (or a projectile carrying an item) to embed itself in a carbon when it is thrown into a target (whether by hand, gun, or explosive wave) with either
	at least 4 throwspeed (EMBED_THROWSPEED_THRESHOLD) or ignore_throwspeed_threshold set to TRUE. Items meant to be used as shrapnel for projectiles should have ignore_throwspeed_threshold set to true.

	Whether we're dealing with a direct /obj/item (throwing a knife at someone) or an /obj/projectile with a shrapnel_type, how we handle things plays out the same, with one extra step separating them.
	Items simply make their COMSIG_MOVABLE_IMPACT_ZONE check, while projectiles check on COMSIG_PROJECTILE_SELF_ON_HIT.
	Upon a projectile hitting a valid target, it spawns whatever type of payload it has defined, then has that try to embed itself in the target on its own.

	Otherwise non-embeddable or stickable items can be made embeddable/stickable through wizard events/sticky tape/admin memes.
*/

/datum/element/embed
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	var/initialized = FALSE /// whether we can skip assigning all the vars (since these are bespoke elements, we don't have to reset the vars every time we attach to something, we already know what we are!)

	// all of this stuff is explained in _DEFINES/combat.dm
	var/embed_chance
	var/fall_chance
	var/pain_chance
	var/pain_mult
	var/remove_pain_mult
	var/impact_pain_mult
	var/rip_time
	var/ignore_throwspeed_threshold
	var/jostle_chance
	var/jostle_pain_mult
	var/pain_stam_pct
	var/payload_type

/datum/element/embed/Attach(datum/target, embed_chance, fall_chance, pain_chance, pain_mult, remove_pain_mult, impact_pain_mult, rip_time, ignore_throwspeed_threshold, jostle_chance, jostle_pain_mult, pain_stam_pct, projectile_payload=/obj/item/shard)
	. = ..()

	if(!isitem(target) && !isprojectile(target))
		return ELEMENT_INCOMPATIBLE

	RegisterSignal(target, COMSIG_ELEMENT_ATTACH, .proc/severancePackage)
	if(isitem(target))
		RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, .proc/checkEmbed)
		RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/examined)
		RegisterSignal(target, COMSIG_EMBED_TRY_FORCE, .proc/tryForceEmbed)
		RegisterSignal(target, COMSIG_ITEM_DISABLE_EMBED, .proc/detachFromWeapon)
		if(!initialized)
			src.embed_chance = embed_chance
			src.fall_chance = fall_chance
			src.pain_chance = pain_chance
			src.pain_mult = pain_mult
			src.remove_pain_mult = remove_pain_mult
			src.rip_time = rip_time
			src.impact_pain_mult = impact_pain_mult
			src.ignore_throwspeed_threshold = ignore_throwspeed_threshold
			src.jostle_chance = jostle_chance
			src.jostle_pain_mult = jostle_pain_mult
			src.pain_stam_pct = pain_stam_pct
			initialized = TRUE
	else
		payload_type = projectile_payload
		/* SEPTIC EDIT REMOVAL
		RegisterSignal(target, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/checkEmbedProjectile)
		*/
		//SEPTIC EDIT BEGIN
		RegisterSignal(target, COMSIG_PROJECTILE_POSTHIT, .proc/projectile_posthit)
		//SEPTIC EDIT END

/datum/element/embed/Detach(obj/target)
	. = ..()
	if(isitem(target))
		UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ELEMENT_ATTACH, COMSIG_MOVABLE_IMPACT, COMSIG_PARENT_EXAMINE, COMSIG_EMBED_TRY_FORCE, COMSIG_ITEM_DISABLE_EMBED))
	else
		/* SEPTIC EDIT REMOVAL
		UnregisterSignal(target, list(COMSIG_PROJECTILE_SELF_ON_HIT, COMSIG_ELEMENT_ATTACH))
		*/
		//SEPTIC EDIT BEGIN
		UnregisterSignal(target, list(COMSIG_PROJECTILE_POSTHIT, COMSIG_ELEMENT_ATTACH))
		//SEPTIC EDIT END

/// Checking to see if we're gonna embed into a human
/datum/element/embed/proc/checkEmbed(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum, forced=FALSE)
	SIGNAL_HANDLER

	if(!istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE))
		return

	var/flying_speed = throwingdatum?.speed || weapon.throw_speed

	if(!forced && (flying_speed < EMBED_THROWSPEED_THRESHOLD && !ignore_throwspeed_threshold)) // check if it's a forced embed, and if not, if it's going fast enough to proc embedding
		return

	var/actual_chance = embed_chance
	var/penetrative_behaviour = 1 //Keep this above 1, as it is a multiplier for the pen_mod for determining actual embed chance.
	if(weapon.weak_against_armour)
		penetrative_behaviour = ARMOR_WEAKENED_MULTIPLIER

	if(throwingdatum?.speed > weapon.throw_speed)
		actual_chance += (throwingdatum.speed - weapon.throw_speed) * EMBED_CHANCE_SPEED_BONUS

	if(!weapon.isEmbedHarmless()) // all the armor in the world won't save you from a kick me sign
		var/armor = max(victim.run_armor_check(hit_zone, BULLET, silent=TRUE), victim.run_armor_check(hit_zone, BOMB, silent=TRUE)) * 0.5 // we'll be nice and take the better of bullet and bomb armor, halved

		if(armor) // we only care about armor penetration if there's actually armor to penetrate
			var/pen_mod = -(armor * penetrative_behaviour) // if our shrapnel is weak into armor, then we restore our armor to the full value.
			actual_chance += pen_mod // doing the armor pen as a separate calc just in case this ever gets expanded on
			if(actual_chance <= 0)
				victim.visible_message(span_danger("[weapon] bounces off [victim]'s armor, unable to embed!"), span_notice("[weapon] bounces off your armor, unable to embed!"), vision_distance = COMBAT_MESSAGE_RANGE)
				return

	if(!prob(actual_chance))
		return

	var/obj/item/bodypart/limb = victim.get_bodypart(hit_zone) || pick(victim.bodyparts)
	/* SEPTIC EDIT REMOVAL
	victim.AddComponent(/datum/component/embedded,\
		weapon,\
		throwingdatum,\
		part = limb,\
		embed_chance = embed_chance,\
		fall_chance = fall_chance,\
		pain_chance = pain_chance,\
		pain_mult = pain_mult,\
		remove_pain_mult = remove_pain_mult,\
		rip_time = rip_time,\
		ignore_throwspeed_threshold = ignore_throwspeed_threshold,\
		jostle_chance = jostle_chance,\
		jostle_pain_mult = jostle_pain_mult,\
		pain_stam_pct = pain_stam_pct)
	*/
	//SEPTIC EDIT BEGIN
	var/supply_injury = limb.last_injury
	if(!weapon.isEmbedHarmless() && (!limb.last_injury || !(limb.last_injury.damage_type in list(WOUND_SLASH, WOUND_PIERCE))) )
		supply_injury = limb.create_injury(WOUND_PIERCE, weapon.w_class * 4)

	victim.AddComponent(/datum/component/embedded,\
		weapon,\
		throwingdatum,\
		part = limb,\
		embed_chance = embed_chance,\
		fall_chance = fall_chance,\
		pain_chance = pain_chance,\
		pain_mult = pain_mult,\
		remove_pain_mult = remove_pain_mult,\
		rip_time = rip_time,\
		ignore_throwspeed_threshold = ignore_throwspeed_threshold,\
		jostle_chance = jostle_chance,\
		jostle_pain_mult = jostle_pain_mult,\
		pain_stam_pct = pain_stam_pct,\
		supplied_injury = supply_injury,\
		)
	//SEPTIC EDIT END

	return TRUE

///A different embed element has been attached, so we'll detach and let them handle things
/datum/element/embed/proc/severancePackage(obj/weapon, datum/element/E)
	SIGNAL_HANDLER

	if(istype(E, /datum/element/embed))
		Detach(weapon)

///If we don't want to be embeddable anymore (deactivating an e-dagger for instance)
/datum/element/embed/proc/detachFromWeapon(obj/weapon)
	SIGNAL_HANDLER

	Detach(weapon)

///Someone inspected our embeddable item
/datum/element/embed/proc/examined(obj/item/I, mob/user, list/examine_list)
	SIGNAL_HANDLER

	if(I.isEmbedHarmless())
		examine_list += "[I] feels sticky, and could probably get stuck to someone if thrown properly!"
	else
		examine_list += "[I] has a fine point, and could probably embed in someone if thrown properly!"

/**
 * checkEmbedProjectile() is what we get when a projectile with a defined shrapnel_type impacts a target.
 *
 * If we hit a valid target, we create the shrapnel_type object and immediately call tryEmbed() on it, targeting what we impacted. That will lead
 * it to call tryForceEmbed() on its own embed element (it's out of our hands here, our projectile is done), where it will run through all the checks it needs to.
 */
/datum/element/embed/proc/checkEmbedProjectile(obj/projectile/P, atom/movable/firer, atom/hit, angle, hit_zone)
	SIGNAL_HANDLER

	if(!iscarbon(hit))
		Detach(P)
		return // we don't care

	var/obj/item/payload = new payload_type(get_turf(hit))
	if(istype(payload, /obj/item/shrapnel/bullet))
		payload.name = P.name
	payload.embedding = P.embedding
	payload.updateEmbedding()
	var/mob/living/carbon/C = hit
	var/obj/item/bodypart/limb = C.get_bodypart(hit_zone)
	if(!limb)
		limb = C.get_bodypart()

	payload.tryEmbed(limb) // 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
	Detach(P)

/**
 * tryForceEmbed() is called here when we fire COMSIG_EMBED_TRY_FORCE from [/obj/item/proc/tryEmbed]. Mostly, this means we're a piece of shrapnel from a projectile that just impacted something, and we're trying to embed in it.
 *
 * The reason for this extra mucking about is avoiding having to do an extra hitby(), and annoying the target by impacting them once with the projectile, then again with the shrapnel, and possibly
 * AGAIN if we actually embed. This way, we save on at least one message.
 *
 * Arguments:
 * * I- the item we're trying to insert into the target
 * * target- what we're trying to shish-kabob, either a bodypart or a carbon
 * * hit_zone- if our target is a carbon, try to hit them in this zone, if we don't have one, pick a random one. If our target is a bodypart, we already know where we're hitting.
 * * forced- if we want this to succeed 100%
 */
/datum/element/embed/proc/tryForceEmbed(obj/item/I, atom/target, hit_zone, forced=FALSE)
	SIGNAL_HANDLER

	var/obj/item/bodypart/limb
	var/mob/living/carbon/C

	if(!forced && !prob(embed_chance))
		return

	if(iscarbon(target))
		C = target
		if(!hit_zone)
			limb = pick(C.bodyparts)
			hit_zone = limb.body_zone
	else if(isbodypart(target))
		limb = target
		hit_zone = limb.body_zone
		C = limb.owner

	return checkEmbed(I, C, hit_zone, forced=TRUE)


code:
/datum/element/empprotection
	element_flags = ELEMENT_DETACH | ELEMENT_BESPOKE
	id_arg_index = 2
	var/flags = NONE

/datum/element/empprotection/Attach(datum/target, _flags)
	. = ..()
	if(. == ELEMENT_INCOMPATIBLE || !isatom(target))
		return ELEMENT_INCOMPATIBLE
	flags = _flags
	RegisterSignal(target, COMSIG_ATOM_EMP_ACT, .proc/getEmpFlags)

/datum/element/empprotection/Detach(atom/target)
	UnregisterSignal(target, COMSIG_ATOM_EMP_ACT)
	return ..()

/datum/element/empprotection/proc/getEmpFlags(datum/source, severity)
	SIGNAL_HANDLER

	return flags


code:
#define CLUMSY_ATTACK_SELF_CHANCE 50
#define EYESTAB_BLEEDING_THRESHOLD 10

/// An element that lets you stab people in the eyes when targeting them
/datum/element/eyestab
	element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
	id_arg_index = 2
/* SEPTIC EDIT REMOVAL
	/// The amount of damage to do per eyestab
	var/damage = 7

/datum/element/eyestab/Attach(datum/target, damage)
	. = ..()

	if (!isitem(target))
		return ELEMENT_INCOMPATIBLE

	if (!isnull(damage))
		src.damage = damage

	RegisterSignal(target, COMSIG_ITEM_ATTACK, .proc/on_item_attack)

/datum/element/eyestab/Detach(datum/source, ...)
	. = ..()

	UnregisterSignal(source, COMSIG_ITEM_ATTACK)

/datum/element/eyestab/proc/on_item_attack(datum/source, mob/living/target, mob/living/user)
	SIGNAL_HANDLER

	if (user.zone_selected == BODY_ZONE_PRECISE_EYES)
		if (HAS_TRAIT(user, TRAIT_CLUMSY) && prob(CLUMSY_ATTACK_SELF_CHANCE))
			target = user

		perform_eyestab(source, target, user)

		return COMPONENT_SKIP_ATTACK

/datum/element/eyestab/proc/perform_eyestab(obj/item/item, mob/living/target, mob/living/user)
	var/obj/item/bodypart/target_limb = target.get_bodypart(BODY_ZONE_HEAD)

	if (ishuman(target) && isnull(target_limb))
		return

	if (target.is_eyes_covered())
		to_chat(user, span_warning("You failed to stab [target.p_their()] eyes, you need to remove [target.p_their()] eye protection first!"))
		return

	if (isalien(target))
		to_chat(user, span_warning("You cannot locate any eyes on this creature!"))
		return

	if (isbrain(target))
		to_chat(user, span_warning("You cannot locate any organic eyes on this brain!"))
		return

	item.add_fingerprint(user)

	playsound(item, item.hitsound, 30, TRUE, -1)

	user.do_attack_animation(target)

	if (target == user)
		user.visible_message(
			span_danger("[user] stabs [user.p_them()]self in the eyes with [item]!"),
			span_userdanger("You stab yourself in the eyes with [item]!"),
		)
	else
		target.visible_message(
			span_danger("[user] stabs [target] in the eye with [item]!"),
			span_userdanger("[user] stabs you in the eye with [item]!"),
		)

	if (target_limb)
		target.apply_damage(damage, BRUTE, target_limb)
	else
		target.take_bodypart_damage(damage)

	SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "eye_stab", /datum/mood_event/eye_stab)

	log_combat(user, target, "attacked", "[item.name]", "(Combat mode: [user.combat_mode ? "On" : "Off"])")

	var/obj/item/organ/eyes/eyes = target.getorganslot(ORGAN_SLOT_EYES)
	if (!eyes)
		return

	target.adjust_blurriness(3)
	eyes.applyOrganDamage(rand(2,4))

	if(eyes.damage < EYESTAB_BLEEDING_THRESHOLD)
		return

	target.adjust_blurriness(15)
	if (target.stat != DEAD)
		to_chat(target, span_danger("Your eyes start to bleed profusely!"))

	if (!target.is_blind() && !HAS_TRAIT(target, TRAIT_NEARSIGHT))
		to_chat(target, span_danger("You become nearsighted!"))

	target.become_nearsighted(EYE_DAMAGE)

	if (prob(50))
		if (target.stat != DEAD && target.drop_all_held_items())
			to_chat(target, span_danger("You drop what you're holding and clutch at your eyes!"))
		target.adjust_blurriness(10)
		target.Unconscious(20)
		target.Paralyze(40)

	if (prob(eyes.damage - EYESTAB_BLEEDING_THRESHOLD + 1))
		target.become_blind(EYE_DAMAGE)
		to_chat(target, span_danger("You go blind!"))
*/
#undef CLUMSY_ATTACK_SELF_CHANCE
#undef EYESTAB_BLEEDING_THRESHOLD


code:
/**
 * Can be applied to /atom/movable subtypes to make them apply fire stacks to things they hit
 */
/datum/element/firestacker
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	/// How many firestacks to apply per hit
	var/amount

/datum/element/firestacker/Attach(datum/target, amount)
	. = ..()

	if(!ismovable(target))
		return ELEMENT_INCOMPATIBLE

	src.amount = amount

	RegisterSignal(target, COMSIG_MOVABLE_IMPACT, .proc/impact, override = TRUE)
	if(isitem(target))
		RegisterSignal(target, COMSIG_ITEM_ATTACK, .proc/item_attack, override = TRUE)
		RegisterSignal(target, COMSIG_ITEM_ATTACK_SELF, .proc/item_attack_self, override = TRUE)

/datum/element/firestacker/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, list(COMSIG_MOVABLE_IMPACT, COMSIG_ITEM_ATTACK, COMSIG_ITEM_ATTACK_SELF))

/datum/element/firestacker/proc/stack_on(datum/owner, mob/living/target)
	target.adjust_fire_stacks(amount)

/datum/element/firestacker/proc/impact(datum/source, atom/hit_atom, datum/thrownthing/throwingdatum)
	SIGNAL_HANDLER

	if(isliving(hit_atom))
		stack_on(source, hit_atom)

/datum/element/firestacker/proc/item_attack(datum/source, atom/movable/target, mob/living/user)
	SIGNAL_HANDLER

	if(isliving(target))
		stack_on(source, target)

/datum/element/firestacker/proc/item_attack_self(datum/source, mob/user)
	SIGNAL_HANDLER

	if(isliving(user))
		stack_on(source, user)


code:
// If an item has the dunkable element, it's able to be dunked into reagent containers like beakers and glasses.
// Dunking the item into a container will transfer reagents from the container to the item.
/datum/element/dunkable
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	var/dunk_amount // the amount of reagents that will be transfered from the container to the item on each click

/datum/element/dunkable/Attach(datum/target, amount_per_dunk)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE
	dunk_amount = amount_per_dunk
	RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, .proc/get_dunked)

/datum/element/dunkable/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_ITEM_AFTERATTACK)

/datum/element/dunkable/proc/get_dunked(datum/source, atom/target, mob/user, proximity_flag)
	SIGNAL_HANDLER

	if(!proximity_flag) // if the user is not adjacent to the container
		return
	var/obj/item/reagent_containers/container = target // the container we're trying to dunk into
	if(istype(container) && container.reagent_flags & DUNKABLE) // container should be a valid target for dunking
		if(!container.is_drainable())
			to_chat(user, span_warning("[container] is unable to be dunked in!"))
			return
		var/obj/item/I = source // the item that has the dunkable element
		if(container.reagents.trans_to(I, dunk_amount, transfered_by = user)) //if reagents were transfered, show the message
			to_chat(user, span_notice("You dunk \the [I] into \the [container]."))
			return
		if(!container.reagents.total_volume)
			to_chat(user, span_warning("[container] is empty!"))
		else
			to_chat(user, span_warning("[I] is full!"))


code:
// If an item has the food_trash element it will drop an item when it is consumed.
/datum/element/food_trash
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	/// The type of trash that is spawned by this element
	var/atom/trash
	///Flags of the trash element that change its behavior
	var/flags
	///Generate trash proc path
	var/generate_trash_procpath

/datum/element/food_trash/Attach(datum/target, atom/trash, flags, generate_trash_proc)
	. = ..()
	if(!isatom(target))
		return ELEMENT_INCOMPATIBLE
	src.trash = trash
	src.flags = flags
	RegisterSignal(target, COMSIG_FOOD_CONSUMED, .proc/generate_trash)
	if(!generate_trash_procpath && generate_trash_proc)
		generate_trash_procpath = generate_trash_proc
	if(flags & FOOD_TRASH_OPENABLE)
		RegisterSignal(target, COMSIG_ITEM_ATTACK_SELF, .proc/open_trash)
	if(flags & FOOD_TRASH_POPABLE)
		RegisterSignal(target, COMSIG_FOOD_CROSSED, .proc/food_crossed)
	RegisterSignal(target, COMSIG_ITEM_ON_GRIND, .proc/generate_trash)
	RegisterSignal(target, COMSIG_ITEM_ON_JUICE, .proc/generate_trash)
	RegisterSignal(target, COMSIG_ITEM_ON_COMPOSTED, .proc/generate_trash)
	RegisterSignal(target, COMSIG_ITEM_SOLD_TO_CUSTOMER, .proc/generate_trash)

/datum/element/food_trash/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_FOOD_CONSUMED)

/datum/element/food_trash/proc/generate_trash(datum/source, mob/living/eater, mob/living/feeder)
	SIGNAL_HANDLER

	///cringy signal_handler shouldnt be needed if you dont want to return but oh well
	INVOKE_ASYNC(src, .proc/async_generate_trash, source)

/datum/element/food_trash/proc/async_generate_trash(datum/source)
	var/atom/edible_object = source

	var/obj/item/trash_item = generate_trash_procpath ? call(source, generate_trash_procpath)() : new trash(edible_object.drop_location())

	if(isliving(edible_object.loc))
		var/mob/living/food_holding_mob = edible_object.loc
		food_holding_mob.put_in_hands(trash_item)

/datum/element/food_trash/proc/food_crossed(datum/source, mob/crosser, bitecount)
	SIGNAL_HANDLER

	if(!isliving(crosser) || bitecount) // can't pop opened chips
		return
	var/mob/living/popper = crosser
	if(popper.mob_size < MOB_SIZE_HUMAN)
		return

	playsound(source, 'sound/effects/chipbagpop.ogg', 100)

	popper.visible_message(span_danger("[popper] steps on \the [source], popping the bag!"), span_danger("You step on \the [source], popping the bag!"), span_danger("You hear a sharp crack!"), COMBAT_MESSAGE_RANGE)
	INVOKE_ASYNC(src, .proc/async_generate_trash, source)
	qdel(source)

/datum/element/food_trash/proc/open_trash(datum/source, mob/user)
	SIGNAL_HANDLER

	to_chat(user, span_notice("You open the [source], revealing \a [initial(trash.name)]."))

	INVOKE_ASYNC(src, .proc/async_generate_trash, source)
	qdel(source)


code:
// If an item has the processable item, it can be processed into another item with a specific tool. This adds generic behavior for those actions to make it easier to set-up generically.
/datum/element/processable
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	///The type of atom this creates when the processing recipe is used.
	var/atom/result_atom_type
	///The tool behaviour for this processing recipe
	var/tool_behaviour
	///Time to process the atom
	var/time_to_process
	///Amount of the resulting actor this will create
	var/amount_created

/datum/element/processable/Attach(datum/target, tool_behaviour, result_atom_type, amount_created = 3, time_to_process = 20)
	. = ..()
	if(!isatom(target))
		return ELEMENT_INCOMPATIBLE

	src.tool_behaviour = tool_behaviour
	src.amount_created = amount_created
	src.time_to_process = time_to_process
	src.result_atom_type = result_atom_type

	RegisterSignal(target, COMSIG_ATOM_TOOL_ACT(tool_behaviour), .proc/try_process)
	RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/OnExamine)

/datum/element/processable/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, list(COMSIG_ATOM_TOOL_ACT(tool_behaviour), COMSIG_PARENT_EXAMINE))

/datum/element/processable/proc/try_process(datum/source, mob/living/user, obj/item/I, list/mutable_recipes)
	SIGNAL_HANDLER

	mutable_recipes += list(list(TOOL_PROCESSING_RESULT = result_atom_type, TOOL_PROCESSING_AMOUNT = amount_created, TOOL_PROCESSING_TIME = time_to_process))

///So people know what the frick they're doing without reading from a wiki page (I mean they will inevitably but i'm trying to help, ok?)
/datum/element/processable/proc/OnExamine(atom/source, mob/user, list/examine_list)
	SIGNAL_HANDLER

	if(amount_created > 1)
		examine_list += span_notice("It can be turned into [amount_created] [initial(result_atom_type.name)]s with [tool_behaviour_name(tool_behaviour)]!")
	else
		examine_list += span_notice("It can be turned into \a [initial(result_atom_type.name)] with [tool_behaviour_name(tool_behaviour)]!")


code:
///This component can be used to give something value for venues
/datum/element/venue_price
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	var/venue_price

/datum/element/venue_price/Attach(datum/target, venue_price)
	. = ..()
	if(!venue_price)
		stack_trace("A venue_price element was attached to something without specifying an actual price.")
		return ELEMENT_INCOMPATIBLE
	src.venue_price = venue_price
	RegisterSignal(target, COMSIG_ITEM_SOLD_TO_CUSTOMER, .proc/item_sold)

/datum/element/venue_price/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, COMSIG_ITEM_SOLD_TO_CUSTOMER)

/datum/element/venue_price/proc/item_sold(datum/thing_sold, mob/living/simple_animal/robot_customer/sold_to, obj/item/container)
	SIGNAL_HANDLER

	var/datum/venue/venue_to_pay = sold_to.ai_controller?.blackboard[BB_CUSTOMER_ATTENDING_VENUE]

	new /obj/item/holochip(get_turf(container), venue_price)
	venue_to_pay.total_income += venue_price
	playsound(get_turf(container), 'sound/effects/cashregister.ogg', 60, TRUE)