code:
///Your favourite Jojoke. Used for the gloves of the north star.
/datum/component/wearertargeting/punchcooldown
	signals = list(COMSIG_HUMAN_MELEE_UNARMED_ATTACK)
	mobtype = /mob/living/carbon
	proctype = .proc/reducecooldown
	valid_slots = list(ITEM_SLOT_GLOVES)
	///The warcry this generates
	var/warcry = "AT"

/datum/component/wearertargeting/punchcooldown/Initialize()
	. = ..()
	if(. == COMPONENT_INCOMPATIBLE)
		return
	RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/changewarcry)

///Called on COMSIG_HUMAN_MELEE_UNARMED_ATTACK. Yells the warcry and and reduces punch cooldown.
/datum/component/wearertargeting/punchcooldown/proc/reducecooldown(mob/living/carbon/M, atom/target)
	/* SEPTIC EDIT REMOVAL
	if(M.combat_mode && isliving(target))
	*/
	//SEPTIC EDIT BEGIN
	if((M.a_intent == INTENT_HARM) && isliving(target))
	//SEPTIC EDIT END
		M.changeNext_move(CLICK_CD_RAPID)
		if(warcry)
			M.say(warcry, ignore_spam = TRUE, forced = "north star warcry")

///Called on COMSIG_ITEM_ATTACK_SELF. Allows you to change the warcry.
/datum/component/wearertargeting/punchcooldown/proc/changewarcry(datum/source, mob/user)
	SIGNAL_HANDLER
	INVOKE_ASYNC(src, .proc/do_changewarcry, user)

/datum/component/wearertargeting/punchcooldown/proc/do_changewarcry(mob/user)
	var/input = stripped_input(user,"What do you want your battlecry to be? Max length of 6 characters.", ,"", 7)
	if(!QDELETED(src) && !QDELETED(user) && !user.Adjacent(parent))
		return
	if(input)
		warcry = input


code:
#define RAD_AMOUNT_LOW 50
#define RAD_AMOUNT_MEDIUM 200
#define RAD_AMOUNT_HIGH 500
#define RAD_AMOUNT_EXTREME 1000

/datum/component/radioactive
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS

	var/source
	var/hl3_release_date //the half-life measured in ticks
	var/strength
	var/can_contaminate

/datum/component/radioactive/Initialize(_strength=0, _source, _half_life=RAD_HALF_LIFE, _can_contaminate=TRUE)
	strength = _strength
	source = _source
	hl3_release_date = _half_life
	can_contaminate = _can_contaminate
	if(istype(parent, /atom))
		RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/rad_examine)
		RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/rad_clean)
		if(istype(parent, /obj/item))
			RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/rad_attack)
			RegisterSignal(parent, COMSIG_ITEM_ATTACK_OBJ, .proc/rad_attack)
	else
		return COMPONENT_INCOMPATIBLE
	if(strength > RAD_MINIMUM_CONTAMINATION)
		SSradiation.warn(src)
	//Let's make er glow
	//This relies on parent not being a turf or something. IF YOU CHANGE THAT, CHANGE THIS
	var/atom/movable/master = parent
	master.add_filter("rad_glow", 2, list("type" = "outline", "color" = "#39ff1430", "size" = 2))
	addtimer(CALLBACK(src, .proc/glow_loop, master), rand(1,19))//Things should look uneven
	START_PROCESSING(SSradiation, src)

/datum/component/radioactive/Destroy()
	STOP_PROCESSING(SSradiation, src)
	var/atom/movable/master = parent
	master.remove_filter("rad_glow")
	return ..()

/datum/component/radioactive/process(delta_time)
	if(!DT_PROB(50, delta_time))
		return
	radiation_pulse(parent, strength, RAD_DISTANCE_COEFFICIENT*2, FALSE, can_contaminate)
	if(!hl3_release_date)
		return
	strength -= strength / hl3_release_date
	if(strength <= RAD_BACKGROUND_RADIATION)
		qdel(src)
		return PROCESS_KILL

/datum/component/radioactive/proc/glow_loop(atom/movable/master)
	var/filter = master.get_filter("rad_glow")
	if(filter)
		animate(filter, alpha = 110, time = 15, loop = -1)
		animate(alpha = 40, time = 25)

/datum/component/radioactive/InheritComponent(datum/component/C, i_am_original, _strength, _source, _half_life, _can_contaminate)
	if(!i_am_original)
		return
	if(!hl3_release_date) // Permanently radioactive things don't get to grow stronger
		return
	if(C)
		var/datum/component/radioactive/other = C
		strength = max(strength, other.strength)
	else
		strength = max(strength, _strength)

/datum/component/radioactive/proc/rad_examine(datum/source, mob/user, list/out)
	SIGNAL_HANDLER

	var/atom/master = parent

	var/list/fragments = list()
	if(get_dist(master, user) <= 1)
		fragments += "The air around [master] feels warm"
	switch(strength)
		if(0 to RAD_AMOUNT_LOW)
			if(length(fragments))
				fragments += "."
		if(RAD_AMOUNT_LOW to RAD_AMOUNT_MEDIUM)
			fragments += "[length(fragments) ? " and [master.p_they()] " : "[master] "]feel[master.p_s()] weird to look at."
		if(RAD_AMOUNT_MEDIUM to RAD_AMOUNT_HIGH)
			fragments += "[length(fragments) ? " and [master.p_they()] " : "[master] "]seem[master.p_s()] to be glowing a bit."
		if(RAD_AMOUNT_HIGH to INFINITY) //At this level the object can contaminate other objects
			fragments += "[length(fragments) ? " and [master.p_they()] " : "[master] "]hurt[master.p_s()] to look at."

	if(length(fragments))
		out += span_warning("[fragments.Join()]")

/datum/component/radioactive/proc/rad_attack(datum/source, atom/movable/target, mob/living/user)
	SIGNAL_HANDLER

	radiation_pulse(parent, strength/20)
	target.rad_act(strength/2)
	if(!hl3_release_date)
		return
	strength -= strength / hl3_release_date

/datum/component/radioactive/proc/rad_clean(datum/source, clean_types)
	SIGNAL_HANDLER

	if(QDELETED(src))
		return COMPONENT_CLEANED

	if(!(clean_types & CLEAN_TYPE_RADIATION))
		return COMPONENT_CLEANED

	if(!(clean_types & CLEAN_TYPE_WEAK))
		qdel(src)
		return COMPONENT_CLEANED

	strength = max(0, (strength - (RAD_BACKGROUND_RADIATION * 2)))
	if(strength <= RAD_BACKGROUND_RADIATION)
		qdel(src)
		return COMPONENT_CLEANED

#undef RAD_AMOUNT_LOW
#undef RAD_AMOUNT_MEDIUM
#undef RAD_AMOUNT_HIGH
#undef RAD_AMOUNT_EXTREME

repo_name: AnalWerewolf/sundown

license: mit


code:
/**
 *
 * Allows the parent to act similarly to the Altar of Gods with modularity. Invoke and Sect Selection is done via attacking with a bible. This means you cannot sacrifice Bibles (you shouldn't want to do this anyways although now that I mentioned it you probably will want to).
 *
 */
/datum/component/religious_tool
	dupe_mode = COMPONENT_DUPE_UNIQUE
	/// Enables access to the global sect directly
	var/datum/religion_sect/easy_access_sect
	/// Prevents double selecting sects
	var/selecting_sect = FALSE
	/// What extent do we want this religious tool to act? In case you don't want full access to the list. Generated on New
	var/operation_flags
	/// The rite currently being invoked
	var/datum/religion_rites/performing_rite
	///Sets the type for catalyst
	var/catalyst_type = /obj/item/storage/book/bible
	///Enables overide of COMPONENT_NO_AFTERATTACK, not recommended as it means you can potentially cause damage to the item using the catalyst.
	var/force_catalyst_afterattack = FALSE
	var/datum/callback/after_sect_select_cb

/datum/component/religious_tool/Initialize(_flags = ALL, _force_catalyst_afterattack = FALSE, _after_sect_select_cb, override_catalyst_type)
	. = ..()
	SetGlobalToLocal() //attempt to connect on start in case one already exists!
	operation_flags = _flags
	force_catalyst_afterattack = _force_catalyst_afterattack
	after_sect_select_cb = _after_sect_select_cb
	if(override_catalyst_type)
		catalyst_type = override_catalyst_type

/datum/component/religious_tool/RegisterWithParent()
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY,.proc/AttemptActions)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)

/datum/component/religious_tool/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_PARENT_ATTACKBY, COMSIG_PARENT_EXAMINE))

/**
 * Sets the easy access variable to the global if it exists.
 */
/datum/component/religious_tool/proc/SetGlobalToLocal()
	if(easy_access_sect)
		return TRUE
	if(!GLOB.religious_sect)
		return FALSE
	easy_access_sect = GLOB.religious_sect
	if(after_sect_select_cb)
		after_sect_select_cb.Invoke()
	return TRUE

/**
 * Since all of these involve attackby, we require mega proc. Handles Invocation, Sacrificing, And Selection of Sects.
 */
/datum/component/religious_tool/proc/AttemptActions(datum/source, obj/item/the_item, mob/living/user)
	SIGNAL_HANDLER

	if(istype(the_item, catalyst_type))
		INVOKE_ASYNC(src, /datum.proc/ui_interact, user) //asynchronous to avoid sleeping in a signal

	/**********Sacrificing**********/
	else if(operation_flags & RELIGION_TOOL_SACRIFICE)
		if(!easy_access_sect?.can_sacrifice(the_item,user))
			return
		easy_access_sect.on_sacrifice(the_item,user)
		return COMPONENT_NO_AFTERATTACK

/datum/component/religious_tool/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "ReligiousTool")
		ui.open()
	return COMPONENT_NO_AFTERATTACK

/datum/component/religious_tool/ui_state(mob/user)
	if(!iscarbon(usr))
		return GLOB.never_state
	var/mob/living/carbon/carbon = usr
	if(!carbon.is_holding_item_of_type(catalyst_type))
		return GLOB.never_state
	return GLOB.default_state

/datum/component/religious_tool/ui_data(mob/user)
	var/list/data = list()
	//cannot find global vars, so lets offer options
	if(!SetGlobalToLocal())
		data["sects"] = generate_available_sects(user)
		data["alignment"] = ALIGNMENT_NEUT //neutral theme if you have no sect
	else
		data["sects"] = null
		data["name"] = easy_access_sect.name
		data["desc"] = easy_access_sect.desc
		data["quote"] = easy_access_sect.quote
		data["alignment"] = easy_access_sect.alignment
		data["icon"] = easy_access_sect.tgui_icon
		data["favordesc"] = easy_access_sect.tool_examine(user)
		data["favor"] = easy_access_sect.favor
		data["deity"] = GLOB.deity
		data["rites"] = generate_available_rites()
		data["wanted"] = generate_sacrifice_list()

	var/atom/atom_parent = parent
	data["toolname"] = atom_parent.name
	data["can_select_sect"] = (operation_flags & RELIGION_TOOL_SECTSELECT)
	data["can_invoke_rite"] = (operation_flags & RELIGION_TOOL_INVOKE)
	data["can_sacrifice_item"] = (operation_flags & RELIGION_TOOL_SACRIFICE)
	return data

/datum/component/religious_tool/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
	. = ..()
	switch(action)
		if("sect_select")
			select_sect(usr, params["path"])
		if("perform_rite")
			perform_rite(usr, params["path"])
		else
			to_chat(world, action)

/// Select the sect, called from [/datum/component/religious_tool/proc/AttemptActions]
/datum/component/religious_tool/proc/select_sect(mob/living/user, path)
	if(user.mind.holy_role != HOLY_ROLE_HIGHPRIEST)
		to_chat(user, "You are not the high priest, and therefore cannot select a religious sect.")
		return
	if(!user.canUseTopic(parent, BE_CLOSE, FALSE, NO_TK))
		to_chat(user,span_warning("You cannot select a sect at this time."))
		return
	if(GLOB.religious_sect)
		return
	GLOB.religious_sect = new path()
	for(var/i in GLOB.player_list)
		if(!isliving(i))
			continue
		var/mob/living/am_i_holy_living = i
		if(!am_i_holy_living.mind?.holy_role)
			continue
		GLOB.religious_sect.on_conversion(am_i_holy_living)
	easy_access_sect = GLOB.religious_sect
	after_sect_select_cb.Invoke()

/// Perform the rite, called from [/datum/component/religious_tool/proc/AttemptActions]
/datum/component/religious_tool/proc/perform_rite(mob/living/user, path)
	if(user.mind.holy_role == HOLY_ROLE_DEACON)
		to_chat(user, "You are merely a deacon of [GLOB.deity], and therefore cannot perform rites.")
		return
	if(performing_rite)
		to_chat(user, "There is a rite currently being performed here already.")
		return
	if(!user.canUseTopic(parent, BE_CLOSE, FALSE, NO_TK))
		to_chat(user,span_warning("You are not close enough to perform the rite."))
		return
	performing_rite = new path(parent)
	if(!performing_rite.perform_rite(user, parent))
		QDEL_NULL(performing_rite)
	else
		performing_rite.invoke_effect(user, parent)
		easy_access_sect.adjust_favor(-performing_rite.favor_cost)
		QDEL_NULL(performing_rite)

/**
 * Generates a list of available sects to the user. Intended to support custom-availability sects.
 */
