code:
/// The range at which a singularity is considered "contained" to admins
#define FIELD_CONTAINMENT_DISTANCE 30

/// What's the chance that, when a singularity moves, it'll go to its target?
#define CHANCE_TO_MOVE_TO_TARGET 60

/// Things that maybe move around and does stuff to things around them
/// Used for the singularity (duh) and Nar'Sie
/datum/component/singularity
	/// Callback for consuming objects (for example, Nar'Sie replaces this to call narsie_act)
	var/datum/callback/consume_callback

	/// The range to pull in stuff around it
	var/consume_range

	/// Does this singularity move?
	var/roaming

	/// The chosen direction to drift in
	var/drifting_dir

	/// How many tiles out to pull in
	var/grav_pull

	/// The last direction we failed to move in (for example: if we are contained)
	var/last_failed_movement

	/// How big is the singularity?
	var/singularity_size

	/// Should we disregard the possibility of failed movements? Used by stage five singularities
	var/disregard_failed_movements

	/// Can this singularity be BSA'd?
	var/bsa_targetable

	/// Should the admins be alerted when this is created?
	var/notify_admins

	/// If specified, the singularity will slowly move to this target
	var/atom/target

/datum/component/singularity/Initialize(
	bsa_targetable = TRUE,
	consume_range = 0,
	consume_callback = CALLBACK(src, .proc/default_singularity_act),
	disregard_failed_movements = FALSE,
	grav_pull = 4,
	notify_admins = TRUE,
	singularity_size = STAGE_ONE,
	roaming = TRUE,
)
	if (!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	src.bsa_targetable = bsa_targetable
	src.consume_callback = consume_callback
	src.consume_range = consume_range
	src.disregard_failed_movements = disregard_failed_movements
	src.grav_pull = grav_pull
	src.notify_admins = notify_admins
	src.roaming = roaming
	src.singularity_size = singularity_size

/datum/component/singularity/RegisterWithParent()
	START_PROCESSING(SSdcs, src)

	// The singularity stops drifting for no man!
	parent.AddElement(/datum/element/forced_gravity, FALSE)

	parent.AddElement(/datum/element/bsa_blocker)
	RegisterSignal(parent, COMSIG_ATOM_BSA_BEAM, .proc/bluespace_reaction)

	RegisterSignal(parent, COMSIG_ATOM_BLOB_ACT, .proc/block_blob)

	RegisterSignal(parent, list(
		COMSIG_ATOM_ATTACK_ANIMAL,
		COMSIG_ATOM_ATTACK_HAND,
		COMSIG_ATOM_ATTACK_PAW,
	), .proc/consume_attack)
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/consume_attackby)

	RegisterSignal(parent, COMSIG_MOVABLE_PRE_MOVE, .proc/moved)
	RegisterSignal(parent, COMSIG_ATOM_BUMPED, .proc/consume)
	var/static/list/loc_connections = list(
		COMSIG_ATOM_ENTERED = .proc/on_entered,
	)
	AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)

	RegisterSignal(parent, COMSIG_ATOM_BULLET_ACT, .proc/consume_bullets)

	if (notify_admins)
		admin_investigate_setup()

	GLOB.singularities |= src

/datum/component/singularity/Destroy(force, silent)
	GLOB.singularities -= src
	QDEL_NULL(consume_callback)
	target = null

	return ..()

/datum/component/singularity/UnregisterFromParent()
	STOP_PROCESSING(SSdcs, src)

	parent.RemoveElement(/datum/element/bsa_blocker)
	parent.RemoveElement(/datum/element/forced_gravity)

	UnregisterSignal(parent, list(
		COMSIG_ATOM_ATTACK_ANIMAL,
		COMSIG_ATOM_ATTACK_HAND,
		COMSIG_ATOM_ATTACK_PAW,
		COMSIG_ATOM_BLOB_ACT,
		COMSIG_ATOM_BSA_BEAM,
		COMSIG_ATOM_BULLET_ACT,
		COMSIG_ATOM_BUMPED,
		COMSIG_MOVABLE_PRE_MOVE,
		COMSIG_PARENT_ATTACKBY,
	))

/datum/component/singularity/process(delta_time)
	if (roaming)
		move()
	eat()

/datum/component/singularity/proc/block_blob()
	SIGNAL_HANDLER

	return COMPONENT_CANCEL_BLOB_ACT

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

/datum/component/singularity/proc/consume(datum/source, atom/thing)
	SIGNAL_HANDLER
	if (thing == parent)
		stack_trace("Singularity tried to consume itself.")
		return

	consume_callback?.Invoke(thing, src)

/datum/component/singularity/proc/consume_attack(datum/source, mob/user)
	SIGNAL_HANDLER

	consume(source, user)
	return COMPONENT_CANCEL_ATTACK_CHAIN

/datum/component/singularity/proc/consume_attackby(datum/source, obj/item/item, mob/user)
	SIGNAL_HANDLER

	consume(source, user)

// Will there be an impact? Who knows.  Will we see it? No.
/datum/component/singularity/proc/consume_bullets(datum/source, obj/projectile/projectile)
	SIGNAL_HANDLER

	qdel(projectile)

/// Calls singularity_act on the thing passed, usually destroying the object
/datum/component/singularity/proc/default_singularity_act(atom/thing)
	thing.singularity_act(singularity_size, parent)

/datum/component/singularity/proc/eat()
	var/atom/atom_parent = parent

	for (var/_tile in spiral_range_turfs(grav_pull, parent))
		var/turf/tile = _tile
		if (!tile || !isturf(atom_parent.loc))
			continue
		if (get_dist(tile, parent) > consume_range)
			tile.singularity_pull(src, singularity_size)
		else
			consume(src, tile)

		for (var/_thing in tile)
			var/atom/thing = _thing

			// Because we can possibly yield in the middle of iteration, let's make sure what were looking at is still there
			// Without this, you get "Qdeleted thing being thrown around"
			if (QDELETED(thing))
				continue

			if (isturf(atom_parent.loc) && thing != parent)
				var/atom/movable/movable_thing = thing
				if (get_dist(movable_thing, parent) > consume_range)
					movable_thing.singularity_pull(parent, singularity_size)
				else
					consume(src, movable_thing)

			CHECK_TICK

/datum/component/singularity/proc/move()
	var/drifting_dir = pick(GLOB.alldirs - last_failed_movement)

	if (!QDELETED(target) && prob(CHANCE_TO_MOVE_TO_TARGET))
		drifting_dir = get_dir(parent, target)

	step(parent, drifting_dir)

/datum/component/singularity/proc/moved(datum/source, atom/new_location)
	SIGNAL_HANDLER

	var/atom/atom_parent = parent
	var/current_direction = atom_parent.dir
	var/turf/current_turf = get_turf(parent)

	for(var/dir in GLOB.cardinals)
		if(current_direction & dir)
			current_turf = get_step(current_turf, dir)
			if(!current_turf)
				break
			// eat the stuff if we're going to move into it so it doesn't mess up our movement
			for(var/atom/thing_on_turf in current_turf.contents)
				consume(src, thing_on_turf)
			consume(src, current_turf)

	if(disregard_failed_movements || check_turfs_in(current_direction))
		last_failed_movement = null
	else
		last_failed_movement = current_direction
		return COMPONENT_MOVABLE_BLOCK_PRE_MOVE

/datum/component/singularity/proc/can_move(turf/to_move)
	if (!to_move)
		return FALSE

	for (var/_thing in to_move)
		var/atom/thing = _thing
		if (SEND_SIGNAL(thing, COMSIG_ATOM_SINGULARITY_TRY_MOVE) & SINGULARITY_TRY_MOVE_BLOCK)
			return FALSE

	return TRUE

/// Makes sure we don't move out of the z-level by checking the turfs around us.
/// Takes in the direction we're going, and optionally how many steps forward to look.
/// If steps are not provided, it will be inferred by singularity_size.
/datum/component/singularity/proc/check_turfs_in(direction, steps)
	if (!direction)
		return FALSE
	var/atom/atom_parent = parent
	if (!steps)
		switch (singularity_size)
			if (STAGE_ONE)
				steps = 1
			if (STAGE_TWO)
				steps = 3//Yes this is right
			if (STAGE_THREE)
				steps = 3
			if (STAGE_FOUR)
				steps = 4
			if (STAGE_FIVE)
				steps = 5
	var/list/turfs = list()
	var/turf/farthest_turf = atom_parent.loc
	for (var/_ = 1 to steps)
		farthest_turf = get_step(farthest_turf, direction)
	if (!isturf(farthest_turf))
		return FALSE
	turfs.Add(farthest_turf)
	var/dir2
	var/dir3
	switch (direction)
		if (NORTH || SOUTH)
			dir2 = EAST
			dir3 = WEST
		if (EAST || WEST)
			dir2 = NORTH
			dir3 = SOUTH
	var/turf/farthest_perpendicular_turf = farthest_turf
	for (var/_ = 1 to steps - 1)
		farthest_perpendicular_turf = get_step(farthest_perpendicular_turf, dir2)
		if (!isturf(farthest_perpendicular_turf))
			return FALSE
		turfs.Add(farthest_perpendicular_turf)
	for (var/_ = 1 to steps - 1)
		farthest_turf = get_step(farthest_turf, dir3)
		if (!isturf(farthest_turf))
			return FALSE
		turfs.Add(farthest_turf)
	for (var/turf_in_range in turfs)
		if (isnull(turf_in_range))
			continue
		if (!can_move(turf_in_range))
			return FALSE
	return TRUE

/// Logs to admins that a singularity was created
/datum/component/singularity/proc/admin_investigate_setup()
	var/turf/spawned_turf = get_turf(parent)
	message_admins("A singulo has been created at [ADMIN_VERBOSEJMP(spawned_turf)].")
	var/atom/atom_parent = parent
	atom_parent.investigate_log("was made a singularity at [AREACOORD(spawned_turf)].", INVESTIGATE_SINGULO)

