code:
/// Allows an item to  be used to initiate surgeries.
/datum/element/surgery_initiator
/* SEPTIC EDIT REMOVAL
	element_flags = ELEMENT_DETACH

/datum/element/surgery_initiator/Attach(datum/target)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_ITEM_ATTACK, .proc/initiate_surgery_moment)

/datum/element/surgery_initiator/Detach(datum/source)
	UnregisterSignal(source, COMSIG_ITEM_ATTACK)
	return ..()

/// Does the surgery initiation.
/datum/element/surgery_initiator/proc/initiate_surgery_moment(datum/source, atom/target, mob/user)
	SIGNAL_HANDLER
	if(!isliving(target))
		return
	INVOKE_ASYNC(src, .proc/do_initiate_surgery_moment, source, target, user)
	return COMPONENT_CANCEL_ATTACK_CHAIN

/datum/element/surgery_initiator/proc/do_initiate_surgery_moment(datum/source, atom/target, mob/user)
	var/mob/living/livingtarget = target
	var/mob/living/carbon/carbontarget
	var/obj/item/bodypart/affecting
	var/selected_zone = user.zone_selected
	if(iscarbon(livingtarget))
		carbontarget = livingtarget
		affecting = carbontarget.get_bodypart(check_zone(selected_zone))

	var/datum/surgery/current_surgery

	for(var/i_one in livingtarget.surgeries)
		var/datum/surgery/surgeryloop = i_one
		if(surgeryloop.location == selected_zone)
			current_surgery = surgeryloop

	if(!current_surgery)
		var/list/all_surgeries = GLOB.surgeries_list.Copy()
		var/list/available_surgeries = list()

		for(var/i_two in all_surgeries)
			var/datum/surgery/surgeryloop_two = i_two
			if(!surgeryloop_two.possible_locs.Find(selected_zone))
				continue
			if(affecting)
				if(!surgeryloop_two.requires_bodypart)
					continue
				if(surgeryloop_two.requires_bodypart_type && affecting.status != surgeryloop_two.requires_bodypart_type)
					continue
				if(surgeryloop_two.requires_real_bodypart && affecting.is_pseudopart)
					continue
			else if(carbontarget && surgeryloop_two.requires_bodypart) //mob with no limb in surgery zone when we need a limb
				continue
			if(surgeryloop_two.lying_required && livingtarget.body_position != LYING_DOWN)
				continue
			if(!surgeryloop_two.can_start(user, livingtarget))
				continue
			for(var/path in surgeryloop_two.target_mobtypes)
				if(istype(livingtarget, path))
					available_surgeries[surgeryloop_two.name] = surgeryloop_two
					break

		if(!available_surgeries.len)
			return

		var/pick_your_surgery = input("Begin which procedure?", "Surgery", null, null) as null|anything in sort_list(available_surgeries)
		if(pick_your_surgery && user?.Adjacent(livingtarget) && (source in user))
			var/datum/surgery/surgeryinstance_notonmob = available_surgeries[pick_your_surgery]

			for(var/i_three in livingtarget.surgeries)
				var/datum/surgery/surgeryloop_three = i_three
				if(surgeryloop_three.location == selected_zone)
					return //during the input() another surgery was started at the same location.

			//we check that the surgery is still doable after the input() wait.
			if(carbontarget)
				affecting = carbontarget.get_bodypart(check_zone(selected_zone))
			if(affecting)
				if(!surgeryinstance_notonmob.requires_bodypart)
					return
				if(surgeryinstance_notonmob.requires_bodypart_type && affecting.status != surgeryinstance_notonmob.requires_bodypart_type)
					return
			else if(carbontarget && surgeryinstance_notonmob.requires_bodypart)
				return
			if(surgeryinstance_notonmob.lying_required && livingtarget.body_position != LYING_DOWN)
				return
			if(!surgeryinstance_notonmob.can_start(user, livingtarget))
				return

			if(surgeryinstance_notonmob.ignore_clothes || get_location_accessible(livingtarget, selected_zone))
				var/datum/surgery/procedure = new surgeryinstance_notonmob.type(livingtarget, selected_zone, affecting)
				ADD_TRAIT(livingtarget, TRAIT_ALLOWED_HONORBOUND_ATTACK, TRAIT_GENERIC)
				user.visible_message(span_notice("[user] drapes [source] over [livingtarget]'s [parse_zone(selected_zone)] to prepare for surgery."), \
					span_notice("You drape [source] over [livingtarget]'s [parse_zone(selected_zone)] to prepare for \an [procedure.name]."))

				log_combat(user, livingtarget, "operated on", null, "(OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])")
			else
				to_chat(user, span_warning("You need to expose [livingtarget]'s [parse_zone(selected_zone)] first!"))

	else if(!current_surgery.step_in_progress)
		attempt_cancel_surgery(current_surgery, source, livingtarget, user)

/// Does the surgery de-initiation.
/datum/element/surgery_initiator/proc/attempt_cancel_surgery(datum/surgery/the_surgery, obj/item/the_item, mob/living/the_patient, mob/user)
	var/selected_zone = user.zone_selected

	if(the_surgery.status == 1)
		the_patient.surgeries -= the_surgery
		REMOVE_TRAIT(the_patient, TRAIT_ALLOWED_HONORBOUND_ATTACK, TRAIT_GENERIC)
		user.visible_message(span_notice("[user] removes [the_item] from [the_patient]'s [parse_zone(selected_zone)]."), \
			span_notice("You remove [the_item] from [the_patient]'s [parse_zone(selected_zone)]."))
		qdel(the_surgery)
		return

	if(!the_surgery.can_cancel)
		return

	var/required_tool_type = TOOL_CAUTERY
	var/obj/item/close_tool = user.get_inactive_held_item()
	var/is_robotic = the_surgery.requires_bodypart_type == BODYPART_ROBOTIC

	if(is_robotic)
		required_tool_type = TOOL_SCREWDRIVER

	if(iscyborg(user))
		close_tool = locate(/obj/item/cautery) in user.held_items
		if(!close_tool)
			to_chat(user, span_warning("You need to equip a cautery in an inactive slot to stop [the_patient]'s surgery!"))
			return
	else if(!close_tool || close_tool.tool_behaviour != required_tool_type)
		to_chat(user, span_warning("You need to hold a [is_robotic ? "screwdriver" : "cautery"] in your inactive hand to stop [the_patient]'s surgery!"))
		return

	if(the_surgery.operated_bodypart)
		the_surgery.operated_bodypart.generic_bleedstacks -= 5

	the_patient.surgeries -= the_surgery
	REMOVE_TRAIT(the_patient, TRAIT_ALLOWED_HONORBOUND_ATTACK, TRAIT_GENERIC)
	user.visible_message(span_notice("[user] closes [the_patient]'s [parse_zone(selected_zone)] with [close_tool] and removes [the_item]."), \
		span_notice("You close [the_patient]'s [parse_zone(selected_zone)] with [close_tool] and remove [the_item]."))
	qdel(the_surgery)
*/


code:
/*!

This element is used in vat growing to allow for the object to be

*/
/datum/element/swabable
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	///The define of the cell_line list to use
	var/cell_line_define
	///The define of the cell_virus list to use
	var/virus_define
	///Amount of cell lines on a single sample
	var/cell_line_amount
	///The chance the sample will be infected with a virus.
	var/virus_chance

///Listens for the swab signal and then generate a sample based on pre-determined lists that are saved as GLOBs. this allows us to have very few swabbable element instances.
/datum/element/swabable/Attach(datum/target, cell_line_define, virus_define, cell_line_amount = 1, virus_chance = 10)
	. = ..()
	if(!isatom(target) || isarea(target))
		return ELEMENT_INCOMPATIBLE

	RegisterSignal(target, COMSIG_SWAB_FOR_SAMPLES, .proc/GetSwabbed)

	src.cell_line_define = cell_line_define
	src.virus_define = virus_define
	src.cell_line_amount = cell_line_amount
	src.virus_chance = virus_chance

///Stops listening to the swab signal; you can no longer be swabbed.
/datum/element/swabable/Detach(datum/source)
	. = ..()
	if(!isatom(source) || isarea(source))
		return ELEMENT_INCOMPATIBLE
	UnregisterSignal(source, COMSIG_SWAB_FOR_SAMPLES)

///Ran when the parent is swabbed by an object that can swab that type of obj. The list is sent by ref, which means the thing which sent the signal will still have the updated list.
/datum/element/swabable/proc/GetSwabbed(datum/source, list/mutable_results)
	SIGNAL_HANDLER
	. = COMPONENT_SWAB_FOUND //Return this so the swabbing component knows hes a good boy and found something that needs swabbing.

	LAZYADD(mutable_results, GenerateSample())
	Detach(source)

///Generates a /datum/biological_sample
/datum/element/swabable/proc/GenerateSample()
	var/datum/biological_sample/generated_sample = new
	generated_sample.GenerateSample(cell_line_define, virus_define, cell_line_amount, virus_chance)
	return generated_sample

repo_name: AnalWerewolf/sundown

license: mit


code:
/**
 * tenacious element; which makes the parent move faster while crawling
 *
 * Used by sparring sect!
 */
/datum/element/tenacious
	element_flags = ELEMENT_DETACH

/datum/element/tenacious/Attach(datum/target)
	. = ..()

	if(!ishuman(target))
		return COMPONENT_INCOMPATIBLE
	var/mob/living/carbon/human/valid_target = target
	on_stat_change(valid_target, new_stat = valid_target.stat) //immediately try adding movement bonus if they're in soft crit
	RegisterSignal(target, COMSIG_MOB_STATCHANGE, .proc/on_stat_change)
	ADD_TRAIT(target, TRAIT_TENACIOUS, INNATE_TRAIT)

/datum/element/tenacious/Detach(datum/target)
	UnregisterSignal(target, COMSIG_MOB_STATCHANGE)
	REMOVE_TRAIT(target, TRAIT_TENACIOUS, INNATE_TRAIT)
	return ..()

///signal called by the stat of the target changing
/datum/element/tenacious/proc/on_stat_change(mob/living/carbon/human/target, new_stat)
	SIGNAL_HANDLER

	if(new_stat == SOFT_CRIT)
		target.balloon_alert(target, "your tenacity kicks in")
		target.add_movespeed_modifier(/datum/movespeed_modifier/tenacious)
	else
		target.balloon_alert(target, "your tenacity wears off")
		target.remove_movespeed_modifier(/datum/movespeed_modifier/tenacious)


code:
/**
 * Tool flash bespoke element
 *
 * Flashes the user when using this tool
 */
/datum/element/tool_flash
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	/// Strength of the flash
	var/flash_strength

/datum/element/tool_flash/Attach(datum/target, flash_strength)
	. = ..()
	if(!isitem(target))
		return ELEMENT_INCOMPATIBLE

	src.flash_strength = flash_strength

	RegisterSignal(target, COMSIG_TOOL_IN_USE, .proc/prob_flash)
	RegisterSignal(target, COMSIG_TOOL_START_USE, .proc/flash)

/datum/element/tool_flash/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, list(COMSIG_TOOL_IN_USE, COMSIG_TOOL_START_USE))

/datum/element/tool_flash/proc/prob_flash(datum/source, mob/living/user)
	SIGNAL_HANDLER

	if(prob(90))
		return
	flash(source, user)

/datum/element/tool_flash/proc/flash(datum/source, mob/living/user)
	SIGNAL_HANDLER

	if(user && get_dist(get_turf(source), get_turf(user)) <= 1)
		user.flash_act(min(flash_strength,1))


code:
/datum/element/turf_z_transparency
	element_flags = ELEMENT_DETACH
	var/show_bottom_level = FALSE

///This proc sets up the signals to handle updating viscontents when turfs above/below update. Handle plane and layer here too so that they don't cover other obs/turfs in Dream Maker
/datum/element/turf_z_transparency/Attach(datum/target, show_bottom_level = TRUE)
	. = ..()
	if(!isturf(target))
		return ELEMENT_INCOMPATIBLE

	var/turf/our_turf = target

	src.show_bottom_level = show_bottom_level

	our_turf.plane = OPENSPACE_PLANE
	our_turf.layer = OPENSPACE_LAYER

	RegisterSignal(target, COMSIG_TURF_MULTIZ_DEL, .proc/on_multiz_turf_del)
	RegisterSignal(target, COMSIG_TURF_MULTIZ_NEW, .proc/on_multiz_turf_new)

	ADD_TRAIT(our_turf, TURF_Z_TRANSPARENT_TRAIT, TURF_TRAIT)

	update_multiz(our_turf, TRUE, TRUE)

/datum/element/turf_z_transparency/Detach(datum/source)
	. = ..()
	var/turf/our_turf = source
	our_turf.vis_contents.len = 0
	UnregisterSignal(our_turf, list(COMSIG_TURF_MULTIZ_NEW, COMSIG_TURF_MULTIZ_DEL))
	REMOVE_TRAIT(our_turf, TURF_Z_TRANSPARENT_TRAIT, TURF_TRAIT)