/datum/component/religious_tool/proc/generate_available_sects(mob/user)
	var/list/sects_to_pick = list()
	var/human_highpriest = ishuman(user)
	var/mob/living/carbon/human/highpriest = user
	for(var/path in subtypesof(/datum/religion_sect))
		if(human_highpriest && initial(easy_access_sect.invalidating_qualities))
			var/datum/species/highpriest_species = highpriest.dna.species
			if(initial(easy_access_sect.invalidating_qualities) in highpriest_species.inherent_traits)
				continue
		var/list/sect = list()
		var/datum/religion_sect/not_a_real_instance_rs = path
		sect["name"] = initial(not_a_real_instance_rs.name)
		sect["desc"] = initial(not_a_real_instance_rs.desc)
		sect["alignment"] = initial(not_a_real_instance_rs.alignment)
		sect["quote"] = initial(not_a_real_instance_rs.quote)
		sect["icon"] = initial(not_a_real_instance_rs.tgui_icon)
		sect["path"] = path
		sects_to_pick += list(sect)
	return sects_to_pick

/**
 * Generates available rites to pick from. It expects the sect to be picked by the time it was called (by tgui data)
 */
/datum/component/religious_tool/proc/generate_available_rites()
	var/list/rites_to_pick = list()
	for(var/path in easy_access_sect.rites_list)
		///checks to invalidate
		var/list/rite = list()
		var/datum/religion_rites/rite_type = path
		rite["name"] = initial(rite_type.name)
		rite["desc"] = initial(rite_type.desc)
		var/cost = initial(rite_type.favor_cost)
		rite["favor"] = cost
		rite["can_cast"] = cost > easy_access_sect.favor
		rite["path"] = path
		rites_to_pick += list(rite)
	return rites_to_pick

/**
 * Generates an english list (so string) of wanted sac items. Returns null if no targets!
 */
/datum/component/religious_tool/proc/generate_sacrifice_list()
	if(!easy_access_sect.desired_items)
		return //specifically null so the data sends as such
	var/list/item_names = list()
	for(var/atom/sac_type as anything in easy_access_sect.desired_items)
		var/append = easy_access_sect.desired_items[sac_type]
		var/entry = "[initial(sac_type.name)]s [append]"
		item_names += entry
	return english_list(item_names)

/**
 * Appends to examine so the user knows it can be used for religious purposes.
 */
/datum/component/religious_tool/proc/on_examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER

	var/can_i_see = FALSE
	if(isobserver(user))
		can_i_see = TRUE
	else if(isliving(user))
		var/mob/living/L = user
		if(L.mind?.holy_role)
			can_i_see = TRUE

	if(!can_i_see)
		return
	examine_list += span_notice("Use a bible to interact with this.")
	if(!easy_access_sect)
		if(operation_flags & RELIGION_TOOL_SECTSELECT)
			examine_list += span_notice("This looks like it can be used to select a sect.")
			return
	if(operation_flags & RELIGION_TOOL_SACRIFICE)//this can be moved around if things change but usually no rites == no sacrifice
		examine_list += span_notice("Desired items can be used on this to increase favor.")
	if(easy_access_sect.rites_list && operation_flags & RELIGION_TOOL_INVOKE)
		examine_list += span_notice("You can invoke rites from this.")


code:
/*
This component allows machines to connect remotely to a material container
(namely an /obj/machinery/ore_silo) elsewhere. It offers optional graceful
fallback to a local material storage in case remote storage is unavailable, and
handles linking back and forth.
*/

/datum/component/remote_materials
	// Three possible states:
	// 1. silo exists, materials is parented to silo
	// 2. silo is null, materials is parented to parent
	// 3. silo is null, materials is null
	var/obj/machinery/ore_silo/silo
	var/datum/component/material_container/mat_container
	var/category
	var/allow_standalone
	var/local_size = INFINITY
	///Flags used when converting inserted materials into their component materials.
	var/mat_container_flags = NONE

/datum/component/remote_materials/Initialize(category, mapload, allow_standalone = TRUE, force_connect = FALSE, mat_container_flags=NONE)
	if (!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	src.category = category
	src.allow_standalone = allow_standalone
	src.mat_container_flags = mat_container_flags

	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy)
	RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), .proc/OnMultitool)

	var/turf/T = get_turf(parent)
	if (force_connect || (mapload && is_station_level(T.z)))
		addtimer(CALLBACK(src, .proc/LateInitialize))
	else if (allow_standalone)
		_MakeLocal()

/datum/component/remote_materials/proc/LateInitialize()
	silo = GLOB.ore_silo_default
	if (silo)
		silo.connected += src
		mat_container = silo.GetComponent(/datum/component/material_container)
	else
		_MakeLocal()

/datum/component/remote_materials/Destroy()
	if (silo)
		silo.connected -= src
		silo.updateUsrDialog()
		silo = null
		mat_container = null
	else if (mat_container)
		// specify explicitly in case the other component is deleted first
		var/atom/P = parent
		mat_container.retrieve_all(P.drop_location())
	return ..()

/datum/component/remote_materials/proc/_MakeLocal()
	silo = null

	var/static/list/allowed_mats = list(
		/datum/material/iron,
		/datum/material/glass,
		/datum/material/silver,
		/datum/material/gold,
		/datum/material/diamond,
		/datum/material/plasma,
		/datum/material/uranium,
		/datum/material/bananium,
		/datum/material/titanium,
		/datum/material/bluespace,
		/datum/material/plastic,
		)

	mat_container = parent.AddComponent(/datum/component/material_container, allowed_mats, local_size, mat_container_flags, allowed_items=/obj/item/stack)

/datum/component/remote_materials/proc/set_local_size(size)
	local_size = size
	if (!silo && mat_container)
		mat_container.max_amount = size

// called if disconnected by ore silo UI or destruction
/datum/component/remote_materials/proc/disconnect_from(obj/machinery/ore_silo/old_silo)
	if (!old_silo || silo != old_silo)
		return
	silo = null
	mat_container = null
	if (allow_standalone)
		_MakeLocal()

/datum/component/remote_materials/proc/OnAttackBy(datum/source, obj/item/I, mob/user)
	SIGNAL_HANDLER

	if (silo && istype(I, /obj/item/stack))
		if (silo.remote_attackby(parent, user, I, mat_container_flags))
			return COMPONENT_NO_AFTERATTACK

/datum/component/remote_materials/proc/OnMultitool(datum/source, mob/user, obj/item/I)
	SIGNAL_HANDLER

	if(!I.multitool_check_buffer(user, I))
		return COMPONENT_BLOCK_TOOL_ATTACK
	var/obj/item/multitool/M = I
	if (!QDELETED(M.buffer) && istype(M.buffer, /obj/machinery/ore_silo))
		if (silo == M.buffer)
			to_chat(user, span_warning("[parent] is already connected to [silo]!"))
			return COMPONENT_BLOCK_TOOL_ATTACK
		if (silo)
			silo.connected -= src
			silo.updateUsrDialog()
		else if (mat_container)
			mat_container.retrieve_all()
			qdel(mat_container)
		silo = M.buffer
		silo.connected += src
		silo.updateUsrDialog()
		mat_container = silo.GetComponent(/datum/component/material_container)
		to_chat(user, span_notice("You connect [parent] to [silo] from the multitool's buffer."))
		return COMPONENT_BLOCK_TOOL_ATTACK

/datum/component/remote_materials/proc/on_hold()
	return silo?.holds["[get_area(parent)]/[category]"]

/datum/component/remote_materials/proc/silo_log(obj/machinery/M, action, amount, noun, list/mats)
	if (silo)
		silo.silo_log(M || parent, action, amount, noun, mats)

/datum/component/remote_materials/proc/format_amount()
	if (mat_container)
		return "[mat_container.total_amount] / [mat_container.max_amount == INFINITY ? "Unlimited" : mat_container.max_amount] ([silo ? "remote" : "local"])"
	else
		return "0 / 0"

/// Ejects the given material ref and logs it, or says out loud the problem.
/datum/component/remote_materials/proc/eject_sheets(datum/material/material_ref, eject_amount)
	var/atom/movable/movable_parent = parent
	if (!istype(movable_parent))
		return 0

	if (!mat_container)
		movable_parent.say("No access to material storage, please contact the quartermaster.")
		return 0
	if (on_hold())
		movable_parent.say("Mineral access is on hold, please contact the quartermaster.")
		return 0
	var/count = mat_container.retrieve_sheets(eject_amount, material_ref, movable_parent.drop_location())
	var/list/matlist = list()
	matlist[material_ref] = eject_amount
	silo_log(parent, "ejected", -count, "sheets", matlist)
	return count


code:
/**
	The rename component.

	This component is used to manage names and descriptions changed with the pen.

	Atoms can only have one instance of this component at a time.

	When a player renames or changes the description of an atom with a pen, this component gets applied to it.
	If a player resets the name and description, they will be reverted to their state before being changed and the component will be removed.
 */
/datum/component/rename
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
	///The name the player is applying to the parent.
	var/custom_name
	///The desc the player is applying to the parent.
	var/custom_desc
	///The name before the player changed it.
	var/original_name
	///The desc before the player changed it.
	var/original_desc

/datum/component/rename/Initialize(custom_name, custom_desc)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	src.custom_name = custom_name
	src.custom_desc = custom_desc
	apply_rename()

/**
	This proc will fire after the parent's name or desc is changed with a pen, which is trying to apply another rename component.
	Since the parent already has a rename component, it will remove the old one and apply the new one.
	The name and description changes will be merged or overwritten.
*/
/datum/component/rename/InheritComponent(datum/component/rename/new_comp , i_am_original, custom_name, custom_desc)
	revert_rename()
	if(new_comp)
		src.custom_name = new_comp.custom_name
		src.custom_desc = new_comp.custom_desc
	else
		src.custom_name = custom_name
		src.custom_desc = custom_desc
	apply_rename()

///Saves the current name and description before changing them to the player's inputs.
/datum/component/rename/proc/apply_rename()
	var/atom/owner = parent
	original_name = owner.name
	original_desc = owner.desc
	owner.name = custom_name
	owner.desc = custom_desc

///Reverts the name and description to the state before they were changed.
/datum/component/rename/proc/revert_rename()
	var/atom/owner = parent
	owner.name = original_name
	owner.desc = original_desc

/datum/component/rename/proc/remove_component()
	revert_rename()
	qdel(src)

/datum/component/rename/Destroy()
	revert_rename()
	return ..()


code:
/**
 * This is the riding component, which is applied to a movable atom by the [ridable element][/datum/element/ridable] when a mob is successfully buckled to said movable.
 *
 * This component lives for as long as at least one mob is buckled to the parent. Once all mobs are unbuckled, the component is deleted, until another mob is buckled in
 * and we make a new riding component, so on and so forth until the sun explodes.
 */

/datum/component/riding
	dupe_mode = COMPONENT_DUPE_UNIQUE

	var/last_move_diagonal = FALSE
	///tick delay between movements, lower = faster, higher = slower
	var/vehicle_move_delay = 2

	/**
	 * If the driver needs a certain item in hand (or inserted, for vehicles) to drive this. For vehicles, this must be duplicated on the actual vehicle object in their
	 * [/obj/vehicle/var/key_type] variable because the vehicle objects still have a few special checks/functions of their own I'm not porting over to the riding component
	 * quite yet. Make sure if you define it on the vehicle, you define it here too.
	 */
	var/keytype

	/// position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one.
	var/list/riding_offsets = list()
	/// ["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change.
	var/list/directional_vehicle_layers = list()
	/// same as above but instead of layer you have a list(px, py)
	var/list/directional_vehicle_offsets = list()
	/// allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
	var/list/allowed_turf_typecache
	/// allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
	var/list/forbid_turf_typecache
	/// We don't need roads where we're going if this is TRUE, allow normal movement in space tiles
	var/override_allow_spacemove = FALSE

	/**
	 * Ride check flags defined for the specific riding component types, so we know if we need arms, legs, or whatever.
	 * Takes additional flags from the ridable element and the buckle proc (buckle_mob_flags) for riding cyborgs/humans in case we need to reserve arms
	 */
	var/ride_check_flags = NONE
	/// For telling someone they can't drive
	COOLDOWN_DECLARE(message_cooldown)
	/// For telling someone they can't drive
	COOLDOWN_DECLARE(vehicle_move_cooldown)

/datum/component/riding/Initialize(mob/living/riding_mob, force = FALSE, buckle_mob_flags= NONE, potion_boost = FALSE)
	if(!ismovable(parent))
		return COMPONENT_INCOMPATIBLE

	handle_specials(riding_mob)
	riding_mob.updating_glide_size = FALSE
	ride_check_flags |= buckle_mob_flags

	if(potion_boost)
		vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_delay) * 0.85, 0.01)

/datum/component/riding/RegisterWithParent()
	. = ..()
	RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/vehicle_turned)
	RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, .proc/vehicle_mob_unbuckle)
	RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle)
	RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved)
	RegisterSignal(parent, COMSIG_MOVABLE_BUMP, .proc/vehicle_bump)

/**
 * This proc handles all of the proc calls to things like set_vehicle_dir_layer() that a type of riding datum needs to call on creation
 *
 * The original riding component had these procs all called from the ridden object itself through the use of GetComponent() and LoadComponent()
 * This was obviously problematic for componentization, but while lots of the variables being set were able to be moved to component variables,
 * the proc calls couldn't be. Thus, anything that has to do an initial proc call should be handled here.
 */
/datum/component/riding/proc/handle_specials()
	return

/// This proc is called when a rider unbuckles, whether they chose to or not. If there's no more riders, this will be the riding component's death knell.
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/rider, force = FALSE)
	SIGNAL_HANDLER

	var/atom/movable/movable_parent = parent
	restore_position(rider)
	unequip_buckle_inhands(rider)
	rider.updating_glide_size = TRUE
	if(!movable_parent.has_buckled_mobs())
		qdel(src)