/// Fired when the singularity is fired at with the BSA and deletes it
/datum/component/singularity/proc/bluespace_reaction()
	SIGNAL_HANDLER
	if (!bsa_targetable)
		return

	var/atom/atom_parent = parent
	atom_parent.investigate_log("has been shot by bluespace artillery and destroyed.", INVESTIGATE_SINGULO)
	qdel(parent)

#undef CHANCE_TO_MOVE_TO_TARGET
#undef FIELD_CONTAINMENT_DISTANCE


code:
/datum/component/wearertargeting/sitcomlaughter
	valid_slots = list(ITEM_SLOT_HANDS, ITEM_SLOT_BELT, ITEM_SLOT_ID, ITEM_SLOT_LPOCKET, ITEM_SLOT_RPOCKET, ITEM_SLOT_SUITSTORE, ITEM_SLOT_DEX_STORAGE)
	signals = list(COMSIG_MOB_CREAMED, COMSIG_ON_CARBON_SLIP, COMSIG_ON_VENDOR_CRUSH, COMSIG_MOB_CLUMSY_SHOOT_FOOT)
	proctype = .proc/EngageInComedy
	mobtype = /mob/living
	///Sounds used for when user has a sitcom action occur
	var/list/comedysounds = list('sound/items/SitcomLaugh1.ogg', 'sound/items/SitcomLaugh2.ogg', 'sound/items/SitcomLaugh3.ogg')
	///Invoked in EngageInComedy is ran
	var/datum/callback/post_comedy_callback
	///Cooldown for inbetween laughs
	var/cooldown_time = 3 SECONDS
	/// Delay before laugh starts
	var/laugh_delay = 0.4 SECONDS

	COOLDOWN_DECLARE(laugh_cooldown)

/datum/component/wearertargeting/sitcomlaughter/Initialize(post_comedy_callback, laugh_delay)
	. = ..()
	if(.) //If this is set something went wrong and we shouldn't init
		return
	src.post_comedy_callback = post_comedy_callback
	if(laugh_delay)
		src.laugh_delay = laugh_delay

///Play the laugh track if any of the signals related to comedy have been sent.
/datum/component/wearertargeting/sitcomlaughter/proc/EngageInComedy(datum/source)
	SIGNAL_HANDLER
	if(!COOLDOWN_FINISHED(src, laugh_cooldown))
		return
	addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, parent, pick(comedysounds), 100, FALSE, SHORT_RANGE_SOUND_EXTRARANGE), laugh_delay)
	post_comedy_callback?.Invoke(source)
	COOLDOWN_START(src, laugh_cooldown, cooldown_time)

repo_name: AnalWerewolf/sundown

license: mit


code:
/datum/component/sizzle
	var/mutable_appearance/sizzling
	var/sizzlealpha = 0
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS

/datum/component/sizzle/Initialize()
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	setup_sizzle()

/datum/component/sizzle/InheritComponent(datum/component/C, i_am_original)
	var/atom/food = parent
	sizzlealpha += 5
	sizzling.alpha = sizzlealpha
	food.cut_overlay(sizzling)
	food.add_overlay(sizzling)

/datum/component/sizzle/proc/setup_sizzle()
	var/atom/food = parent
	var/icon/grill_marks = icon(initial(food.icon), initial(food.icon_state)) //we only want to apply grill marks to the initial icon_state for each object
	grill_marks.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
	grill_marks.Blend(icon('icons/obj/kitchen.dmi', "grillmarks"), ICON_MULTIPLY) //adds grill marks and the remaining white areas become transparent
	sizzling = new(grill_marks)
	sizzling.alpha = sizzlealpha
	food.add_overlay(sizzling)


code:
/// Slippery component, for making anything slippery. Of course.
/datum/component/slippery
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
	/// If the slip forces you to drop held items.
	var/force_drop_items = FALSE
	/// How long the slip keeps you knocked down.
	var/knockdown_time = 0
	/// How long the slip paralyzes for.
	var/paralyze_time = 0
	/// Flags for how slippery the parent is. See [__DEFINES/mobs.dm]
	var/lube_flags
	/// A proc callback to call on slip.
	var/datum/callback/callback
	/// If parent is an item, this is the person currently holding/wearing the parent (or the parent if no one is holding it)
	var/mob/living/holder
	/// Whitelist of item slots the parent can be equipped in that make the holder slippery. If null or empty, it will always make the holder slippery.
	var/list/slot_whitelist = list(ITEM_SLOT_OCLOTHING, ITEM_SLOT_ICLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_HEAD, ITEM_SLOT_MASK, ITEM_SLOT_BELT, ITEM_SLOT_NECK)
	///what we give to connect_loc by default, makes slippable mobs moving over us slip
	var/static/list/default_connections = list(
		COMSIG_ATOM_ENTERED = .proc/Slip,
	)

	///what we give to connect_loc if we're an item and get equipped by a mob. makes slippable mobs moving over our holder slip
	var/static/list/holder_connections = list(
		COMSIG_ATOM_ENTERED = .proc/Slip_on_wearer,
	)

	/// The connect_loc_behalf component for the holder_connections list.
	var/datum/weakref/holder_connect_loc_behalf

/datum/component/slippery/Initialize(knockdown, lube_flags = NONE, datum/callback/callback, paralyze, force_drop = FALSE, slot_whitelist)
	src.knockdown_time = max(knockdown, 0)
	src.paralyze_time = max(paralyze, 0)
	src.force_drop_items = force_drop
	src.lube_flags = lube_flags
	src.callback = callback
	if(slot_whitelist)
		src.slot_whitelist = slot_whitelist

	add_connect_loc_behalf_to_parent()
	if(ismovable(parent))
		if(isitem(parent))
			RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
			RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
	else
		RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/Slip)

/datum/component/slippery/proc/add_connect_loc_behalf_to_parent()
	if(ismovable(parent))
		AddComponent(/datum/component/connect_loc_behalf, parent, default_connections)

/datum/component/slippery/InheritComponent(datum/component/slippery/component, i_am_original, knockdown, lube_flags = NONE, datum/callback/callback, paralyze, force_drop = FALSE, slot_whitelist)
	if(component)
		knockdown = component.knockdown_time
		lube_flags = component.lube_flags
		callback = component.callback
		paralyze = component.paralyze_time
		force_drop = component.force_drop_items
		slot_whitelist = component.slot_whitelist

	src.knockdown_time = max(knockdown, 0)
	src.paralyze_time = max(paralyze, 0)
	src.force_drop_items = force_drop
	src.lube_flags = lube_flags
	src.callback = callback
	if(slot_whitelist)
		src.slot_whitelist = slot_whitelist
/*
 * The proc that does the sliping. Invokes the slip callback we have set.
 *
 * source - the source of the signal
 * AM - the atom/movable that is being slipped.
 */