///Updates the viscontents or underlays below this tile.
/datum/element/turf_z_transparency/proc/update_multiz(turf/our_turf, prune_on_fail = FALSE, init = FALSE)
	var/turf/below_turf = our_turf.below()
	if(!below_turf)
		our_turf.vis_contents.len = 0
		if(!show_bottom_level(our_turf) && prune_on_fail) //If we cant show whats below, and we prune on fail, change the turf to plating as a fallback
			our_turf.ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
			return FALSE
	if(init)
		our_turf.vis_contents += below_turf

	if(isclosedturf(our_turf)) //Show girders below closed turfs
		var/mutable_appearance/girder_underlay = mutable_appearance('icons/obj/structures.dmi', "girder", layer = TURF_LAYER-0.01)
		girder_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
		our_turf.underlays += girder_underlay
		var/mutable_appearance/plating_underlay = mutable_appearance('icons/turf/floors.dmi', "plating", layer = TURF_LAYER-0.02)
		plating_underlay = RESET_ALPHA | RESET_COLOR
		our_turf.underlays += plating_underlay
	return TRUE

/datum/element/turf_z_transparency/proc/on_multiz_turf_del(turf/our_turf, turf/below_turf, dir)
	SIGNAL_HANDLER

	if(dir != DOWN)
		return

	update_multiz(our_turf)

/datum/element/turf_z_transparency/proc/on_multiz_turf_new(turf/our_turf, turf/below_turf, dir)
	SIGNAL_HANDLER

	if(dir != DOWN)
		return

	update_multiz(our_turf)

///Called when there is no real turf below this turf
/datum/element/turf_z_transparency/proc/show_bottom_level(turf/our_turf)
	if(!show_bottom_level)
		return FALSE
	var/turf/path = SSmapping.level_trait(our_turf.z, ZTRAIT_BASETURF) || /turf/open/space
	if(!ispath(path))
		path = text2path(path)
		if(!ispath(path))
			warning("Z-level [our_turf.z] has invalid baseturf '[SSmapping.level_trait(our_turf.z, ZTRAIT_BASETURF)]'")
			path = /turf/open/space
	var/mutable_appearance/underlay_appearance = mutable_appearance(initial(path.icon), initial(path.icon_state), layer = TURF_LAYER-0.02, plane = PLANE_SPACE)
	underlay_appearance.appearance_flags = RESET_ALPHA | RESET_COLOR
	our_turf.underlays += underlay_appearance
	return TRUE


code:
///Add to an object if you want to be able to be hidden under tiles
/datum/element/undertile
	element_flags = ELEMENT_BESPOKE | COMPONENT_DUPE_HIGHLANDER
	id_arg_index = 2

	///the invisiblity trait applied, like TRAIT_T_RAY_VISIBLE
	var/invisibility_trait
	///level of invisibility applied when under a tile. Could be INVISIBILITY_OBSERVER if you still want it to be visible to ghosts
	var/invisibility_level
	///an overlay for the tile if we wish to apply that
	var/tile_overlay
	///whether we use alpha or not. TRUE uses ALPHA_UNDERTILE because otherwise we have 200 different instances of this element for different alphas
	var/use_alpha
	///We will switch between anchored and unanchored. for stuff like satchels that shouldn't be pullable under tiles but are otherwise unanchored
	var/use_anchor

/datum/element/undertile/Attach(datum/target, invisibility_trait, invisibility_level = INVISIBILITY_MAXIMUM, tile_overlay, use_alpha = TRUE, use_anchor = FALSE)
	. = ..()
	if(!ismovable(target))
		return ELEMENT_INCOMPATIBLE

	RegisterSignal(target, COMSIG_OBJ_HIDE, .proc/hide)

	src.invisibility_trait = invisibility_trait
	src.invisibility_level = invisibility_level
	src.tile_overlay = tile_overlay
	src.use_alpha = use_alpha
	src.use_anchor = use_anchor

///called when a tile has been covered or uncovered
/datum/element/undertile/proc/hide(atom/movable/source, covered)
	SIGNAL_HANDLER

	source.invisibility = covered ? invisibility_level : 0

	var/turf/T = get_turf(source)

	if(covered)
		if(invisibility_trait)
			ADD_TRAIT(source, invisibility_trait, TRAIT_GENERIC)
		if(tile_overlay)
			T.add_overlay(tile_overlay)
		if(use_alpha)
			source.alpha = ALPHA_UNDERTILE
		if(use_anchor)
			source.set_anchored(TRUE)

	else
		if(invisibility_trait)
			REMOVE_TRAIT(source, invisibility_trait, TRAIT_GENERIC)
		if(tile_overlay)
			T.overlays -= tile_overlay
		if(use_alpha)
			source.alpha = 255
		if(use_anchor)
			source.set_anchored(FALSE)

/datum/element/undertile/Detach(atom/movable/AM, visibility_trait, invisibility_level = INVISIBILITY_MAXIMUM)
	. = ..()

	hide(AM, FALSE)


code:
//Prevents calling anything in update_icon() like update_icon_state() or update_overlays()

/datum/element/update_icon_blocker/Attach(datum/target)
	. = ..()
	if(!istype(target, /atom))
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_ATOM_UPDATE_ICON, .proc/block_update_icon)

/datum/element/update_icon_blocker/proc/block_update_icon()
	SIGNAL_HANDLER

	return COMSIG_ATOM_NO_UPDATE_ICON_STATE | COMSIG_ATOM_NO_UPDATE_OVERLAYS


code:
//update_icon() may change the onmob icons
//Very good name, I know

/datum/element/update_icon_updates_onmob/Attach(datum/target)
	. = ..()
	if(!istype(target, /obj/item))
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, .proc/update_onmob)

/datum/element/update_icon_updates_onmob/proc/update_onmob(obj/item/target)
	SIGNAL_HANDLER

	if(ismob(target.loc))
		var/mob/M = target.loc
		if(M.is_holding(target))
			M.update_inv_hands()
		else
			M.regenerate_icons() //yeah this is shit, but we don't know which update_foo() proc to call instead so we'll call them all


code:
/**
 * Venomous element; which makes the attacks of the simplemob attached poison the enemy.
 *
 * Used for spiders and bees!
 */
/datum/element/venomous
	element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
	id_arg_index = 2
	///Path of the reagent added
	var/poison_type
	///How much of the reagent added. if it's a list, it'll pick a range with the range being list(lower_value, upper_value)
	var/list/amount_added

/datum/element/venomous/Attach(datum/target, poison_type, amount_added)
	. = ..()

	if(isgun(target))
		RegisterSignal(target, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
	else if(isitem(target))
		RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, .proc/item_afterattack)
	else if(ishostile(target) || isbasicmob(target))
		RegisterSignal(target, COMSIG_HOSTILE_POST_ATTACKINGTARGET, .proc/hostile_attackingtarget)
	else
		return ELEMENT_INCOMPATIBLE

	src.poison_type = poison_type
	src.amount_added = amount_added

/datum/element/venomous/Detach(datum/target)
	UnregisterSignal(target, list(COMSIG_PROJECTILE_ON_HIT, COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_POST_ATTACKINGTARGET))
	return ..()

/datum/element/venomous/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
	SIGNAL_HANDLER

	add_reagent(target)

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

	if(!proximity_flag)
		return
	add_reagent(target)

/datum/element/venomous/proc/hostile_attackingtarget(mob/living/simple_animal/hostile/attacker, atom/target, success)
	SIGNAL_HANDLER

	if(!success)
		return
	add_reagent(target)

/datum/element/venomous/proc/add_reagent(mob/living/target)
	if(!istype(target))
		return
	if(target.stat == DEAD)
		return
	var/final_amount_added
	if(islist(amount_added))
		final_amount_added = rand(amount_added[1], amount_added[2])
	else
		final_amount_added = amount_added
	target.reagents?.add_reagent(poison_type, final_amount_added)


code:
/// An element to make an /obj explode based on gas pressure when broken
/datum/element/volatile_gas_storage
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2

	/// The minimum pressure of the gas storage to consider an explosion when broken
	var/minimum_explosive_pressure
	/// The max pressure to stop scaling the explosion at, you can go higher but the explosion range will stay at max
	var/max_explosive_pressure
	/// The max explsion range at the max pressure
	var/max_explosive_force

/datum/element/volatile_gas_storage/Attach(datum/target, minimum_explosive_pressure=5000, max_explosive_pressure=100000, max_explosive_force=9)
	. = ..()
	if(istype(target, /obj/machinery/atmospherics/components))
		RegisterSignal(target, COMSIG_ATOM_BREAK, .proc/AtmosComponentBreak)
	else if(isobj(target))
		RegisterSignal(target, COMSIG_ATOM_BREAK, .proc/ObjBreak)
	else
		return ELEMENT_INCOMPATIBLE

	src.minimum_explosive_pressure = minimum_explosive_pressure
	src.max_explosive_pressure = max_explosive_pressure
	src.max_explosive_force = max_explosive_force

/datum/element/volatile_gas_storage/Detach(datum/source, ...)
	. = ..()
	UnregisterSignal(source, COMSIG_ATOM_BREAK)

/datum/element/volatile_gas_storage/proc/Break(atom/origin, datum/gas_mixture/released_gas)
	var/expelled_pressure = min(released_gas?.return_pressure(), max_explosive_pressure)

	if(expelled_pressure < minimum_explosive_pressure)
		return

	var/explosive_force = CEILING((expelled_pressure / max_explosive_pressure) * max_explosive_force , 1)
	// This is supposed to represent only shrapnel and no fire
	// Maybe one day we'll get something a bit better
	explosion(get_turf(origin), light_impact_range=explosive_force, smoke=FALSE, explosion_cause = origin)

/datum/element/volatile_gas_storage/proc/AtmosComponentBreak(obj/machinery/atmospherics/components/owner)
	SIGNAL_HANDLER
	for(var/datum/gas_mixture/gas_contents as anything in owner.airs)
		if(!gas_contents)
			continue
		Break(owner, gas_contents)

/datum/element/volatile_gas_storage/proc/ObjBreak(obj/owner)
	SIGNAL_HANDLER
	Break(owner, owner.return_air())


code:
/datum/element/waddling

/datum/element/waddling/Attach(datum/target)
	. = ..()
	if(!ismovable(target))
		return ELEMENT_INCOMPATIBLE
	if(isliving(target))
		RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/LivingWaddle)
	else
		RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/Waddle)

/datum/element/waddling/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, COMSIG_MOVABLE_MOVED)

/datum/element/waddling/proc/LivingWaddle(mob/living/target)
	SIGNAL_HANDLER

	if(target.incapacitated() || target.body_position == LYING_DOWN)
		return
	Waddle(target)

/datum/element/waddling/proc/Waddle(atom/movable/target)
	SIGNAL_HANDLER

	animate(target, pixel_z = 4, time = 0)
	var/prev_trans = matrix(target.transform)
	animate(pixel_z = 0, transform = turn(target.transform, pick(-12, 0, 12)), time=2)
	animate(pixel_z = 0, transform = prev_trans, time = 0)


code:
/// An element that lets you engrave walls when right click is used
/datum/element/wall_engraver
	element_flags = ELEMENT_DETACH

/datum/element/wall_engraver/Attach(datum/target)
	. = ..()

	if (!isitem(target))
		return ELEMENT_INCOMPATIBLE

	RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	RegisterSignal(target, COMSIG_ITEM_PRE_ATTACK_SECONDARY, .proc/on_item_pre_attack_secondary)

/datum/element/wall_engraver/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, COMSIG_PARENT_EXAMINE)
	UnregisterSignal(source, COMSIG_ITEM_PRE_ATTACK_SECONDARY)

///signal called on parent being examined
/datum/element/wall_engraver/proc/on_examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER
	examine_list += span_notice("You can engrave some walls with your secondary attack if you can think of something interesting to engrave.")

///signal called on parent being used to right click attack something
/datum/element/wall_engraver/proc/on_item_pre_attack_secondary(datum/source, atom/target, mob/living/user)
	SIGNAL_HANDLER

	INVOKE_ASYNC(src, .proc/try_chisel, source, target, user)

	return COMPONENT_CANCEL_ATTACK_CHAIN

/datum/element/wall_engraver/proc/try_chisel(obj/item/item, turf/closed/wall, mob/living/user)
	if(!istype(wall) || !user.mind)
		return
	if(HAS_TRAIT_FROM(wall, TRAIT_NOT_ENGRAVABLE, INNATE_TRAIT))
		user.balloon_alert(user, "wall cannot be engraved!")
		return
	if(HAS_TRAIT_FROM(wall, TRAIT_NOT_ENGRAVABLE, TRAIT_GENERIC))
		user.balloon_alert(user, "wall has already been engraved!")
		return
	if(!user.mind?.memories?.len)
		user.balloon_alert(user, "nothing memorable to engrave!")
		return
	var/datum/memory/memory_to_engrave = user.mind.select_memory("engrave")
	if(!memory_to_engrave)
		return
	if(!user.Adjacent(wall))
		return
	item.add_fingerprint(user)
	playsound(item, item.hitsound, 30, TRUE, -1)
	user.do_attack_animation(wall)
	user.balloon_alert(user, "engraving wall...")
	if(!do_after(user, 5 SECONDS, target = wall))
		return
	user.balloon_alert(user, "wall engraved")
	user.do_attack_animation(wall)

	var/do_persistent_save = TRUE
	if(memory_to_engrave.memory_flags & MEMORY_FLAG_NOPERSISTENCE)
		do_persistent_save = FALSE

	var/engraved_story = memory_to_engrave.generate_story(STORY_ENGRAVING, STORY_FLAG_DATED)

	if(!engraved_story)
		CRASH("Tried to submit a memory with an invalid story [memory_to_engrave]")

	wall.AddComponent(/datum/component/engraved, memory_to_engrave.generate_story(STORY_ENGRAVING, STORY_FLAG_DATED), persistent_save = do_persistent_save, story_value = memory_to_engrave.story_value)
	memory_to_engrave.memory_flags |= MEMORY_FLAG_ALREADY_USED
	//while someone just engraved a story "worth engraving" we should add this to SSpersistence for a possible prison tattoo

	if(do_persistent_save)
		var/list/tattoo_entry = list()

		var/tattoo_story = memory_to_engrave.generate_story(STORY_TATTOO)

		if(tattoo_story)
			CRASH("Tried to submit a memory with an invalid story [memory_to_engrave]")

		tattoo_entry["story"] = tattoo_story
		SSpersistence.prison_tattoos_to_save += list(tattoo_entry)