/// This proc is called when a rider buckles, allowing for offsets to be set properly
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/rider, force = FALSE)
	SIGNAL_HANDLER

	var/atom/movable/movable_parent = parent
	handle_vehicle_layer(movable_parent.dir)
	handle_vehicle_offsets(movable_parent.dir)

/// Some ridable atoms may want to only show on top of the rider in certain directions, like wheelchairs
/datum/component/riding/proc/handle_vehicle_layer(dir)
	var/atom/movable/AM = parent
	var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER)
	. = defaults["[dir]"]
	if(directional_vehicle_layers["[dir]"])
		. = directional_vehicle_layers["[dir]"]
	if(isnull(.)) //you can set it to null to not change it.
		. = AM.layer
	AM.layer = .

/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer)
	directional_vehicle_layers["[dir]"] = layer

/// This is called after the ridden atom is successfully moved and is used to handle icon stuff
/datum/component/riding/proc/vehicle_moved(datum/source, oldloc, dir, forced)
	SIGNAL_HANDLER

	var/atom/movable/movable_parent = parent
	if (isnull(dir))
		dir = movable_parent.dir
	movable_parent.set_glide_size(DELAY_TO_GLIDE_SIZE(vehicle_move_delay))
	for (var/m in movable_parent.buckled_mobs)
		var/mob/buckled_mob = m
		ride_check(buckled_mob)
	if(QDELETED(src))
		return // runtimed with piggy's without this, look into this more
	handle_vehicle_offsets(dir)
	handle_vehicle_layer(dir)

/// Turning is like moving
/datum/component/riding/proc/vehicle_turned(datum/source, _old_dir, new_dir)
	SIGNAL_HANDLER

	vehicle_moved(source, null, new_dir)

/// Check to see if we have all of the necessary bodyparts and not-falling-over statuses we need to stay onboard
/datum/component/riding/proc/ride_check(mob/living/rider)
	return

/datum/component/riding/proc/handle_vehicle_offsets(dir)
	var/atom/movable/AM = parent
	var/AM_dir = "[dir]"
	var/passindex = 0
	if(!AM.has_buckled_mobs())
		return

	for(var/m in AM.buckled_mobs)
		passindex++
		var/mob/living/buckled_mob = m
		var/list/offsets = get_offsets(passindex)
		buckled_mob.setDir(dir)
		dir_loop:
			for(var/offsetdir in offsets)
				if(offsetdir == AM_dir)
					var/list/diroffsets = offsets[offsetdir]
					buckled_mob.pixel_x = diroffsets[1]
					if(diroffsets.len >= 2)
						buckled_mob.pixel_y = diroffsets[2]
					if(diroffsets.len == 3)
						buckled_mob.layer = diroffsets[3]
					break dir_loop
	var/list/static/default_vehicle_pixel_offsets = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0))
	var/px = default_vehicle_pixel_offsets[AM_dir]
	var/py = default_vehicle_pixel_offsets[AM_dir]
	if(directional_vehicle_offsets[AM_dir])
		if(isnull(directional_vehicle_offsets[AM_dir]))
			px = AM.pixel_x
			py = AM.pixel_y
		else
			px = directional_vehicle_offsets[AM_dir][1]
			py = directional_vehicle_offsets[AM_dir][2]
	AM.pixel_x = px
	AM.pixel_y = py

/datum/component/riding/proc/set_vehicle_dir_offsets(dir, x, y)
	directional_vehicle_offsets["[dir]"] = list(x, y)

//Override this to set your vehicle's various pixel offsets
/datum/component/riding/proc/get_offsets(pass_index) // list(dir = x, y, layer)
	. = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0))
	if(riding_offsets["[pass_index]"])
		. = riding_offsets["[pass_index]"]
	else if(riding_offsets["[RIDING_OFFSET_ALL]"])
		. = riding_offsets["[RIDING_OFFSET_ALL]"]

/datum/component/riding/proc/set_riding_offsets(index, list/offsets)
	if(!islist(offsets))
		return FALSE
	riding_offsets["[index]"] = offsets

/**
 * This proc is used to see if we have the appropriate key to drive this atom, if such a key is needed. Returns FALSE if we don't have what we need to drive.
 *
 * Still needs to be neatened up and spruced up with proper OOP, as a result of vehicles having their own key handling from other ridable atoms
 */
/datum/component/riding/proc/keycheck(mob/user)
	if(!keytype)
		return TRUE

	if(isvehicle(parent))
		var/obj/vehicle/vehicle_parent = parent
		return istype(vehicle_parent.inserted_key, keytype)

	return user.is_holding_item_of_type(keytype)

//BUCKLE HOOKS
/datum/component/riding/proc/restore_position(mob/living/buckled_mob)
	if(buckled_mob)
		buckled_mob.pixel_x = buckled_mob.base_pixel_x
		buckled_mob.pixel_y = buckled_mob.base_pixel_y
		if(buckled_mob.client)
			buckled_mob.client.view_size.resetToDefault()

//MOVEMENT
/datum/component/riding/proc/turf_check(turf/next, turf/current)
	if(allowed_turf_typecache && !allowed_turf_typecache[next.type])
		return allowed_turf_typecache[current.type]
	else if(forbid_turf_typecache && forbid_turf_typecache[next.type])
		return !forbid_turf_typecache[current.type]
	return TRUE

/// Every time the driver tries to move, this is called to see if they can actually drive and move the vehicle (via relaymove)
/datum/component/riding/proc/driver_move(atom/movable/movable_parent, mob/living/user, direction)
	SIGNAL_HANDLER
	return

/// So we can check all occupants when we bump a door to see if anyone has access
/datum/component/riding/proc/vehicle_bump(atom/movable/movable_parent, obj/machinery/door/possible_bumped_door)
	SIGNAL_HANDLER
	if(!istype(possible_bumped_door))
		return
	for(var/occupant in movable_parent.buckled_mobs)
		INVOKE_ASYNC(possible_bumped_door, /obj/machinery/door/.proc/bumpopen, occupant)

/datum/component/riding/proc/Unbuckle(atom/movable/M)
	addtimer(CALLBACK(parent, /atom/movable/.proc/unbuckle_mob, M), 0, TIMER_UNIQUE)

/datum/component/riding/proc/Process_Spacemove(direction)
	var/atom/movable/AM = parent
	return override_allow_spacemove || AM.has_gravity()

/// currently replicated from ridable because we need this behavior here too, see if we can deal with that
/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user)
	var/atom/movable/AM = parent
	for(var/obj/item/riding_offhand/O in user.contents)
		if(O.parent != AM)
			CRASH("RIDING OFFHAND ON WRONG MOB")
		if(O.selfdeleting)
			continue
		else
			qdel(O)
	return TRUE


code:
// For any mob that can be ridden

/datum/component/riding/creature
	/// If TRUE, this creature's movements can be controlled by the rider while mounted (as opposed to riding cyborgs and humans, which is passive)
	var/can_be_driven = TRUE
	/// If TRUE, this creature's abilities can be triggered by the rider while mounted
	var/can_use_abilities = FALSE
	var/list/shared_action_buttons = list()

/datum/component/riding/creature/Initialize(mob/living/riding_mob, force = FALSE, ride_check_flags = NONE, potion_boost = FALSE)
	if(!isliving(parent))
		return COMPONENT_INCOMPATIBLE

	. = ..()
	var/mob/living/living_parent = parent
	living_parent.stop_pulling() // was only used on humans previously, may change some other behavior
	log_riding(living_parent, riding_mob)
	riding_mob.set_glide_size(living_parent.glide_size)
	handle_vehicle_offsets(living_parent.dir)

	if(can_use_abilities)
		setup_abilities(riding_mob)

	if(isanimal(parent))
		var/mob/living/simple_animal/simple_parent = parent
		simple_parent.stop_automated_movement = TRUE

/datum/component/riding/creature/Destroy(force, silent)
	unequip_buckle_inhands(parent)
	if(isanimal(parent))
		var/mob/living/simple_animal/simple_parent = parent
		simple_parent.stop_automated_movement = FALSE
	return ..()

/datum/component/riding/creature/RegisterWithParent()
	. = ..()
	RegisterSignal(parent, COMSIG_MOB_EMOTE, .proc/check_emote)
	if(can_be_driven)
		RegisterSignal(parent, COMSIG_RIDDEN_DRIVER_MOVE, .proc/driver_move) // this isn't needed on riding humans or cyborgs since the rider can't control them

/// Creatures need to be logged when being mounted
/datum/component/riding/creature/proc/log_riding(mob/living/living_parent, mob/living/rider)
	if(!istype(living_parent) || !istype(rider))
		return

	living_parent.log_message("is now being ridden by [rider]", LOG_ATTACK, color="pink")
	rider.log_message("started riding [living_parent]", LOG_ATTACK, color="pink")

// this applies to humans and most creatures, but is replaced again for cyborgs
/datum/component/riding/creature/ride_check(mob/living/rider)
	var/mob/living/living_parent = parent

	var/kick_us_off
	if(living_parent.body_position != STANDING_UP) // if we move while on the ground, the rider falls off
		kick_us_off = TRUE
	// for piggybacks and (redundant?) borg riding, check if the rider is stunned/restrained
	else if((ride_check_flags & RIDER_NEEDS_ARMS) && (HAS_TRAIT(rider, TRAIT_RESTRAINED) || rider.incapacitated(TRUE, TRUE)))
		kick_us_off = TRUE
	// for fireman carries, check if the ridden is stunned/restrained
	else if((ride_check_flags & CARRIER_NEEDS_ARM) && (HAS_TRAIT(living_parent, TRAIT_RESTRAINED) || living_parent.incapacitated(TRUE, TRUE)))
		kick_us_off = TRUE

	if(!kick_us_off)
		return TRUE

	rider.visible_message(span_warning("[rider] falls off of [living_parent]!"), \
					span_warning("You fall off of [living_parent]!"))
	rider.Paralyze(1 SECONDS)
	rider.Knockdown(4 SECONDS)
	living_parent.unbuckle_mob(rider)

/datum/component/riding/creature/vehicle_mob_buckle(datum/source, mob/living/rider, force = FALSE)
	// Ensure that the /mob/post_buckle_mob(mob/living/M) does not mess us up with layers
	// If we do not do this override we'll be stuck with the above proc (+ 0.1)-ing our rider's layer incorrectly
	rider.layer = initial(rider.layer)
	return ..()

/datum/component/riding/creature/vehicle_mob_unbuckle(mob/living/living_parent, mob/living/former_rider, force = FALSE)
	if(istype(living_parent) && istype(former_rider))
		living_parent.log_message("is no longer being ridden by [former_rider]", LOG_ATTACK, color="pink")
		former_rider.log_message("is no longer riding [living_parent]", LOG_ATTACK, color="pink")
	remove_abilities(former_rider)
	// We gotta reset those layers at some point, don't we?
	former_rider.layer = MOB_LAYER
	living_parent.layer = MOB_LAYER
	return ..()

/datum/component/riding/creature/driver_move(atom/movable/movable_parent, mob/living/user, direction)
	if(!COOLDOWN_FINISHED(src, vehicle_move_cooldown))
		return COMPONENT_DRIVER_BLOCK_MOVE
	if(!keycheck(user))
		if(ispath(keytype, /obj/item))
			var/obj/item/key = keytype
			to_chat(user, span_warning("You need a [initial(key.name)] to ride [movable_parent]!"))
		return COMPONENT_DRIVER_BLOCK_MOVE
	var/mob/living/living_parent = parent
	var/turf/next = get_step(living_parent, direction)
	step(living_parent, direction)
	last_move_diagonal = ((direction & (direction - 1)) && (living_parent.loc == next))
	COOLDOWN_START(src, vehicle_move_cooldown, (last_move_diagonal? 2 : 1) * vehicle_move_delay)

/// Yeets the rider off, used for animals and cyborgs, redefined for humans who shove their piggyback rider off
/datum/component/riding/creature/proc/force_dismount(mob/living/rider, gentle = FALSE)
	var/atom/movable/movable_parent = parent
	movable_parent.unbuckle_mob(rider)

	if(!isanimal(movable_parent) && !iscyborg(movable_parent))
		return

	var/turf/target = get_edge_target_turf(movable_parent, movable_parent.dir)
	var/turf/targetm = get_step(get_turf(movable_parent), movable_parent.dir)
	rider.Move(targetm)
	rider.Knockdown(3 SECONDS)
	if(gentle)
		rider.visible_message(span_warning("[rider] is thrown clear of [movable_parent]!"), \
		span_warning("You're thrown clear of [movable_parent]!"))
		rider.throw_at(target, 8, 3, movable_parent, gentle = TRUE)
	else
		rider.visible_message(span_warning("[rider] is thrown violently from [movable_parent]!"), \
		span_warning("You're thrown violently from [movable_parent]!"))
		rider.throw_at(target, 14, 5, movable_parent, gentle = FALSE)

/// If we're a cyborg or animal and we spin, we yeet whoever's on us off us
/datum/component/riding/creature/proc/check_emote(mob/living/user, datum/emote/emote)
	SIGNAL_HANDLER
	if((!iscyborg(user) && !isanimal(user)) || !istype(emote, /datum/emote/spin))
		return

	for(var/mob/yeet_mob in user.buckled_mobs)
		/* SEPTIC EDIT REMOVAL
		force_dismount(yeet_mob, (!user.combat_mode)) // gentle on help, byeeee if not
		*/
		//SEPTIC EDIT BEGIN
		force_dismount(yeet_mob, IS_NOT_HARM_INTENT(user, null)) // gentle out of harm, byeeee if not
		//SEPTIC EDIT END