/datum/component/slippery/proc/Slip(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
	SIGNAL_HANDLER
	if(!isliving(arrived))
		return
	var/mob/living/victim = arrived
	if(!(victim.movement_type & FLYING) && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback)
		callback.Invoke(victim)

/*
 * Gets called when COMSIG_ITEM_EQUIPPED is sent to parent.
 * This proc register slip signals to the equipper.
 * If we have a slot whitelist, we only register the signals if the slot is valid (ex: clown PDA only slips in ID or belt slot).
 *
 * source - the source of the signal
 * equipper - the mob we're equipping the slippery thing to
 * slot - the slot we're equipping the slippery thing to on the equipper.
 */
/datum/component/slippery/proc/on_equip(datum/source, mob/equipper, slot)
	SIGNAL_HANDLER

	if((!LAZYLEN(slot_whitelist) || (slot in slot_whitelist)) && isliving(equipper))
		holder = equipper
		qdel(GetComponent(/datum/component/connect_loc_behalf))
		AddComponent(/datum/component/connect_loc_behalf, holder, holder_connections)
		RegisterSignal(holder, COMSIG_PARENT_PREQDELETED, .proc/holder_deleted)

/*
 * Detects if the holder mob is deleted.
 * If our holder mob is the holder set in this component, we null it.
 *
 * source - the source of the signal
 * possible_holder - the mob being deleted.
 */
/datum/component/slippery/proc/holder_deleted(datum/source, datum/possible_holder)
	SIGNAL_HANDLER

	if(possible_holder == holder)
		holder = null

/*
 * Gets called when COMSIG_ITEM_DROPPED is sent to parent.
 * Makes our holder mob un-slippery.
 *
 * source - the source of the signal
 * user - the mob that was formerly wearing our slippery item.
 */
/datum/component/slippery/proc/on_drop(datum/source, mob/user)
	SIGNAL_HANDLER

	UnregisterSignal(user, COMSIG_PARENT_PREQDELETED)

	qdel(GetComponent(/datum/component/connect_loc_behalf))
	add_connect_loc_behalf_to_parent()

	holder = null

/*
 * The slip proc, but for equipped items.
 * Slips the person who crossed us if we're lying down and unbuckled.
 *
 * source - the source of the signal
 * AM - the atom/movable that slipped on us.
 */
/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
	SIGNAL_HANDLER

	if(holder.body_position == LYING_DOWN && !holder.buckled)
		Slip(source, arrived)

/datum/component/slippery/UnregisterFromParent()
	. = ..()
	qdel(GetComponent(/datum/component/connect_loc_behalf))

/// Used for making the clown PDA only slip if the clown is wearing his shoes and the elusive banana-skin belt
/datum/component/slippery/clowning

/datum/component/slippery/clowning/Slip_on_wearer(datum/source, atom/movable/AM)
	var/obj/item/I = holder.get_item_by_slot(ITEM_SLOT_FEET)
	if(holder.body_position == LYING_DOWN && !holder.buckled)
		if(istype(I, /obj/item/clothing/shoes/clown_shoes))
			Slip(source, AM)
		else
			to_chat(holder,span_warning("[parent] failed to slip anyone. Perhaps I shouldn't have abandoned my legacy..."))


code:
//adds godmode while in the container, prevents moving, and clears these effects up after leaving the stone
/datum/component/soulstoned
	var/atom/movable/container

/datum/component/soulstoned/Initialize(atom/movable/container)
	if(!isanimal(parent))
		return COMPONENT_INCOMPATIBLE
	var/mob/living/simple_animal/S = parent

	src.container = container

	S.forceMove(container)
	S.fully_heal()
	ADD_TRAIT(S, TRAIT_IMMOBILIZED, SOULSTONE_TRAIT)
	ADD_TRAIT(S, TRAIT_HANDS_BLOCKED, SOULSTONE_TRAIT)
	S.status_flags |= GODMODE

	RegisterSignal(S, COMSIG_MOVABLE_MOVED, .proc/free_prisoner)

/datum/component/soulstoned/proc/free_prisoner()
	SIGNAL_HANDLER

	var/mob/living/simple_animal/S = parent
	if(S.loc != container)
		qdel(src)

/datum/component/soulstoned/UnregisterFromParent()
	var/mob/living/simple_animal/S = parent
	S.status_flags &= ~GODMODE
	REMOVE_TRAIT(S, TRAIT_IMMOBILIZED, SOULSTONE_TRAIT)
	REMOVE_TRAIT(S, TRAIT_HANDS_BLOCKED, SOULSTONE_TRAIT)


code:
/*This is the sound_player component. It can be attached to any datum and register any signal to play the sound(s) you want, when you want. Used for the honk virus as an example
	Usage :
		target.AddComponent(/datum/component/sound_player, args)
	Arguments :
		custom_volume : Used to define a custom volume. Default : 30
		custom_sounds : Used to define a list of custom sounds that will be picked at random when play_sound() is triggered. Default : list('sound/items/bikehorn.ogg')
		amount : Used to define an amount of time the component will work before deleting itself. Default : -1
		signal_or_sig_list : Used to register the signal(s) you want to play the sound when they are sent. Default : None
*/
/datum/component/sound_player
	var/volume = 30
	var/list/sounds = list('sound/items/bikehorn.ogg')
	var/amount_left = -1

/datum/component/sound_player/Initialize(custom_volume, custom_sounds, amount, signal_or_sig_list)
	if(!isnull(custom_volume))
		volume = custom_volume

	if(!isnull(custom_sounds))
		sounds = custom_sounds

	if(!isnull(amount))
		amount_left = amount

	RegisterSignal(parent, signal_or_sig_list, .proc/play_sound) //Registers all the signals in signal_or_sig_list.

/*play_sound() os the proc that actually plays the sound.
	If amount_left is equal to -1, the component is infinite and will never delete itself.
*/
/datum/component/sound_player/proc/play_sound()
	SIGNAL_HANDLER
	playsound(parent, pick_weight(sounds), volume, TRUE)
	switch(amount_left)
		if(-1)
			return
		if(1) //Last use.
			qdel(src)
			return
		else
			amount_left --


code:
/datum/component/spawner
	var/mob_types = list(/mob/living/simple_animal/hostile/carp)
	var/spawn_time = 300 //30 seconds default
	var/list/spawned_mobs = list()
	var/spawn_delay = 0
	var/max_mobs = 5
	var/spawn_text = "emerges from"
	var/list/faction = list("mining")

/datum/component/spawner/Initialize(_mob_types, _spawn_time, _faction, _spawn_text, _max_mobs)
	if(_spawn_time)
		spawn_time=_spawn_time
	if(_mob_types)
		mob_types=_mob_types
	if(_faction)
		faction=_faction
	if(_spawn_text)
		spawn_text=_spawn_text
	if(_max_mobs)
		max_mobs=_max_mobs

	RegisterSignal(parent, list(COMSIG_PARENT_QDELETING), .proc/stop_spawning)
	START_PROCESSING(SSprocessing, src)

/datum/component/spawner/process()
	try_spawn_mob()

/datum/component/spawner/proc/stop_spawning(force)
	SIGNAL_HANDLER

	STOP_PROCESSING(SSprocessing, src)
	for(var/mob/living/simple_animal/L in spawned_mobs)
		if(L.nest == src)
			L.nest = null
	spawned_mobs = null

/datum/component/spawner/proc/try_spawn_mob()
	var/atom/P = parent
	if(spawned_mobs.len >= max_mobs)
		return
	if(spawn_delay > world.time)
		return
	spawn_delay = world.time + spawn_time
	var/chosen_mob_type = pick(mob_types)
	var/mob/living/simple_animal/L = new chosen_mob_type(P.loc)
	L.flags_1 |= (P.flags_1 & ADMIN_SPAWNED_1)
	spawned_mobs += L
	L.nest = src
	L.faction = src.faction
	P.visible_message(span_danger("[L] [spawn_text] [P]."))


code:
// This component is for forcing strange things into your pocket that fall out if you fall down
// Yes this exists purely for the spaghetti meme

/datum/component/spill
	can_transfer = TRUE
	var/preexisting_slot_flags

	var/list/droptext
	var/list/dropsound
	var/drop_memory

// droptext is an arglist for visible_message
// dropsound is a list of potential sounds that gets picked from
/datum/component/spill/Initialize(list/_droptext, list/_dropsound, _drop_memory)
	if(!isitem(parent))
		return COMPONENT_INCOMPATIBLE

	if(_droptext && !islist(_droptext))
		_droptext = list(_droptext)
	droptext = _droptext

	if(_dropsound && !islist(_dropsound))
		_dropsound = list(_dropsound)

	dropsound = _dropsound
	drop_memory = _drop_memory

/datum/component/spill/PostTransfer()
	if(!isitem(parent))
		return COMPONENT_INCOMPATIBLE

/datum/component/spill/RegisterWithParent()
	RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react)
	RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react)
	var/obj/item/master = parent
	preexisting_slot_flags = master.slot_flags
	master.slot_flags |= ITEM_SLOT_POCKETS

/datum/component/spill/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
	var/obj/item/master = parent
	if(!(preexisting_slot_flags & ITEM_SLOT_POCKETS))
		master.slot_flags &= ~ITEM_SLOT_POCKETS

/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot)
	SIGNAL_HANDLER

	if(slot == ITEM_SLOT_LPOCKET || slot == ITEM_SLOT_RPOCKET)
		RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE)
	else
		UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN)

/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper)
	SIGNAL_HANDLER

	UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN)

/datum/component/spill/proc/knockdown_react(mob/living/fool, amount)
	SIGNAL_HANDLER

	if(amount <= 0)
		return

	var/obj/item/master = parent
	fool.dropItemToGround(master)
	if(droptext)
		fool.visible_message(arglist(droptext))
	if(dropsound)
		playsound(master, pick(dropsound), 30)
	if(drop_memory)
		fool.mind?.add_memory(MEMORY_SPAGHETTI_SPILL, list(DETAIL_PROTAGONIST = fool), story_value = STORY_VALUE_OKAY, memory_flags = MEMORY_CHECK_BLINDNESS)


code:
/**
 * spinny.dm
 *
 * It's a component that spins things a whole bunch, like [proc/dance_rotate] but without the sleeps
*/
/datum/component/spinny
	dupe_mode = COMPONENT_DUPE_UNIQUE
	/// How many turns are left?
	var/steps_left
	/// Turns clockwise by default, or counterclockwise if the reverse argument is TRUE
	var/turn_degrees = 90

/datum/component/spinny/Initialize(steps = 12, reverse = FALSE)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	steps_left = steps
	turn_degrees = (reverse ? -90 : 90)
	START_PROCESSING(SSfastprocess, src)

/datum/component/spinny/Destroy(force, silent)
	STOP_PROCESSING(SSfastprocess, src)
	return ..()

/datum/component/spinny/process(delta_time)
	steps_left--
	var/atom/spinny_boy = parent
	if(!istype(spinny_boy) || steps_left <= 0)
		qdel(src)
		return

	// 25% chance to make 2 turns instead of 1 since the old dance_rotate wasn't strictly clockwise/counterclockwise
	spinny_boy.setDir(turn(spinny_boy.dir, turn_degrees * (prob(25) ? 2 : 1)))


code:
/**
 * spirit holding component; for items to have spirits inside of them for "advice"
 *
 * Used for the possessed blade and fantasy affixes
 */
/datum/component/spirit_holding
	///bool on if this component is currently polling for observers to inhabit the item
	var/attempting_awakening = FALSE
	///mob contained in the item.
	var/mob/living/simple_animal/shade/bound_spirit

/datum/component/spirit_holding/Initialize()
	if(!ismovable(parent)) //you may apply this to mobs, i take no responsibility for how that works out
		return COMPONENT_INCOMPATIBLE

/datum/component/spirit_holding/Destroy(force, silent)
	. = ..()
	if(bound_spirit)
		QDEL_NULL(bound_spirit)

/datum/component/spirit_holding/RegisterWithParent()
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/on_attack_self)
	RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/on_destroy)

/datum/component/spirit_holding/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_PARENT_EXAMINE, COMSIG_ITEM_ATTACK_SELF, COMSIG_PARENT_QDELETING))

///signal fired on examining the parent
/datum/component/spirit_holding/proc/on_examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER
	if(!bound_spirit)
		examine_list += span_notice("[parent] sleeps. Use [parent] in your hands to attempt to awaken it.")
		return
	examine_list += span_notice("[parent] is alive.")

///signal fired on self attacking parent
/datum/component/spirit_holding/proc/on_attack_self(datum/source, mob/user)
	SIGNAL_HANDLER
	INVOKE_ASYNC(src, .proc/attempt_spirit_awaken, user)

/**
 * attempt_spirit_awaken: called from on_attack_self, polls ghosts to possess the item in the form
 * of a mob sitting inside the item itself
 *
 * Arguments:
 * * awakener: user who interacted with the blade
 */