code:
#define HITS_TO_CRIT(damage) round(100 / damage, 0.1)
/**
 *
 * The purpose of this element is to widely provide the ability to examine an object and determine its stats, with the ability to add
 * additional notes or information based on type or other factors
 *
 */
/datum/element/weapon_description
	element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
	id_arg_index = 2

	// Additional proc to be run for specific object types
	var/attached_proc

	// Flavor text crimes used in build_weapon_text()
	var/list/crimes = list("Assaults", "Third Degree Murders", "Robberies", "Terrorist Attacks", "Different Felonies", "Felinies", "Counts of Tax Evasion", "Mutinies")
	var/list/victims = list("a human", "a moth", "a felinid", "a lizard", "a particularly resilient slime", "a syndicate agent", "a clown", "a mime", "a mortal foe", "an innocent bystander")

/datum/element/weapon_description/Attach(datum/target, attached_proc)
	. = ..()
	if(!isitem(target)) // Do not attach this to anything that isn't an item
		return ELEMENT_INCOMPATIBLE
	RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/warning_label)
	RegisterSignal(target, COMSIG_TOPIC, .proc/topic_handler)
	// Don't perform the assignment if there is nothing to assign, or if we already have something for this bespoke element
	if(attached_proc && !src.attached_proc)
		src.attached_proc = attached_proc

/datum/element/weapon_description/Detach(datum/target)
	. = ..()
	UnregisterSignal(target, list(COMSIG_PARENT_EXAMINE, COMSIG_TOPIC))

/**
 *
 * This proc is called when the user examines an object with the associated element. This produces a hyperlinked
 * text line provided that the given item meets the weapon-determining criteria (Sufficient force or notes)
 *
 * Arguments:
 * 	* source - Object being examined, cast into an item variable
 *  * user - Unused
 *  * examine_texts - The output text list of the original examine function
 */
/datum/element/weapon_description/proc/warning_label(obj/item/item, mob/user, list/examine_texts)
	SIGNAL_HANDLER

	if(item.force >= 5 || item.throwforce >= 5 || item.override_notes || item.offensive_notes || attached_proc) /// Only show this tag for items that could feasibly be weapons, shields, or those that have special notes
		examine_texts += span_notice("It appears to have an ever-updating bluespace warning label.")

/**
 *
 * Details the stats of the examined weapon
 *
 * This function is called when the user clicks the hyperlink provided by
 * warning_label(). It calls build_label_text() and outputs its return value to the user
 *
 * Arguments:
 *  * source - Object being examined, sent to build_label_text()
 *  * href-list - List provided by the href of input values, used to know what hyperlinked action is being attempted
 */

/datum/element/weapon_description/proc/topic_handler(atom/source, user, href_list)
	SIGNAL_HANDLER

	if(href_list["examine"])
		to_chat(user, span_notice("[build_label_text(source)]"))

/**
 *
 * Compiles a warning label detailing various statistics of the examined weapon
 *
 * This function is called by the "examine" function of Topic(), and compiles a number of relevant
 * weapon stats into a message that is then shown to the user
 * Arguments:
 *  * source - The object whose stats are being examined
 */
/datum/element/weapon_description/proc/build_label_text(obj/item/source)
	var/list/readout = list("") // Readout is used to store the text block output to the user so it all can be sent in one message

	// Meaningless flavor text. The number of crimes is constantly changing because of the complex Nanotrasen legal system and the esoteric nature of time itself!
	readout += "[span_warning("WARNING:")] This item has been marked as dangerous by the NT legal team because of its use in [span_warning("[rand(2,99)] [crimes[rand(1, crimes.len)]]")] in the past hour.\n"

	// Doesn't show the base notes for items that have the override notes variable set to true
	if(!source.override_notes)
		// Make sure not to divide by 0 on accident
		if(source.force > 0)
			readout += "Our extensive research has shown that it takes a mere [span_warning("[HITS_TO_CRIT(source.force)] hit\s")] to beat down [victims[rand(1, victims.len)]] with no armor."
		else
			readout += "Our extensive research found that you couldn't beat anyone to death with this if you tried."

		if(source.throwforce > 0)
			readout += "If you decide to throw this object instead, one will take [span_warning("[HITS_TO_CRIT(source.throwforce)] hit\s")] before collapsing."
		else
			readout += "If you decide to throw this object instead, then you will have trouble damaging anything."
		if(source.armour_penetration > 0 || source.block_chance > 0)
			readout += "This item has proven itself [span_warning("[weapon_tag_convert(source.armour_penetration)]")] of piercing armor and [span_warning("[weapon_tag_convert(source.block_chance)]")] of blocking attacks."
	// Custom manual notes
	if(source.offensive_notes)
		readout += source.offensive_notes

	// Check if we have an additional proc, if so, add it to the readout
	if(attached_proc)
		readout += call(source, attached_proc)()

	// Finally bringing the fields together
	return readout.Join("\n")

/**
 *
 * Converts percentile based stats to an adjective appropriate for the
 * examined warning label
 *
 * Arguments:
 *  * tag_val: The value of the item to be added to the tag
 */
/datum/element/weapon_description/proc/weapon_tag_convert(tag_val)
	switch(tag_val)
		if(0)
			return "INCAPABLE"
		if(1 to 25)
			return "BARELY CAPABLE"
		if(26 to 50)
			return "CAPABLE"
		if(51 to 75)
			return "VERY CAPABLE"
		if(76 to INFINITY)
			return "EXTREMELY CAPABLE"
		else
			return "STRANGELY CAPABLE"


code:
///This element just handles creating and destroying an area sound manager that's hooked into weather stuff
/datum/element/weather_listener
	element_flags = ELEMENT_BESPOKE
	id_arg_index = 2
	var/weather_type
	//What events to change the track on
	var/list/sound_change_signals
	//The weather type we're working with
	var/weather_trait
	//The playlist of sounds to draw from. Pass by ref
	var/list/playlist

/datum/element/weather_listener/Attach(datum/target, w_type, trait, weather_playlist)
	. = ..()
	if(!weather_type)
		weather_type = w_type
		sound_change_signals = list(
			COMSIG_WEATHER_TELEGRAPH(weather_type),
			COMSIG_WEATHER_START(weather_type),
			COMSIG_WEATHER_WINDDOWN(weather_type),
			COMSIG_WEATHER_END(weather_type)
		)
		weather_trait = trait
		playlist = weather_playlist

	RegisterSignal(target, COMSIG_MOVABLE_Z_CHANGED, .proc/handle_z_level_change, override = TRUE)
	RegisterSignal(target, COMSIG_MOB_LOGOUT, .proc/handle_logout, override = TRUE)

/datum/element/weather_listener/Detach(datum/source)
	. = ..()
	UnregisterSignal(source, COMSIG_MOVABLE_Z_CHANGED, COMSIG_MOB_LOGOUT)

/datum/element/weather_listener/proc/handle_z_level_change(datum/source, old_z, new_z)
	SIGNAL_HANDLER
	var/list/fitting_z_levels = SSmapping.levels_by_trait(weather_trait)
	if(!(new_z in fitting_z_levels))
		return
	var/datum/component/our_comp = source.AddComponent(/datum/component/area_sound_manager, playlist, list(), COMSIG_MOB_LOGOUT, fitting_z_levels)
	our_comp.RegisterSignal(SSdcs, sound_change_signals, /datum/component/area_sound_manager/proc/handle_change)

/datum/element/weather_listener/proc/handle_logout(datum/source)
	SIGNAL_HANDLER
	source.RemoveElement(/datum/element/weather_listener, weather_type, weather_trait, playlist)


code:
#define EMOTE_VISIBLE 1
#define EMOTE_AUDIBLE 2

/**
 * # Emote
 *
 * Most of the text that's not someone talking is based off of this.
 *
 * Yes, the displayed message is stored on the datum, it would cause problems
 * for emotes with a message that can vary, but that's handled differently in
 * run_emote(), so be sure to use can_message_change if you plan to have
 * different displayed messages from player to player.
 *
 */
/datum/emote
	/// What calls the emote.
	var/key = ""
	/// This will also call the emote.
	var/key_third_person = ""
	/// Message displayed when emote is used.
	var/message = ""
	/// Message displayed if the user is a mime.
	var/message_mime = ""
	/// Message displayed if the user is a grown alien.
	var/message_alien = ""
	/// Message displayed if the user is an alien larva.
	var/message_larva = ""
	/// Message displayed if the user is a robot.
	var/message_robot = ""
	/// Message displayed if the user is an AI.
	var/message_AI = ""
	/// Message displayed if the user is a monkey.
	var/message_monkey = ""
	/// Message to display if the user is a simple_animal.
	var/message_simple = ""
	/// Message with %t at the end to allow adding params to the message, like for mobs doing an emote relatively to something else.
	var/message_param = ""
	/// Whether the emote is visible or audible.
	var/emote_type = EMOTE_VISIBLE
	/// Checks if the mob can use its hands before performing the emote.
	var/hands_use_check = FALSE
	/// Will only work if the emote is EMOTE_AUDIBLE.
	var/muzzle_ignore = FALSE
	/// Types that are allowed to use that emote.
	var/list/mob_type_allowed_typecache = /mob
	/// Types that are NOT allowed to use that emote.
	var/list/mob_type_blacklist_typecache
	/// Types that can use this emote regardless of their state.
	var/list/mob_type_ignore_stat_typecache
	/// In which state can you use this emote? (Check stat.dm for a full list of them)
	var/stat_allowed = CONSCIOUS
	/// Sound to play when emote is called.
	var/sound
	/// Used for the honk borg emote.
	var/vary = FALSE
	/// Can only code call this event instead of the player.
	var/only_forced_audio = FALSE
	/// The cooldown between the uses of the emote.
	var/cooldown = 0.8 SECONDS
	/// Does this message have a message that can be modified by the user?
	var/can_message_change = FALSE
	/// How long is the cooldown on the audio of the emote, if it has one?
	var/audio_cooldown = 2 SECONDS

/datum/emote/New()
	if (ispath(mob_type_allowed_typecache))
		switch (mob_type_allowed_typecache)
			if (/mob)
				mob_type_allowed_typecache = GLOB.typecache_mob
			if (/mob/living)
				mob_type_allowed_typecache = GLOB.typecache_living
			else
				mob_type_allowed_typecache = typecacheof(mob_type_allowed_typecache)
	else
		mob_type_allowed_typecache = typecacheof(mob_type_allowed_typecache)
	mob_type_blacklist_typecache = typecacheof(mob_type_blacklist_typecache)
	mob_type_ignore_stat_typecache = typecacheof(mob_type_ignore_stat_typecache)

/**
 * Handles the modifications and execution of emotes.
 *
 * Arguments:
 * * user - Person that is trying to send the emote.
 * * params - Parameters added after the emote.
 * * type_override - Override to the current emote_type.
 * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
 *
 * Returns TRUE if it was able to run the emote, FALSE otherwise.
 */
/datum/emote/proc/run_emote(mob/user, params, type_override, intentional = FALSE)
	. = TRUE
	if(!can_run_emote(user, TRUE, intentional))
		return FALSE
	var/msg = select_message_type(user, message, intentional)
	if(params && message_param)
		msg = select_param(user, params)

	msg = replace_pronoun(user, msg)

	if(!msg)
		return

	user.log_message(msg, LOG_EMOTE)
	var/dchatmsg = "[user] [msg]"

	var/tmp_sound = get_sound(user)
	if(tmp_sound && should_play_sound(user, intentional) && !TIMER_COOLDOWN_CHECK(user, type))
		TIMER_COOLDOWN_START(user, type, audio_cooldown)
		playsound(user, tmp_sound, 50, vary)

	var/user_turf = get_turf(user)
	if (user.client)
		for(var/mob/ghost as anything in GLOB.dead_mob_list)
			if(!ghost.client || isnewplayer(ghost))
				continue
			if(ghost.client.prefs.chat_toggles & CHAT_GHOSTSIGHT && !(ghost in viewers(user_turf, null)))
				ghost.show_message("[FOLLOW_LINK(ghost, user)] [dchatmsg]")

	if(emote_type == EMOTE_AUDIBLE)
		user.audible_message(msg, deaf_message = "You see how [user] [msg]", audible_message_flags = EMOTE_MESSAGE)
	else
		user.visible_message(msg, blind_message = "You hear how [user] [msg]", visible_message_flags = EMOTE_MESSAGE)

	SEND_SIGNAL(user, COMSIG_MOB_EMOTED(key))