/// If the ridden creature has abilities, and some var yet to be made is set to TRUE, the rider will be able to control those abilities
/datum/component/riding/creature/proc/setup_abilities(mob/living/rider)
	if(!istype(parent, /mob/living))
		return

	var/mob/living/ridden_creature = parent

	for(var/ability in ridden_creature.abilities)
		var/obj/effect/proc_holder/proc_holder = ability
		if(!proc_holder.action)
			return
		proc_holder.action.Share(rider)

/// Takes away the riding parent's abilities from the rider
/datum/component/riding/creature/proc/remove_abilities(mob/living/rider)
	if(!istype(parent, /mob/living))
		return

	var/mob/living/ridden_creature = parent

	for(var/ability in ridden_creature.abilities)
		var/obj/effect/proc_holder/proc_holder = ability
		if(!proc_holder.action)
			return
		if(rider == proc_holder.ranged_ability_user)
			proc_holder.remove_ranged_ability()
		proc_holder.action.Unshare(rider)

///////Yes, I said humans. No, this won't end well...//////////
/datum/component/riding/creature/human
	can_be_driven = FALSE

/datum/component/riding/creature/human/Initialize(mob/living/riding_mob, force = FALSE, ride_check_flags = NONE, potion_boost = FALSE)
	. = ..()
	var/mob/living/carbon/human/human_parent = parent
	human_parent.add_movespeed_modifier(/datum/movespeed_modifier/human_carry)

	if(ride_check_flags & RIDER_NEEDS_ARMS) // piggyback
		human_parent.buckle_lying = 0
		// the riding mob is made nondense so they don't bump into any dense atoms the carrier is pulling,
		// since pulled movables are moved before buckled movables
		riding_mob.set_density(FALSE)
	else if(ride_check_flags & CARRIER_NEEDS_ARM) // fireman
		human_parent.buckle_lying = 90

/datum/component/riding/creature/human/RegisterWithParent()
	. = ..()
	RegisterSignal(parent, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, .proc/on_host_unarmed_melee)
	RegisterSignal(parent, COMSIG_LIVING_SET_BODY_POSITION, .proc/check_carrier_fall_over)

/datum/component/riding/creature/human/log_riding(mob/living/living_parent, mob/living/rider)
	if(!istype(living_parent) || !istype(rider))
		return

	if(ride_check_flags & RIDER_NEEDS_ARMS) // piggyback
		living_parent.log_message("started giving [rider] a piggyback ride", LOG_ATTACK, color="pink")
		rider.log_message("started piggyback riding [living_parent]", LOG_ATTACK, color="pink")
	else if(ride_check_flags & CARRIER_NEEDS_ARM) // fireman
		living_parent.log_message("started fireman carrying [rider]", LOG_ATTACK, color="pink")
		rider.log_message("was fireman carried by [living_parent]", LOG_ATTACK, color="pink")

/datum/component/riding/creature/human/vehicle_mob_unbuckle(datum/source, mob/living/former_rider, force = FALSE)
	unequip_buckle_inhands(parent)
	var/mob/living/carbon/human/H = parent
	H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry)
	former_rider.set_density(!former_rider.body_position)
	return ..()

/// If the carrier shoves the person they're carrying, force the carried mob off
/datum/component/riding/creature/human/proc/on_host_unarmed_melee(mob/living/carbon/human/human_parent, atom/target, proximity, modifiers)
	SIGNAL_HANDLER
	/* SEPTIC EDIT REMOVAL
	if(LAZYACCESS(modifiers, RIGHT_CLICK) && (target in human_parent.buckled_mobs))
	*/
	if(human_parent.a_intent == INTENT_DISARM && (target in human_parent.buckled_mobs))
		force_dismount(target)
		return COMPONENT_CANCEL_ATTACK_CHAIN

/// If the carrier gets knocked over, force the rider(s) off and see if someone got hurt
/datum/component/riding/creature/human/proc/check_carrier_fall_over(mob/living/carbon/human/human_parent)
	SIGNAL_HANDLER

	for(var/i in human_parent.buckled_mobs)
		var/mob/living/rider = i
		human_parent.unbuckle_mob(rider)
		rider.Paralyze(1 SECONDS)
		rider.Knockdown(4 SECONDS)
		human_parent.visible_message(span_danger("[rider] topples off of [human_parent] as they both fall to the ground!"), \
					span_warning("You fall to the ground, bringing [rider] with you!"), span_hear("You hear two consecutive thuds."), COMBAT_MESSAGE_RANGE, ignored_mobs=rider)
		to_chat(rider, span_danger("[human_parent] falls to the ground, bringing you with [human_parent.p_them()]!"))

/datum/component/riding/creature/human/handle_vehicle_layer(dir)
	var/atom/movable/AM = parent
	if(!AM.buckled_mobs || !AM.buckled_mobs.len)
		AM.layer = MOB_LAYER
		return

	for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied
		M.layer = MOB_LAYER

	if(!AM.buckle_lying) // rider is vertical, must be piggybacking
		if(dir == SOUTH)
			AM.layer = MOB_ABOVE_PIGGYBACK_LAYER
		else
			AM.layer = MOB_BELOW_PIGGYBACK_LAYER
	else  // laying flat, we must be firemanning the rider
		if(dir == NORTH)
			AM.layer = MOB_BELOW_PIGGYBACK_LAYER
		else
			AM.layer = MOB_ABOVE_PIGGYBACK_LAYER

/datum/component/riding/creature/human/get_offsets(pass_index)
	var/mob/living/carbon/human/H = parent
	if(H.buckle_lying)
		return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6))
	else
		return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4))

/datum/component/riding/creature/human/force_dismount(mob/living/dismounted_rider)
	var/atom/movable/AM = parent
	AM.unbuckle_mob(dismounted_rider)
	dismounted_rider.Paralyze(1 SECONDS)
	dismounted_rider.Knockdown(4 SECONDS)
	dismounted_rider.visible_message(span_warning("[AM] pushes [dismounted_rider] off of [AM.p_them()]!"), \
						span_warning("[AM] pushes you off of [AM.p_them()]!"))

//Now onto cyborg riding//
/datum/component/riding/creature/cyborg
	can_be_driven = FALSE

/datum/component/riding/creature/cyborg/ride_check(mob/living/user)
	var/mob/living/silicon/robot/robot_parent = parent
	if(!iscarbon(user))
		return
	var/mob/living/carbon/carbonuser = user
	if(!carbonuser.usable_hands)
		Unbuckle(user)
		to_chat(user, span_warning("You can't grab onto [robot_parent] with no hands!"))

/datum/component/riding/creature/cyborg/handle_vehicle_layer(dir)
	var/atom/movable/robot_parent = parent
	if(dir == SOUTH)
		robot_parent.layer = MOB_ABOVE_PIGGYBACK_LAYER
	else
		robot_parent.layer = MOB_BELOW_PIGGYBACK_LAYER

/datum/component/riding/creature/cyborg/get_offsets(pass_index) // list(dir = x, y, layer)
	return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3))

/datum/component/riding/creature/cyborg/handle_vehicle_offsets(dir)
	var/mob/living/silicon/robot/robot_parent = parent

	for(var/mob/living/rider in robot_parent.buckled_mobs)
		rider.setDir(dir)
		if(istype(robot_parent.model))
			rider.pixel_x = robot_parent.model.ride_offset_x[dir2text(dir)]
			rider.pixel_y = robot_parent.model.ride_offset_y[dir2text(dir)]

//now onto every other ridable mob//

/datum/component/riding/creature/mulebot/handle_specials()
	. = ..()
	var/atom/movable/movable_parent = parent
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 12), TEXT_SOUTH = list(0, 12), TEXT_EAST = list(0, 12), TEXT_WEST = list(0, 12)))
	set_vehicle_dir_layer(SOUTH, movable_parent.layer) //vehicles default to ABOVE_MOB_LAYER while moving, let's make sure that doesn't happen while a mob is riding us.
	set_vehicle_dir_layer(NORTH, movable_parent.layer)
	set_vehicle_dir_layer(EAST, movable_parent.layer)
	set_vehicle_dir_layer(WEST, movable_parent.layer)

/datum/component/riding/creature/cow/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 8), TEXT_SOUTH = list(0, 8), TEXT_EAST = list(-2, 8), TEXT_WEST = list(2, 8)))
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, OBJ_LAYER)
	set_vehicle_dir_layer(WEST, OBJ_LAYER)

/datum/component/riding/creature/bear/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(1, 8), TEXT_SOUTH = list(1, 8), TEXT_EAST = list(-3, 6), TEXT_WEST = list(3, 6)))
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(WEST, ABOVE_MOB_LAYER)

/datum/component/riding/creature/carp
	override_allow_spacemove = TRUE

/datum/component/riding/creature/carp/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 13), TEXT_SOUTH = list(0, 15), TEXT_EAST = list(-2, 12), TEXT_WEST = list(2, 12)))
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, OBJ_LAYER)
	set_vehicle_dir_layer(WEST, OBJ_LAYER)

/datum/component/riding/creature/megacarp/handle_specials()
	. = ..()
	var/atom/movable/movable_parent = parent
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(1, 8), TEXT_SOUTH = list(1, 8), TEXT_EAST = list(-3, 6), TEXT_WEST = list(3, 6)))
	set_vehicle_dir_offsets(SOUTH, movable_parent.pixel_x, 0)
	set_vehicle_dir_offsets(NORTH, movable_parent.pixel_x, 0)
	set_vehicle_dir_offsets(EAST, movable_parent.pixel_x, 0)
	set_vehicle_dir_offsets(WEST, movable_parent.pixel_x, 0)
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, OBJ_LAYER)
	set_vehicle_dir_layer(WEST, OBJ_LAYER)

/datum/component/riding/creature/vatbeast
	override_allow_spacemove = TRUE
	can_use_abilities = TRUE

/datum/component/riding/creature/vatbeast/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 15), TEXT_SOUTH = list(0, 15), TEXT_EAST = list(-10, 15), TEXT_WEST = list(10, 15)))
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, OBJ_LAYER)
	set_vehicle_dir_layer(WEST, OBJ_LAYER)

/datum/component/riding/creature/goliath
	keytype = /obj/item/key/lasso

/datum/component/riding/creature/goliath/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 8), TEXT_SOUTH = list(0, 8), TEXT_EAST = list(-2, 8), TEXT_WEST = list(2, 8)))
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, OBJ_LAYER)
	set_vehicle_dir_layer(WEST, OBJ_LAYER)

/datum/component/riding/creature/glutton/handle_specials()
	. = ..()
	var/atom/movable/movable_parent = parent
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 24), TEXT_SOUTH = list(0, 24), TEXT_EAST = list(-16, 24), TEXT_WEST = list(16, 24)))
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(WEST, ABOVE_MOB_LAYER)
	set_vehicle_dir_offsets(SOUTH, movable_parent.pixel_x, 0)
	set_vehicle_dir_offsets(NORTH, movable_parent.pixel_x, 0)
	set_vehicle_dir_offsets(EAST, movable_parent.pixel_x, 0)
	set_vehicle_dir_offsets(WEST, movable_parent.pixel_x, 0)


code:
// For any /obj/vehicle's that can be ridden

/datum/component/riding/vehicle/Initialize(mob/living/riding_mob, force = FALSE, ride_check_flags = (RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS), potion_boost = FALSE)
	if(!isvehicle(parent))
		return COMPONENT_INCOMPATIBLE
	return ..()

/datum/component/riding/vehicle/RegisterWithParent()
	. = ..()
	RegisterSignal(parent, COMSIG_RIDDEN_DRIVER_MOVE, .proc/driver_move)