/datum/component/spirit_holding/proc/attempt_spirit_awaken(mob/awakener)
	if(attempting_awakening)
		to_chat(awakener, span_warning("You are already trying to awaken [parent]!"))
		return
	if(!(GLOB.ghost_role_flags & GHOSTROLE_STATION_SENTIENCE))
		to_chat(awakener, span_warning("Anomalous otherworldly energies block you from awakening [parent]!"))
		return

	attempting_awakening = TRUE
	to_chat(awakener, span_notice("You attempt to wake the spirit of [parent]..."))

	var/list/candidates = poll_ghost_candidates("Do you want to play as the spirit of [awakener.real_name]'s blade?", ROLE_PAI, FALSE, 100, POLL_IGNORE_POSSESSED_BLADE)
	if(!LAZYLEN(candidates))
		to_chat(awakener, span_warning("[parent] is dormant. Maybe you can try again later."))
		attempting_awakening = FALSE
		return

	var/mob/dead/observer/chosen_spirit = pick(candidates)
	bound_spirit = new(parent)
	bound_spirit.ckey = chosen_spirit.ckey
	bound_spirit.fully_replace_character_name(null, "The spirit of [parent]")
	bound_spirit.status_flags |= GODMODE
	bound_spirit.copy_languages(awakener, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the awakener.
	bound_spirit.update_atom_languages()
	bound_spirit.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue
	var/input = sanitize_name(stripped_input(bound_spirit, "What are you named?", ,"", MAX_NAME_LEN))
	if(parent && input)
		parent = input
		bound_spirit.fully_replace_character_name(null, "The spirit of [input]")

	//prevents awakening it again + new signals for a now-possessed item
	attempting_awakening = FALSE
	UnregisterSignal(parent, COMSIG_ITEM_ATTACK_SELF)
	RegisterSignal(parent, COMSIG_ATOM_RELAYMOVE, .proc/block_buckle_message)
	RegisterSignal(parent, COMSIG_BIBLE_SMACKED, .proc/on_bible_smacked)

///signal fired from a mob moving inside the parent
/datum/component/spirit_holding/proc/block_buckle_message(datum/source, mob/living/user, direction)
	SIGNAL_HANDLER
	return COMSIG_BLOCK_RELAYMOVE

/datum/component/spirit_holding/proc/on_bible_smacked(datum/source, mob/living/user, direction)
	SIGNAL_HANDLER
	INVOKE_ASYNC(src, .proc/attempt_exorcism, user)

/**
 * attempt_exorcism: called from on_bible_smacked, takes time and if successful
 * resets the item to a pre-possessed state
 *
 * Arguments:
 * * exorcist: user who is attempting to remove the spirit
 */
/datum/component/spirit_holding/proc/attempt_exorcism(mob/exorcist)
	var/atom/movable/exorcised_movable = parent
	to_chat(exorcist, span_notice("You begin to exorcise [parent]..."))
	playsound(parent, 'sound/hallucinations/veryfar_noise.ogg',40,TRUE)
	if(!do_after(exorcist, 4 SECONDS, target = exorcised_movable))
		return
	playsound(parent, 'sound/effects/pray_chaplain.ogg',60,TRUE)
	UnregisterSignal(exorcised_movable, list(COMSIG_ATOM_RELAYMOVE, COMSIG_BIBLE_SMACKED))
	RegisterSignal(exorcised_movable, COMSIG_ITEM_ATTACK_SELF, .proc/on_attack_self)
	to_chat(bound_spirit, span_userdanger("You were exorcised!"))
	QDEL_NULL(bound_spirit)
	exorcised_movable.name = initial(exorcised_movable.name)
	exorcist.visible_message(span_notice("[exorcist] exorcises [exorcised_movable]!"), \
						span_notice("You successfully exorcise [exorcised_movable]!"))
	return COMSIG_END_BIBLE_CHAIN

///signal fired from parent being destroyed
/datum/component/spirit_holding/proc/on_destroy(datum/source)
	SIGNAL_HANDLER
	to_chat(bound_spirit, span_userdanger("You were destroyed!"))
	QDEL_NULL(bound_spirit)


code:
///This component allows something to be when crossed, for example for cockroaches.
/datum/component/squashable
	///Chance on crossed to be squashed
	var/squash_chance = 50
	///How much brute is applied when mob is squashed
	var/squash_damage = 1
	///Squash flags, for extra checks etcetera.
	var/squash_flags = NONE
	///Special callback to call on squash instead, for things like hauberoach
	var/datum/callback/on_squash_callback
	///signal list given to connect_loc
	var/static/list/loc_connections = list(
		COMSIG_ATOM_ENTERED = .proc/on_entered,
	)

/datum/component/squashable/Initialize(squash_chance, squash_damage, squash_flags, squash_callback)
	. = ..()
	if(!isliving(parent))
		return COMPONENT_INCOMPATIBLE
	if(squash_chance)
		src.squash_chance = squash_chance
	if(squash_damage)
		src.squash_damage = squash_damage
	if(squash_flags)
		src.squash_flags = squash_flags
	if(!src.on_squash_callback && squash_callback)
		on_squash_callback = CALLBACK(parent, squash_callback)

	AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)

///Handles the squashing of the mob
/datum/component/squashable/proc/on_entered(turf/source_turf, atom/movable/crossing_movable)
	SIGNAL_HANDLER

	if(parent == crossing_movable)
		return

	var/mob/living/parent_as_living = parent

	if(squash_flags & SQUASHED_SHOULD_BE_DOWN && parent_as_living.body_position != LYING_DOWN)
		return

	var/should_squash = prob(squash_chance)

	if(should_squash && on_squash_callback)
		if(on_squash_callback.Invoke(parent_as_living, crossing_movable))
			return //Everything worked, we're done!
	if(isliving(crossing_movable))
		var/mob/living/crossing_mob = crossing_movable
		if(crossing_mob.mob_size > MOB_SIZE_SMALL && !(crossing_mob.movement_type & FLYING))
			if(HAS_TRAIT(crossing_mob, TRAIT_PACIFISM))
				crossing_mob.visible_message(span_notice("[crossing_mob] carefully steps over [parent_as_living]."), span_notice("You carefully step over [parent_as_living] to avoid hurting it."))
				return
			if(should_squash)
				crossing_mob.visible_message(span_notice("[crossing_mob] squashed [parent_as_living]."), span_notice("You squashed [parent_as_living]."))
				Squish(parent_as_living)
			else
				parent_as_living.visible_message(span_notice("[parent_as_living] avoids getting crushed."))
	else if(isstructure(crossing_movable))
		if(should_squash)
			crossing_movable.visible_message(span_notice("[parent_as_living] is crushed under [crossing_movable]."))
			Squish(parent_as_living)
		else
			parent_as_living.visible_message(span_notice("[parent_as_living] avoids getting crushed."))

/datum/component/squashable/proc/Squish(mob/living/target)
	if(squash_flags & SQUASHED_SHOULD_BE_GIBBED)
		target.gib()
	else
		target.adjustBruteLoss(squash_damage)

/datum/component/squashable/UnregisterFromParent()
	. = ..()
	qdel(GetComponent(/datum/component/connect_loc_behalf))


code:
/datum/component/squeak
	var/static/list/default_squeak_sounds = list('sound/items/toysqueak1.ogg'=1, 'sound/items/toysqueak2.ogg'=1, 'sound/items/toysqueak3.ogg'=1)
	var/list/override_squeak_sounds
	var/mob/holder

	var/squeak_chance = 100
	var/volume = 30

	// This is so shoes don't squeak every step
	var/steps = 0
	var/step_delay = 1

	// This is to stop squeak spam from inhand usage
	var/last_use = 0
	var/use_delay = 20

	///extra-range for this component's sound
	var/sound_extra_range = -1
	///when sounds start falling off for the squeak
	var/sound_falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE
	///sound exponent for squeak. Defaults to 10 as squeaking is loud and annoying enough.
	var/sound_falloff_exponent = 10

	///what we set connect_loc to if parent is an item
	var/static/list/item_connections = list(
		COMSIG_ATOM_ENTERED = .proc/play_squeak_crossed,
	)

/datum/component/squeak/Initialize(custom_sounds, volume_override, chance_override, step_delay_override, use_delay_override, extrarange, falloff_exponent, fallof_distance)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	RegisterSignal(parent, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_PARENT_ATTACKBY), .proc/play_squeak)
	if(ismovable(parent))
		RegisterSignal(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_IMPACT, COMSIG_PROJECTILE_BEFORE_FIRE), .proc/play_squeak)

		AddComponent(/datum/component/connect_loc_behalf, parent, item_connections)
		RegisterSignal(parent, COMSIG_MOVABLE_DISPOSING, .proc/disposing_react)
		if(isitem(parent))
			RegisterSignal(parent, list(COMSIG_ITEM_ATTACK, COMSIG_ITEM_ATTACK_OBJ, COMSIG_ITEM_HIT_REACT), .proc/play_squeak)
			RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/use_squeak)
			RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
			RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
			if(istype(parent, /obj/item/clothing/shoes))
				RegisterSignal(parent, COMSIG_SHOES_STEP_ACTION, .proc/step_squeak)
		else if(isstructure(parent))
			RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/use_squeak)

	if(istype(parent, /obj/item/organ/liver))
		// Liver squeaking is depending on them functioning like a clown's liver
		RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_COMEDY_METABOLISM), .proc/on_comedy_metabolism_removal)

	override_squeak_sounds = custom_sounds
	if(chance_override)
		squeak_chance = chance_override
	if(volume_override)
		volume = volume_override
	if(isnum(step_delay_override))
		step_delay = step_delay_override
	if(isnum(use_delay_override))
		use_delay = use_delay_override
	if(isnum(extrarange))
		sound_extra_range = extrarange
	if(isnum(falloff_exponent))
		sound_falloff_exponent = falloff_exponent
	if(isnum(fallof_distance))
		sound_falloff_distance = fallof_distance

/datum/component/squeak/UnregisterFromParent()
	. = ..()
	qdel(GetComponent(/datum/component/connect_loc_behalf))

/datum/component/squeak/proc/play_squeak()
	SIGNAL_HANDLER

	if(prob(squeak_chance))
		if(!override_squeak_sounds)
			playsound(parent, pick_weight(default_squeak_sounds), volume, TRUE, sound_extra_range, sound_falloff_exponent, falloff_distance = sound_falloff_distance)
		else
			playsound(parent, pick_weight(override_squeak_sounds), volume, TRUE, sound_extra_range, sound_falloff_exponent, falloff_distance = sound_falloff_distance)