/**
 * For handling emote cooldown, return true to allow the emote to happen.
 *
 * Arguments:
 * * user - Person that is trying to send the emote.
 * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
 *
 * Returns FALSE if the cooldown is not over, TRUE if the cooldown is over.
 */
/datum/emote/proc/check_cooldown(mob/user, intentional)
	if(!intentional)
		return TRUE
	if(user.emotes_used && user.emotes_used[src] + cooldown > world.time)
		var/datum/emote/default_emote = /datum/emote
		if(cooldown > initial(default_emote.cooldown)) // only worry about longer-than-normal emotes
			to_chat(user, span_danger("You must wait another [DisplayTimeText(user.emotes_used[src] - world.time + cooldown)] before using that emote."))
		return FALSE
	if(!user.emotes_used)
		user.emotes_used = list()
	user.emotes_used[src] = world.time
	return TRUE

/**
 * To get the sound that the emote plays, for special sound interactions depending on the mob.
 *
 * Arguments:
 * * user - Person that is trying to send the emote.
 *
 * Returns the sound that will be made while sending the emote.
 */
/datum/emote/proc/get_sound(mob/living/user)
	return sound //by default just return this var.

/**
 * To replace pronouns in the inputed string with the user's proper pronouns.
 *
 * Arguments:
 * * user - Person that is trying to send the emote.
 * * msg - The string to modify.
 *
 * Returns the modified msg string.
 */
/datum/emote/proc/replace_pronoun(mob/user, msg)
	if(findtext(msg, "their"))
		msg = replacetext(msg, "their", user.p_their())
	if(findtext(msg, "them"))
		msg = replacetext(msg, "them", user.p_them())
	if(findtext(msg, "they"))
		msg = replacetext(msg, "they", user.p_they())
	if(findtext(msg, "%s"))
		msg = replacetext(msg, "%s", user.p_s())
	return msg

/**
 * Selects the message type to override the message with.
 *
 * Arguments:
 * * user - Person that is trying to send the emote.
 * * msg - The string to modify.
 * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
 *
 * Returns the new message, or msg directly, if no change was needed.
 */
/datum/emote/proc/select_message_type(mob/user, msg, intentional)
	// Basically, we don't care that the others can use datum variables, because they're never going to change.
	. = msg
	if(!muzzle_ignore && user.is_muzzled() && emote_type == EMOTE_AUDIBLE)
		return "makes a [pick("strong ", "weak ", "")]noise."
	if(user.mind && user.mind.miming && message_mime)
		. = message_mime
	if(isalienadult(user) && message_alien)
		. = message_alien
	else if(islarva(user) && message_larva)
		. = message_larva
	else if(iscyborg(user) && message_robot)
		. = message_robot
	else if(isAI(user) && message_AI)
		. = message_AI
	else if(ismonkey(user) && message_monkey)
		. = message_monkey
	else if(isanimal(user) && message_simple)
		. = message_simple

/**
 * Replaces the %t in the message in message_param by params.
 *
 * Arguments:
 * * user - Person that is trying to send the emote.
 * * params - Parameters added after the emote.
 *
 * Returns the modified string.
 */
/datum/emote/proc/select_param(mob/user, params)
	return replacetext(message_param, "%t", params)

/**
 * Check to see if the user is allowed to run the emote.
 *
 * Arguments:
 * * user - Person that is trying to send the emote.
 * * status_check - Bool that says whether we should check their stat or not.
 * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
 *
 * Returns a bool about whether or not the user can run the emote.
 */