/datum/component/riding/vehicle/driver_move(atom/movable/movable_parent, mob/living/user, direction)
	if(!COOLDOWN_FINISHED(src, vehicle_move_cooldown))
		return COMPONENT_DRIVER_BLOCK_MOVE
	var/obj/vehicle/vehicle_parent = parent

	if(!keycheck(user))
		if(COOLDOWN_FINISHED(src, message_cooldown))
			to_chat(user, span_warning("[vehicle_parent] has no key inserted!"))
			COOLDOWN_START(src, message_cooldown, 5 SECONDS)
		return COMPONENT_DRIVER_BLOCK_MOVE

	if(HAS_TRAIT(user, TRAIT_INCAPACITATED))
		if(ride_check_flags & UNBUCKLE_DISABLED_RIDER)
			vehicle_parent.unbuckle_mob(user, TRUE)
			user.visible_message(span_danger("[user] falls off \the [vehicle_parent]."),\
			span_danger("You slip off \the [vehicle_parent] as your body slumps!"))
			user.Stun(3 SECONDS)

		if(COOLDOWN_FINISHED(src, message_cooldown))
			to_chat(user, span_warning("You cannot operate \the [vehicle_parent] right now!"))
			COOLDOWN_START(src, message_cooldown, 5 SECONDS)
		return COMPONENT_DRIVER_BLOCK_MOVE

	if(ride_check_flags & RIDER_NEEDS_LEGS && HAS_TRAIT(user, TRAIT_FLOORED))
		if(ride_check_flags & UNBUCKLE_DISABLED_RIDER)
			vehicle_parent.unbuckle_mob(user, TRUE)
			user.visible_message(span_danger("[user] falls off \the [vehicle_parent]."),\
			span_danger("You fall off \the [vehicle_parent] while trying to operate it while unable to stand!"))
			user.Stun(3 SECONDS)

		if(COOLDOWN_FINISHED(src, message_cooldown))
			to_chat(user, span_warning("You can't seem to manage that while unable to stand up enough to move \the [vehicle_parent]..."))
			COOLDOWN_START(src, message_cooldown, 5 SECONDS)
		return COMPONENT_DRIVER_BLOCK_MOVE

	if(ride_check_flags & RIDER_NEEDS_ARMS && HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
		if(ride_check_flags & UNBUCKLE_DISABLED_RIDER)
			vehicle_parent.unbuckle_mob(user, TRUE)
			user.visible_message(span_danger("[user] falls off \the [vehicle_parent]."),\
			span_danger("You fall off \the [vehicle_parent] while trying to operate it without being able to hold on!"))
			user.Stun(3 SECONDS)

		if(COOLDOWN_FINISHED(src, message_cooldown))
			to_chat(user, span_warning("You can't seem to hold onto \the [vehicle_parent] to move it..."))
			COOLDOWN_START(src, message_cooldown, 5 SECONDS)
		return COMPONENT_DRIVER_BLOCK_MOVE

	handle_ride(user, direction)

/// This handles the actual movement for vehicles once [/datum/component/riding/vehicle/proc/driver_move] has given us the green light
/datum/component/riding/vehicle/proc/handle_ride(mob/user, direction)
	var/atom/movable/movable_parent = parent

	var/turf/next = get_step(movable_parent, direction)
	var/turf/current = get_turf(movable_parent)
	if(!istype(next) || !istype(current))
		return //not happening.
	if(!turf_check(next, current))
		to_chat(user, span_warning("\The [movable_parent] can not go onto [next]!"))
		return
	if(!Process_Spacemove(direction) || !isturf(movable_parent.loc))
		return

	step(movable_parent, direction)
	last_move_diagonal = ((direction & (direction - 1)) && (movable_parent.loc == next))
	COOLDOWN_START(src, vehicle_move_cooldown, (last_move_diagonal? 2 : 1) * vehicle_move_delay)

	if(QDELETED(src))
		return
	handle_vehicle_layer(movable_parent.dir)
	handle_vehicle_offsets(movable_parent.dir)
	return TRUE

/datum/component/riding/vehicle/atv
	keytype = /obj/item/key/atv
	ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
	vehicle_move_delay = 1.5

/datum/component/riding/vehicle/atv/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(0, 4), TEXT_WEST = list( 0, 4)))
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, OBJ_LAYER)
	set_vehicle_dir_layer(WEST, OBJ_LAYER)

/datum/component/riding/vehicle/bicycle
	ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER
	vehicle_move_delay = 0

/datum/component/riding/vehicle/bicycle/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(0, 4), TEXT_WEST = list( 0, 4)))

/datum/component/riding/vehicle/lavaboat
	ride_check_flags = NONE // not sure
	keytype = /obj/item/oar
	var/allowed_turf = /turf/open/lava

/datum/component/riding/vehicle/lavaboat/handle_specials()
	. = ..()
	allowed_turf_typecache = typecacheof(allowed_turf)

/datum/component/riding/vehicle/lavaboat/dragonboat
	vehicle_move_delay = 1

/datum/component/riding/vehicle/lavaboat/dragonboat/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(1, 2), TEXT_SOUTH = list(1, 2), TEXT_EAST = list(1, 2), TEXT_WEST = list( 1, 2)))

/datum/component/riding/vehicle/lavaboat/dragonboat
	vehicle_move_delay = 1
	keytype = null

/datum/component/riding/vehicle/janicart
	keytype = /obj/item/key/janitor

/datum/component/riding/vehicle/janicart/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 7), TEXT_EAST = list(-12, 7), TEXT_WEST = list( 12, 7)))

/datum/component/riding/vehicle/scooter
	ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER

/datum/component/riding/vehicle/scooter/handle_specials(mob/living/riding_mob)
	. = ..()
	if(iscyborg(riding_mob))
		set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0), TEXT_SOUTH = list(0), TEXT_EAST = list(0), TEXT_WEST = list(2)))
	else
		set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(2), TEXT_SOUTH = list(-2), TEXT_EAST = list(0), TEXT_WEST = list(2)))

/datum/component/riding/vehicle/scooter/skateboard
	vehicle_move_delay = 1.5
	ride_check_flags = RIDER_NEEDS_LEGS | UNBUCKLE_DISABLED_RIDER

/datum/component/riding/vehicle/scooter/skateboard/handle_specials()
	. = ..()
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(NORTH, OBJ_LAYER)
	set_vehicle_dir_layer(EAST, OBJ_LAYER)
	set_vehicle_dir_layer(WEST, OBJ_LAYER)

/datum/component/riding/vehicle/scooter/skateboard/wheelys
	vehicle_move_delay = 0

/datum/component/riding/vehicle/scooter/skateboard/wheelys/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0), TEXT_SOUTH = list(0), TEXT_EAST = list(0), TEXT_WEST = list(0)))

/datum/component/riding/vehicle/scooter/skateboard/wheelys/rollerskates
	vehicle_move_delay = 1.5

/datum/component/riding/vehicle/scooter/skateboard/wheelys/skishoes
	vehicle_move_delay = 1

/datum/component/riding/vehicle/scooter/skateboard/wheelys/skishoes/handle_specials()
	. = ..()
	allowed_turf_typecache = typecacheof(list(/turf/open/floor/plating/asteroid/snow, /turf/open/floor/grass/snow, /turf/open/floor/holofloor/snow, /turf/open/floor/plating/ice))

/datum/component/riding/vehicle/secway
	keytype = /obj/item/key/security
	vehicle_move_delay = 1.75
	ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER

/datum/component/riding/vehicle/secway/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(0, 4), TEXT_WEST = list( 0, 4)))
	set_vehicle_dir_layer(SOUTH, ABOVE_MOB_LAYER)

/datum/component/riding/vehicle/secway/driver_move(mob/living/user, direction)
	var/obj/vehicle/ridden/secway/the_secway = parent

	if(keycheck(user) && the_secway.eddie_murphy)
		if(COOLDOWN_FINISHED(src, message_cooldown))
			the_secway.visible_message(span_warning("[the_secway] sputters and refuses to move!"))
			playsound(get_turf(the_secway), 'sound/effects/stall.ogg', 70)
			COOLDOWN_START(src, message_cooldown, 0.75 SECONDS)
		return COMPONENT_DRIVER_BLOCK_MOVE
	return ..()

/datum/component/riding/vehicle/speedbike
	vehicle_move_delay = 0
	override_allow_spacemove = TRUE
	ride_check_flags = RIDER_NEEDS_LEGS | RIDER_NEEDS_ARMS | UNBUCKLE_DISABLED_RIDER

/datum/component/riding/vehicle/speedbike/handle_specials()
	. = ..()
	set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(0, -8), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-10, 5), TEXT_WEST = list( 10, 5)))
	set_vehicle_dir_offsets(NORTH, -16, -16)
	set_vehicle_dir_offsets(SOUTH, -16, -16)
	set_vehicle_dir_offsets(EAST, -18, 0)
	set_vehicle_dir_offsets(WEST, -18, 0)

/datum/component/riding/vehicle/speedwagon
	vehicle_move_delay = 0

/datum/component/riding/vehicle/speedwagon/handle_specials()
	. = ..()
	set_riding_offsets(1, list(TEXT_NORTH = list(-10, -4), TEXT_SOUTH = list(16, 3), TEXT_EAST = list(-4, 30), TEXT_WEST = list(4, -3)))
	set_riding_offsets(2, list(TEXT_NORTH = list(19, -5, 4), TEXT_SOUTH = list(-13, 3, 4), TEXT_EAST = list(-4, -3, 4.1), TEXT_WEST = list(4, 28, 3.9)))
	set_riding_offsets(3, list(TEXT_NORTH = list(-10, -18, 4.2), TEXT_SOUTH = list(16, 25, 3.9), TEXT_EAST = list(-22, 30), TEXT_WEST = list(22, -3, 4.1)))
	set_riding_offsets(4, list(TEXT_NORTH = list(19, -18, 4.2), TEXT_SOUTH = list(-13, 25, 3.9), TEXT_EAST = list(-22, 3, 3.9), TEXT_WEST = list(22, 28)))
	set_vehicle_dir_offsets(NORTH, -48, -48)
	set_vehicle_dir_offsets(SOUTH, -48, -48)
	set_vehicle_dir_offsets(EAST, -48, -48)
	set_vehicle_dir_offsets(WEST, -48, -48)
	for(var/i in GLOB.cardinals)
		set_vehicle_dir_layer(i, BELOW_MOB_LAYER)

/datum/component/riding/vehicle/wheelchair
	vehicle_move_delay = 0
	ride_check_flags = RIDER_NEEDS_ARMS

/datum/component/riding/vehicle/wheelchair/handle_specials()
	. = ..()
	set_vehicle_dir_layer(SOUTH, OBJ_LAYER)
	set_vehicle_dir_layer(NORTH, ABOVE_MOB_LAYER)
	set_vehicle_dir_layer(EAST, OBJ_LAYER)
	set_vehicle_dir_layer(WEST, OBJ_LAYER)

// special messaging for those without arms
/datum/component/riding/vehicle/wheelchair/hand/driver_move(obj/vehicle/vehicle_parent, mob/living/user, direction)
	var/delay_multiplier = 6.7 // magic number from wheelchair code
	vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_delay) * delay_multiplier) / clamp(user.usable_hands, 1, 2)
	return ..()

/datum/component/riding/vehicle/wheelchair/motorized/driver_move(obj/vehicle/vehicle_parent, mob/living/user, direction)
	var/speed = 1 // Should never be under 1
	var/delay_multiplier = 6.7 // magic number from wheelchair code

	var/obj/vehicle/ridden/wheelchair/motorized/our_chair = parent
	for(var/obj/item/stock_parts/manipulator/M in our_chair.contents)
		speed += M.rating
	vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_delay) * delay_multiplier) / speed
	return ..()

/datum/component/riding/vehicle/wheelchair/motorized/handle_ride(mob/user, direction)
	. = ..()
	var/obj/vehicle/ridden/wheelchair/motorized/our_chair = parent
	if(istype(our_chair) && our_chair.power_cell)
		our_chair.power_cell.use(our_chair.power_usage / max(our_chair.power_efficiency, 1) * 0.05)


code:
#define REAGENT_BLOCKER (1<<0)
#define TEMPERATURE_BLOCKER (1<<1)
#define HUSK_BLOCKER (1<<2)

///Makes a thing rotting, carries with it a start delay and some things that can halt the rot, along with infection logic
/datum/component/rot
	///The time we were created, allows for cheese smell
	var/start_time = 0
	///The delay in ticks between the start of rot and effects kicking in
	var/start_delay = 0
	///The time in ticks before a rot component reaches its full effectiveness
	var/scaling_delay = 0
	///How strong is the rot? used for scaling different aspects of the component. Between 0 and 1
	var/strength = 0
	///Is the component active right now?
	var/active = FALSE
	///Bitfield of sources preventing the component from rotting
	var/blockers = NONE

	var/static/list/loc_connections = list(
		COMSIG_ATOM_ENTERED = .proc/on_entered,
	)

/datum/component/rot/Initialize(delay, scaling, severity)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	if(isliving(parent))
		var/mob/living/living_parent = parent
		//I think this can break in cases where someone becomes a robot post death, but I uh, I don't know how to account for that
		if(!(living_parent.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD)))
			return COMPONENT_INCOMPATIBLE

	start_delay = delay
	scaling_delay = scaling
	strength = severity

	RegisterSignal(parent, list(COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_ANIMAL, COMSIG_ATOM_ATTACK_HAND), .proc/rot_react_touch)
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/rot_hit_react)
	if(ismovable(parent))
		AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)
		RegisterSignal(parent, COMSIG_MOVABLE_BUMP, .proc/rot_react)
	if(isliving(parent))
		RegisterSignal(parent, COMSIG_LIVING_REVIVE, .proc/react_to_revive) //mobs stop this when they come to life
		RegisterSignal(parent, COMSIG_LIVING_GET_PULLED, .proc/rot_react_touch)
	if(iscarbon(parent))
		var/mob/living/carbon/carbon_parent = parent
		RegisterSignal(carbon_parent.reagents, list(COMSIG_REAGENTS_ADD_REAGENT,
			COMSIG_REAGENTS_REM_REAGENT,
			COMSIG_REAGENTS_DEL_REAGENT), .proc/check_reagent)
		RegisterSignal(parent, list(SIGNAL_ADDTRAIT(TRAIT_HUSK), SIGNAL_REMOVETRAIT(TRAIT_HUSK)), .proc/check_husk_trait)
		check_reagent(carbon_parent.reagents, null)
		check_husk_trait(null)
	if(ishuman(parent))
		var/mob/living/carbon/human/human_parent = parent
		RegisterSignal(parent, COMSIG_HUMAN_CORETEMP_CHANGE, .proc/check_for_temperature)
		check_for_temperature(null, 0, human_parent.coretemperature)

	start_up(NONE) //If nothing's blocking it, start

/datum/component/rot/UnregisterFromParent()
	. = ..()
	if(ismovable(parent))
		qdel(GetComponent(/datum/component/connect_loc_behalf))

///One of two procs that modifies blockers, this one handles removing a blocker and potentially restarting the rot
/datum/component/rot/proc/start_up(blocker_type)
	blockers &= ~blocker_type //Yeet the type
	if(blockers || active)  //If it's not empty
		return
	start_time = world.time
	active = TRUE