/datum/component/squeak/proc/step_squeak()
	SIGNAL_HANDLER

	if(steps > step_delay)
		play_squeak()
		steps = 0
	else
		steps++

/datum/component/squeak/proc/play_squeak_crossed(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
	SIGNAL_HANDLER

	if(isitem(arrived))
		var/obj/item/I = arrived
		if(I.item_flags & ABSTRACT)
			return
	if(arrived.movement_type & (FLYING|FLOATING) || !arrived.has_gravity())
		return
	var/atom/current_parent = parent
	if(isturf(current_parent?.loc))
		play_squeak()

/datum/component/squeak/proc/use_squeak()
	SIGNAL_HANDLER

	if(last_use + use_delay < world.time)
		last_use = world.time
		play_squeak()

/datum/component/squeak/proc/on_equip(datum/source, mob/equipper, slot)
	SIGNAL_HANDLER
	holder = equipper
	RegisterSignal(holder, COMSIG_MOVABLE_DISPOSING, .proc/disposing_react, override=TRUE)
	RegisterSignal(holder, COMSIG_PARENT_PREQDELETED, .proc/holder_deleted, override=TRUE)
	//override for the preqdeleted is necessary because putting parent in hands sends the signal that this proc is registered towards,
	//so putting an object in hands and then equipping the item on a clothing slot (without dropping it first)
	//will always runtime without override = TRUE

/datum/component/squeak/proc/on_drop(datum/source, mob/user)
	SIGNAL_HANDLER
	UnregisterSignal(user, COMSIG_MOVABLE_DISPOSING)
	UnregisterSignal(user, COMSIG_PARENT_PREQDELETED)
	holder = null

///just gets rid of the reference to holder in the case that theyre qdeleted
/datum/component/squeak/proc/holder_deleted(datum/source, datum/possible_holder)
	SIGNAL_HANDLER
	if(possible_holder == holder)
		holder = null

// Disposal pipes related shits
/datum/component/squeak/proc/disposing_react(datum/source, obj/structure/disposalholder/holder, obj/machinery/disposal/source)
	SIGNAL_HANDLER

	//We don't need to worry about unregistering this signal as it will happen for us automaticaly when the holder is qdeleted
	RegisterSignal(holder, COMSIG_ATOM_DIR_CHANGE, .proc/holder_dir_change)

/datum/component/squeak/proc/holder_dir_change(datum/source, old_dir, new_dir)
	SIGNAL_HANDLER

	//If the dir changes it means we're going through a bend in the pipes, let's pretend we bumped the wall
	if(old_dir != new_dir)
		play_squeak()

/datum/component/squeak/proc/on_comedy_metabolism_removal(datum/source, trait)
	SIGNAL_HANDLER

	qdel(src)


code:
/// Teleports the movable atom back to a safe turf on the station if it leaves the z-level or becomes inaccessible.
/datum/component/stationloving
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
	/// If TRUE, notifies admins when parent is teleported back to the station.
	var/inform_admins = FALSE
	var/disallow_soul_imbue = TRUE
	/// If FALSE, prevents parent from being qdel'd unless it's a force = TRUE qdel.
	var/allow_item_destruction = FALSE

/datum/component/stationloving/Initialize(inform_admins = FALSE, allow_item_destruction = FALSE)
	if(!ismovable(parent))
		return COMPONENT_INCOMPATIBLE
	RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, .proc/on_parent_z_change)
	RegisterSignal(parent, COMSIG_MOVABLE_SECLUDED_LOCATION, .proc/on_parent_unreachable)
	RegisterSignal(parent, COMSIG_PARENT_PREQDELETED, .proc/on_parent_pre_qdeleted)
	RegisterSignal(parent, COMSIG_ITEM_IMBUE_SOUL, .proc/check_soul_imbue)
	RegisterSignal(parent, COMSIG_ITEM_MARK_RETRIEVAL, .proc/check_mark_retrieval)
	src.inform_admins = inform_admins
	src.allow_item_destruction = allow_item_destruction

	// Just in case something is being created outside of station/centcom
	if(!atom_in_bounds(parent))
		relocate()

/datum/component/stationloving/InheritComponent(datum/component/stationloving/newc, original, inform_admins, allow_death)
	if (original)
		if (newc)
			inform_admins = newc.inform_admins
			allow_death = newc.allow_item_destruction
		else
			inform_admins = inform_admins

/// Teleports parent to a safe turf on the station z-level.
/datum/component/stationloving/proc/relocate()
	var/target_turf = find_safe_turf()

	if(!target_turf)
		if(GLOB.blobstart.len > 0)
			target_turf = get_turf(pick(GLOB.blobstart))
		else
			CRASH("Unable to find a blobstart landmark")

	var/atom/movable/movable_parent = parent
	playsound(movable_parent, 'sound/machines/synth_no.ogg', 5, TRUE)
	movable_parent.forceMove(target_turf)
	to_chat(get(parent, /mob), span_danger("You can't help but feel that you just lost something back there..."))

	return target_turf

/// Signal handler when the parent has changed z-levels.
/// Checks to make sure it's a valid destination, if it's not then it relacates the parent instead.
/datum/component/stationloving/proc/on_parent_z_change(datum/source, turf/old_turf, turf/new_turf)
	SIGNAL_HANDLER

	if(atom_in_bounds(parent))
		return

	var/turf/current_turf = get_turf(parent)
	var/turf/new_destination = relocate()
	log_game("[parent] attempted to be moved out of bounds from [loc_name(old_turf)] to [loc_name(current_turf)]. Moving it to [loc_name(new_destination)].")
	if(inform_admins)
		message_admins("[parent] attempted to be moved out of bounds from [ADMIN_VERBOSEJMP(old_turf)] to [ADMIN_VERBOSEJMP(current_turf)]. Moving it to [ADMIN_VERBOSEJMP(new_destination)].")

	return COMPONENT_MOVABLE_BLOCK_PRE_MOVE

/datum/component/stationloving/proc/check_soul_imbue()
	SIGNAL_HANDLER

	return disallow_soul_imbue

/datum/component/stationloving/proc/check_mark_retrieval()
	SIGNAL_HANDLER

	return COMPONENT_BLOCK_MARK_RETRIEVAL

/// Checks whether a given atom's turf is within bounds. Returns TRUE if it is, FALSE if it isn't.
/datum/component/stationloving/proc/atom_in_bounds(atom/atom_to_check)
	var/static/list/allowed_shuttles = typecacheof(list(/area/shuttle/syndicate, /area/shuttle/escape, /area/shuttle/pod_1, /area/shuttle/pod_2, /area/shuttle/pod_3, /area/shuttle/pod_4))
	var/static/list/disallowed_centcom_areas = typecacheof(list(/area/abductor_ship, /area/awaymission/errorroom))
	var/turf/destination_turf = get_turf(atom_to_check)
	if (!destination_turf)
		return FALSE
	var/area/destination_area = destination_turf.loc
	if (is_station_level(destination_turf.z))
		return TRUE
	if (is_centcom_level(destination_turf.z))
		if (is_type_in_typecache(destination_area, disallowed_centcom_areas))
			return FALSE
		return TRUE
	if (is_reserved_level(destination_turf.z))
		if (is_type_in_typecache(destination_area, allowed_shuttles))
			return TRUE

	return FALSE

/// Signal handler for before the parent is qdel'd. Can prevent the parent from being deleted where allow_item_destruction is FALSE and force is FALSE.
/datum/component/stationloving/proc/on_parent_pre_qdeleted(datum/source, force)
	SIGNAL_HANDLER

	var/turf/current_turf = get_turf(parent)

	if(inform_admins && force)
		message_admins("[parent] has been !!force deleted!! in [ADMIN_VERBOSEJMP(current_turf)].")
		log_game("[parent] has been !!force deleted!! in [loc_name(current_turf)].")

	if(force || allow_item_destruction)
		UnregisterSignal(parent, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_SECLUDED_LOCATION))
		return FALSE

	var/turf/new_turf = relocate()
	log_game("[parent] has been destroyed in [loc_name(current_turf)]. Preventing destruction and moving it to [loc_name(new_turf)].")
	if(inform_admins)
		message_admins("[parent] has been destroyed in [ADMIN_VERBOSEJMP(current_turf)]. Preventing destruction and moving it to [ADMIN_VERBOSEJMP(new_turf)].")
	return TRUE

/// Signal handler for when the parent enters an unreachable location. Always relocates the parent.
/datum/component/stationloving/proc/on_parent_unreachable()
	SIGNAL_HANDLER

	var/turf/current_turf = get_turf(parent)
	var/turf/new_turf = relocate()
	log_game("[parent] has been moved to unreachable location in [loc_name(current_turf)]. Moving it to [loc_name(new_turf)].")
	if(inform_admins)
		message_admins("[parent] has been moved to unreachable location in [ADMIN_VERBOSEJMP(current_turf)]. Moving it to [ADMIN_VERBOSEJMP(new_turf)].")

	return COMPONENT_MOVABLE_BLOCK_PRE_MOVE


code:
#define PUNISHMENT_MURDER "murder"
#define PUNISHMENT_GIB "gib"
#define PUNISHMENT_TELEPORT "teleport"

//very similar to stationloving, but more made for mobs and not objects. used on derelict drones currently

/*
This component is similar to stationloving in that it is meant to keep something on the z-level
The difference is that stationloving is for objects and stationstuck is for mobs.
It has a punishment variable that is what happens to the parent when they leave the z-level. See punish() documentation
*/
/datum/component/stationstuck
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
	var/punishment = PUNISHMENT_GIB //see defines above
	var/stuck_zlevel
	var/message = ""

/datum/component/stationstuck/Initialize(_punishment = PUNISHMENT_GIB, _message = "")
	if(!isliving(parent))
		return COMPONENT_INCOMPATIBLE
	var/mob/living/L = parent
	RegisterSignal(L, list(COMSIG_MOVABLE_Z_CHANGED), .proc/punish)
	punishment = _punishment
	message = _message
	stuck_zlevel = L.z