/datum/emote/proc/can_run_emote(mob/user, status_check = TRUE, intentional = FALSE)
	. = TRUE
	if(!is_type_in_typecache(user, mob_type_allowed_typecache))
		return FALSE
	if(is_type_in_typecache(user, mob_type_blacklist_typecache))
		return FALSE
	if(status_check && !is_type_in_typecache(user, mob_type_ignore_stat_typecache))
		if(user.stat > stat_allowed)
			if(!intentional)
				return FALSE
			switch(user.stat)
				if(SOFT_CRIT)
					to_chat(user, span_warning("You cannot [key] while in a critical condition!"))
				if(UNCONSCIOUS, HARD_CRIT)
					to_chat(user, span_warning("You cannot [key] while unconscious!"))
				if(DEAD)
					to_chat(user, span_warning("You cannot [key] while dead!"))
			return FALSE
		if(hands_use_check && HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
			if(!intentional)
				return FALSE
			to_chat(user, span_warning("You cannot use your hands to [key] right now!"))
			return FALSE

	if(isliving(user))
		var/mob/living/sender = user
		if(HAS_TRAIT(sender, TRAIT_EMOTEMUTE))
			return FALSE

/**
 * Check to see if the user should play a sound when performing the emote.
 *
 * Arguments:
 * * user - Person that is doing the emote.
 * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE).
 *
 * Returns a bool about whether or not the user should play a sound when performing the emote.
 */
/datum/emote/proc/should_play_sound(mob/user, intentional = FALSE)
	if(only_forced_audio && intentional)
		return FALSE
	return TRUE

/**
* Allows the intrepid coder to send a basic emote
* Takes text as input, sends it out to those who need to know after some light parsing
* If you need something more complex, make it into a datum emote
* Arguments:
* * text - The text to send out
*
* Returns TRUE if it was able to run the emote, FALSE otherwise.
*/
/mob/proc/manual_emote(text) //Just override the song and dance
	. = TRUE
	if(stat != CONSCIOUS)
		return

	if(!text)
		CRASH("Someone passed nothing to manual_emote(), fix it")

	log_message(text, LOG_EMOTE)

	var/ghost_text = "[src] [text]"

	var/origin_turf = get_turf(src)
	if(client)
		for(var/mob/ghost as anything in GLOB.dead_mob_list)
			if(!ghost.client || isnewplayer(ghost))
				continue
			if(ghost.client.prefs.chat_toggles & CHAT_GHOSTSIGHT && !(ghost in viewers(origin_turf, null)))
				ghost.show_message("[FOLLOW_LINK(ghost, src)] [ghost_text]")

	visible_message(text, visible_message_flags = EMOTE_MESSAGE)


code:
/datum/ert
	var/mobtype = /mob/living/carbon/human
	var/team = /datum/team/ert
	var/opendoors = TRUE
	var/leader_role = /datum/antagonist/ert/commander
	var/enforce_human = TRUE
	var/roles = list(/datum/antagonist/ert/security, /datum/antagonist/ert/medic, /datum/antagonist/ert/engineer) //List of possible roles to be assigned to ERT members.
	var/rename_team
	var/code
	var/mission = "Assist the station."
	var/teamsize = 5
	var/polldesc
	/// If TRUE, gives the team members "[role] [random last name]" style names
	var/random_names = TRUE
	/// If TRUE, the admin who created the response team will be spawned in the briefing room in their preferred briefing outfit (assuming they're a ghost)
	var/spawn_admin = FALSE
	/// If TRUE, we try and pick one of the most experienced players who volunteered to fill the leader slot
	var/leader_experience = TRUE

/datum/ert/New()
	if (!polldesc)
		polldesc = "a Code [code] Nanotrasen Emergency Response Team"

/datum/ert/blue
	opendoors = FALSE
	code = "Blue"

/datum/ert/amber
	code = "Amber"

/datum/ert/red
	leader_role = /datum/antagonist/ert/commander/red
	roles = list(/datum/antagonist/ert/security/red, /datum/antagonist/ert/medic/red, /datum/antagonist/ert/engineer/red)
	code = "Red"

/datum/ert/deathsquad
	roles = list(/datum/antagonist/ert/deathsquad)
	leader_role = /datum/antagonist/ert/deathsquad/leader
	rename_team = "Deathsquad"
	code = "Delta"
	mission = "Leave no witnesses."
	polldesc = "an elite Nanotrasen Strike Team"

/datum/ert/marine
	leader_role = /datum/antagonist/ert/marine
	roles = list(/datum/antagonist/ert/marine/security, /datum/antagonist/ert/marine/engineer, /datum/antagonist/ert/marine/medic)
	rename_team = "Marine Squad"
	polldesc = "an 'elite' Nanotrasen Strike Team"
	opendoors = FALSE

/datum/ert/centcom_official
	code = "Green"
	teamsize = 1
	opendoors = FALSE
	leader_role = /datum/antagonist/ert/official
	roles = list(/datum/antagonist/ert/official)
	rename_team = "CentCom Officials"
	polldesc = "a CentCom Official"
	random_names = FALSE
	leader_experience = FALSE

/datum/ert/centcom_official/New()
	mission = "Conduct a routine performance review of [station_name()] and its Captain."

/datum/ert/inquisition
	roles = list(/datum/antagonist/ert/chaplain/inquisitor, /datum/antagonist/ert/security/inquisitor, /datum/antagonist/ert/medic/inquisitor)
	leader_role = /datum/antagonist/ert/commander/inquisitor
	rename_team = "Inquisition"
	mission = "Destroy any traces of paranormal activity aboard the station."
	polldesc = "a Nanotrasen paranormal response team"

/datum/ert/janitor
	roles = list(/datum/antagonist/ert/janitor, /datum/antagonist/ert/janitor/heavy)
	leader_role = /datum/antagonist/ert/janitor/heavy
	teamsize = 4
	opendoors = FALSE
	rename_team = "Janitor"
	mission = "Clean up EVERYTHING."
	polldesc = "a Nanotrasen Janitorial Response Team"

/datum/ert/intern
	roles = list(/datum/antagonist/ert/intern)
	leader_role = /datum/antagonist/ert/intern/leader
	teamsize = 7
	opendoors = FALSE
	rename_team = "Horde of Interns"
	mission = "Assist in conflict resolution."
	polldesc = "an unpaid internship opportunity with Nanotrasen"
	random_names = FALSE

/datum/ert/intern/unarmed
	roles = list(/datum/antagonist/ert/intern/unarmed)
	leader_role = /datum/antagonist/ert/intern/leader/unarmed
	rename_team = "Unarmed Horde of Interns"

/datum/ert/erp
	roles = list(/datum/antagonist/ert/security/party, /datum/antagonist/ert/clown/party, /datum/antagonist/ert/engineer/party, /datum/antagonist/ert/janitor/party)
	leader_role = /datum/antagonist/ert/commander/party
	opendoors = FALSE
	rename_team = "Emergency Response Party"
	mission = "Create entertainment for the crew."
	polldesc = "a Code Rainbow Nanotrasen Emergency Response Party"
	code = "Rainbow"


code:
//Just new and forget
/datum/forced_movement
	var/atom/movable/victim
	var/atom/target
	var/last_processed
	var/steps_per_tick
	var/allow_climbing
	var/datum/callback/on_step
	var/moved_at_all = FALSE
															//as fast as ssfastprocess
/datum/forced_movement/New(atom/movable/_victim, atom/_target, _steps_per_tick = 0.5, _allow_climbing = FALSE, datum/callback/_on_step = null)
	victim = _victim
	target = _target
	steps_per_tick = _steps_per_tick
	allow_climbing = _allow_climbing
	on_step = _on_step

	. = ..()

	if(_victim && _target && _steps_per_tick && !_victim.force_moving)
		last_processed = world.time
		_victim.force_moving = src
		START_PROCESSING(SSfastprocess, src)
	else
		qdel(src) //if you want to overwrite the current forced movement, call qdel(victim.force_moving) before creating this

/datum/forced_movement/Destroy()
	if(victim.force_moving == src)
		victim.force_moving = null
		if(moved_at_all)
			victim.forceMove(victim.loc) //get the side effects of moving here that require us to currently not be force_moving aka reslipping on ice
		STOP_PROCESSING(SSfastprocess, src)
	victim = null
	target = null
	return ..()

/datum/forced_movement/process()
	if(QDELETED(victim) || !victim.loc || QDELETED(target) || !target.loc)
		qdel(src)
		return
	var/steps_to_take = round(steps_per_tick * (world.time - last_processed))
	if(steps_to_take)
		for(var/i in 1 to steps_to_take)
			if(TryMove())
				moved_at_all = TRUE
				if(on_step)
					on_step.InvokeAsync()
			else
				qdel(src)
				return
		last_processed = world.time

/datum/forced_movement/proc/TryMove(recursive = FALSE)
	if(QDELETED(src)) //Our previous step caused deletion of this datum
		return

	var/atom/movable/vic = victim //sanic
	var/atom/tar = target

	if(!recursive)
		. = step_towards(vic, tar)

	//shit way for getting around corners
	if(!.)
		if(tar.x > vic.x)
			if(step(vic, EAST))
				. = TRUE
		else if(tar.x < vic.x)
			if(step(vic, WEST))
				. = TRUE

		if(!.)
			if(tar.y > vic.y)
				if(step(vic, NORTH))
					. = TRUE
			else if(tar.y < vic.y)
				if(step(vic, SOUTH))
					. = TRUE

			if(!.)
				if(recursive)
					return FALSE
				else
					. = TryMove(TRUE)

	. = . && (vic.loc != tar.loc)


code:
# Greyscale Auto-Generated Sprites (GAGS)

If you're wanting to add easy recolors for your sprite then this is the system for you. Features include:

- Multiple color layers so your sprite can be generated from more than one color.
- Mixed greyscale and colored sprite layers; You can choose to only greyscale a part of the sprite or have premade filters applied to layers.
- Blend modes; Instead of just putting layers of sprites on top of eachother you can use the more advanced blend modes.
- Reusable configurations; You can reference greyscale sprites from within the configuration of another, allowing you to have a bunch of styles with minimal additional configuration.

## Other Documents

- [Basic follow along guide on hackmd](https://hackmd.io/@tgstation/GAGS-Walkthrough)

## Broad overview

There are three main parts to GAGS that you'll need to be aware of when adding a new greyscale sprite:

- The json configuration

All configuration files can be found in [code/datums/greyscale/json_configs](./json_configs) and is where you control how your icons are combined together along with the colors specified from in code.

- The dmi file

It contains the sprites that will be used as the basis for the rest of the generation process. You can only have one dmi file specified per configuration but if you want to split up your sprites you can reference other configurations instead.

- The configuration type

This is simply some pointers in the code linking together your dmi and the json configuration.

## Json Configuration File

The json is made up of some metadata and a list of layers used while creating the sprite. Inner lists are processed as their own chunk before being applied elsewhere, this is useful when you start using more advanced blend modes. Most of the time though you're just going to want a list of icons overlaid on top of eachother.

```json
{
	"icon_state_name": [
		{
			"type": "reference",
			"reference_type": "/datum/greyscale_config/some_other_config",
			"blend_mode": "overlay",
			"color_ids": [ 1 ]
		},
		[
			{
				"type": "icon_state",
				"icon_state": "highlights",
				"blend_mode": "overlay",
				"color_ids": [ 2 ]
			},
			{
				"type": "reference",
				"reference_type": "/datum/greyscale_config/sparkle_effect",
				"blend_mode": "add"
			}
		]
	]
}
```

In this example, we start off by creating a sprite specified by a different configuration. The "type" key is required to specify the kind of layer you defining. Once that is done, the next two layers are grouped together, so they will be generated into one sprite before being applied to any sprites outside their group. You can think of it as an order of operations.

The first of the two in the inner group is an "icon_state", this means that the icon will be retrieved from the associated dmi file using the "icon_state" key.

The last layer is another reference type. Note that you don't need to give colors to every layer if the layer does not need any colors applied to it.

"blend_mode" and "color_ids" are special, all layer types have them. The blend mode is what controls how that layer's finished product gets merged together with the rest of the sprite. The color ids control what colors are passed in to the layer.

Once it is done generating it will be placed in an icon file with the icon state of "icon_state_name". You can use any name you like here.

## Dmi File

There are no special requirements from the dmi file to work with this system. You just need to specify the icon file in code and the icon_state in the json configuration.

## Dm Code

While the amount of dm code required to make a greyscale sprite was minimized as much as possible, some small amount is required anyway if you want anything to use it.

As an example:
```c
/datum/greyscale_config/canister
	icon_file = 'icons/obj/atmospherics/canisters/default.dmi'
	json_config = 'code/datums/greyscale/json_configs/canister_default.json'
```
And that's all you need to make it usable by other code:

```c
/obj/machinery/portable_atmospherics/canister
	...
	greyscale_config = /datum/greyscale_config/canister
	greyscale_colors = "#ee4242"
```

More configurations can be found in [code/datums/greyscale/greyscale_configs.dm](./greyscale_configs.dm)

If you want your item to be colorable in a vending machine (or other places if there's ever any support added for that), you should do it like this:

```c
/obj/item/clothing/head/beret
	...
	flags_1 = IS_PLAYER_COLORABLE_1
```
However, **be extremely careful**, as this *requires* that you put *all* of the object's `flags_1` flags in that statement all over again. It's ugly, I know, but there's no
better way to do this with BYOND just yet. You can put multiple flags like this (not real flags):
```c
/obj/item/clothing/head/beret
	...
	flags_1 = IS_PLAYER_COLORABLE_1 | THIS_IS_A_FAKE_FLAG | THIS_IS_ANOTHER_FAKE_FLAG
```

## Debugging

If you're making a new greyscale sprite you sometimes want to be able to see how layers got generated or maybe you're just tweaking some colors. Rather than rebooting the server with every change there is a greyscale modification menu that can be found in the vv dropdown menu for the greyscale object. Here you can change colors, preview the results, and reload everything from their files.


code:
#define MAX_SANE_LAYERS 50

/// A datum tying together a greyscale configuration and dmi file. Required for using GAGS and handles the code interactions.
/datum/greyscale_config
	/// User friendly name used in the debug menu
	var/name

	/// Reference to the json config file
	var/json_config

	/// Reference to the dmi file for this config
	var/icon_file

	/// An optional var to set that tells the material system what material this configuration is for.
	/// Use a typepath here, not an instance.
	var/datum/material/material_skin

	///////////////////////////////////////////////////////////////////////////////////////////
	// Do not set any further vars, the json file specified above is what generates the object

	/// Spritesheet width of the icon_file
	var/width

	/// Spritesheet height of the icon_file
	var/height

	/// String path to the json file, used for reloading
	var/string_json_config

	/// The md5 file hash for the json configuration. Used to check if the file has changed
	var/json_config_hash

	/// String path to the icon file, used for reloading
	var/string_icon_file

	/// The md5 file hash for the icon file. Used to check if the file has changed
	var/icon_file_hash

	/// A list of icon states and their layers
	var/list/icon_states

	/// A list of all layers irrespective of nesting
	var/list/flat_all_layers

	/// A list of types to update in the world whenever a config changes
	var/list/live_edit_types

	/// How many colors are expected to be given when building the sprite
	var/expected_colors = 0

	/// Generated icons keyed by their color arguments
	var/list/icon_cache

// There's more sanity checking here than normal because this is designed for spriters to work with
// Sensible error messages that tell you exactly what's wrong is the best way to make this easy to use
/datum/greyscale_config/New()
	if(!json_config)
		stack_trace("Greyscale config object [DebugName()] is missing a json configuration, make sure `json_config` has been assigned a value.")
	string_json_config = "[json_config]"
	if(!icon_file)
		stack_trace("Greyscale config object [DebugName()] is missing an icon file, make sure `icon_file` has been assigned a value.")
	string_icon_file = "[icon_file]"
	if(!name)
		stack_trace("Greyscale config object [DebugName()] is missing a name, make sure `name` has been assigned a value.")

/datum/greyscale_config/Destroy(force, ...)
	if(!force)
		return QDEL_HINT_LETMELIVE
	return ..()

/datum/greyscale_config/process(delta_time)
	if(!Refresh(loadFromDisk=TRUE))
		return
	if(!live_edit_types)
		return
	for(var/atom/thing in world)
		if(live_edit_types[thing.type])
			thing.update_greyscale()

/datum/greyscale_config/proc/EnableAutoRefresh(live_type)
	message_admins("Config auto refresh has been enabled for '[live_type]' with configuration [DebugName()]. Expect heavy lag.")
	if(live_type)
		if(!live_edit_types)
			live_edit_types = list()
		live_edit_types += typecacheof(live_type)
	START_PROCESSING(SSgreyscale, src)

/datum/greyscale_config/proc/DisableAutoRefresh(live_type, remove_all=FALSE)
	if(!remove_all && !(live_type in live_edit_types))
		return
	message_admins("Config auto refresh has been disabled for '[live_type]' with configuration [DebugName()]")
	if(remove_all)
		live_edit_types = null
	else if(live_type && live_edit_types)
		live_edit_types -= typecacheof(live_type)
	if(!length(live_edit_types))
		live_edit_types = null
		STOP_PROCESSING(SSgreyscale, src)

/// Call this proc to handle all the data extraction from the json configuration. Can be forced to load values from disk instead of memory.
/datum/greyscale_config/proc/Refresh(loadFromDisk=FALSE)
	if(loadFromDisk)
		var/changed = FALSE

		json_config = file(string_json_config)
		var/json_hash = md5asfile(json_config)
		if(json_config_hash != json_hash)
			json_config_hash = json_hash
			changed = TRUE

		icon_file = file(string_icon_file)
		var/icon_hash = md5asfile(icon_file)
		if(icon_file_hash != icon_hash)
			icon_file_hash = icon_hash
			changed = TRUE

		for(var/datum/greyscale_layer/layer as anything in flat_all_layers)
			if(layer.DiskRefresh())
				changed = TRUE

		if(!changed)
			return FALSE

	var/list/raw = json_decode(file2text(json_config))
	ReadIconStateConfiguration(raw)

	if(!length(icon_states))
		CRASH("The json configuration [DebugName()] doesn't have any icon states.")

	icon_cache = list()

	ReadMetadata()

	SEND_SIGNAL(src, COMSIG_GREYSCALE_CONFIG_REFRESHED)

	return TRUE

/// Called after every config has refreshed, this proc handles data verification that depends on multiple entwined configurations.
/datum/greyscale_config/proc/CrossVerify()
	for(var/icon_state in icon_states)
		var/list/verification_targets = icon_states[icon_state]
		verification_targets = verification_targets.Copy()
		while(length(verification_targets))
			var/datum/greyscale_layer/layer = verification_targets[length(verification_targets)]
			verification_targets.len--
			if(islist(layer))
				verification_targets += layer
				continue
			layer.CrossVerify()

/// Gets the name used for debug purposes
/datum/greyscale_config/proc/DebugName()
	var/display_name = name || "MISSING_NAME"
	return "[display_name] ([icon_file]|[json_config])"

/// Takes the json icon state configuration and puts it into a more processed format.
/datum/greyscale_config/proc/ReadIconStateConfiguration(list/data)
	icon_states = list()
	for(var/state in data)
		var/list/raw_layers = data[state]
		if(!length(raw_layers))
			stack_trace("The json configuration [DebugName()] for icon state '[state]' is missing any layers.")
			continue
		if(icon_states[state])
			stack_trace("The json configuration [DebugName()] has a duplicate icon state '[state]' and is being overriden.")
		icon_states[state] = ReadLayersFromJson(raw_layers)

/// Takes the json layers configuration and puts it into a more processed format
/datum/greyscale_config/proc/ReadLayersFromJson(list/data)
	var/list/output = ReadLayerGroup(data)
	return output[1]

/datum/greyscale_config/proc/ReadLayerGroup(list/data)
	if(!islist(data[1]))
		var/layer_type = SSgreyscale.layer_types[data["type"]]
		if(!layer_type)
			CRASH("An unknown layer type was specified in the json of greyscale configuration [DebugName()]: [data["layer_type"]]")
		return new layer_type(icon_file, data.Copy()) // We don't want anything in there touching our version of the data
	var/list/output = list()
	for(var/list/group as anything in data)
		output += ReadLayerGroup(group)
	if(length(output)) // Adding lists to lists unwraps the top level so here we are
		output = list(output)
	return output

/// Reads layer configurations to take out some useful overall information
/datum/greyscale_config/proc/ReadMetadata()
	var/icon/source = icon(icon_file)
	height = source.Height()
	width = source.Width()

	var/list/datum/greyscale_layer/all_layers = list()
	for(var/state in icon_states)
		var/list/to_process = list(icon_states[state])
		var/list/state_layers = list()

		while(length(to_process))
			var/current = to_process[length(to_process)]
			to_process.len--
			if(islist(current))
				to_process += current
			else
				state_layers += current

		all_layers += state_layers

		if(length(state_layers) > MAX_SANE_LAYERS)
			stack_trace("[DebugName()] icon state '[state]' has [length(state_layers)] layers which is larger than the max of [MAX_SANE_LAYERS].")

	flat_all_layers = list()
	var/list/color_groups = list()
	var/largest_id = 0
	for(var/datum/greyscale_layer/layer as anything in all_layers)
		flat_all_layers += layer
		for(var/id in layer.color_ids)
			if(!isnum(id))
				continue
			largest_id = max(id, largest_id)
			color_groups["[id]"] = TRUE

	for(var/i in 1 to largest_id)
		if(color_groups["[i]"])
			continue
		stack_trace("Color Ids are required to be sequential and start from 1. [DebugName()] has a max id of [largest_id] but is missing [i].")

	expected_colors = length(color_groups)

/// For saving a dmi to disk, useful for debug mainly
/datum/greyscale_config/proc/SaveOutput(color_string)
	var/icon/icon_output = GenerateBundle(color_string)
	fcopy(icon_output, "tmp/gags_debug_output.dmi")

/// Actually create the icon and color it in, handles caching
/datum/greyscale_config/proc/Generate(color_string)
	var/key = color_string
	var/icon/new_icon = icon_cache[key]
	if(new_icon)
		return icon(new_icon)

	var/icon/icon_bundle = GenerateBundle(color_string)
	icon_bundle = fcopy_rsc(icon_bundle)
	icon_cache[key] = icon_bundle
	var/icon/output = icon(icon_bundle)
	return output

/// Handles the actual icon manipulation to create the spritesheet
/datum/greyscale_config/proc/GenerateBundle(list/colors, list/render_steps)
	if(!istype(colors))
		colors = SSgreyscale.ParseColorString(colors)
	if(length(colors) != expected_colors)
		CRASH("[DebugName()] expected [expected_colors] color arguments but only received [length(colors)]")

	var/list/generated_icons = list()
	for(var/icon_state in icon_states)
		var/list/icon_state_steps
		if(render_steps)
			icon_state_steps = render_steps[icon_state] = list()
		var/icon/generated_icon = GenerateLayerGroup(colors, icon_states[icon_state], icon_state_steps)
		// We read a pixel to force the icon to be fully generated before we let it loose into the world
		// I hate this
		generated_icon.GetPixel(1, 1)
		generated_icons[icon_state] = generated_icon

	var/icon/icon_bundle = generated_icons[""] || icon('icons/testing/greyscale_error.dmi')
	icon_bundle.Scale(width, height)
	generated_icons -= ""

	for(var/icon_state in generated_icons)
		icon_bundle.Insert(generated_icons[icon_state], icon_state)

	return icon_bundle

/// Internal recursive proc to handle nested layer groups
/datum/greyscale_config/proc/GenerateLayerGroup(list/colors, list/group, list/render_steps)
	var/icon/new_icon
	for(var/datum/greyscale_layer/layer as anything in group)
		var/icon/layer_icon
		if(islist(layer))
			layer_icon = GenerateLayerGroup(colors, layer, render_steps)
			layer = layer[1] // When there are multiple layers in a group like this we use the first one's blend mode
		else
			layer_icon = layer.Generate(colors, render_steps)

		if(!new_icon)
			new_icon = layer_icon
		else
			new_icon.Blend(layer_icon, layer.blend_mode)

		// These are so we can see the result of every step of the process in the preview ui
		if(render_steps)
			var/list/icon_data = list()
			render_steps += list(icon_data)
			icon_data["config_name"] = name
			icon_data["step"] = icon(layer_icon)
			icon_data["result"] = icon(new_icon)
	return new_icon

/datum/greyscale_config/proc/GenerateDebug(colors)
	var/list/output = list()
	var/list/debug_steps = list()
	output["steps"] = debug_steps

	output["icon"] = GenerateBundle(colors, debug_steps)
	return output

#undef MAX_SANE_LAYERS


code:
/datum/greyscale_config/canister
	name = "Default Canister"
	icon_file = 'icons/obj/atmospherics/canisters.dmi'
	json_config = 'code/datums/greyscale/json_configs/canister_default.json'

/datum/greyscale_config/canister/base
	name = "Base Canister Style"
	json_config = 'code/datums/greyscale/json_configs/canister_base.json'

/datum/greyscale_config/canister/post_effects
	name = "Canister Post-Effects"
	json_config = 'code/datums/greyscale/json_configs/canister_post_effects.json'

/datum/greyscale_config/canister/stripe
	name = "Single Striped Canister"
	json_config = 'code/datums/greyscale/json_configs/canister_stripe.json'

/datum/greyscale_config/canister/double_stripe
	name = "Double Striped Canister"
	json_config = 'code/datums/greyscale/json_configs/canister_double_stripe.json'

/datum/greyscale_config/canister/hazard
	name = "Hazard Striped Canister"
	json_config = 'code/datums/greyscale/json_configs/canister_hazard.json'

/datum/greyscale_config/prototype_canister
	name = "Prototype Canister"
	icon_file = 'icons/obj/atmospherics/prototype_canister.dmi'
	json_config = 'code/datums/greyscale/json_configs/canister_proto.json'

/datum/greyscale_config/stationary_canister
	name = "Stationary Canister"
	icon_file = 'icons/obj/atmospherics/stationary_canisters.dmi'
	json_config = 'code/datums/greyscale/json_configs/smooth_canister_stationary.json'

/datum/greyscale_config/carp
	name = "Space Carp"
	icon_file = 'icons/mob/carp.dmi'
	json_config = 'code/datums/greyscale/json_configs/carp.json'

/datum/greyscale_config/carp/disk_mouth
	name = "Space Carp, Disk in Mouth"
	json_config = 'code/datums/greyscale/json_configs/carp_disk_mouth.json'

/datum/greyscale_config/wirecutters
	name = "Wirecutters"
	icon_file = 'icons/obj/tools.dmi'
	json_config = 'code/datums/greyscale/json_configs/wirecutters.json'

/datum/greyscale_config/screwdriver
	name = "Screwdriver"
	icon_file = 'icons/obj/tools.dmi'
	json_config = 'code/datums/greyscale/json_configs/screwdriver.json'

/datum/greyscale_config/screwdriver_inhand_left
	name = "Held Screwdriver, Left"
	icon_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
	json_config = 'code/datums/greyscale/json_configs/screwdriver_worn.json'

/datum/greyscale_config/screwdriver_inhand_right
	name = "Held Screwdriver, Right"
	icon_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
	json_config = 'code/datums/greyscale/json_configs/screwdriver_worn.json'

/datum/greyscale_config/screwdriver_belt
	name = "Belt Worn Screwdriver"
	icon_file = 'icons/obj/clothing/belt_overlays.dmi'
	json_config = 'code/datums/greyscale/json_configs/screwdriver_worn.json'

/datum/greyscale_config/jumpsuit
	name = "Jumpsuit"
	icon_file = 'icons/obj/clothing/under/color.dmi'
	json_config = 'code/datums/greyscale/json_configs/jumpsuit.json'

/datum/greyscale_config/jumpsuit_worn
	name = "Worn Jumpsuit"
	icon_file = 'icons/mob/clothing/under/color.dmi'
	json_config = 'code/datums/greyscale/json_configs/jumpsuit_worn.json'

/datum/greyscale_config/jumpsuit_inhand_left
	name = "Held Jumpsuit, Left"
	icon_file = 'icons/mob/inhands/clothing_lefthand.dmi'
	json_config = 'code/datums/greyscale/json_configs/jumpsuit_inhand.json'

/datum/greyscale_config/jumpsuit_inhand_right
	name = "Held Jumpsuit, Right"
	icon_file = 'icons/mob/inhands/clothing_righthand.dmi'
	json_config = 'code/datums/greyscale/json_configs/jumpsuit_inhand.json'

/datum/greyscale_config/jumpsuit_prison
	name = "Prison Jumpsuit"
	icon_file = 'icons/obj/clothing/under/color.dmi'
	json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison.json'

/datum/greyscale_config/jumpsuit_prison_worn
	name = "Worn Prison Jumpsuit"
	icon_file = 'icons/mob/clothing/under/color.dmi'
	json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison_worn.json'

/datum/greyscale_config/jumpsuit_prison_inhand_left
	name = "Held Prison Jumpsuit, Left"
	icon_file = 'icons/mob/inhands/clothing_lefthand.dmi'
	json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison_inhand.json'

/datum/greyscale_config/jumpsuit_prison_inhand_right
	name = "Held Prison Jumpsuit, Right"
	icon_file = 'icons/mob/inhands/clothing_righthand.dmi'
	json_config = 'code/datums/greyscale/json_configs/jumpsuit_prison_inhand.json'

/datum/greyscale_config/pda
	name = "PDA"
	icon_file = 'icons/obj/pda.dmi'
	json_config = 'code/datums/greyscale/json_configs/pda.json'

/datum/greyscale_config/pda/chaplain
	name = "Chaplain PDA"
	json_config = 'code/datums/greyscale/json_configs/pda_chaplain.json'

/datum/greyscale_config/pda/captain
	name = "Captain PDA"
	json_config = 'code/datums/greyscale/json_configs/pda_captain.json'

/datum/greyscale_config/pda/clown
	name = "Clown PDA"
	json_config = 'code/datums/greyscale/json_configs/pda_clown.json'

/datum/greyscale_config/pda/head
	name = "Head PDA"
	json_config = 'code/datums/greyscale/json_configs/pda_head.json'

/datum/greyscale_config/pda/mime
	name = "Mime PDA"
	json_config = 'code/datums/greyscale/json_configs/pda_mime.json'

/datum/greyscale_config/pda/stripe_split
	name = "Split Stripe PDA"
	json_config = 'code/datums/greyscale/json_configs/pda_stripe_split.json'

/datum/greyscale_config/pda/stripe_thick
	name = "Thick Stripe PDA"
	json_config = 'code/datums/greyscale/json_configs/pda_stripe_thick.json'

/datum/greyscale_config/pda/stripe_thick/head
	name = "Head Thick Stripe PDA"
	json_config = 'code/datums/greyscale/json_configs/pda_stripe_thick_head.json'

/datum/greyscale_config/sneakers
	name = "Sneakers"
	icon_file = 'icons/obj/clothing/shoes.dmi'
	json_config = 'code/datums/greyscale/json_configs/sneakers.json'

/datum/greyscale_config/sneakers_worn
	name = "Worn Sneakers"
	icon_file = 'icons/mob/clothing/feet.dmi'
	json_config = 'code/datums/greyscale/json_configs/sneakers_worn.json'

/datum/greyscale_config/sneakers_orange
	name = "Orange Sneakers"
	icon_file = 'icons/obj/clothing/shoes.dmi'
	json_config = 'code/datums/greyscale/json_configs/sneakers_orange.json'

/datum/greyscale_config/sneakers_orange_worn
	name = "Worn Orange Sneakers"
	icon_file = 'icons/mob/clothing/feet.dmi'
	json_config = 'code/datums/greyscale/json_configs/sneakers_orange_worn.json'

/datum/greyscale_config/sneakers_wheelys
	name = "Wheeled Sneakers"
	icon_file = 'icons/obj/clothing/shoes.dmi'
	json_config = 'code/datums/greyscale/json_configs/sneakers_wheelys.json'

/datum/greyscale_config/sneakers_marisa
	name = "Marisa Sneakers"
	icon_file = 'icons/obj/clothing/shoes.dmi'
	json_config = 'code/datums/greyscale/json_configs/sneakers_marisa.json'

/datum/greyscale_config/circuit
	name = "Circuit Board"
	icon_file = 'icons/obj/module.dmi'
	json_config = 'code/datums/greyscale/json_configs/circuit.json'

/datum/greyscale_config/sombrero
	name = "Sombrero"
	icon_file = 'icons/obj/clothing/head/sombrero.dmi'
	json_config = 'code/datums/greyscale/json_configs/sombrero.json'

/datum/greyscale_config/sombrero/base
	name = "Base Sombrero Style"
	json_config = 'code/datums/greyscale/json_configs/sombrero_base.json'

/datum/greyscale_config/sombrero/lefthand
	name = "Held Sombrero, Left"
	json_config = 'code/datums/greyscale/json_configs/sombrero_lefthand.json'

/datum/greyscale_config/sombrero/base_lefthand
	name = "Base Held Sombrero Style, Left"
	json_config = 'code/datums/greyscale/json_configs/sombrero_base_lefthand.json'

/datum/greyscale_config/sombrero/righthand
	name = "Held Sombrero, Right"
	json_config = 'code/datums/greyscale/json_configs/sombrero_righthand.json'

/datum/greyscale_config/sombrero/base_righthand
	name = "Base Held Sombrero Style, Right"
	json_config = 'code/datums/greyscale/json_configs/sombrero_base_righthand.json'

/datum/greyscale_config/sombrero/worn
	name = "Worn Sombrero"
	json_config = 'code/datums/greyscale/json_configs/sombrero_worn.json'

/datum/greyscale_config/sombrero/base_worn
	name = "Base Worn Sombrero Style"
	json_config = 'code/datums/greyscale/json_configs/sombrero_base_worn.json'

/datum/greyscale_config/winter_coats
	name = "Winter Coat"
	icon_file = 'icons/obj/clothing/suits/wintercoat.dmi'
	json_config = 'code/datums/greyscale/json_configs/wintercoats.json'

/datum/greyscale_config/winter_coats/worn
	name = "Winter Coat Worn"
	icon_file = 'icons/mob/clothing/suits/wintercoat.dmi'
	json_config = 'code/datums/greyscale/json_configs/wintercoats_worn.json'

/datum/greyscale_config/winter_hoods
	name = "Winter Coat Hood"
	icon_file = 'icons/obj/clothing/head/winterhood.dmi'
	json_config = 'code/datums/greyscale/json_configs/wintercoats_hood.json'

/datum/greyscale_config/winter_hoods/worn
	name = "Winter Coat Hood Worn"
	icon_file = 'icons/mob/clothing/head/winterhood.dmi'
	json_config = 'code/datums/greyscale/json_configs/wintercoats_hood_worn.json'

/datum/greyscale_config/plush_lizard
	name = "Plushie Lizard"
	icon_file = 'icons/obj/plushes.dmi'
	json_config = 'code/datums/greyscale/json_configs/plushie_lizard.json'

/datum/greyscale_config/flower_simple
	name = "Flower"
	icon_file = 'icons/obj/hydroponics/harvest.dmi'
	json_config = 'code/datums/greyscale/json_configs/simple_flower.json'

/datum/greyscale_config/flower_simple_worn
	name = "Flower Worn"
	icon_file = 'icons/mob/clothing/head/hydroponics.dmi'
	json_config = 'code/datums/greyscale/json_configs/simple_flower_worn.json'

/datum/greyscale_config/cleric_mace
	name = "Base Cleric Mace"
	icon_file = 'icons/obj/items/cleric_mace.dmi'
	json_config = 'code/datums/greyscale/json_configs/items/cleric_mace.json'

/datum/greyscale_config/cleric_mace_lefthand
	name = "Base Held Cleric Mace, Left"
	icon_file = 'icons/obj/items/cleric_mace.dmi'
	json_config = 'code/datums/greyscale/json_configs/items/cleric_mace_lefthand.json'

/datum/greyscale_config/cleric_mace_righthand
	name = "Base Held Cleric Mace, Right"
	icon_file = 'icons/obj/items/cleric_mace.dmi'
	json_config = 'code/datums/greyscale/json_configs/items/cleric_mace_righthand.json'

/datum/greyscale_config/cleric_mace_worn
	name = "Base Worn Cleric Mace"
	icon_file = 'icons/obj/items/cleric_mace.dmi'
	json_config = 'code/datums/greyscale/json_configs/items/cleric_mace_worn.json'

/datum/greyscale_config/cleric_mace/gold
	name = "Gold Cleric Mace"
	material_skin = /datum/material/gold
	json_config = 'code/datums/greyscale/json_configs/items/cleric_mace_gold.json'

/datum/greyscale_config/cleric_mace_lefthand/gold
	name = "Gold Held Cleric Mace, Left"
	material_skin = /datum/material/gold
	json_config = 'code/datums/greyscale/json_configs/items/cleric_mace_lefthand_gold.json'

/datum/greyscale_config/cleric_mace_righthand/gold
	name = "Gold Held Cleric Mace, Right"
	material_skin = /datum/material/gold
	json_config = 'code/datums/greyscale/json_configs/items/cleric_mace_righthand_gold.json'

/datum/greyscale_config/cleric_mace_worn/gold
	name = "Gold Worn Cleric Mace"
	icon_file = 'icons/obj/items/cleric_mace.dmi'
	json_config = 'code/datums/greyscale/json_configs/items/cleric_mace_worn_gold.json'

/datum/greyscale_config/ceremonial_blade
	name = "Base Ceremonial Blade"
	icon_file = 'icons/obj/items/ritual_weapon.dmi'
	json_config = 'code/datums/greyscale/json_configs/items/ceremonial_blade.json'

/datum/greyscale_config/ceremonial_blade_lefthand
	name = "Base Held Ceremonial Blade, Left"
	icon_file = 'icons/obj/items/ritual_weapon.dmi'
	json_config = 'code/datums/greyscale/json_configs/items/ceremonial_blade_lefthand.json'

/datum/greyscale_config/ceremonial_blade_righthand
	name = "Base Held Ceremonial Blade, Right"
	icon_file = 'icons/obj/items/ritual_weapon.dmi'
	json_config = 'code/datums/greyscale/json_configs/items/ceremonial_blade_righthand.json'

/datum/greyscale_config/beret
	name = "Beret"
	icon_file = 'icons/obj/clothing/head/beret.dmi'
	json_config = 'code/datums/greyscale/json_configs/beret.json'

/datum/greyscale_config/beret/worn
	name = "Beret Worn"
	json_config = 'code/datums/greyscale/json_configs/beret_worn.json'

/datum/greyscale_config/beret_badge
	name = "Beret With Badge"
	icon_file = 'icons/obj/clothing/head/beret.dmi'
	json_config = 'code/datums/greyscale/json_configs/beret_badge.json'

/datum/greyscale_config/beret_badge/worn
	name = "Beret With Badge Worn"
	json_config = 'code/datums/greyscale/json_configs/beret_badge_worn.json'

/datum/greyscale_config/suspenders
	name = "Suspenders"
	icon_file = 'icons/obj/clothing/belts.dmi'
	json_config = 'code/datums/greyscale/json_configs/suspenders.json'

/datum/greyscale_config/suspenders/worn
	name = "Worn Suspenders"
	icon_file = 'icons/mob/clothing/suit.dmi'
	json_config = 'code/datums/greyscale/json_configs/suspenders_worn.json'

/datum/greyscale_config/giftdeliverybox
	name = "Gift Box"
	icon_file = 'icons/obj/storage.dmi'
	json_config = 'code/datums/greyscale/json_configs/giftdeliverybox.json'

/datum/greyscale_config/giftdeliverycloset
	name = "Gift Closet"
	icon_file = 'icons/obj/storage.dmi'
	json_config = 'code/datums/greyscale/json_configs/giftdeliverycloset.json'

/datum/greyscale_config/giftdeliverycrate
	name = "Gift Crate"
	icon_file = 'icons/obj/storage.dmi'
	json_config = 'code/datums/greyscale/json_configs/giftdeliverycrate.json'

/datum/greyscale_config/giftdeliverypackage5
	name = "Gift Package 5 (Biggest)"
	icon_file = 'icons/obj/storage.dmi'
	json_config = 'code/datums/greyscale/json_configs/giftdeliverypackage5.json'

/datum/greyscale_config/giftdeliverypackage4
	name = "Gift Package 4"
	icon_file = 'icons/obj/storage.dmi'
	json_config = 'code/datums/greyscale/json_configs/giftdeliverypackage4.json'

/datum/greyscale_config/giftdeliverypackage3
	name = "Gift Package 3"
	icon_file = 'icons/obj/storage.dmi'
	json_config = 'code/datums/greyscale/json_configs/giftdeliverypackage3.json'

/datum/greyscale_config/giftdeliverypackage2
	name = "Gift Package 2"
	icon_file = 'icons/obj/storage.dmi'
	json_config = 'code/datums/greyscale/json_configs/giftdeliverypackage2.json'

/datum/greyscale_config/giftdeliverypackage1
	name = "Gift Package 1 (Smallest)"
	icon_file = 'icons/obj/storage.dmi'
	json_config = 'code/datums/greyscale/json_configs/giftdeliverypackage1.json'

/datum/greyscale_config/wrap_paper
	name = "Wrapping Paper"
	icon_file = 'icons/obj/stack_objects.dmi'
	json_config = 'code/datums/greyscale/json_configs/wrap_paper.json'

/datum/greyscale_config/meter
	name = "Meter"
	icon_file = 'icons/obj/atmospherics/pipes/meter.dmi'
	json_config = 'code/datums/greyscale/json_configs/meter.json'

/datum/greyscale_config/thermomachine
	name = "Thermomachine"
	icon_file = 'icons/obj/atmospherics/components/thermomachine.dmi'
	json_config = 'code/datums/greyscale/json_configs/thermomachine.json'

/datum/greyscale_config/janicart_upgrade
	name = "Janicart Upgrade"
	icon_file = 'icons/obj/janicart_upgrade.dmi'
	json_config = 'code/datums/greyscale/json_configs/janicart_upgrade.json'

/datum/greyscale_config/janicart_upgrade/installed
	json_config = 'code/datums/greyscale/json_configs/janicart_upgrade_installed.json'

/datum/greyscale_config/bench_middle
	name = "Bench Middle"
	icon_file = 'icons/obj/sofa.dmi'
	json_config = 'code/datums/greyscale/json_configs/bench_middle.json'

/datum/greyscale_config/bench_left
	name = "Bench Left"
	icon_file = 'icons/obj/sofa.dmi'
	json_config = 'code/datums/greyscale/json_configs/bench_left.json'

/datum/greyscale_config/bench_right
	name = "Bench Right"
	icon_file = 'icons/obj/sofa.dmi'
	json_config = 'code/datums/greyscale/json_configs/bench_right.json'

/datum/greyscale_config/bench_corner
	name = "Bench Corner"
	icon_file = 'icons/obj/sofa.dmi'
	json_config = 'code/datums/greyscale/json_configs/bench_corner.json'

/datum/greyscale_config/ctf_standard
	name = "CTF Standard Vest"
	icon_file = 'icons/obj/clothing/suits/ctf.dmi'
	json_config = 'code/datums/greyscale/json_configs/ctf_standard.json'

/datum/greyscale_config/ctf_standard_worn
	name = "Worn CTF Standard Vest"
	icon_file = 'icons/mob/clothing/suits/ctf.dmi'
	json_config = 'code/datums/greyscale/json_configs/ctf_standard_worn.json'

/datum/greyscale_config/ctf_light
	name = "CTF Light Vest"
	icon_file = 'icons/obj/clothing/suits/ctf.dmi'
	json_config = 'code/datums/greyscale/json_configs/ctf_light.json'

/datum/greyscale_config/ctf_light_worn
	name = "Worn CTF Light Vest"
	icon_file = 'icons/mob/clothing/suits/ctf.dmi'
	json_config = 'code/datums/greyscale/json_configs/ctf_light_worn.json'


code:
/datum/greyscale_config/shimmer
	name = "Shimmer Effect"
	icon_file = 'icons/effects/shimmer.dmi'
	json_config = 'code/datums/greyscale/json_configs/material_effects/shimmer.json'


icon_state: beret

blend_mode: overlay


icon_state: badge


icon_state: base

blend_mode: add

blend_mode: multiply


type: reference

icon_state: window-base


blend_mode: subtract


icon_state: outline

icon_state: lights


icon_state: stand

icon_state: decals

icon_state: light


icon_state: stripe


icon_state: standard


icon_state: handle


icon_state: default


icon_state: housing

icon_state: bristles

icon_state: accent


icon_state: jumpsuit

icon_state: jumpskirt


icon_state: screwdriver


icon_state: smooth

icon_state: window-fg

icon_state: window-bg


icon_state: dongles


icon_state: cloth

icon_state: ornamentation

icon_state: plate


icon_state: suspenders


icon_state: blinking


icon_state: neck

icon_state: sleeves

icon_state: arms

icon_state: bodytop

icon_state: bodymiddle

icon_state: bodybottom

icon_state: hood


icon_state: cutters


code:
/// Takes a json list and extracts a single value.
/// Subtypes represent different conversions of that value.
/datum/json_reader

/// Takes a value read directly from json and verifies/converts as needed to a result
/datum/json_reader/proc/ReadJson(value)
	return

/datum/json_reader/text/ReadJson(value)
	if(!istext(value))
		CRASH("Text value expected but got '[value]'")
	return value

/datum/json_reader/number/ReadJson(value)
	var/newvalue = text2num(value)
	if(!isnum(newvalue))
		CRASH("Number expected but got [newvalue]")
	return newvalue

/datum/json_reader/number_color_list/ReadJson(list/value)
	if(!istype(value))
		CRASH("Expected a list but got [value]")
	var/list/new_values = list()
	for(var/number_string in value)
		var/new_value = text2num(number_string)
		if(!isnum(new_value))
			if(!istext(number_string) || number_string[1] != "#")
				stack_trace("Expected list to only contain numbers or colors but got '[number_string]'")
				continue
			new_value = number_string
		new_values += new_value
	return new_values

/datum/json_reader/blend_mode
	var/static/list/blend_modes = list(
		"add" = ICON_ADD,
		"subtract" = ICON_SUBTRACT,
		"multiply" = ICON_MULTIPLY,
		"or" = ICON_OR,
		"overlay" = ICON_OVERLAY,
		"underlay" = ICON_UNDERLAY,
	)

/datum/json_reader/blend_mode/ReadJson(value)
	var/new_value = blend_modes[lowertext(value)]
	if(isnull(new_value))
		CRASH("Blend mode expected but got '[value]'")
	return new_value

/datum/json_reader/greyscale_config/ReadJson(value)
	var/newvalue = SSgreyscale.configurations[value]
	if(!newvalue)
		CRASH("Greyscale configuration type expected but got '[value]'")
	return newvalue


code:
/datum/greyscale_layer
	var/layer_type
	var/list/color_ids
	var/blend_mode

	var/static/list/json_readers

/datum/greyscale_layer/New(icon_file, list/json_data)
	if(!json_readers)
		json_readers = list()
		for(var/path in subtypesof(/datum/json_reader))
			json_readers[path] = new path

	json_data -= "type" // This is used to look us up and doesn't need to be verified like the rest of the data

	ReadJsonData(json_data)
	Initialize(icon_file)

/// Override this to do initial set up
/datum/greyscale_layer/proc/Initialize(icon_file)
	return

/// Override this if you need to do something during a full config refresh from disk, return TRUE if something was changed
/datum/greyscale_layer/proc/DiskRefresh()
	return FALSE

/// Handles the processing of the json data and conversion to correct value types.
/// Will error on incorrect, missing, or unexpected values.
/datum/greyscale_layer/proc/ReadJsonData(list/json_data)
	var/list/required_values = list()
	var/list/optional_values = list()
	GetExpectedValues(required_values, optional_values)
	for(var/keyname in json_data)
		if(required_values[keyname] && optional_values[keyname])
			stack_trace("Key '[keyname]' found in both required and optional lists. Make sure keys are only in one or the other.")
			continue
		if(!required_values[keyname] && !optional_values[keyname])
			stack_trace("Unknown key found in json for [src]: '[keyname]'")
			continue
		if(!(keyname in vars))
			stack_trace("[src] expects a value from '[keyname]' but has no var to hold the output.")
			continue
		var/datum/json_reader/reader = required_values[keyname] || optional_values[keyname]
		reader = json_readers[reader]
		if(!reader)
			stack_trace("[src] has an invalid json reader type '[required_values[keyname]]' for key '[keyname]'.")
			continue
		vars[keyname] = reader.ReadJson(json_data[keyname])

	// Final check to make sure we got everything we needed
	for(var/keyname in required_values)
		if(isnull(json_data[keyname]))
			stack_trace("[src] is missing required json data key '[keyname]'.")

/// Gathers information from the layer about what variables are expected in the json.
/// Override and add to the two argument lists if you want extra information in your layer.
/// The lists are formatted like keyname:keytype_define.
/// The key name is assigned to the var named the same on the layer type.
/datum/greyscale_layer/proc/GetExpectedValues(list/required_values, list/optional_values)
	optional_values[NAMEOF(src, color_ids)] = /datum/json_reader/number_color_list
	required_values[NAMEOF(src, blend_mode)] = /datum/json_reader/blend_mode

/// Use this proc for extra verification needed by a particular layer, gets run after all greyscale configs have finished reading their json files.
/datum/greyscale_layer/proc/CrossVerify()
	return

/// Used to actualy create the layer using the given colors
/// Do not override, use InternalGenerate instead
/datum/greyscale_layer/proc/Generate(list/colors, list/render_steps)
	var/list/processed_colors = list()
	for(var/i in color_ids)
		if(isnum(i))
			processed_colors += colors[i]
		else
			processed_colors += i
	return InternalGenerate(processed_colors, render_steps)

/// Override this to implement layers.
/// The colors var will only contain colors that this layer is configured to use.
/datum/greyscale_layer/proc/InternalGenerate(list/colors, list/render_steps)

////////////////////////////////////////////////////////
// Subtypes

/// The most basic greyscale layer; a layer which is created from a single icon_state in the given icon file
/datum/greyscale_layer/icon_state
	layer_type = "icon_state"
	var/icon_state
	var/icon/icon
	var/color_id

/datum/greyscale_layer/icon_state/Initialize(icon_file)
	. = ..()
	var/list/icon_states = icon_states(icon_file)
	if(!(icon_state in icon_states))
		CRASH("Configured icon state \[[icon_state]\] was not found in [icon_file]. Double check your json configuration.")
	icon = new(icon_file, icon_state)

	if(length(color_ids) > 1)
		CRASH("Icon state layers can not have more than one color id")

/datum/greyscale_layer/icon_state/GetExpectedValues(list/required_values, list/optional_values)
	. = ..()
	required_values[NAMEOF(src, icon_state)] = /datum/json_reader/text

/datum/greyscale_layer/icon_state/InternalGenerate(list/colors, list/render_steps)
	. = ..()
	var/icon/new_icon = icon(icon)
	if(length(colors))
		new_icon.Blend(colors[1], ICON_MULTIPLY)
	return new_icon

/// A layer created by using another greyscale icon's configuration
/datum/greyscale_layer/reference
	layer_type = "reference"
	var/icon_state = ""
	var/datum/greyscale_config/reference_type

/datum/greyscale_layer/reference/GetExpectedValues(list/required_values, list/optional_values)
	. = ..()
	optional_values[NAMEOF(src, icon_state)] = /datum/json_reader/text
	required_values[NAMEOF(src, reference_type)] = /datum/json_reader/greyscale_config

/datum/greyscale_layer/reference/DiskRefresh()
	. = ..()
	return reference_type.Refresh(loadFromDisk=TRUE)

/datum/greyscale_layer/reference/CrossVerify()
	. = ..()
	if(!reference_type.icon_states[icon_state])
		CRASH("[src] expects icon_state '[icon_state]' but referenced configuration '[reference_type]' does not have it.")

/datum/greyscale_layer/reference/InternalGenerate(list/colors, list/render_steps)
	var/icon/generated_icon
	if(render_steps)
		var/list/reference_data = list()
		generated_icon = reference_type.GenerateBundle(colors, reference_data)
		render_steps += reference_data[icon_state]
	else
		generated_icon = reference_type.Generate(colors.Join())
	return icon(generated_icon, icon_state)


code:
/datum/hailer_phrase
	var/phrase_text = null
	var/phrase_sound = null

/datum/hailer_phrase/emag
	phrase_text = "FUCK YOUR CUNT YOU SHIT EATING COCKSTORM AND EAT A DONG FUCKING ASS RAMMING SHIT FUCK EAT PENISES IN YOUR FUCK FACE AND SHIT OUT ABORTIONS OF FUCK AND POO AND SHIT IN YOUR ASS YOU COCK FUCK SHIT MONKEY FUCK ASS WANKER FROM THE DEPTHS OF SHIT."
	phrase_sound = "emag"

/datum/hailer_phrase/halt
	phrase_text = "HALT! HALT! HALT!"
	phrase_sound = "halt"

/datum/hailer_phrase/bobby
	phrase_text = "Stop in the name of the Law."
	phrase_sound = "bobby"

/datum/hailer_phrase/compliance
	phrase_text = "Compliance is in your best interest."
	phrase_sound = "compliance"

/datum/hailer_phrase/justice
	phrase_text = "Prepare for justice!"
	phrase_sound = "justice"

/datum/hailer_phrase/running
	phrase_text = "Running will only increase your sentence."
	phrase_sound = "running"

/datum/hailer_phrase/dontmove
	phrase_text = "Don't move, Creep!"
	phrase_sound = "dontmove"

/datum/hailer_phrase/floor
	phrase_text = "Down on the floor, Creep!"
	phrase_sound = "floor"

/datum/hailer_phrase/robocop
	phrase_text = "Dead or alive you're coming with me."
	phrase_sound = "robocop"

/datum/hailer_phrase/god
	phrase_text = "God made today for the crooks we could not catch yesterday."
	phrase_sound = "god"

/datum/hailer_phrase/freeze
	phrase_text = "Freeze, Scum Bag!"
	phrase_sound = "freeze"

/datum/hailer_phrase/imperial
	phrase_text = "Stop right there, criminal scum!"
	phrase_sound = "imperial"

/datum/hailer_phrase/bash
	phrase_text = "Stop or I'll bash you."
	phrase_sound = "bash"

/datum/hailer_phrase/harry
	phrase_text = "Go ahead, make my day."
	phrase_sound = "harry"

/datum/hailer_phrase/asshole
	phrase_text = "Stop breaking the law, ass hole."
	phrase_sound = "asshole"

/datum/hailer_phrase/stfu
	phrase_text = "You have the right to shut the fuck up."
	phrase_sound = "stfu"

/datum/hailer_phrase/shutup
	phrase_text = "Shut up crime!"
	phrase_sound = "shutup"

/datum/hailer_phrase/super
	phrase_text = "Face the wrath of the golden bolt."
	phrase_sound = "super"

/datum/hailer_phrase/dredd
	phrase_text = "I am, the LAW!"
	phrase_sound = "dredd"


code:
/*
 * WARRANTY VOID IF CODE USED
 */

/datum/events
	var/list/events

/datum/events/New()
	..()
	events = new

/datum/events/Destroy()
	for(var/elist in events)
		for(var/e in events[elist])
			qdel(e)
	events = null
	return ..()

/datum/events/proc/addEventType(event_type as text)
	if(!(event_type in events) || !islist(events[event_type]))
		events[event_type] = list()
		return TRUE
	return FALSE

// Arguments: event_type as text, proc_holder as datum, proc_name as text
// Returns: New event, null on error.
/datum/events/proc/addEvent(event_type as text, datum/callback/cb)
	if(!event_type || !cb)
		return
	addEventType(event_type)
	var/list/event = events[event_type]
	event += cb
	return cb

//  Arguments: event_type as text, any number of additional arguments to pass to event handler
//  Returns: null
/datum/events/proc/fireEvent(eventName, ...)

	var/list/event = LAZYACCESS(events,eventName)
	if(istype(event))
		for(var/E in event)
			var/datum/callback/cb = E
			cb.InvokeAsync(arglist(args.Copy(2)))

// Arguments: event_type as text, E as /datum/event
// Returns: TRUE if event cleared, FALSE on error

/datum/events/proc/clearEvent(event_type as text, datum/callback/cb)
	if(!event_type || !cb)
		return FALSE
	var/list/event = LAZYACCESS(events,event_type)
	event -= cb
	qdel(cb)
	return TRUE


code:
/datum/getrev
	var/commit  // git rev-parse HEAD
	var/date
	var/originmastercommit  // git rev-parse origin/master
	var/list/testmerge = list()

/datum/getrev/New()
	commit = rustg_git_revparse("HEAD")
	if(commit)
		date = rustg_git_commit_date(commit)
	originmastercommit = rustg_git_revparse("origin/master")

/datum/getrev/proc/load_tgs_info()
	testmerge = world.TgsTestMerges()
	var/datum/tgs_revision_information/revinfo = world.TgsRevision()
	if(revinfo)
		commit = revinfo.commit
		originmastercommit = revinfo.origin_commit
		date = revinfo.timestamp || rustg_git_commit_date(commit)

	// goes to DD log and config_error.txt
	log_world(get_log_message())

/datum/getrev/proc/get_log_message()
	var/list/msg = list()
	msg += "Running /tg/ revision: [date]"
	if(originmastercommit)
		msg += "origin/master: [originmastercommit]"

	for(var/line in testmerge)
		var/datum/tgs_revision_information/test_merge/tm = line
		msg += "Test merge active of PR #[tm.number] commit [tm.head_commit]"
		SSblackbox.record_feedback("associative", "testmerged_prs", 1, list("number" = "[tm.number]", "commit" = "[tm.head_commit]", "title" = "[tm.title]", "author" = "[tm.author]"))

	if(commit && commit != originmastercommit)
		msg += "HEAD: [commit]"
	else if(!originmastercommit)
		msg += "No commit information"

	return msg.Join("\n")

/datum/getrev/proc/GetTestMergeInfo(header = TRUE)
	if(!testmerge.len)
		return ""
	. = header ? "The following pull requests are currently test merged:
" : ""
	for(var/line in testmerge)
		var/datum/tgs_revision_information/test_merge/tm = line
		var/cm = tm.head_commit
		var/details = ": '" + html_encode(tm.title) + "' by " + html_encode(tm.author) + " at commit " + html_encode(copytext_char(cm, 1, 11))
		. += "#[tm.number][details]
"

/client/verb/showrevinfo()
	set category = "OOC"
	set name = "Show Server Revision"
	set desc = "Check the current server code revision"

	var/list/msg = list("")
	// Round ID
	if(GLOB.round_id)
		msg += "Round ID: [GLOB.round_id]"

	msg += "BYOND Version: [world.byond_version].[world.byond_build]"
	if(DM_VERSION != world.byond_version || DM_BUILD != world.byond_build)
		msg += "Compiled with BYOND Version: [DM_VERSION].[DM_BUILD]"

	// Revision information
	var/datum/getrev/revdata = GLOB.revdata
	msg += "Server revision compiled on: [revdata.date]"
	var/pc = revdata.originmastercommit
	if(pc)
		msg += "Master commit: [pc]"
	if(revdata.testmerge.len)
		msg += revdata.GetTestMergeInfo()
	if(revdata.commit && revdata.commit != revdata.originmastercommit)
		msg += "Local commit: [revdata.commit]"
	else if(!pc)
		msg += "No commit information"
	if(world.TgsAvailable())
		var/datum/tgs_version/version = world.TgsVersion()
		msg += "TGS version: [version.raw_parameter]"
		var/datum/tgs_version/api_version = world.TgsApiVersion()
		msg += "DMAPI version: [api_version.raw_parameter]"

	// Game mode odds
	msg += "
Current Informational Settings:"
	msg += "Protect Authority Roles From Traitor: [CONFIG_GET(flag/protect_roles_from_antagonist)]"
	msg += "Protect Assistant Role From Traitor: [CONFIG_GET(flag/protect_assistant_from_antagonist)]"
	msg += "Enforce Human Authority: [CONFIG_GET(flag/enforce_human_authority)]"
	msg += "Allow Latejoin Antagonists: [CONFIG_GET(flag/allow_latejoin_antagonists)]"
	to_chat(src, "[msg.Join("
")]")


code:
/datum/icon_snapshot
	var/name
	var/icon
	var/icon_state
	var/list/overlays

/datum/icon_snapshot/proc/makeImg()
	if(!icon || !icon_state)
		return
	var/obj/temp = new
	temp.icon = icon
	temp.icon_state = icon_state
	temp.overlays = overlays.Copy()
	var/icon/tempicon = getFlatIcon(temp) // TODO Actually write something less heavy-handed for this
	return tempicon


code:
/**
	Stack End Detector.
	Can detect if a given code stack has exited, used by the mc for stack overflow detection.

 **/
/datum/stack_end_detector
	var/datum/weakref/_WF
	var/datum/stack_canary/_canary

/datum/stack_end_detector/New()
	_canary = new()
	_WF = WEAKREF(_canary)

/** Prime the stack overflow detector.
	Store the return value of this proc call in a proc level var.
	Can only be called once.
**/
/datum/stack_end_detector/proc/prime_canary()
	if (!_canary)
		CRASH("Prime_canary called twice")
	. = _canary
	_canary = null

/// Returns true if the stack is still going. Calling before the canary has been primed also returns true
/datum/stack_end_detector/proc/check()
	return !!_WF.resolve()

/// Stack canary. Will go away if the stack it was primed by is ended by byond for return or stack overflow reasons.
/datum/stack_canary

/// empty proc to avoid warnings about unused variables. Call this proc on your canary in the stack it's watching.
/datum/stack_canary/proc/use_variable()