///One of two procs that modifies blockers, this one handles adding a blocker and potentially ending the rot
/datum/component/rot/proc/rest(blocker_type)
	var/old_blockers = blockers
	blockers |= blocker_type
	if(old_blockers || !active) //If it had anything before this
		return
	start_delay = max((start_time + start_delay) - world.time, 0) //Account for the time spent rotting
	active = FALSE

/datum/component/rot/proc/react_to_revive()
	SIGNAL_HANDLER
	qdel(src)

/datum/component/rot/proc/check_reagent(datum/reagents/source, datum/reagent/modified)
	SIGNAL_HANDLER
	if(modified && !istype(modified, /datum/reagent/toxin/formaldehyde) && !istype(modified, /datum/reagent/cryostylane))
		return
	if(source.has_reagent(/datum/reagent/toxin/formaldehyde, 15) || source.has_reagent(/datum/reagent/cryostylane))
		rest(REAGENT_BLOCKER)
		return
	start_up(REAGENT_BLOCKER)

/datum/component/rot/proc/check_for_temperature(datum/source, old_temp, new_temp)
	SIGNAL_HANDLER
	if(new_temp <= T0C-10)
		rest(TEMPERATURE_BLOCKER)
		return
	start_up(TEMPERATURE_BLOCKER)

/datum/component/rot/proc/check_husk_trait()
	SIGNAL_HANDLER
	if(HAS_TRAIT(parent, TRAIT_HUSK))
		rest(HUSK_BLOCKER)
		return
	start_up(HUSK_BLOCKER)

/datum/component/rot/proc/rot_hit_react(datum/source, obj/item/hit_with, mob/living/attacker, params)
	SIGNAL_HANDLER
	rot_react_touch(source, attacker)

/datum/component/rot/proc/rot_react_touch(datum/source, mob/living/react_to)
	SIGNAL_HANDLER
	rot_react(source, react_to, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))

/// Triggered when something enters the component's parent.
/datum/component/rot/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
	SIGNAL_HANDLER
	rot_react(source, arrived)

///The main bit of logic for the rot component, does a temperature check and has a chance to infect react_to
/datum/component/rot/proc/rot_react(source, mob/living/react_to, target_zone = null)
	SIGNAL_HANDLER
	if(!isliving(react_to))
		return

	// Don't infect if you're chilled (I'd like to link this with the signals, but I can't come up with a good way to pull it off)
	var/atom/atom_parent = parent
	var/datum/gas_mixture/our_mix = atom_parent.return_air()
	if(our_mix?.temperature <= T0C-10)
		return

	if(!active)
		return

	var/time_delta = world.time - start_time
	// Wait a bit before decaying
	if(time_delta < start_delay)
		return

	var/time_scaling = min((time_delta - start_delay) / scaling_delay, 1)

	if(!prob(strength * 1 * time_scaling))
		return

	//We're running just under the "worst disease", since we don't want these to be too strong
	var/datum/disease/advance/random/rand_disease = new(rand(4 * strength * time_scaling), rand(strength * 5 * time_scaling))
	rand_disease.name = "Unknown"
	react_to.ContactContractDisease(rand_disease, target_zone)

#undef REAGENT_BLOCKER
#undef TEMPERATURE_BLOCKER
#undef HUSK_BLOCKER


code:
#define ROTATION_ALTCLICK (1<<0)
#define ROTATION_WRENCH (1<<1)
#define ROTATION_VERBS (1<<2)
#define ROTATION_COUNTERCLOCKWISE (1<<3)
#define ROTATION_CLOCKWISE (1<<4)
#define ROTATION_FLIP (1<<5)

/datum/component/simple_rotation
	var/datum/callback/can_user_rotate //Checks if user can rotate
	var/datum/callback/can_be_rotated  //Check if object can be rotated at all
	var/datum/callback/after_rotation     //Additional stuff to do after rotation

	var/rotation_flags = NONE
	var/default_rotation_direction = ROTATION_CLOCKWISE

/datum/component/simple_rotation/Initialize(rotation_flags = NONE ,can_user_rotate,can_be_rotated,after_rotation)
	if(!ismovable(parent))
		return COMPONENT_INCOMPATIBLE

	//throw if no rotation direction is specificed ?

	src.rotation_flags = rotation_flags

	if(can_user_rotate)
		src.can_user_rotate = can_user_rotate
	else
		src.can_user_rotate = CALLBACK(src,.proc/default_can_user_rotate)

	if(can_be_rotated)
		src.can_be_rotated = can_be_rotated
	else
		src.can_be_rotated = CALLBACK(src,.proc/default_can_be_rotated)

	if(after_rotation)
		src.after_rotation = after_rotation
	else
		src.after_rotation = CALLBACK(src,.proc/default_after_rotation)

	//Try Clockwise,counter,flip in order
	if(src.rotation_flags & ROTATION_FLIP)
		default_rotation_direction = ROTATION_FLIP
	if(src.rotation_flags & ROTATION_COUNTERCLOCKWISE)
		default_rotation_direction = ROTATION_COUNTERCLOCKWISE
	if(src.rotation_flags & ROTATION_CLOCKWISE)
		default_rotation_direction = ROTATION_CLOCKWISE

/datum/component/simple_rotation/proc/add_signals()
	if(rotation_flags & ROTATION_ALTCLICK)
		RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/HandRot)
		RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/ExamineMessage)
	if(rotation_flags & ROTATION_WRENCH)
		RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH), .proc/WrenchRot)

/datum/component/simple_rotation/proc/add_verbs()
	if(rotation_flags & ROTATION_VERBS)
		var/atom/movable/AM = parent
		if(rotation_flags & ROTATION_FLIP)
			AM.verbs += /atom/movable/proc/simple_rotate_flip
		if(src.rotation_flags & ROTATION_CLOCKWISE)
			AM.verbs += /atom/movable/proc/simple_rotate_clockwise
		if(src.rotation_flags & ROTATION_COUNTERCLOCKWISE)
			AM.verbs += /atom/movable/proc/simple_rotate_counterclockwise

/datum/component/simple_rotation/proc/remove_verbs()
	if(parent)
		var/atom/movable/AM = parent
		AM.verbs -= /atom/movable/proc/simple_rotate_flip
		AM.verbs -= /atom/movable/proc/simple_rotate_clockwise
		AM.verbs -= /atom/movable/proc/simple_rotate_counterclockwise

/datum/component/simple_rotation/proc/remove_signals()
	UnregisterSignal(parent, list(COMSIG_CLICK_ALT, COMSIG_PARENT_EXAMINE, COMSIG_PARENT_ATTACKBY))

/datum/component/simple_rotation/RegisterWithParent()
	add_verbs()
	add_signals()
	. = ..()

/datum/component/simple_rotation/PostTransfer()
	//Because of the callbacks which we don't track cleanly we can't transfer this
	//item cleanly, better to let the new of the new item create a new rotation datum
	//instead (there's no real state worth transferring)
	return COMPONENT_NOTRANSFER

/datum/component/simple_rotation/UnregisterFromParent()
	remove_verbs()
	remove_signals()
	. = ..()

/datum/component/simple_rotation/Destroy()
	QDEL_NULL(can_user_rotate)
	QDEL_NULL(can_be_rotated)
	QDEL_NULL(after_rotation)
	//Signals + verbs removed via UnRegister
	. = ..()

/datum/component/simple_rotation/ClearFromParent()
	remove_verbs()
	return ..()

/datum/component/simple_rotation/proc/ExamineMessage(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER

	if(rotation_flags & ROTATION_ALTCLICK)
		examine_list += span_notice("Alt-click to rotate it clockwise.")

/datum/component/simple_rotation/proc/HandRot(datum/source, mob/user, rotation = default_rotation_direction)
	SIGNAL_HANDLER

	if(!can_be_rotated.Invoke(user, rotation) || !can_user_rotate.Invoke(user, rotation))
		return
	BaseRot(user, rotation)

/datum/component/simple_rotation/proc/WrenchRot(datum/source, obj/item/I, mob/living/user)
	SIGNAL_HANDLER

	if(!can_be_rotated.Invoke(user,default_rotation_direction) || !can_user_rotate.Invoke(user,default_rotation_direction))
		return
	BaseRot(user,default_rotation_direction)
	return COMPONENT_BLOCK_TOOL_ATTACK

/datum/component/simple_rotation/proc/BaseRot(mob/user,rotation_type)
	var/atom/movable/AM = parent
	var/rot_degree
	switch(rotation_type)
		if(ROTATION_CLOCKWISE)
			rot_degree = -90
		if(ROTATION_COUNTERCLOCKWISE)
			rot_degree = 90
		if(ROTATION_FLIP)
			rot_degree = 180
	AM.setDir(turn(AM.dir,rot_degree))
	after_rotation.Invoke(user,rotation_type)

/datum/component/simple_rotation/proc/default_can_user_rotate(mob/living/user, rotation_type)
	if(!istype(user) || !user.canUseTopic(parent, BE_CLOSE, NO_DEXTERITY, FALSE, !iscyborg(user)))
		return FALSE
	return TRUE

/datum/component/simple_rotation/proc/default_can_be_rotated(mob/user, rotation_type)
	var/atom/movable/AM = parent
	return !AM.anchored

/datum/component/simple_rotation/proc/default_after_rotation(mob/user, rotation_type)
	to_chat(user,span_notice("You [rotation_type == ROTATION_FLIP ? "flip" : "rotate"] [parent]."))

/atom/movable/proc/simple_rotate_clockwise()
	set name = "Rotate Clockwise"
	set category = "Object"
	set src in oview(1)
	var/datum/component/simple_rotation/rotcomp = GetComponent(/datum/component/simple_rotation)
	if(rotcomp)
		rotcomp.HandRot(null,usr,ROTATION_CLOCKWISE)

/atom/movable/proc/simple_rotate_counterclockwise()
	set name = "Rotate Counter-Clockwise"
	set category = "Object"
	set src in oview(1)
	var/datum/component/simple_rotation/rotcomp = GetComponent(/datum/component/simple_rotation)
	if(rotcomp)
		rotcomp.HandRot(null,usr,ROTATION_COUNTERCLOCKWISE)

/atom/movable/proc/simple_rotate_flip()
	set name = "Flip"
	set category = "Object"
	set src in oview(1)
	var/datum/component/simple_rotation/rotcomp = GetComponent(/datum/component/simple_rotation)
	if(rotcomp)
		rotcomp.HandRot(null,usr,ROTATION_FLIP)


code:
/// Makes an atom a shell that is able to take in an attached circuit.
/datum/component/shell
	dupe_mode = COMPONENT_DUPE_UNIQUE

	/// The circuitboard attached to this shell
	var/obj/item/integrated_circuit/attached_circuit

	/// Flags containing what this shell can do
	var/shell_flags = NONE

	/// The capacity of the shell.
	var/capacity = INFINITY

	/// A list of components that cannot be removed
	var/list/obj/item/circuit_component/unremovable_circuit_components

	var/locked = FALSE

/datum/component/shell/Initialize(unremovable_circuit_components, capacity, shell_flags, starting_circuit)
	. = ..()
	if(!ismovable(parent))
		return COMPONENT_INCOMPATIBLE

	src.shell_flags = shell_flags || src.shell_flags
	src.capacity = capacity || src.capacity
	set_unremovable_circuit_components(unremovable_circuit_components)

	if(starting_circuit)
		attach_circuit(starting_circuit)

/datum/component/shell/RegisterWithParent()
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/on_attack_by)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/on_attack_ghost)
	if(!(shell_flags & SHELL_FLAG_CIRCUIT_FIXED))
		RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), .proc/on_screwdriver_act)
		RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), .proc/on_multitool_act)
		RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_object_deconstruct)
	if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR)
		RegisterSignal(parent, COMSIG_MOVABLE_SET_ANCHORED, .proc/on_set_anchored)
	RegisterSignal(parent, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, .proc/on_atom_usb_cable_try_attach)
	RegisterSignal(parent, COMSIG_MOVABLE_CIRCUIT_LOADED, .proc/on_load)

/datum/component/shell/proc/set_unremovable_circuit_components(list/components)
	if(unremovable_circuit_components)
		QDEL_LIST(unremovable_circuit_components)

	unremovable_circuit_components = list()

	for(var/obj/item/circuit_component/circuit_component as anything in components)
		if(ispath(circuit_component))
			circuit_component = new circuit_component()
		circuit_component.removable = FALSE
		circuit_component.set_circuit_size(0)
		RegisterSignal(circuit_component, COMSIG_CIRCUIT_COMPONENT_SAVE, .proc/save_component)
		unremovable_circuit_components += circuit_component

/datum/component/shell/proc/save_component(datum/source, list/objects)
	SIGNAL_HANDLER
	objects += parent

/datum/component/shell/proc/on_load(datum/source, obj/item/integrated_circuit/circuit, list/components)
	SIGNAL_HANDLER
	var/list/components_in_list = list()
	for(var/obj/item/circuit_component/component as anything in components)
		components_in_list += component.type

	for(var/obj/item/circuit_component/component as anything in unremovable_circuit_components)
		if(component.type in components_in_list)
			continue
		var/new_type = component.type
		components += new new_type()
	set_unremovable_circuit_components(components)
	attach_circuit(circuit)