/datum/component/stationstuck/InheritComponent(datum/component/stationstuck/newc, original, _punishment, _message)
	if(newc)
		punishment = newc.punishment
		message = newc.message
	else
		punishment = _punishment
		message = _message

/**
 * Called when parent leaves the zlevel this is set to (aka whichever zlevel it was on when it was added)
 * Sends a message, then does an effect depending on what the punishment was.
 *
 * Punishments:
 * * PUNISHMENT_MURDER: kills parent
 * * PUNISHMENT_GIB: gibs parent
 * * PUNISHMENT_TELEPORT:  finds a safe turf if possible, or a completely random one if not.
 */
/datum/component/stationstuck/proc/punish()
	SIGNAL_HANDLER

	var/mob/living/L = parent
	if(message)
		var/span = punishment == PUNISHMENT_TELEPORT ? "danger" : "userdanger"
		to_chat(L, "[message]")
	switch(punishment)
		if(PUNISHMENT_MURDER)
			L.death()
		if(PUNISHMENT_GIB)
			L.gib()
		if(PUNISHMENT_TELEPORT)
			var/targetturf = find_safe_turf(stuck_zlevel)
			if(!targetturf)
				targetturf = locate(world.maxx/2,world.maxy/2,stuck_zlevel)
			L.forceMove(targetturf)


code:
// External storage-related logic:
// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages
// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement

/datum/component/storage/concrete
	can_transfer = TRUE
	var/drop_all_on_deconstruct = TRUE
	var/drop_all_on_destroy = FALSE
	var/transfer_contents_on_component_transfer = FALSE
	var/list/datum/component/storage/slaves = list()

	var/list/_contents_limbo // Where objects go to live mid transfer
	var/list/_user_limbo // The last users before the component started moving

/datum/component/storage/concrete/Initialize()
	. = ..()
	RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del)
	RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct)

/datum/component/storage/concrete/Destroy()
	var/atom/real_location = real_location()
	for(var/atom/_A in real_location)
		_A.mouse_opacity = initial(_A.mouse_opacity)
	if(drop_all_on_destroy)
		do_quick_empty()
	for(var/i in slaves)
		var/datum/component/storage/slave = i
		slave.change_master(null)
	QDEL_LIST(_contents_limbo)
	_user_limbo = null
	return ..()

/datum/component/storage/concrete/master()
	return src

/datum/component/storage/concrete/real_location()
	return parent

/datum/component/storage/concrete/PreTransfer()
	if(is_using)
		_user_limbo = is_using.Copy()
		close_all()
	if(transfer_contents_on_component_transfer)
		_contents_limbo = list()
		for(var/atom/movable/AM in parent)
			_contents_limbo += AM
			AM.moveToNullspace()

/datum/component/storage/concrete/PostTransfer()
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	if(transfer_contents_on_component_transfer)
		for(var/i in _contents_limbo)
			var/atom/movable/AM = i
			AM.forceMove(parent)
		_contents_limbo = null
	if(_user_limbo)
		for(var/i in _user_limbo)
			show_to(i)
		_user_limbo = null

/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE)
	. = TRUE
	var/atom/real_location = real_location()
	if(I.loc != real_location)
		I.forceMove(real_location)
	refresh_mob_views()

/datum/component/storage/concrete/refresh_mob_views()
	. = ..()
	for(var/i in slaves)
		var/datum/component/storage/slave = i
		slave.refresh_mob_views()

/datum/component/storage/concrete/emp_act(datum/source, severity)
	if(emp_shielded)
		return
	var/atom/real_location = real_location()
	for(var/i in real_location)
		var/atom/A = i
		A.emp_act(severity)

/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S)
	if(S == src)
		return FALSE
	slaves += S
	return TRUE

/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S)
	slaves -= S
	return FALSE

/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A)
	SIGNAL_HANDLER
	var/atom/real_location = parent
	if(A in real_location)
		usr = null
		remove_from_storage(A, null)

/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled)
	SIGNAL_HANDLER
	if(drop_all_on_deconstruct)
		do_quick_empty()

/datum/component/storage/concrete/can_see_contents()
	. = ..()
	for(var/i in slaves)
		var/datum/component/storage/slave = i
		. |= slave.can_see_contents()

//Resets screen loc and other vars of something being removed from storage.
/datum/component/storage/concrete/_removal_reset(atom/movable/thing)
	thing.layer = initial(thing.layer)
	thing.plane = initial(thing.plane)
	thing.mouse_opacity = initial(thing.mouse_opacity)
	if(thing.maptext)
		thing.maptext = ""

/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location)
	//Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped
	//or moving objects, things that should never happen
	var/atom/parent = src.parent
	var/list/seeing_mobs = can_see_contents()
	for(var/mob/M in seeing_mobs)
		M.client.screen -= AM
	if(isitem(AM))
		var/obj/item/removed_item = AM
		removed_item.item_flags &= ~IN_STORAGE
		if(ismob(parent.loc))
			var/mob/carrying_mob = parent.loc
			removed_item.dropped(carrying_mob, TRUE)
	if(new_location)
		//Reset the items values
		_removal_reset(AM)
		AM.forceMove(new_location)
		//We don't want to call this if the item is being destroyed
		AM.on_exit_storage(src)
	else
		//Being destroyed, just move to nullspace now (so it's not in contents for the icon update)
		AM.moveToNullspace()
	refresh_mob_views()
	if(isobj(parent))
		var/obj/O = parent
		O.update_appearance()
	return TRUE

/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M)
	return TRUE

/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M)
	. = handle_item_insertion(I, prevent_warning, M, slave)
	if(. && !prevent_warning)
		slave.mob_item_insertion_feedback(usr, M, I)

/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum
	var/datum/component/storage/concrete/master = master()
	var/atom/parent = src.parent
	var/moved = FALSE
	if(!istype(I))
		return FALSE
	if(M)
		//SEPTIC EDIT BEGIN
		if(!worn_check(parent, M))
			return FALSE
		//SEPTIC EDIT END
		if(!M.temporarilyRemoveItemFromInventory(I))
			return FALSE
		else
			moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever.
	if(I.pulledby)
		I.pulledby.stop_pulling()
	if(silent)
		prevent_warning = TRUE
	if(!_insert_physical_item(I))
		if(moved)
			if(M)
				if(!M.put_in_active_hand(I))
					I.forceMove(parent.drop_location())
			else
				I.forceMove(parent.drop_location())
		return FALSE
	I.on_enter_storage(master)
	I.item_flags |= IN_STORAGE
	refresh_mob_views()
	I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt
	if(M)
		if(M.client && M.active_storage != src)
			M.client.screen -= I
		if(M.observers && M.observers.len)
			for(var/i in M.observers)
				var/mob/dead/observe = i
				if(observe.client && observe.active_storage != src)
					observe.client.screen -= I
		if(!remote)
			parent.add_fingerprint(M)
			if(!prevent_warning)
				mob_item_insertion_feedback(usr, M, I)
	update_icon()
	return TRUE

/datum/component/storage/concrete/update_icon()
	if(isobj(parent))
		var/obj/O = parent
		O.update_appearance()
	for(var/i in slaves)
		var/datum/component/storage/slave = i
		slave.update_icon()


code:
/datum/component/storage/concrete/bluespace/bag_of_holding/handle_item_insertion(obj/item/W, prevent_warning = FALSE, mob/living/user)
	var/atom/A = parent
	if(A == W) //don't put yourself into yourself.
		return
	var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.get_all_contents(), typecacheof(/obj/item/storage/backpack/holding))
	matching -= A
	if(istype(W, /obj/item/storage/backpack/holding) || matching.len)
		INVOKE_ASYNC(src, .proc/recursive_insertion, W, user)
		return
	. = ..()

/datum/component/storage/concrete/bluespace/bag_of_holding/proc/recursive_insertion(obj/item/W, mob/living/user)
	var/atom/A = parent
	var/safety = tgui_alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", list("Proceed", "Abort"))
	if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user)))
		return
	var/turf/loccheck = get_turf(A)
	to_chat(user, span_danger("The Bluespace interfaces of the two devices catastrophically malfunction!"))
	qdel(W)
	playsound(loccheck,'sound/effects/supermatter.ogg', 200, TRUE)

	message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].")
	log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].")

	user.gib(TRUE, TRUE, TRUE)
	new/obj/boh_tear(loccheck)
	qdel(A)


code:
/datum/component/storage/concrete/bluespace
	var/dumping_range = 8
	var/dumping_sound = 'sound/items/pshoom.ogg'
	var/alt_sound = 'sound/items/pshoom_2.ogg'

/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M)
	var/atom/A = parent
	if(A.Adjacent(M))
		var/atom/dumping_location = dest.get_dumping_location()
		var/turf/bagT = get_turf(parent)
		var/turf/destT = get_turf(dumping_location)
		if(destT && bagT && bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range)
			if(dumping_location.storage_contents_dump_act(src, M))
				if(alt_sound && prob(1))
					playsound(src, alt_sound, 40, TRUE)
				else
					playsound(src, dumping_sound, 40, TRUE)
				M.Beam(dumping_location, icon_state="rped_upgrade", time=5)
				return TRUE
		to_chat(M, span_hear("The [A.name] buzzes."))
		playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
	return FALSE


code:
/datum/component/storage/concrete/extract_inventory
	max_combined_w_class = WEIGHT_CLASS_TINY * 3
	max_items = 3
	insert_preposition = "in"
//These need to be false in order for the extract's food to be unextractable
//from the inventory
	attack_hand_interact = FALSE
	quickdraw = FALSE
	can_transfer = FALSE
	drop_all_on_deconstruct = FALSE
	locked = TRUE //True in order to prevent messing with the inventory in any way other than the specified ways on reproductive.dm
	rustle_sound = FALSE
	silent = TRUE
	var/obj/item/slimecross/reproductive/parentSlimeExtract