/datum/component/shell/UnregisterFromParent()
	UnregisterSignal(parent, list(
		COMSIG_PARENT_ATTACKBY,
		COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER),
		COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL),
		COMSIG_OBJ_DECONSTRUCT,
		COMSIG_MOVABLE_SET_ANCHORED,
		COMSIG_PARENT_EXAMINE,
		COMSIG_ATOM_ATTACK_GHOST,
		COMSIG_ATOM_USB_CABLE_TRY_ATTACH,
		COMSIG_MOVABLE_CIRCUIT_LOADED,
	))

	QDEL_NULL(attached_circuit)

/datum/component/shell/Destroy(force, silent)
	QDEL_LIST(unremovable_circuit_components)
	return ..()

/datum/component/shell/proc/on_object_deconstruct()
	SIGNAL_HANDLER
	if(!attached_circuit?.admin_only)
		remove_circuit()

/datum/component/shell/proc/on_attack_ghost(datum/source, mob/dead/observer/ghost)
	SIGNAL_HANDLER
	if(!is_authorized(ghost))
		return

	if(attached_circuit)
		INVOKE_ASYNC(attached_circuit, /datum.proc/ui_interact, ghost)

/datum/component/shell/proc/on_examine(atom/movable/source, mob/user, list/examine_text)
	SIGNAL_HANDLER
	if(!is_authorized(user))
		return

	if(!attached_circuit)
		examine_text += span_notice("There is no integrated circuit attached.")
		return

	examine_text += span_notice("There is an integrated circuit attached. Use a multitool to access the wiring. Use a screwdriver to remove it from [source].")
	examine_text += span_notice("The cover panel to the integrated circuit is [locked? "locked" : "unlocked"].")
	var/obj/item/stock_parts/cell/cell = attached_circuit.cell
	examine_text += span_notice("The charge meter reads [cell ? round(cell.percent(), 1) : 0]%.")
	if((shell_flags & SHELL_FLAG_REQUIRE_ANCHOR) && !source.anchored)
		examine_text += span_notice("The integrated circuit is non-functional whilst the shell is unanchored.")

	if (shell_flags & SHELL_FLAG_USB_PORT)
		examine_text += span_notice("There is a <b>USB port</b> on the front.")

/**
 * Called when the shell is anchored.
 *
 * Only applies if the shell has SHELL_FLAG_REQUIRE_ANCHOR.
 * Disables the integrated circuit if unanchored, otherwise enable the circuit.
 */
/datum/component/shell/proc/on_set_anchored(atom/movable/source, previous_value)
	SIGNAL_HANDLER
	attached_circuit?.on = source.anchored

/**
 * Called when an item hits the parent. This is the method to add the circuitboard to the component.
 */
/datum/component/shell/proc/on_attack_by(atom/source, obj/item/item, mob/living/attacker)
	SIGNAL_HANDLER
	if(!is_authorized(attacker))
		return

	if(istype(item, /obj/item/stock_parts/cell))
		source.balloon_alert(attacker, "can't put cell in directly!")
		return

	if(istype(item, /obj/item/inducer))
		var/obj/item/inducer/inducer = item
		INVOKE_ASYNC(inducer, /obj/item.proc/attack_atom, attached_circuit, attacker, list())
		return COMPONENT_NO_AFTERATTACK

	if(attached_circuit)
		if(attached_circuit.owner_id && item == attached_circuit.owner_id.resolve())
			set_locked(!locked)
			source.balloon_alert(attacker, "[locked? "locked" : "unlocked"] [source]")
			return COMPONENT_NO_AFTERATTACK

		if(!attached_circuit.owner_id && istype(item, /obj/item/card/id))
			source.balloon_alert(attacker, "owner id set for [item]")
			attached_circuit.owner_id = WEAKREF(item)
			return COMPONENT_NO_AFTERATTACK

		if(istype(item, /obj/item/circuit_component))
			attached_circuit.add_component_manually(item, attacker)
			return COMPONENT_NO_AFTERATTACK

	if(!istype(item, /obj/item/integrated_circuit))
		return
	var/obj/item/integrated_circuit/logic_board = item
	. = COMPONENT_NO_AFTERATTACK

	if(logic_board.shell) // I'll be surprised if this ever happens
		return

	if(attached_circuit)
		source.balloon_alert(attacker, "there is already a circuitboard inside!")
		return

	if(logic_board.current_size > capacity)
		source.balloon_alert(attacker, "this is too large to fit into [parent]!")
		return

	logic_board.inserter_mind = WEAKREF(attacker.mind)
	attach_circuit(logic_board, attacker)

/// Sets whether the shell is locked or not
/datum/component/shell/proc/set_locked(new_value)
	locked = new_value
	attached_circuit?.set_locked(new_value)

/datum/component/shell/proc/on_multitool_act(atom/source, mob/user, obj/item/tool)
	SIGNAL_HANDLER
	if(!is_authorized(user))
		return

	if(!attached_circuit)
		return

	if(locked)
		if(shell_flags & SHELL_FLAG_ALLOW_FAILURE_ACTION)
			return
		source.balloon_alert(user, "it's locked!")
		return COMPONENT_BLOCK_TOOL_ATTACK

	attached_circuit.interact(user)
	return COMPONENT_BLOCK_TOOL_ATTACK

/**
 * Called when a screwdriver is used on the parent. Removes the circuitboard from the component.
 */
/datum/component/shell/proc/on_screwdriver_act(atom/source, mob/user, obj/item/tool)
	SIGNAL_HANDLER
	if(!is_authorized(user))
		return

	if(!attached_circuit)
		return

	if(locked)
		if(shell_flags & SHELL_FLAG_ALLOW_FAILURE_ACTION)
			return
		source.balloon_alert(user, "it's locked!")
		return COMPONENT_BLOCK_TOOL_ATTACK

	tool.play_tool_sound(parent)
	source.balloon_alert(user, "you unscrew [attached_circuit] from [parent].")
	remove_circuit()
	return COMPONENT_BLOCK_TOOL_ATTACK

/**
 * Checks for when the circuitboard moves. If it moves, removes it from the component.
 */
/datum/component/shell/proc/on_circuit_moved(obj/item/integrated_circuit/circuit, atom/old_loc)
	SIGNAL_HANDLER
	if(circuit.loc != parent)
		remove_circuit()

/**
 * Checks for when the circuitboard deletes so that it can be unassigned.
 */
/datum/component/shell/proc/on_circuit_delete(datum/source)
	SIGNAL_HANDLER
	remove_circuit()

/datum/component/shell/proc/on_circuit_add_component_manually(atom/source, obj/item/circuit_component/added_comp, mob/living/user)
	SIGNAL_HANDLER
	if(locked)
		source.balloon_alert(user, "it's locked!")
		return COMPONENT_CANCEL_ADD_COMPONENT

	if(attached_circuit.current_size + added_comp.circuit_size > capacity)
		source.balloon_alert(user, "it won't fit!")
		return COMPONENT_CANCEL_ADD_COMPONENT

/**
 * Attaches a circuit to the parent. Doesn't do any checks to see for any existing circuits so that should be done beforehand.
 */
/datum/component/shell/proc/attach_circuit(obj/item/integrated_circuit/circuitboard, mob/living/user)
	var/atom/movable/parent_atom = parent
	if(user && !user.transferItemToLoc(circuitboard, parent_atom))
		return
	locked = FALSE
	attached_circuit = circuitboard
	RegisterSignal(circuitboard, COMSIG_MOVABLE_MOVED, .proc/on_circuit_moved)
	RegisterSignal(circuitboard, COMSIG_PARENT_QDELETING, .proc/on_circuit_delete)
	for(var/obj/item/circuit_component/to_add as anything in unremovable_circuit_components)
		to_add.forceMove(attached_circuit)
		attached_circuit.add_component(to_add)
	RegisterSignal(circuitboard, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, .proc/on_circuit_add_component_manually)
	attached_circuit.set_shell(parent_atom)
	if(attached_circuit.display_name != "")
		parent_atom.name = "[initial(parent_atom.name)] ([attached_circuit.display_name])"
	attached_circuit.set_locked(FALSE)

	if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR)
		attached_circuit.on = parent_atom.anchored

	if(circuitboard.loc != parent_atom)
		circuitboard.forceMove(parent_atom)

/**
 * Removes the circuit from the component. Doesn't do any checks to see for an existing circuit so that should be done beforehand.
 */
/datum/component/shell/proc/remove_circuit()
	attached_circuit.on = TRUE
	attached_circuit.remove_current_shell()
	UnregisterSignal(attached_circuit, list(
		COMSIG_MOVABLE_MOVED,
		COMSIG_PARENT_QDELETING,
		COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY,
	))
	if(attached_circuit.loc == parent)
		var/atom/parent_atom = parent
		attached_circuit.forceMove(parent_atom.drop_location())

	for(var/obj/item/circuit_component/to_remove as anything in unremovable_circuit_components)
		attached_circuit.remove_component(to_remove)
		to_remove.moveToNullspace()
	attached_circuit.set_locked(FALSE)
	attached_circuit = null

/datum/component/shell/proc/on_atom_usb_cable_try_attach(atom/source, obj/item/usb_cable/usb_cable, mob/user)
	SIGNAL_HANDLER
	if(!is_authorized(user))
		return

	if (!(shell_flags & SHELL_FLAG_USB_PORT))
		source.balloon_alert(user, "this shell has no usb ports")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	if (isnull(attached_circuit))
		source.balloon_alert(user, "no circuit inside")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	usb_cable.attached_circuit = attached_circuit
	return COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT

/**
 * Determines if a user is authorized to see the existance of this shell. Returns false if they are not
 *
 * Arguments:
 * * user - The user to check if they are authorized
 */
/datum/component/shell/proc/is_authorized(mob/user)
	if(shell_flags & SHELL_FLAG_CIRCUIT_FIXED)
		return FALSE

	if(attached_circuit?.admin_only)
		if(check_rights_for(user.client, R_VAREDIT))
			return TRUE
		return FALSE

	return TRUE


code:
/**
 * The shielded component causes the parent item to nullify a certain number of attacks against the wearer, see: shielded hardsuits.
 */

/datum/component/shielded
	/// The person currently wearing us
	var/mob/living/wearer
	/// How many charges we can have max, and how many we start with
	var/max_charges
	/// How many charges we currently have
	var/current_charges
	/// How long we have to avoid being hit to replenish charges. If set to 0, we never recharge lost charges
	var/recharge_start_delay = 20 SECONDS
	/// Once we go unhit long enough to recharge, we replenish charges this often. The floor is effectively 1 second, AKA how often SSdcs processes
	var/charge_increment_delay = 1 SECONDS
	/// How many charges we recover on each charge increment
	var/charge_recovery = 1
	/// What .dmi we're pulling the shield icon from
	var/shield_icon_file = 'icons/effects/effects.dmi'
	/// What icon is used when someone has a functional shield up
	var/shield_icon = "shield-old"
	/// Do we still shield if we're being held in-hand? If FALSE, it needs to be equipped to a slot to work
	var/shield_inhand = FALSE
	/// Should the shield lose charges equal to the damage dealt by a hit?
	var/lose_multiple_charges = FALSE
	/// The cooldown tracking when we were last hit
	COOLDOWN_DECLARE(recently_hit_cd)
	/// The cooldown tracking when we last replenished a charge
	COOLDOWN_DECLARE(charge_add_cd)
	/// A callback for the sparks/message that play when a charge is used, see [/datum/component/shielded/proc/default_run_hit_callback]
	var/datum/callback/on_hit_effects

/datum/component/shielded/Initialize(max_charges = 3, recharge_start_delay = 20 SECONDS, charge_increment_delay = 1 SECONDS, charge_recovery = 1, lose_multiple_charges = FALSE, shield_icon_file = 'icons/effects/effects.dmi', shield_icon = "shield-old", shield_inhand = FALSE, run_hit_callback)
	if(!isitem(parent) || max_charges <= 0)
		return COMPONENT_INCOMPATIBLE

	src.max_charges = max_charges
	src.recharge_start_delay = recharge_start_delay
	src.charge_increment_delay = charge_increment_delay
	src.charge_recovery = charge_recovery
	src.lose_multiple_charges = lose_multiple_charges
	src.shield_icon_file = shield_icon_file
	src.shield_icon = shield_icon
	src.shield_inhand = shield_inhand
	src.on_hit_effects = run_hit_callback || CALLBACK(src, .proc/default_run_hit_callback)

	current_charges = max_charges
	if(recharge_start_delay)
		START_PROCESSING(SSdcs, src)

/datum/component/shielded/Destroy(force, silent)
	if(wearer)
		shield_icon = "broken"
		UnregisterSignal(wearer, COMSIG_ATOM_UPDATE_OVERLAYS)
		wearer.update_appearance(UPDATE_ICON)
		wearer = null
	QDEL_NULL(on_hit_effects)
	return ..()

/datum/component/shielded/RegisterWithParent()
	RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equipped)
	RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/lost_wearer)
	RegisterSignal(parent, COMSIG_ITEM_HIT_REACT, .proc/on_hit_react)
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/check_recharge_rune)

/datum/component/shielded/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED, COMSIG_ITEM_HIT_REACT, COMSIG_PARENT_ATTACKBY))

// Handle recharging, if we want to
/datum/component/shielded/process(delta_time)
	if(current_charges >= max_charges)
		STOP_PROCESSING(SSdcs, src)
		return

	if(!COOLDOWN_FINISHED(src, recently_hit_cd))
		return
	if(!COOLDOWN_FINISHED(src, charge_add_cd))
		return

	var/obj/item/item_parent = parent
	COOLDOWN_START(src, charge_add_cd, charge_increment_delay)
	adjust_charge(charge_recovery) // set the number of charges to current + recovery per increment, clamped from zero to max_charges
	playsound(item_parent, 'sound/magic/charge.ogg', 50, TRUE)
	if(current_charges == max_charges)
		playsound(item_parent, 'sound/machines/ding.ogg', 50, TRUE)

/datum/component/shielded/proc/adjust_charge(change)
	current_charges = clamp(current_charges + change, 0, max_charges)
	if(wearer)
		wearer.update_appearance(UPDATE_ICON)

/// Check if we've been equipped to a valid slot to shield
/datum/component/shielded/proc/on_equipped(datum/source, mob/user, slot)
	SIGNAL_HANDLER

	if(slot == ITEM_SLOT_HANDS && !shield_inhand)
		lost_wearer(source, user)
		return

	wearer = user
	RegisterSignal(wearer, COMSIG_ATOM_UPDATE_OVERLAYS, .proc/on_update_overlays)
	RegisterSignal(wearer, COMSIG_PARENT_QDELETING, .proc/lost_wearer)
	if(current_charges)
		wearer.update_appearance(UPDATE_ICON)

/// Either we've been dropped or our wearer has been QDEL'd. Either way, they're no longer our problem
/datum/component/shielded/proc/lost_wearer(datum/source, mob/user)
	SIGNAL_HANDLER

	if(wearer)
		UnregisterSignal(wearer, list(COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_PARENT_QDELETING))
		wearer.update_appearance(UPDATE_ICON)
		wearer = null

/// Used to draw the shield overlay on the wearer
/datum/component/shielded/proc/on_update_overlays(atom/parent_atom, list/overlays)
	SIGNAL_HANDLER

	overlays += mutable_appearance(shield_icon_file, (current_charges > 0 ? shield_icon : "broken"), MOB_SHIELD_LAYER)

/**
 * This proc fires when we're hit, and is responsible for checking if we're charged, then deducting one + returning that we're blocking if so.
 * It then runs the callback in [/datum/component/shielded/var/on_hit_effects] which handles the messages/sparks (so the visuals)
 */
/datum/component/shielded/proc/on_hit_react(datum/source, mob/living/carbon/human/owner, atom/movable/hitby, attack_text, final_block_chance, damage, attack_type)
	SIGNAL_HANDLER

	COOLDOWN_START(src, recently_hit_cd, recharge_start_delay)

	if(current_charges <= 0)
		return
	. = COMPONENT_HIT_REACTION_BLOCK

	var/charge_loss = 1 // how many charges do we lose

	if(lose_multiple_charges) // if the shield has health like damage we'll lose charges equal to the damage of the hit
		charge_loss = damage

	adjust_charge(-charge_loss)

	INVOKE_ASYNC(src, .proc/actually_run_hit_callback, owner, attack_text, current_charges)

	if(!recharge_start_delay) // if recharge_start_delay is 0, we don't recharge
		if(!current_charges) // obviously if someone ever adds a manual way to replenish charges, change this
			qdel(src)
		return

	START_PROCESSING(SSdcs, src) // if we DO recharge, start processing so we can do that

/// The wrapper to invoke the on_hit callback, so we don't have to worry about blocking in the signal handler
/datum/component/shielded/proc/actually_run_hit_callback(mob/living/owner, attack_text, current_charges)
	on_hit_effects.Invoke(owner, attack_text, current_charges)

/// Default on_hit proc, since cult robes are stupid and have different descriptions/sparks
/datum/component/shielded/proc/default_run_hit_callback(mob/living/owner, attack_text, current_charges)
	do_sparks(2, TRUE, owner)
	owner.visible_message("<span class='danger'>[owner]'s shields deflect [attack_text] in a shower of sparks!<span>")
	if(current_charges <= 0)
		owner.visible_message(span_warning("[owner]'s shield overloads!"))

/datum/component/shielded/proc/check_recharge_rune(datum/source, obj/item/wizard_armour_charge/recharge_rune, mob/living/user)
	SIGNAL_HANDLER

	if(!istype(recharge_rune))
		return
	. = COMPONENT_NO_AFTERATTACK
	if(!istype(parent, /obj/item/clothing/suit/space/hardsuit/shielded/wizard))
		to_chat(user, span_warning("The rune can only be used on battlemage armour!"))
		return

	current_charges += recharge_rune.restored_charges
	to_chat(user, span_notice("You charge \the [parent]. It can now absorb [current_charges] hits."))
	qdel(recharge_rune)


code:
/datum/component/shrink
	var/olddens
	var/oldopac
	dupe_mode = COMPONENT_DUPE_HIGHLANDER

/datum/component/shrink/Initialize(shrink_time)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	var/atom/parent_atom = parent
	parent_atom.transform = parent_atom.transform.Scale(0.5,0.5)
	olddens = parent_atom.density
	oldopac = parent_atom.opacity
	parent_atom.set_density(FALSE)
	parent_atom.set_opacity(FALSE)
	if(isliving(parent_atom))
		var/mob/living/L = parent_atom
		L.add_movespeed_modifier(/datum/movespeed_modifier/shrink_ray)
		if(iscarbon(L))
			var/mob/living/carbon/C = L
			C.unequip_everything()
			C.visible_message(span_warning("[C]'s belongings fall off of [C.p_them()] as they shrink down!"),
			span_userdanger("Your belongings fall away as everything grows bigger!"))
			if(ishuman(C))
				var/mob/living/carbon/human/H = C
				H.physiology.damage_resistance -= 100//carbons take double damage while shrunk
	parent_atom.visible_message(span_warning("[parent_atom] shrinks down to a tiny size!"),
	span_userdanger("Everything grows bigger!"))
	QDEL_IN(src, shrink_time)

/datum/component/shrink/Destroy()
	var/atom/parent_atom = parent
	parent_atom.transform = parent_atom.transform.Scale(2,2)
	parent_atom.set_density(olddens)
	parent_atom.set_opacity(oldopac)
	if(isliving(parent_atom))
		var/mob/living/L = parent_atom
		L.remove_movespeed_modifier(/datum/movespeed_modifier/shrink_ray)
		if(ishuman(L))
			var/mob/living/carbon/human/H = L
			H.physiology.damage_resistance += 100
	..()


code:
#define SHY_COMPONENT_CACHE_TIME 0.5 SECONDS

/// You can't use items on anyone other than yourself if there are other living mobs around you
/datum/component/shy
	can_transfer = TRUE
	/// How close you are before you get shy
	var/shy_range = 4
	/// Typecache of mob types you are okay around
	var/list/whitelist
	/// Message shown when you are is_shy
	var/message = "You find yourself too shy to do that around %TARGET!"
	/// Are you shy around a dead body?
	var/dead_shy = FALSE
	/// Invalidate last_result at this time
	COOLDOWN_DECLARE(result_cooldown)
	/// What was our last result?
	var/last_result = FALSE

/datum/component/shy/Initialize(whitelist, shy_range, message, dead_shy)
	if(!ismob(parent))
		return COMPONENT_INCOMPATIBLE
	src.whitelist = whitelist
	if(shy_range)
		src.shy_range = shy_range
	if(message)
		src.message = message
	if(dead_shy)
		src.dead_shy = dead_shy

/datum/component/shy/RegisterWithParent()
	RegisterSignal(parent, COMSIG_MOB_CLICKON, .proc/on_clickon)
	RegisterSignal(parent, COMSIG_LIVING_TRY_PULL, .proc/on_try_pull)
	RegisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), .proc/on_unarmed_attack)
	RegisterSignal(parent, COMSIG_TRY_STRIP, .proc/on_try_strip)
	RegisterSignal(parent, COMSIG_TRY_ALT_ACTION, .proc/on_try_alt_action)

/datum/component/shy/UnregisterFromParent()
	UnregisterSignal(parent, list(
		COMSIG_MOB_CLICKON,
		COMSIG_LIVING_TRY_PULL,
		COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK,
		COMSIG_TRY_STRIP,
		COMSIG_TRY_ALT_ACTION,
	))

/datum/component/shy/PostTransfer()
	if(!ismob(parent))
		return COMPONENT_INCOMPATIBLE

/datum/component/shy/InheritComponent(datum/component/shy/friend, i_am_original, list/arguments)
	if(i_am_original)
		shy_range = friend.shy_range
		whitelist = friend.whitelist
		message = friend.message

/// Returns TRUE or FALSE if you are within shy_range tiles from a /mob/living
/datum/component/shy/proc/is_shy(atom/target)
	var/result = FALSE
	var/mob/owner = parent

	if(target in owner.DirectAccess())
		return

	if(!COOLDOWN_FINISHED(src, result_cooldown))
		return last_result

	var/list/strangers = view(shy_range, get_turf(owner))

	if(length(strangers) && locate(/mob/living) in strangers)
		for(var/mob/living/person in strangers)
			if(person != owner && !is_type_in_typecache(person, whitelist) && (person.stat != DEAD || dead_shy))
				to_chat(owner, span_warning("[replacetext(message, "%TARGET", person)]"))
				result = TRUE
				break

	last_result = result
	COOLDOWN_START(src, result_cooldown, SHY_COMPONENT_CACHE_TIME)
	return result

/datum/component/shy/proc/on_clickon(datum/source, atom/target, list/modifiers)
	SIGNAL_HANDLER
	if(modifiers[SHIFT_CLICK]) //let them examine their surroundings.
		return
	return is_shy(target) && COMSIG_MOB_CANCEL_CLICKON

/datum/component/shy/proc/on_try_pull(datum/source, atom/movable/target, force)
	SIGNAL_HANDLER
	return is_shy(target) && COMSIG_LIVING_CANCEL_PULL

/datum/component/shy/proc/on_unarmed_attack(datum/source, atom/target, proximity, modifiers)
	SIGNAL_HANDLER
	return is_shy(target) && COMPONENT_CANCEL_ATTACK_CHAIN

/datum/component/shy/proc/on_try_strip(datum/source, atom/target, obj/item/equipping)
	SIGNAL_HANDLER
	return is_shy(target) && COMPONENT_CANT_STRIP

/datum/component/shy/proc/on_try_alt_action(datum/source, atom/target)
	SIGNAL_HANDLER
	return is_shy(target) && COMPONENT_CANT_ALT_ACTION

#undef SHY_COMPONENT_CACHE_TIME


code:
/// You can't use items on anyone other than yourself if you stand in a blacklisted room
/datum/component/shy_in_room
	can_transfer = TRUE
	/// Typecache of areas you can't stand
	var/list/blacklist
	/// Message shown when you are in a blacklisted room
	var/message = "%ROOM is too creepy to do that!"

/// _blacklist, and _message map to vars
/datum/component/shy_in_room/Initialize(blacklist, message)
	if(!ismob(parent))
		return COMPONENT_INCOMPATIBLE
	src.blacklist = blacklist
	if(message)
		src.message = message

/datum/component/shy_in_room/RegisterWithParent()
	RegisterSignal(parent, COMSIG_MOB_CLICKON, .proc/on_clickon)
	RegisterSignal(parent, COMSIG_LIVING_TRY_PULL, .proc/on_try_pull)
	RegisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), .proc/on_unarmed_attack)
	RegisterSignal(parent, COMSIG_TRY_STRIP, .proc/on_try_strip)
	RegisterSignal(parent, COMSIG_TRY_ALT_ACTION, .proc/on_try_alt_action)

/datum/component/shy_in_room/UnregisterFromParent()
	UnregisterSignal(parent, list(
		COMSIG_MOB_CLICKON,
		COMSIG_LIVING_TRY_PULL,
		COMSIG_LIVING_UNARMED_ATTACK,
		COMSIG_HUMAN_EARLY_UNARMED_ATTACK,
		COMSIG_TRY_STRIP,
		COMSIG_TRY_ALT_ACTION,
	))

/datum/component/shy_in_room/PostTransfer()
	if(!ismob(parent))
		return COMPONENT_INCOMPATIBLE

/datum/component/shy_in_room/InheritComponent(datum/component/shy_in_room/friend, i_am_original, list/arguments)
	if(i_am_original)
		blacklist = friend.blacklist
		message = friend.message

/// Returns TRUE or FALSE if you are in a blacklisted area
/datum/component/shy_in_room/proc/is_shy(atom/target)
	var/mob/owner = parent
	if(!length(blacklist) || (target in owner.DirectAccess()))
		return

	var/area/room = get_area(owner)
	if(is_type_in_typecache(room, blacklist))
		to_chat(owner, span_warning("[replacetext(message, "%ROOM", room)]"))
		return TRUE

/datum/component/shy_in_room/proc/on_clickon(datum/source, atom/target, list/modifiers)
	SIGNAL_HANDLER
	if(modifiers[SHIFT_CLICK]) //let them examine their surroundings.
		return
	return is_shy(target) && COMSIG_MOB_CANCEL_CLICKON

/datum/component/shy_in_room/proc/on_try_pull(datum/source, atom/movable/target, force)
	SIGNAL_HANDLER
	return is_shy(target) && COMSIG_LIVING_CANCEL_PULL

/datum/component/shy_in_room/proc/on_unarmed_attack(datum/source, atom/target, proximity, modifiers)
	SIGNAL_HANDLER
	return is_shy(target) && COMPONENT_CANCEL_ATTACK_CHAIN

/datum/component/shy_in_room/proc/on_try_strip(datum/source, atom/target, obj/item/equipping)
	SIGNAL_HANDLER
	return is_shy(target) && COMPONENT_CANT_STRIP

/datum/component/shy_in_room/proc/on_try_alt_action(datum/source, atom/target)
	SIGNAL_HANDLER
	return is_shy(target) && COMPONENT_CANT_ALT_ACTION