/datum/component/storage/concrete/extract_inventory/Initialize()
	. = ..()
	set_holdable(/obj/item/food/monkeycube)
	if(!istype(parent, /obj/item/slimecross/reproductive))
		return COMPONENT_INCOMPATIBLE
	parentSlimeExtract = parent

/datum/component/storage/concrete/extract_inventory/proc/processCubes(obj/item/slimecross/reproductive/parentSlimeExtract, mob/user)

	if(length(parentSlimeExtract.contents) >= max_items)
		QDEL_LIST(parentSlimeExtract.contents)
		createExtracts(parentSlimeExtract,user)

/datum/component/storage/concrete/extract_inventory/proc/createExtracts(obj/item/slimecross/reproductive/parentSlimeExtract, mob/user)
	var/cores = rand(1,4)
	playsound(parentSlimeExtract, 'sound/effects/splat.ogg', 40, TRUE)
	parentSlimeExtract.last_produce = world.time
	to_chat(user, span_notice("[parentSlimeExtract] briefly swells to a massive size, and expels [cores] extract[cores > 1 ? "s":""]!"))
	for(var/i in 1 to cores)
		new parentSlimeExtract.extract_type(parentSlimeExtract.drop_location())


code:
/datum/component/storage/concrete/fish_case
	max_items = 1
	can_hold_trait = TRAIT_FISH_CASE_COMPATIBILE
	can_hold_description = "fish and aquarium equipment"

/datum/component/storage/concrete/fish_case/can_be_inserted(obj/item/I, stop_messages, mob/M)
	/// Activate deferred components if any.
	SEND_SIGNAL(I, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK)
	. = ..()


code:
/datum/component/storage/concrete/implant
	max_w_class = WEIGHT_CLASS_NORMAL
	max_combined_w_class = 6
	max_items = 2
	drop_all_on_destroy = TRUE
	drop_all_on_deconstruct = TRUE
	silent = TRUE
	allow_big_nesting = TRUE

/datum/component/storage/concrete/implant/Initialize()
	. = ..()
	set_holdable(null, list(/obj/item/disk/nuclear))

/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original)
	if(!istype(I))
		return ..()
	max_combined_w_class += I.max_combined_w_class
	max_items += I.max_items


code:
/datum/component/storage/concrete/pockets
	max_items = 2
	max_w_class = WEIGHT_CLASS_SMALL
	max_combined_w_class = 50
	rustle_sound = FALSE

/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user)
	. = ..()
	if(. && silent && !prevent_warning)
		if(quickdraw)
			to_chat(user, span_notice("You discreetly slip [I] into [parent]. Right-click [parent] to remove it."))
		else
			to_chat(user, span_notice("You discreetly slip [I] into [parent]."))

/datum/component/storage/concrete/pockets/small
	max_items = 1
	max_w_class = WEIGHT_CLASS_SMALL
	attack_hand_interact = FALSE

/datum/component/storage/concrete/pockets/tiny
	max_items = 1
	max_w_class = WEIGHT_CLASS_TINY
	attack_hand_interact = FALSE

/datum/component/storage/concrete/pockets/small/fedora/Initialize()
	. = ..()
	var/static/list/exception_cache = typecacheof(list(
		/obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana,
		/obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun
		))
	exception_hold = exception_cache

/datum/component/storage/concrete/pockets/small/fedora/detective
	attack_hand_interact = TRUE // so the detectives would discover pockets in their hats

/datum/component/storage/concrete/pockets/chefhat
	attack_hand_interact = TRUE
	max_items = 1
	max_w_class = WEIGHT_CLASS_NORMAL

/datum/component/storage/concrete/pockets/chefhat/Initialize()
	. = ..()
	set_holdable(list(
		/obj/item/clothing/head/mob_holder,
		/obj/item/food/deadmouse
	))

/datum/component/storage/concrete/pockets/chefhat/can_be_inserted(obj/item/I, stop_messages, mob/M)
	. = ..()
	if(istype(I,/obj/item/clothing/head/mob_holder))
		var/obj/item/clothing/head/mob_holder/mausholder = I
		if(locate(/mob/living/simple_animal/mouse) in mausholder.contents)
			return
		return FALSE

/datum/component/storage/concrete/pockets/shoes
	attack_hand_interact = FALSE
	quickdraw = TRUE
	silent = TRUE

/datum/component/storage/concrete/pockets/shoes/Initialize()
	. = ..()
	set_holdable(list(
		/obj/item/knife, /obj/item/switchblade, /obj/item/pen,
		/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
		/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
		/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
		/obj/item/firing_pin
		),
		list(/obj/item/screwdriver/power)
		)

/datum/component/storage/concrete/pockets/shoes/clown/Initialize()
	. = ..()
	set_holdable(list(
		/obj/item/knife, /obj/item/switchblade, /obj/item/pen,
		/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
		/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
		/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
		/obj/item/firing_pin, /obj/item/bikehorn),
		list(/obj/item/screwdriver/power)
		)

/datum/component/storage/concrete/pockets/pocketprotector
	max_items = 3
	max_w_class = WEIGHT_CLASS_TINY
	var/atom/original_parent

/datum/component/storage/concrete/pockets/pocketprotector/Initialize()
	original_parent = parent
	. = ..()
	set_holdable(list( //Same items as a PDA
		/obj/item/pen,
		/obj/item/toy/crayon,
		/obj/item/lipstick,
		/obj/item/flashlight/pen,
		/obj/item/clothing/mask/cigarette)
		)

/datum/component/storage/concrete/pockets/pocketprotector/real_location()
	// if the component is reparented to a jumpsuit, the items still go in the protector
	return original_parent

/datum/component/storage/concrete/pockets/helmet
	quickdraw = TRUE
	max_combined_w_class = 6

/datum/component/storage/concrete/pockets/helmet/Initialize()
	. = ..()
	set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka,
					  /obj/item/reagent_containers/food/drinks/bottle/molotov,
					  /obj/item/reagent_containers/food/drinks/drinkingglass,
					  /obj/item/ammo_box/a762))

/datum/component/storage/concrete/pockets/void_cloak
	quickdraw = TRUE
	max_items = 3

/datum/component/storage/concrete/pockets/void_cloak/Initialize()
	. = ..()
	var/static/list/exception_cache = typecacheof(list(/obj/item/living_heart,/obj/item/forbidden_book))
	exception_hold = exception_cache


code:
/datum/component/storage/concrete/rped
	collection_mode = COLLECT_EVERYTHING
	allow_quick_gather = TRUE
	allow_quick_empty = TRUE
	click_gather = TRUE
	max_w_class = WEIGHT_CLASS_NORMAL
	max_combined_w_class = 100
	max_items = 50
	display_numerical_stacking = TRUE

/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M)
	. = ..()
	if(!I.get_part_rating())
		if (!stop_messages)
			to_chat(M, span_warning("[parent] only accepts machine parts!"))
		return FALSE

/datum/component/storage/concrete/bluespace/rped
	collection_mode = COLLECT_EVERYTHING
	allow_quick_gather = TRUE
	allow_quick_empty = TRUE
	click_gather = TRUE
	max_w_class = WEIGHT_CLASS_BULKY  // can fit vending refills
	max_combined_w_class = 800
	max_items = 400
	display_numerical_stacking = TRUE

/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M)
	. = ..()
	if(!I.get_part_rating())
		if (!stop_messages)
			to_chat(M, span_warning("[parent] only accepts machine parts!"))
		return FALSE


code:
//Stack-only storage.
/datum/component/storage/concrete/stack
	display_numerical_stacking = TRUE
	var/max_combined_stack_amount = 300
	max_w_class = WEIGHT_CLASS_NORMAL
	max_combined_w_class = WEIGHT_CLASS_NORMAL * 14

/datum/component/storage/concrete/stack/proc/total_stack_amount()
	. = 0
	var/atom/real_location = real_location()
	for(var/i in real_location)
		var/obj/item/stack/S = i
		if(!istype(S))
			continue
		. += S.amount

/datum/component/storage/concrete/stack/proc/remaining_space()
	return max(0, max_combined_stack_amount - total_stack_amount())

//emptying procs do not need modification as stacks automatically merge.

/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE)
	if(!istype(I, /obj/item/stack))
		if(override)
			return ..()
		return FALSE
	var/atom/real_location = real_location()
	var/obj/item/stack/S = I
	var/can_insert = min(S.amount, remaining_space())
	if(!can_insert)
		return FALSE
	for(var/i in real_location) //combine.
		if(QDELETED(I))
			return
		var/obj/item/stack/_S = i
		if(!istype(_S))
			continue
		if(_S.merge_type == S.merge_type)
			_S.add(can_insert)
			S.use(can_insert, TRUE)
			return TRUE
	I = S.split_stack(null, can_insert)
	return ..()

/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location)
	var/atom/real_location = real_location()
	var/obj/item/stack/S = I
	if(!istype(S))
		return ..()
	if(S.amount > S.max_amount)
		var/overrun = S.amount - S.max_amount
		S.amount = S.max_amount
		var/obj/item/stack/temp = new S.type(real_location, overrun)
		handle_item_insertion(temp)
	return ..(S, new_location)

/datum/component/storage/concrete/stack/_process_numerical_display()
	var/atom/real_location = real_location()
	. = list()
	for(var/i in real_location)
		var/obj/item/stack/I = i
		if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks.
			continue
		if(!.[I.merge_type])
			.[I.merge_type] = new /datum/numbered_display(I, I.amount)
		else
			var/datum/numbered_display/ND = .[I.merge_type]
			ND.number += I.amount


code:
/**
 *A storage component to be used on card piles, for use as hands/decks/discard piles. Don't use on something that's not a card pile!
 */
/datum/component/storage/concrete/tcg
	display_numerical_stacking = FALSE
	max_w_class = WEIGHT_CLASS_TINY
	max_items = 30
	max_combined_w_class = WEIGHT_CLASS_TINY * 30
	///The deck that the card pile is using for FAIR PLAY.

/datum/component/storage/concrete/tcg/Initialize()
	. = ..()
	set_holdable(list(/obj/item/tcgcard))

/datum/component/storage/concrete/tcg/PostTransfer()
	. = ..()
	handle_empty_deck()

/datum/component/storage/concrete/tcg/remove_from_storage(atom/movable/AM, atom/new_location)
	. = ..()
	handle_empty_deck()

/datum/component/storage/concrete/tcg/show_to(mob/M)
	. = ..()
	M.visible_message(span_notice("[M] starts to look through the contents of \the [parent]!"), \
					span_notice("You begin looking into the contents of \the [parent]!"))

/datum/component/storage/concrete/tcg/close(mob/M)
	. = ..()
	var/list/card_contents = contents()
	var/obj/temp_parent = parent
	temp_parent.visible_message(span_notice("\the [parent] is shuffled after looking through it."))
	card_contents = shuffle(card_contents)

/datum/component/storage/concrete/tcg/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found)
	. = ..()
	if(!things.len)
		qdel(parent)

/datum/component/storage/concrete/tcg/proc/handle_empty_deck()
	var/list/contents = contents()
	//You can't have a deck of one card!
	if(contents.len == 1)
		var/obj/item/tcgcard_deck/deck = parent
		var/obj/item/tcgcard/card = contents[1]
		remove_from_storage(card, card.drop_location())
		card.flipped = deck.flipped
		card.update_icon_state()
		qdel(parent)


code:
/datum/component/storage/concrete/wallet/open_storage(mob/user)
	if(!isliving(user) || !user.CanReach(parent) || user.incapacitated())
		return FALSE
	if(locked)
		to_chat(user, span_warning("[parent] seems to be locked!"))
		return

	var/obj/item/storage/wallet/A = parent
	if(istype(A) && A.front_id && !issilicon(user) && !(A.item_flags & IN_STORAGE)) //if it's a wallet in storage seeing the full inventory is more useful
		var/obj/item/I = A.front_id
		INVOKE_ASYNC(src, .proc/attempt_put_in_hands, I, user)
		return TRUE
	return ..()


code:
/// --Food storage component--
/// This component lets you slide one item into large foods, such as bread, cheese wheels, or cakes.
/// Consuming food storages with an item inside can cause unique interactions, such as eating glass shards.

/datum/component/food_storage
	/// Reference to what we have in our food.
	var/obj/item/stored_item
	/// The amount of volume the food has on creation - Used for probabilities
	var/initial_volume = 10
	/// Minimum size items that can be inserted
	var/minimum_weight_class = WEIGHT_CLASS_SMALL
	/// What are the odds we bite into the stored item?
	var/bad_chance_of_discovery = 0
	/// What are the odds we see the stored item before we bite it?
	var/good_chance_of_discovery = 100
	/// The stored item was found out somehow.
	var/discovered = FALSE

/datum/component/food_storage/Initialize(_minimum_weight_class = WEIGHT_CLASS_SMALL, _bad_chance = 0, _good_chance = 100)

	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/try_inserting_item)
	RegisterSignal(parent, COMSIG_CLICK_CTRL, .proc/try_removing_item)
	RegisterSignal(parent, COMSIG_FOOD_EATEN, .proc/consume_food_storage)

	var/atom/food = parent
	initial_volume = food.reagents.total_volume

	minimum_weight_class = _minimum_weight_class
	bad_chance_of_discovery = _bad_chance
	good_chance_of_discovery = _good_chance

/datum/component/food_storage/Destroy(force, silent)
	if(stored_item)
		stored_item.forceMove(stored_item.drop_location())
		stored_item.dropped()
		stored_item = null
	. = ..()

/** Begins the process of inserted an item.
 *
 * Clicking on the food storage with an item will begin a do_after, which if successful inserts the item.
 *
 * Arguments
 * inserted_item - the item being placed into the food
 * user - the person inserting the item
 */
/datum/component/food_storage/proc/try_inserting_item(datum/source, obj/item/inserted_item, mob/living/user, params)
	SIGNAL_HANDLER

	// No matryoshka-ing food storage
	if(istype(inserted_item, /obj/item/storage) || IS_EDIBLE(inserted_item))
		return

	//Harm intent will bypass inserting for injecting food with syringes and such
	//if(user.combat_mode) //SEPTIC EDIT REMOVAL
	//SEPTIC EDIT BEGIN
	if(IS_HARM_INTENT(user, params2list(params)))
	//SEPTIC EDIT END
		return

	if(inserted_item.w_class > minimum_weight_class)
		to_chat(user, span_warning("\The [inserted_item.name] won't fit in \the [parent]."))
		return

	if(!QDELETED(stored_item))
		to_chat(user, span_warning("There's something in \the [parent]."))
		return

	if(HAS_TRAIT(inserted_item, TRAIT_NODROP))
		to_chat(user, span_warning("\the [inserted_item] is stuck to your hand, you can't put into \the [parent]!"))
		return

	user.visible_message(span_notice("[user.name] begins inserting [inserted_item.name] into \the [parent]."), \
					span_notice("You start to insert the [inserted_item.name] into \the [parent]."))

	INVOKE_ASYNC(src, .proc/insert_item, inserted_item, user)
	return COMPONENT_CANCEL_ATTACK_CHAIN

/** Begins the process of attempting to remove the stored item.
 *
 * Clicking on food storage on grab intent will begin a do_after, which if successful removes the stored_item.
 *
 * Arguments
 * user - the person removing the item.
 */
/datum/component/food_storage/proc/try_removing_item(datum/source, mob/user)
	SIGNAL_HANDLER

	var/atom/food = parent

	if(QDELETED(stored_item))
		return

	if(!food.can_interact(user))
		return

	user.visible_message(span_notice("[user.name] begins tearing at \the [parent]."), \
					span_notice("You start to rip into \the [parent]."))

	INVOKE_ASYNC(src, .proc/begin_remove_item, user)
	return COMPONENT_CANCEL_ATTACK_CHAIN

/** Inserts the item into the food, after a do_after.
 *
 * Arguments
 * inserted_item - The item being inserted.
 * user - the person inserting the item.
 */
/datum/component/food_storage/proc/insert_item(obj/item/inserted_item, mob/user)
	if(do_after(user, 1.5 SECONDS, target = parent))
		var/atom/food = parent
		to_chat(user, span_notice("You slip [inserted_item.name] inside \the [parent]."))
		inserted_item.forceMove(food)
		user.log_message("[key_name(user)] inserted [inserted_item] into [parent] at [AREACOORD(user)]", LOG_ATTACK)
		food.add_fingerprint(user)
		inserted_item.add_fingerprint(user)

		stored_item = inserted_item

/** Removes the item from the food, after a do_after.
 *
 * Arguments
 * user - person removing the item.
 */
/datum/component/food_storage/proc/begin_remove_item(mob/user)
	if(do_after(user, 10 SECONDS, target = parent))
		remove_item(user)

/**
 * Removes the stored item, putting it in user's hands or on the ground, then updates the reference.
 */
/datum/component/food_storage/proc/remove_item(mob/user)
	if(user.put_in_hands(stored_item))
		user.visible_message(span_warning("[user.name] slowly pulls [stored_item.name] out of \the [parent]."), \
							span_warning("You slowly pull [stored_item.name] out of \the [parent]."))
	else
		stored_item.dropped()
		stored_item.visible_message(span_warning("[stored_item.name] falls out of \the [parent]."))

	update_stored_item()

/** Checks for stored items when the food is eaten.
 *
 * If the food is eaten while an item is stored in it, calculates the odds that the item will be found.
 * Then, if the item is found before being bitten, the item is removed.
 * If the item is found by biting into it, calls on_accidental_consumption on the stored item.
 * Afterwards, removes the item from the food if it was discovered.
 *
 * Arguments
 * target - person doing the eating (can be the same as user)
 * user - person causing the eating to happen
 * bitecount - how many times the current food has been bitten
 * bitesize - how large bties are for this food
 */
/datum/component/food_storage/proc/consume_food_storage(datum/source, mob/living/target, mob/living/user, bitecount, bitesize)
	SIGNAL_HANDLER

	if(QDELETED(stored_item)) //if the stored item was deleted/null...
		if(!update_stored_item()) //check if there's a replacement item
			return

	/// Chance of biting the held item = amount of bites / (intitial reagents / reagents per bite) * 100
	bad_chance_of_discovery = (bitecount / (initial_volume / bitesize))*100
	/// Chance of finding the held item = bad chance - 50
	good_chance_of_discovery = bad_chance_of_discovery - 50

	if(prob(good_chance_of_discovery)) //finding the item, without biting it
		discovered = TRUE
		to_chat(target, span_warning("It feels like there's something in \the [parent]...!"))

	else if(prob(bad_chance_of_discovery)) //finding the item, BY biting it
		user.log_message("[key_name(user)] just fed [key_name(target)] a/an [stored_item] which was hidden in [parent] at [AREACOORD(target)]", LOG_ATTACK)
		discovered = stored_item.on_accidental_consumption(target, user, parent)
		update_stored_item() //make sure if the item was changed, the reference changes as well

	if(!QDELETED(stored_item) && discovered)
		INVOKE_ASYNC(src, .proc/remove_item, user)

/** Updates the reference of the stored item.
 *
 * Checks the food's contents for if an alternate item was placed into the food.
 * If there is an alternate item, updates the reference to the new item.
 * If there isn't, updates the reference to null.
 *
 * Returns FALSE if the ref is nulled, or TRUE is another item replaced it.
 */
/datum/component/food_storage/proc/update_stored_item()
	var/atom/food = parent
	if(!food?.contents.len) //if there's no items in the food or food is deleted somehow
		stored_item = null
		return FALSE

	for(var/obj/item/i in food.contents) //search the food's contents for a replacement item
		if(IS_EDIBLE(i))
			continue
		if(QDELETED(i))
			continue

		stored_item = i //we found something to replace it
		return TRUE

	//if there's nothing else in the food, or we found nothing valid
	stored_item = null
	return FALSE