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
	dupe_mode = COMPONENT_DUPE_UNIQUE
	var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point.

	var/list/can_hold //if this is set, only items, and their children, will fit
	var/list/cant_hold //if this is set, items, and their children, won't fit
	var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit.
	/// If set can only contain stuff with this single trait present.
	var/list/can_hold_trait

	var/can_hold_description

	var/list/mob/is_using //lazy list of mobs looking at the contents of this storage.

	var/locked = FALSE //when locked nothing can see inside or use it.

	var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit.
	var/max_combined_w_class = 14 //max combined sizes of objects that will fit.
	var/max_items = 7 //max number of objects that will fit.

	var/emp_shielded = FALSE

	var/silent = FALSE //whether this makes a message when things are put in.
	var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around.
	var/rustle_sound = TRUE //play rustle sound on interact.
	var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly.
	var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile.

	var/collection_mode = COLLECT_EVERYTHING

	var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray.

	var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number.

	var/atom/movable/screen/storage/boxes //storage display object
	var/atom/movable/screen/close/closer //close button object

	var/allow_big_nesting = FALSE //allow storage objects of the same or greater size.

	var/attack_hand_interact = TRUE //interact on attack hand.
	var/quickdraw = FALSE //altclick interact

	var/datum/action/item_action/storage_gather_mode/modeswitch_action

	//Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future.
	var/screen_max_columns = 7 //These two determine maximum screen sizes.
	var/screen_max_rows = INFINITY
	var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer
	var/screen_pixel_y = 16
	var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise.
	var/screen_start_y = 2
	//End

/datum/component/storage/Initialize(datum/component/storage/concrete/master)
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE
	if(master)
		change_master(master)
	boxes = new(null, src)
	closer = new(null, src)
	orient2hud()

	RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check)
	RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all)
	RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv)

	RegisterSignal(parent, COMSIG_TOPIC, .proc/topic_handle)

	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby)

	RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
	RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand)
	RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act)
	RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost)
	RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views)
	RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh)
	RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react)

	RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept)
	RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self)
	RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup)
	/* SEPTIC EDIT REMOVAL
	RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/update_actions)
	*/
	//SEPTIC EDIT BEGIN
	if(isitem(parent))
		RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equipped)
	//SEPTIC EDIT END
	RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all)
	RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move)

	RegisterSignal(parent, list(COMSIG_CLICK_ALT, COMSIG_ATOM_ATTACK_HAND_SECONDARY), .proc/on_open_storage_click)
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY_SECONDARY, .proc/on_open_storage_attackby)
	RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto)
	RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive)

	update_actions()

/datum/component/storage/Destroy()
	close_all()
	QDEL_NULL(boxes)
	QDEL_NULL(closer)
	LAZYCLEARLIST(is_using)
	return ..()

/datum/component/storage/PreTransfer()
	update_actions()

/datum/component/storage/proc/set_holdable(can_hold_list, cant_hold_list)
	can_hold_description = generate_hold_desc(can_hold_list)

	if (can_hold_list != null)
		can_hold = string_list(typecacheof(can_hold_list))

	if (cant_hold_list != null)
		cant_hold = string_list(typecacheof(cant_hold_list))

/datum/component/storage/proc/generate_hold_desc(can_hold_list)
	var/list/desc = list()

	for(var/valid_type in can_hold_list)
		var/obj/item/valid_item = valid_type
		desc += "\a [initial(valid_item.name)]"

	return "\n\t[span_notice("[desc.Join("\n\t")]")]"

/datum/component/storage/proc/update_actions()
	SIGNAL_HANDLER

	QDEL_NULL(modeswitch_action)
	if(!isitem(parent) || !allow_quick_gather)
		return
	var/obj/item/I = parent
	modeswitch_action = new(I)
	RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger)
	if(I.item_flags & IN_INVENTORY)
		var/mob/M = I.loc
		if(!istype(M))
			return
		modeswitch_action.Grant(M)

/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master)
	if(new_master == src || (!isnull(new_master) && !istype(new_master)))
		return FALSE
	if(master)
		master.on_slave_unlink(src)
	master = new_master
	if(master)
		master.on_slave_link(src)
	return TRUE

/datum/component/storage/proc/master()
	if(master == src)
		return //infinite loops yo.
	return master

/datum/component/storage/proc/real_location()
	var/datum/component/storage/concrete/master = master()
	return master? master.real_location() : null

/datum/component/storage/proc/canreach_react(datum/source, list/next)
	SIGNAL_HANDLER

	var/datum/component/storage/concrete/master = master()
	if(!master)
		return
	. = COMPONENT_ALLOW_REACH
	next += master.parent
	for(var/i in master.slaves)
		var/datum/component/storage/slave = i
		next += slave.parent

/datum/component/storage/proc/on_move()
	SIGNAL_HANDLER

	var/atom/A = parent
	for(var/mob/living/L in can_see_contents())
		/* SEPTIC EDIT REMOVAL
		if(!L.CanReach(A))
		*/
		//SEPTIC EDIT BEGIN
		if(!L.CanReach(A) || !worn_check(A, L, TRUE))
		//SEPTIC EDIT END
			hide_from(L)

/datum/component/storage/proc/attack_self(datum/source, mob/M)
	SIGNAL_HANDLER

	if(locked)
		to_chat(M, span_warning("[parent] seems to be locked!"))
		return FALSE
	//SEPTIC EDIT BEGIN
	if(isitem(parent))
		var/obj/item/I = parent
		if(!worn_check(I, M))
			return FALSE
	//SEPTIC EDIT END
	if((M.get_active_held_item() == parent) && allow_quick_empty)
		INVOKE_ASYNC(src, .proc/quick_empty, M)

/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params)
	SIGNAL_HANDLER

	if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE))
		return FALSE
	. = COMPONENT_CANCEL_ATTACK_CHAIN
	if(locked)
		to_chat(M, span_warning("[parent] seems to be locked!"))
		return FALSE
	var/obj/item/I = O
	//SEPTIC EDIT BEGIN
	if(!worn_check(parent, M))
		return
	//SEPTIC EDIT END
	if(collection_mode == COLLECT_ONE)
		if(can_be_inserted(I, null, M))
			handle_item_insertion(I, null, M)
		return
	if(!isturf(I.loc))
		return
	INVOKE_ASYNC(src, .proc/async_preattack_intercept, I, M)

///async functionality from preattack_intercept
/datum/component/storage/proc/async_preattack_intercept(obj/item/attack_item, mob/pre_attack_mob)
	var/list/things = attack_item.loc.contents.Copy()
	if(collection_mode == COLLECT_SAME)
		things = typecache_filter_list(things, typecacheof(attack_item.type))
	var/len = length(things)
	if(!len)
		to_chat(pre_attack_mob, span_warning("You failed to pick up anything with [parent]!"))
		return
	var/datum/progressbar/progress = new(pre_attack_mob, len, attack_item.loc)
	var/list/rejections = list()
	while(do_after(pre_attack_mob, 1 SECONDS, parent, NONE, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, attack_item.loc, rejections, progress)))
		stoplag(1)
	progress.end_progress()
	to_chat(pre_attack_mob, span_notice("You put everything you could [insert_preposition] [parent]."))

/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress)
	var/atom/source_real_location = src_object.real_location()
	for(var/obj/item/I in things)
		things -= I
		if(I.loc != source_real_location)
			continue
		if(user.active_storage != src_object)
			if(I.on_found(user))
				break
		/* SEPTIC EDIT REMOVAL
		if(can_be_inserted(I,FALSE,user))
		*/
		//SEPTIC EDIT BEGIN
		if(can_be_inserted(I, FALSE, user, TRUE))
		//SEPTIC EDIT END
			handle_item_insertion(I, TRUE, user)
		if (TICK_CHECK)
			progress.update(progress.goal - things.len)
			return TRUE

	progress.update(progress.goal - things.len)
	return FALSE

/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress)
	var/atom/real_location = real_location()
	for(var/obj/item/I in things)
		things -= I
		if(I.loc != thing_loc)
			continue
		if(I.type in rejections) // To limit bag spamming: any given type only complains once
			continue
		if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no
			if(real_location.contents.len >= max_items)
				break
			rejections += I.type // therefore full bags are still a little spammy
			continue

		handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed.

		if (TICK_CHECK)
			progress.update(progress.goal - things.len)
			return TRUE

	progress.update(progress.goal - things.len)
	return FALSE

/datum/component/storage/proc/quick_empty(mob/M)
	var/atom/A = parent
	if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated())
		return
	if(locked)
		to_chat(M, span_warning("[parent] seems to be locked!"))
		return FALSE
	//SEPTIC EDIT BEGIN
	if(isitem(A))
		var/obj/item/I = parent
		if(!worn_check(I, M))
			return FALSE
	//SEPTIC EDIT END
	A.add_fingerprint(M)
	to_chat(M, span_notice("You start dumping out [parent]."))
	var/turf/T = get_turf(A)
	var/list/things = contents()
	var/datum/progressbar/progress = new(M, length(things), T)
	while (do_after(M, 1 SECONDS, T, NONE, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress)))
		stoplag(1)
	progress.end_progress()

/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE)
	var/atom/real_location = real_location()
	for(var/obj/item/I in things)
		things -= I
		if(I.loc != real_location)
			continue
		remove_from_storage(I, target)
		I.pixel_x = rand(-10,10)
		I.pixel_y = rand(-10,10)
		if(trigger_on_found && I.on_found())
			return FALSE
		if(TICK_CHECK)
			progress.update(progress.goal - length(things))
			return TRUE
	progress.update(progress.goal - length(things))
	return FALSE

/datum/component/storage/proc/do_quick_empty(atom/_target)
	if(!_target)
		_target = get_turf(parent)
	if(usr)
		hide_from(usr)
	var/list/contents = contents()
	var/atom/real_location = real_location()
	for(var/obj/item/I in contents)
		if(I.loc != real_location)
			continue
		remove_from_storage(I, _target)
	return TRUE

/datum/component/storage/proc/set_locked(datum/source, new_state)
	SIGNAL_HANDLER

	locked = new_state
	if(locked)
		close_all()

/datum/component/storage/proc/_process_numerical_display()
	. = list()
	var/atom/real_location = real_location()
	for(var/obj/item/I in real_location.contents)
		if(QDELETED(I))
			continue
		if(!.["[I.type]-[I.name]"])
			.["[I.type]-[I.name]"] = new /datum/numbered_display(I, 1)
		else
			var/datum/numbered_display/ND = .["[I.type]-[I.name]"]
			ND.number++

//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing.
/datum/component/storage/proc/orient2hud()
	var/atom/real_location = real_location()
	var/adjusted_contents = real_location.contents.len

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

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

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

/datum/component/storage/proc/show_to(mob/M)
	if(!M.client)
		return FALSE
	var/atom/real_location = real_location()
	if(M.active_storage != src && (M.stat == CONSCIOUS))
		for(var/obj/item/I in real_location)
			if(I.on_found(M))
				return FALSE
	if(M.active_storage)
		M.active_storage.hide_from(M)
	orient2hud()
	M.client.screen |= boxes
	M.client.screen |= closer
	M.client.screen |= real_location.contents
	M.set_active_storage(src)
	LAZYOR(is_using, M)
	RegisterSignal(M, COMSIG_PARENT_QDELETING, .proc/mob_deleted)
	return TRUE

/datum/component/storage/proc/mob_deleted(datum/source)
	SIGNAL_HANDLER
	hide_from(source)

/datum/component/storage/proc/hide_from(mob/M)
	if(M.active_storage == src)
		M.set_active_storage(null)
	LAZYREMOVE(is_using, M)

	UnregisterSignal(M, COMSIG_PARENT_QDELETING)
	if(!M.client)
		return TRUE
	var/atom/real_location = real_location()
	M.client.screen -= boxes
	M.client.screen -= closer
	M.client.screen -= real_location.contents
	return TRUE

/datum/component/storage/proc/close(mob/M)
	hide_from(M)

/datum/component/storage/proc/close_all()
	SIGNAL_HANDLER

	. = FALSE
	for(var/mob/M in can_see_contents())
		close(M)
		. = TRUE //returns TRUE if any mobs actually got a close(M) call

/datum/component/storage/proc/emp_act(datum/source, severity)
	SIGNAL_HANDLER

	if(emp_shielded)
		return
	var/datum/component/storage/concrete/master = master()
	master.emp_act(source, severity)

//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right.
//The numbers are calculated from the bottom-left The bottom-left slot being 1,1.
/datum/component/storage/proc/orient_objs(tx, ty, mx, my)
	var/atom/real_location = real_location()
	var/cx = tx
	var/cy = ty
	boxes.screen_loc = "[tx]:,[ty] to [mx],[my]"
	for(var/obj/O in real_location)
		if(QDELETED(O))
			continue
		O.screen_loc = "[cx],[cy]"
		O.plane = ABOVE_HUD_PLANE
		cx++
		if(cx > mx)
			cx = tx
			cy--
	closer.screen_loc = "[mx+1],[my]"

//Resets something that is being removed from storage.
/datum/component/storage/proc/_removal_reset(atom/movable/thing)
	if(!istype(thing))
		return FALSE
	var/datum/component/storage/concrete/master = master()
	if(!istype(master))
		return FALSE
	return master._removal_reset(thing)

/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/gone, direction)
	SIGNAL_HANDLER

	_removal_reset(gone)
	refresh_mob_views()

//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted
/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location)
	if(!istype(AM))
		return FALSE
	var/datum/component/storage/concrete/master = master()
	if(!istype(master))
		return FALSE
	return master.remove_from_storage(AM, new_location)

/datum/component/storage/proc/refresh_mob_views()
	SIGNAL_HANDLER

	var/list/seeing = can_see_contents()
	for(var/i in seeing)
		show_to(i)
	return TRUE

/datum/component/storage/proc/can_see_contents()
	var/list/cansee = list()
	for(var/mob/M in is_using)
		if(M.active_storage == src && M.client)
			cansee |= M
		else
			LAZYREMOVE(is_using, M)
			UnregisterSignal(M, COMSIG_PARENT_QDELETING)
	return cansee

//Tries to dump content
/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M)
	var/atom/A = parent
	var/atom/dump_destination = dest_object.get_dumping_location()
	if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination))
		if(locked)
			to_chat(M, span_warning("[parent] seems to be locked!"))
			return FALSE
		//SEPTIC EDIT BEGIN
		if(isitem(A))
			var/obj/item/I = parent
			if(!worn_check(I, M))
				return FALSE
		//SEPTIC EDIT END
		if(dump_destination.storage_contents_dump_act(src, M))
			playsound(A, "rustle", 50, TRUE, -5)
			return TRUE
	return FALSE

//This proc is called when you want to place an item into the storage item.
/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params)
	SIGNAL_HANDLER

	if(istype(I, /obj/item/hand_labeler))
		var/obj/item/hand_labeler/labeler = I
		if(labeler.mode)
			return FALSE
	. = TRUE //no afterattack
	if(iscyborg(M))
		return
	if(!can_be_inserted(I, FALSE, M))
		var/atom/real_location = real_location()
		if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit
			return TRUE
		return FALSE
	handle_item_insertion(I, FALSE, M)

/datum/component/storage/proc/return_inv(recursive)
	var/list/ret = list()
	ret |= contents()
	if(recursive)
		for(var/i in ret.Copy())
			var/atom/A = i
			SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE)
	return ret

/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS!
	var/atom/real_location = real_location()
	return real_location.contents.Copy()

//Abuses the fact that lists are just references, or something like that.
/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE)
	SIGNAL_HANDLER

	if(!islist(interface))
		return FALSE
	interface |= return_inv(recursive)
	return TRUE

/datum/component/storage/proc/topic_handle(datum/source, user, href_list)
	SIGNAL_HANDLER

	if(href_list["show_valid_pocket_items"])
		handle_show_valid_items(source, user)

/datum/component/storage/proc/handle_show_valid_items(datum/source, user)
	to_chat(user, span_notice("[source] can hold: [can_hold_description]"))

/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M)
	SIGNAL_HANDLER

	set waitfor = FALSE
	. = COMPONENT_NO_MOUSEDROP
	if(!ismob(M))
		return
	if(!over_object)
		return
	if(ismecha(M.loc)) // stops inventory actions in a mech
		return
	if(M.incapacitated() || !M.canUseStorage())
		return
	var/atom/A = parent
	A.add_fingerprint(M)
	// this must come before the screen objects only block, dunno why it wasn't before
	if(over_object == M)
		user_show_to_mob(M)
	if(!istype(over_object, /atom/movable/screen))
		INVOKE_ASYNC(src, .proc/dump_content_at, over_object, M)
		return
	if(A.loc != M)
		return
	playsound(A, "rustle", 50, TRUE, -5)
	if(istype(over_object, /atom/movable/screen/inventory/hand))
		var/atom/movable/screen/inventory/hand/H = over_object
		M.putItemFromInventoryInHandIfPossible(A, H.held_index)
		return
	A.add_fingerprint(M)

/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE)
	var/atom/A = parent
	if(!istype(M))
		return FALSE
	A.add_fingerprint(M)
	/* SEPTIC EDIT REMOVAL
	if(locked && !force)
		to_chat(M, span_warning("[parent] seems to be locked!"))
		return FALSE
	if(force || M.CanReach(parent, view_only = TRUE))
		show_to(M)
	*/
	//SEPTIC EDIT BEGIN
	if(!force)
		if(locked)
			to_chat(M, "[parent] seems to be locked!")
			return FALSE
		if(isitem(parent))
			var/obj/item/I = parent
			if(!worn_check(I, M))
				return FALSE
		if(!M.CanReach(parent, view_only = TRUE))
			return FALSE
	show_to(M)
	//SEPTIC EDIT END

/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M)
	SIGNAL_HANDLER

	if(isitem(O))
		var/obj/item/I = O
		if(iscarbon(M) || isdrone(M))
			var/mob/living/L = M
			if(!L.incapacitated() && I == L.get_active_held_item())
				/* SEPTIC EDIT REMOVAL
				if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert.
				*/
				//SEPTIC EDIT BEGIN
				if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE, M, TRUE))
				//SEPTIC EDIT END
					handle_item_insertion(I, FALSE, L)

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

/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE)
	return FALSE

//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted()
//The prevent_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once,
//such as when picking up all the items on a tile with one click.
/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote)
	var/atom/parent = src.parent
	var/datum/component/storage/concrete/master = master()
	if(!istype(master))
		return FALSE
	if(silent)
		prevent_warning = TRUE
	if(M)
		parent.add_fingerprint(M)
	. = master.handle_item_insertion_from_slave(src, I, prevent_warning, M)

/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE)
	if(silent && !override)
		return
	if(rustle_sound)
		playsound(parent, "rustle", 50, TRUE, -5)
	//for(var/mob/viewing in viewers(user, null)) //SEPTIC EDIT REMOVAL
	//SEPTIC EDIT BEGIN
	for(var/mob/viewing in fov_viewers(world.view, user))
	//SEPTIC EDIT END
		if(M == viewing)
			to_chat(usr, span_notice("You put [I] [insert_preposition]to [parent]."))
		else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
			viewing.show_message(span_notice("[M] puts [I] [insert_preposition]to [parent]."), MSG_VISUAL)
		else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance...
			viewing.show_message(span_notice("[M] puts [I] [insert_preposition]to [parent]."), MSG_VISUAL)

/datum/component/storage/proc/update_icon()
	if(isobj(parent))
		var/obj/O = parent
		O.update_appearance()

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

/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE)
	SIGNAL_HANDLER

	return can_be_inserted(I, silent, M)

/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M)
	SIGNAL_HANDLER

	return user_show_to_mob(M, TRUE)

/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE)
	SIGNAL_HANDLER

	return user_show_to_mob(showto, force)

/datum/component/storage/proc/on_check()
	SIGNAL_HANDLER

	return TRUE

/datum/component/storage/proc/check_locked()
	SIGNAL_HANDLER

	return locked

/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted)
	SIGNAL_HANDLER

	if(!force)
		if(check_adjacent)
			/* SEPTIC EDIT REMOVAL
			if(!user || !user.CanReach(destination) || !user.CanReach(parent))
			*/
			//SEPTIC EDIT BEGIN
			if(!user || !user.CanReach(destination) || !user.CanReach(parent) || !worn_check(parent, user))
			//SEPTIC EDIT END
				return FALSE
	var/list/taking = typecache_filter_list(contents(), typecacheof(type))
	if(taking.len > amount)
		taking.len = amount
	if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item.
		for(var/i in taking)
			if(remove_from_storage(i, destination))
				inserted |= i
	else
		for(var/i in taking)
			remove_from_storage(i, destination)
	return TRUE

/datum/component/storage/proc/remaining_space_items()
	var/atom/real_location = real_location()
	return max(0, max_items - real_location.contents.len)

/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE)
	SIGNAL_HANDLER

	var/atom/real_location = real_location()
	if(!force)
		amount = min(remaining_space_items(), amount)
	for(var/i in 1 to amount)
		if(!handle_item_insertion(new type(real_location), TRUE))
			return i > 1 //return TRUE only if at least one insertion has been successful.
		if(QDELETED(src))
			return TRUE
	return TRUE

/datum/component/storage/proc/on_attack_hand(datum/source, mob/user)
	SIGNAL_HANDLER

	var/atom/A = parent
	if(!attack_hand_interact)
		return
	if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item
		user.active_storage.close(user)
		close(user)
		. = COMPONENT_CANCEL_ATTACK_CHAIN
		return

	if(rustle_sound)
		playsound(A, "rustle", 50, TRUE, -5)

	//SEPTIC EDIT BEGIN
	if(isitem(A))
		var/obj/item/I = A
		if(!worn_check(I, user, TRUE))
			return FALSE
	//SEPTIC EDIT END

	if(ishuman(user))
		var/mob/living/carbon/human/H = user
		if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket.
			. = COMPONENT_CANCEL_ATTACK_CHAIN
			INVOKE_ASYNC(H, /mob.proc/put_in_hands, A)
			H.l_store = null
			return
		if(H.r_store == A && !H.get_active_held_item())
			. = COMPONENT_CANCEL_ATTACK_CHAIN
			INVOKE_ASYNC(H, /mob.proc/put_in_hands, A)
			H.r_store = null
			return

	if(A.loc == user)
		. = COMPONENT_CANCEL_ATTACK_CHAIN
		if(locked)
			to_chat(user, span_warning("[parent] seems to be locked!"))
		else
			show_to(user)

/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user)
	SIGNAL_HANDLER

	update_actions()
	for(var/mob/M in can_see_contents() - user)
		close(M)

/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE)
	SIGNAL_HANDLER

	if(!(AM in real_location()))
		return FALSE
	return remove_from_storage(AM, new_loc)

/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget)
	SIGNAL_HANDLER

	return do_quick_empty(loctarget)

/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target)
	SIGNAL_HANDLER

	return hide_from(target)

/datum/component/storage/proc/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 FALSE

	. = TRUE
	var/atom/A = parent
	//SEPTIC EDIT BEGIN
	if(!worn_check(A, user))
		return FALSE
	//SEPTIC EDIT END
	if(!quickdraw)
		A.add_fingerprint(user)
		user_show_to_mob(user)
		playsound(A, "rustle", 50, TRUE, -5)
		return

	var/obj/item/to_remove = locate() in real_location()
	if(!to_remove)
		return

	INVOKE_ASYNC(src, .proc/attempt_put_in_hands, to_remove, user)

/datum/component/storage/proc/on_open_storage_click(datum/source, mob/user, list/modifiers)
	SIGNAL_HANDLER

	if(open_storage(user))
		return COMPONENT_CANCEL_ATTACK_CHAIN

/datum/component/storage/proc/on_open_storage_attackby(datum/source, obj/item/weapon, mob/user, params)
	SIGNAL_HANDLER

	if(open_storage(user))
		return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN

///attempt to put an item from contents into the users hands
/datum/component/storage/proc/attempt_put_in_hands(obj/item/to_remove, mob/user)
	var/atom/parent_as_atom = parent

	parent_as_atom.add_fingerprint(user)
	remove_from_storage(to_remove, get_turf(user))
	if(!user.put_in_hands(to_remove))
		to_chat(user, span_notice("You fumble for [to_remove] and it falls on the floor."))
		return
	user.visible_message(span_warning("[user] draws [to_remove] from [parent]!"), span_notice("You draw [to_remove] from [parent]."))

/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source)
	SIGNAL_HANDLER

	gather_mode_switch(source.owner)
	return COMPONENT_ACTION_BLOCK_TRIGGER

/datum/component/storage/proc/gather_mode_switch(mob/user)
	collection_mode = (collection_mode+1)%3
	switch(collection_mode)
		if(COLLECT_SAME)
			to_chat(user, span_notice("[parent] now picks up all items of a single type at once."))
		if(COLLECT_EVERYTHING)
			to_chat(user, span_notice("[parent] now picks up all items in a tile at once."))
		if(COLLECT_ONE)
			to_chat(user, span_notice("[parent] now picks up one item at a time."))


code:
/*
This component attaches to mobs, and makes their pulls !strong!
Basically, the items they pull cannot be pulled (except by the puller)
*/
/datum/component/strong_pull
	var/atom/movable/strongpulling

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

/datum/component/strong_pull/Destroy(force, silent)
	if(strongpulling)
		lose_strong_grip()
	return ..()

/datum/component/strong_pull/RegisterWithParent()
	. = ..()
	RegisterSignal(parent, COMSIG_LIVING_START_PULL, .proc/on_pull)

/**
 * Called when the parent grabs something, adds signals to the object to reject interactions
 */
/datum/component/strong_pull/proc/on_pull(datum/source, atom/movable/pulled, state, force)
	SIGNAL_HANDLER
	strongpulling = pulled
	RegisterSignal(strongpulling, COMSIG_ATOM_CAN_BE_PULLED, .proc/reject_further_pulls)
	RegisterSignal(strongpulling, COMSIG_ATOM_NO_LONGER_PULLED, .proc/on_no_longer_pulled)
	if(istype(strongpulling, /obj/structure/closet) && !istype(strongpulling, /obj/structure/closet/body_bag))
		var/obj/structure/closet/grabbed_closet = strongpulling
		grabbed_closet.strong_grab = TRUE

/**
 * Signal for rejecting further grabs
 */
/datum/component/strong_pull/proc/reject_further_pulls(datum/source, mob/living/puller)
	SIGNAL_HANDLER
	if(puller != parent) //for increasing grabs, you need to have a valid pull. thus, parent should be able to pull the same object again
		return COMSIG_ATOM_CANT_PULL

/*
 * Unregisters signals and stops any buffs to pulling.
 */
/datum/component/strong_pull/proc/lose_strong_grip()
	UnregisterSignal(strongpulling, list(COMSIG_ATOM_CAN_BE_PULLED, COMSIG_ATOM_NO_LONGER_PULLED))
	if(istype(strongpulling, /obj/structure/closet))
		var/obj/structure/closet/ungrabbed_closet = strongpulling
		ungrabbed_closet.strong_grab = FALSE
	strongpulling = null

/**
 * Called when the hooked object is no longer pulled and removes the strong grip.
 */
/datum/component/strong_pull/proc/on_no_longer_pulled(datum/source, atom/movable/last_puller)
	SIGNAL_HANDLER
	lose_strong_grip()

repo_name: AnalWerewolf/sundown

license: mit


code:
/**
 * subtype picker component allows for an item to transform into its subtypes (this is not enforced and you can turn in whatever types, but
 * i used this name as it was incredibly accurate for current usage of the behavior)
 *
 * Used for the null rod to pick the other holy weapons.
 */
/datum/component/subtype_picker
	///A list of types and their menu descriptions
	var/list/subtype2descriptions
	///list given to the radial menu to display, built after init
	var/list/built_radial_list
	///the radial will return a name of the wanted subtype, this is a list of names back to the type, built after init
	var/list/name2subtype
	///optional proc to callback to when the weapon is picked
	var/datum/callback/on_picked_callback

/datum/component/subtype_picker/Initialize(subtype2descriptions, on_picked_callback)
	. = ..()
	if(!isitem(parent))
		return COMPONENT_INCOMPATIBLE
	src.subtype2descriptions = subtype2descriptions
	src.on_picked_callback = on_picked_callback
	build_radial_list()

/datum/component/subtype_picker/RegisterWithParent()
	. = ..()
	RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/on_attack_self)

/datum/component/subtype_picker/UnregisterFromParent()
	. = ..()
	UnregisterSignal(parent, COMSIG_ITEM_ATTACK_SELF)

///signal called by the stat of the target changing
/datum/component/subtype_picker/proc/on_attack_self(datum/target, mob/user)
	SIGNAL_HANDLER
	INVOKE_ASYNC(src, .proc/pick_subtype, target, user)

/**
 * pick_subtype: turns the list of types to their description into all the data radial menus need
 */
/datum/component/subtype_picker/proc/build_radial_list()
	built_radial_list = list()
	name2subtype = list()
	for(var/obj/item/subtype as anything in subtype2descriptions)

		var/datum/radial_menu_choice/option = new
		option.image = image(icon = initial(subtype.icon), icon_state = initial(subtype.icon_state))
		option.info = span_boldnotice(subtype2descriptions[subtype])

		name2subtype[initial(subtype.name)] = subtype
		built_radial_list += list(initial(subtype.name) = option)
	built_radial_list = sort_list(built_radial_list)

/**
 * pick_subtype: called from on_attack_self, shows a user a radial menu of all available null rod reskins and replaces the current null rod with the user's chosen reskinned variant
 *
 * Arguments:
 * * target: parent this element is attached to that is being activated
 * * picker: user who interacted with the item
 */
/datum/component/subtype_picker/proc/pick_subtype(datum/target, mob/picker)

	var/name_of_type = show_radial_menu(picker, target, built_radial_list, custom_check = CALLBACK(src, .proc/check_menu, target, picker), radius = 42, require_near = TRUE)
	if(!name_of_type || !check_menu(target, picker))
		return

	var/picked_subtype = name2subtype[name_of_type]
	on_picked_callback?.Invoke(picked_subtype)
	picked_subtype = new picked_subtype(picker.drop_location())

	qdel(target)
	picker.put_in_hands(picked_subtype)

/**
 * Checks if we are allowed to interact with the radial menu
 *
 * Arguments:
 * * target: parent the radial menu is from
 * * user: the mob interacting with the menu
 */
/datum/component/subtype_picker/proc/check_menu(datum/target, mob/user)
	if(!istype(user))
		return FALSE
	if(QDELETED(target))
		return FALSE
	if(user.incapacitated() || !user.is_holding(target))
		return FALSE
	return TRUE


code:
/datum/component/summoning
	var/list/mob_types = list()
	var/spawn_chance // chance for the mob to spawn on hit in percent
	var/max_mobs
	var/spawn_delay // delay in spawning between mobs (deciseconds)
	var/spawn_text
	var/spawn_sound
	var/list/faction

	var/last_spawned_time = 0
	var/list/spawned_mobs = list()

/datum/component/summoning/Initialize(mob_types, spawn_chance=100, max_mobs=3, spawn_delay=100, spawn_text="appears out of nowhere", spawn_sound='sound/magic/summon_magic.ogg', faction)
	if(!isitem(parent) && !ishostile(parent) && !isgun(parent) && !ismachinery(parent) && !isstructure(parent))
		return COMPONENT_INCOMPATIBLE

	src.mob_types = mob_types
	src.spawn_chance = spawn_chance
	src.max_mobs = max_mobs
	src.spawn_delay = spawn_delay
	src.spawn_text = spawn_text
	src.spawn_sound = spawn_sound
	src.faction = faction

/datum/component/summoning/RegisterWithParent()
	if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
		RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
	else if(isitem(parent))
		RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/item_afterattack)
	else if(ishostile(parent))
		RegisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET, .proc/hostile_attackingtarget)

/datum/component/summoning/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_POST_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))

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

	if(!proximity_flag)
		return
	do_spawn_mob(get_turf(target), user)

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

	if(!success)
		return
	do_spawn_mob(get_turf(target), attacker)

/datum/component/summoning/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
	SIGNAL_HANDLER

	do_spawn_mob(get_turf(target), firer)

/datum/component/summoning/proc/do_spawn_mob(atom/spawn_location, summoner)
	if(spawned_mobs.len >= max_mobs)
		return
	if(last_spawned_time > world.time)
		return
	if(!prob(spawn_chance))
		return
	last_spawned_time = world.time + spawn_delay
	var/chosen_mob_type = pick(mob_types)
	var/mob/living/simple_animal/L = new chosen_mob_type(spawn_location)
	if(ishostile(L))
		var/mob/living/simple_animal/hostile/H = L
		H.friends += summoner // do not attack our summon boy
	spawned_mobs += L
	if(faction != null)
		L.faction = faction
	RegisterSignal(L, COMSIG_LIVING_DEATH, .proc/on_spawned_death) // so we can remove them from the list, etc (for mobs with corpses)
	playsound(spawn_location,spawn_sound, 50, TRUE)
	spawn_location.visible_message(span_danger("[L] [spawn_text]."))

/datum/component/summoning/proc/on_spawned_death(mob/killed, gibbed)
	SIGNAL_HANDLER

	spawned_mobs -= killed


code:
/*!

This component is used in vat growing to swab for microbiological samples which can then be mixed with reagents in a petridish to create a culture plate.

*/
/datum/component/swabbing
	///The current datums on the swab
	var/list/swabbed_items
	///Can we swab objs?
	var/can_swab_objs
	///Can we swab turfs?
	var/can_swab_turfs
	///Can we swab mobs?
	var/can_swab_mobs
	///Callback for update_icon()
	var/datum/callback/update_icons
	///Callback for update_overlays()
	var/datum/callback/update_overlays

/datum/component/swabbing/Initialize(can_swab_objs = TRUE, can_swab_turfs = TRUE, can_swab_mobs = FALSE, datum/callback/update_icons, datum/callback/update_overlays, swab_time = 1 SECONDS, max_items = 3)
	if(!isitem(parent))
		return COMPONENT_INCOMPATIBLE

	RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/try_to_swab)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
	RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, .proc/handle_overlays)
	RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, .proc/handle_icon)

	src.can_swab_objs = can_swab_objs
	src.can_swab_turfs = can_swab_turfs
	src.can_swab_mobs = can_swab_mobs
	src.update_icons = update_icons
	src.update_overlays = update_overlays

/datum/component/swabbing/Destroy()
	. = ..()
	for(var/swabbed in swabbed_items)
		qdel(swabbed)
	QDEL_NULL(update_icons)
	QDEL_NULL(update_overlays)

///Changes examine based on your sample
/datum/component/swabbing/proc/examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER
	if(LAZYLEN(swabbed_items))
		examine_list += span_nicegreen("There is a microbiological sample on [parent]!")
		examine_list += "[span_notice("You can see the following micro-organisms:")]\n"
		for(var/i in swabbed_items)
			var/datum/biological_sample/samp = i
			for(var/organism in samp.micro_organisms)
				var/datum/micro_organism/MO = organism
				examine_list += MO.get_details()

///Ran when you attack an object, tries to get a swab of the object. if a swabbable surface is found it will run behavior and hopefully
/datum/component/swabbing/proc/try_to_swab(datum/source, atom/target, mob/user, params)
	SIGNAL_HANDLER

	if(istype(target, /obj/structure/table))//help how do i do this less shitty
		return NONE //idk bro pls send help

	if(istype(target, /obj/item/petri_dish))
		if(!LAZYLEN(swabbed_items))
			return NONE
		var/obj/item/petri_dish/dish = target
		if(dish.sample)
			return

		var/datum/biological_sample/deposited_sample

		for(var/i in swabbed_items) //Typed in case there is a non sample on the swabbing tool because someone was fucking with swabbable element
			if(!istype(i, /datum/biological_sample/))
				stack_trace("Non biological sample being swabbed, no bueno.")
				continue
			var/datum/biological_sample/sample = i
			//Collapse the samples into one sample; one gooey mess essentialy.
			if(!deposited_sample)
				deposited_sample = sample
			else
				deposited_sample.Merge(sample)

		dish.deposit_sample(user, deposited_sample)
		LAZYCLEARLIST(swabbed_items)

		var/obj/item/I = parent
		I.update_appearance()

		return COMPONENT_CANCEL_ATTACK_CHAIN
	if(!can_swab(target))
		return NONE //Just do the normal attack.

	. = COMPONENT_CANCEL_ATTACK_CHAIN //Point of no return. No more attacking after this.

	if(LAZYLEN(swabbed_items))
		to_chat(user, span_warning("You cannot collect another sample on [parent]!"))
		return

	to_chat(user, span_notice("You start swabbing [target] for samples!"))
	INVOKE_ASYNC(src, .proc/async_try_to_swab, target, user)

/datum/component/swabbing/proc/async_try_to_swab(atom/target, mob/user)
	if(!do_after(user, 3 SECONDS, target)) // Start swabbing boi
		return

	LAZYINITLIST(swabbed_items) //If it isn't initialized, initialize it. As we need to pass it by reference

	if(SEND_SIGNAL(target, COMSIG_SWAB_FOR_SAMPLES, swabbed_items) == NONE) //If we found something to swab now we let the swabbed thing handle what it would do, we just sit back and relax now.
		to_chat(user, span_warning("You do not manage to find a anything on [target]!"))
		return

	to_chat(user, span_nicegreen("You manage to collect a microbiological sample from [target]!"))

	var/obj/item/parent_item = parent
	parent_item.update_appearance()

///Checks if the swabbing component can swab the specific object or nots
/datum/component/swabbing/proc/can_swab(atom/target)
	if(isobj(target))
		return can_swab_objs
	if(isturf(target))
		return can_swab_turfs
	if(ismob(target))
		return can_swab_mobs

///Handle any special overlay cases on the item itself
/datum/component/swabbing/proc/handle_overlays(datum/source, list/overlays)
	SIGNAL_HANDLER
	update_overlays?.Invoke(overlays, swabbed_items)

///Handle any special icon cases on the item itself
/datum/component/swabbing/proc/handle_icon(datum/source)
	SIGNAL_HANDLER
	update_icons?.Invoke(swabbed_items)


code:
/datum/component/swarming
	var/offset_x = 0
	var/offset_y = 0
	var/is_swarming = FALSE
	var/list/swarm_members = list()
	var/static/list/swarming_loc_connections = list(
		COMSIG_ATOM_EXITED =.proc/leave_swarm,
		COMSIG_ATOM_ENTERED = .proc/join_swarm
	)

/datum/component/swarming/Initialize(max_x = 24, max_y = 24)
	if(!ismovable(parent))
		return COMPONENT_INCOMPATIBLE
	offset_x = rand(-max_x, max_x)
	offset_y = rand(-max_y, max_y)

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

/datum/component/swarming/Destroy()
	for(var/other in swarm_members)
		var/datum/component/swarming/other_swarm = other
		other_swarm.swarm_members -= src
		if(!other_swarm.swarm_members.len)
			other_swarm.unswarm()
	swarm_members = null
	return ..()

/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
	SIGNAL_HANDLER

	var/datum/component/swarming/other_swarm = arrived.GetComponent(/datum/component/swarming)
	if(!other_swarm)
		return
	swarm()
	swarm_members |= other_swarm
	other_swarm.swarm()
	other_swarm.swarm_members |= src

/datum/component/swarming/proc/leave_swarm(datum/source, atom/movable/gone, direction)
	SIGNAL_HANDLER

	var/datum/component/swarming/other_swarm = gone.GetComponent(/datum/component/swarming)
	if(!other_swarm || !(other_swarm in swarm_members))
		return
	swarm_members -= other_swarm
	if(!swarm_members.len)
		unswarm()
	other_swarm.swarm_members -= src
	if(!other_swarm.swarm_members.len)
		other_swarm.unswarm()

/datum/component/swarming/proc/swarm()
	var/atom/movable/owner = parent
	if(!is_swarming)
		is_swarming = TRUE
		animate(owner, pixel_x = owner.pixel_x + offset_x, pixel_y = owner.pixel_y + offset_y, time = 2)

/datum/component/swarming/proc/unswarm()
	var/atom/movable/owner = parent
	if(is_swarming)
		animate(owner, pixel_x = owner.pixel_x - offset_x, pixel_y = owner.pixel_y - offset_y, time = 2)
		is_swarming = FALSE


code:
/// how many things can we knock off a table at once by diving into it?
#define MAX_TABLE_MESSES 12

/**
 * For when you want to throw a person at something and have fun stuff happen
 *
 * This component is made for carbon mobs (really, humans), and allows its parent to throw themselves and perform tackles. This is done by enabling throw mode, then clicking on your
 *   intended target with an empty hand. You will then launch toward your target. If you hit a carbon, you'll roll to see how hard you hit them. If you hit a solid non-mob, you'll
 *   roll to see how badly you just messed yourself up. If, along your journey, you hit a table, you'll slam onto it and send up to MAX_TABLE_MESSES (8) /obj/items on the table flying,
 *   and take a bit of extra damage and stun for each thing launched.
 *
 * There are 2 separate """skill rolls""" involved here, which are handled and explained in [rollTackle()][/datum/component/tackler/proc/rollTackle] (for roll 1, carbons), and [splat()][/datum/component/tackler/proc/splat] (for roll 2, walls and solid objects)
*/
/datum/component/tackler
	dupe_mode = COMPONENT_DUPE_UNIQUE

	///If we're currently tackling or are on cooldown. Actually, shit, if I use this to handle cooldowns, then getting thrown by something while on cooldown will count as a tackle..... whatever, i'll fix that next commit
	var/tackling = TRUE
	///How much stamina it takes to launch a tackle
	var/stamina_cost
	///Launching a tackle calls Knockdown on you for this long, so this is your cooldown. Once you stand back up, you can tackle again.
	var/base_knockdown
	///Your max range for how far you can tackle.
	var/range
	///How fast you sail through the air. Standard tackles are 1 speed, but gloves that throw you faster come at a cost: higher speeds make it more likely you'll be badly injured if you fly into a non-mob obstacle.
	var/speed
	///A flat modifier to your roll against your target, as described in [rollTackle()][/datum/component/tackler/proc/rollTackle]. Slightly misleading, skills aren't relevant here, this is a matter of what type of gloves (or whatever) is granting you the ability to tackle.
	var/skill_mod
	///Some gloves, generally ones that increase mobility, may have a minimum distance to fly. Rocket gloves are especially dangerous with this, be sure you'll hit your target or have a clear background if you miss, or else!
	var/min_distance
	///The throwdatum we're currently dealing with, if we need it
	var/datum/thrownthing/tackle

/datum/component/tackler/Initialize(stamina_cost = 25, base_knockdown = 1 SECONDS, range = 4, speed = 1, skill_mod = 0, min_distance = min_distance)
	if(!iscarbon(parent))
		return COMPONENT_INCOMPATIBLE

	src.stamina_cost = stamina_cost
	src.base_knockdown = base_knockdown
	src.range = range
	src.speed = speed
	src.skill_mod = skill_mod
	src.min_distance = min_distance

	var/mob/P = parent
	to_chat(P, span_notice("You are now able to launch tackles! You can do so by activating throw intent, and clicking on your target with an empty hand."))

	addtimer(CALLBACK(src, .proc/resetTackle), base_knockdown, TIMER_STOPPABLE)

/datum/component/tackler/Destroy()
	var/mob/P = parent
	to_chat(P, span_notice("You can no longer tackle."))
	return ..()

/datum/component/tackler/RegisterWithParent()
	RegisterSignal(parent, COMSIG_MOB_CLICKON, .proc/checkTackle)
	RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, .proc/sack)
	RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/registerTackle)

/datum/component/tackler/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_MOB_CLICKON, COMSIG_MOVABLE_IMPACT, COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_POST_THROW))

///Store the thrownthing datum for later use
/datum/component/tackler/proc/registerTackle(mob/living/carbon/user, datum/thrownthing/TT)
	SIGNAL_HANDLER

	tackle = TT
	tackle.thrower = user

///See if we can tackle or not. If we can, leap!
/datum/component/tackler/proc/checkTackle(mob/living/carbon/user, atom/A, list/modifiers)
	SIGNAL_HANDLER

	if(modifiers[ALT_CLICK] || modifiers[SHIFT_CLICK] || modifiers[CTRL_CLICK] || modifiers[MIDDLE_CLICK])
		return

	if(!user.throw_mode || user.get_active_held_item() || user.pulling || user.buckled || user.incapacitated())
		return

	if(!A || !(isturf(A) || isturf(A.loc)))
		return

	if(HAS_TRAIT(user, TRAIT_HULK))
		to_chat(user, span_warning("You're too angry to remember how to tackle!"))
		return

	if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
		to_chat(user, span_warning("You need free use of your hands to tackle!"))
		return

	if(user.body_position == LYING_DOWN)
		to_chat(user, span_warning("You must be standing to tackle!"))
		return

	if(tackling)
		to_chat(user, span_warning("You're not ready to tackle!"))
		return

	if(user.has_movespeed_modifier(/datum/movespeed_modifier/shove)) // can't tackle if you just got shoved
		to_chat(user, span_warning("You're too off balance to tackle!"))
		return

	user.face_atom(A)

	tackling = TRUE
	RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/checkObstacle)
	playsound(user, 'sound/weapons/thudswoosh.ogg', 40, TRUE, -1)

	var/leap_word = isfelinid(user) ? "pounce" : "leap" //If cat, "pounce" instead of "leap".
	if(can_see(user, A, 7))
		user.visible_message(span_warning("[user] [leap_word]s at [A]!"), span_danger("You [leap_word] at [A]!"))
	else
		user.visible_message(span_warning("[user] [leap_word]s!"), span_danger("You [leap_word]!"))

	if(get_dist(user, A) < min_distance)
		A = get_ranged_target_turf(user, get_dir(user, A), min_distance) //TODO: this only works in cardinals/diagonals, make it work with in-betweens too!

	user.Knockdown(base_knockdown, ignore_canstun = TRUE)
	user.adjustStaminaLoss(stamina_cost)
	user.throw_at(A, range, speed, user, FALSE)
	addtimer(CALLBACK(src, .proc/resetTackle), base_knockdown, TIMER_STOPPABLE)
	return(COMSIG_MOB_CANCEL_CLICKON)

/**
 * sack() is called when you actually smack into something, assuming we're mid-tackle. First it deals with smacking into non-carbons, in two cases:
 * * If it's a non-carbon mob, we don't care, get out of here and do normal thrown-into-mob stuff
 * * Else, if it's something dense (walls, machinery, structures, most things other than the floor), go to [/datum/component/tackler/proc/splat] and get ready for some high grade shit
 *
 * If it's a carbon we hit, we'll call rollTackle() which rolls a die and calculates modifiers for both the tackler and target, then gives us a number. Negatives favor the target, while positives favor the tackler.
 * Check [rollTackle()][/datum/component/tackler/proc/rollTackle] for a more thorough explanation on the modifiers at play.
 *
 * Then, we figure out what effect we want, and we get to work! Note that with standard gripper gloves and no modifiers, the range of rolls is (-3, 3). The results are as follows, based on what we rolled:
 * * -inf to -5: Seriously botched tackle, tackler suffers a concussion, brute damage, and a 3 second paralyze, target suffers nothing
 * * -4 to -2: weak tackle, tackler gets 3 second knockdown, target gets shove slowdown but is otherwise fine
 * * -1 to 0: decent tackle, tackler gets up a bit quicker than the target
 * * 1: solid tackle, tackler has more of an advantage getting up quicker
 * * 2 to 4: expert tackle, tackler has sizeable advantage and lands on their feet with a free passive grab
 * * 5 to inf: MONSTER tackle, tackler gets up immediately and gets a free aggressive grab, target takes sizeable stamina damage from the hit and is paralyzed for one and a half seconds and knocked down for three seconds
 *
 * Finally, we return a bitflag to [COMSIG_MOVABLE_IMPACT] that forces the hitpush to false so that we don't knock them away.
*/
/datum/component/tackler/proc/sack(mob/living/carbon/user, atom/hit)
	SIGNAL_HANDLER

	if(!tackling || !tackle)
		return

	user.toggle_throw_mode()
	if(!iscarbon(hit))
		if(hit.density)
			INVOKE_ASYNC(src, .proc/splat, user, hit)
		return

	var/mob/living/carbon/target = hit
	var/mob/living/carbon/human/T = target
	var/mob/living/carbon/human/S = user
	var/tackle_word = isfelinid(user) ? "pounce" : "tackle" //If cat, "pounce" instead of "tackle".

	var/roll = rollTackle(target)
	tackling = FALSE
	tackle.gentle = TRUE

	switch(roll)
		if(-INFINITY to -5)
			user.visible_message(span_danger("[user] botches [user.p_their()] [tackle_word] and slams [user.p_their()] head into [target], knocking [user.p_them()]self silly!"), span_userdanger("You botch your [tackle_word] and slam your head into [target], knocking yourself silly!"), ignored_mobs = target)
			to_chat(target, span_userdanger("[user] botches [user.p_their()] [tackle_word] and slams [user.p_their()] head into you, knocking [user.p_them()]self silly!"))

			user.Paralyze(30)
			var/obj/item/bodypart/head/hed = user.get_bodypart(BODY_ZONE_HEAD)
			if(hed)
				hed.receive_damage(brute=15, updating_health=TRUE, wound_bonus = CANT_WOUND)
			user.gain_trauma(/datum/brain_trauma/mild/concussion)

		if(-4 to -2) // glancing blow at best
			user.visible_message(span_warning("[user] lands a weak [tackle_word] on [target], briefly knocking [target.p_them()] off-balance!"), span_userdanger("You land a weak [tackle_word] on [target], briefly knocking [target.p_them()] off-balance!"), ignored_mobs = target)
			to_chat(target, span_userdanger("[user] lands a weak [tackle_word] on you, briefly knocking you off-balance!"))

			user.Knockdown(30)
			if(ishuman(target) && !T.has_movespeed_modifier(/datum/movespeed_modifier/shove))
				T.add_movespeed_modifier(/datum/movespeed_modifier/shove) // maybe define a slightly more severe/longer slowdown for this
				addtimer(CALLBACK(T, /mob/living/carbon/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH * 2)

		if(-1 to 0) // decent hit, both parties are about equally inconvenienced
			user.visible_message(span_warning("[user] lands a passable [tackle_word] on [target], sending them both tumbling!"), span_userdanger("You land a passable [tackle_word] on [target], sending you both tumbling!"), ignored_mobs = target)
			to_chat(target, span_userdanger("[user] lands a passable [tackle_word] on you, sending you both tumbling!"))

			target.adjustStaminaLoss(stamina_cost)
			target.Paralyze(5)
			user.Knockdown(20)
			target.Knockdown(25)

		if(1 to 2) // solid hit, tackler has a slight advantage
			user.visible_message(span_warning("[user] lands a solid [tackle_word] on [target], knocking them both down hard!"), span_userdanger("You land a solid [tackle_word] on [target], knocking you both down hard!"), ignored_mobs = target)
			to_chat(target, span_userdanger("[user] lands a solid [tackle_word] on you, knocking you both down hard!"))

			target.adjustStaminaLoss(30)
			target.Paralyze(5)
			user.Knockdown(10)
			target.Knockdown(20)

		if(3 to 4) // really good hit, the target is definitely worse off here. Without positive modifiers, this is as good a tackle as you can land
			user.visible_message(span_warning("[user] lands an expert [tackle_word] on [target], knocking [target.p_them()] down hard while landing on [user.p_their()] feet with a passive grip!"), span_userdanger("You land an expert [tackle_word] on [target], knocking [target.p_them()] down hard while landing on your feet with a passive grip!"), ignored_mobs = target)
			to_chat(target, span_userdanger("[user] lands an expert [tackle_word] on you, knocking you down hard and maintaining a passive grab!"))

			user.SetKnockdown(0)
			user.get_up(TRUE)
			user.forceMove(get_turf(target))
			target.adjustStaminaLoss(40)
			target.Paralyze(5)
			target.Knockdown(30)
			if(ishuman(target) && ishuman(user))
				INVOKE_ASYNC(S.dna.species, /datum/species.proc/grab, S, T)
				S.setGrabState(GRAB_PASSIVE)

		if(5 to INFINITY) // absolutely BODIED
			user.visible_message(span_warning("[user] lands a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!"), span_userdanger("You land a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!"), ignored_mobs = target)
			to_chat(target, span_userdanger("[user] lands a monster [tackle_word] on you, knocking you senseless and aggressively pinning you!"))

			user.SetKnockdown(0)
			user.get_up(TRUE)
			user.forceMove(get_turf(target))
			target.adjustStaminaLoss(40)
			target.Paralyze(5)
			target.Knockdown(30)
			if(ishuman(target) && ishuman(user))
				INVOKE_ASYNC(S.dna.species, /datum/species.proc/grab, S, T)
				S.setGrabState(GRAB_AGGRESSIVE)

	return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH

/**
 * This handles all of the modifiers for the actual carbon-on-carbon tackling, and gets its own proc because of how many there are (with plenty more in mind!)
 *
 * The base roll is between (-3, 3), with negative numbers favoring the target, and positive numbers favoring the tackler. The target and the tackler are both assessed for
 * how easy they are to knock over, with clumsiness and dwarfiness being strong maluses for each, and gigantism giving a bonus for each. These numbers and ideas
 * are absolutely subject to change.

 * In addition, after subtracting the defender's mod and adding the attacker's mod to the roll, the component's base (skill) mod is added as well. Some sources of tackles
 * are better at taking people down, like the bruiser and rocket gloves, while the dolphin gloves have a malus in exchange for better mobility.
*/
/datum/component/tackler/proc/rollTackle(mob/living/carbon/target)
	var/defense_mod = 0
	var/attack_mod = 0

	// DE-FENSE
	if(target.drunkenness > 60) // drunks are easier to knock off balance
		defense_mod -= 3
	else if(target.drunkenness > 30)
		defense_mod -= 1
	if(HAS_TRAIT(target, TRAIT_CLUMSY))
		defense_mod -= 2
	if(HAS_TRAIT(target, TRAIT_FAT)) // chonkers are harder to knock over
		defense_mod += 1
	if(HAS_TRAIT(target, TRAIT_GRABWEAKNESS))
		defense_mod -= 2
	if(HAS_TRAIT(target, TRAIT_DWARF))
		defense_mod -= 2
	if(HAS_TRAIT(target, TRAIT_GIANT))
		defense_mod += 2
	if(target.get_organic_health() < 50)
		defense_mod -= 1

	var/leg_wounds = 0 // -1 defense per 2 leg wounds
	for(var/i in target.all_wounds)
		var/datum/wound/iterwound = i
		if((iterwound.limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)))
			leg_wounds++
	defense_mod -= round(leg_wounds * 0.5)

	if(ishuman(target))
		var/mob/living/carbon/human/T = target
		var/suit_slot = T.get_item_by_slot(ITEM_SLOT_OCLOTHING)

		if(isnull(T.wear_suit) && isnull(T.w_uniform)) // who honestly puts all of their effort into tackling a naked guy?
			defense_mod += 2
		if(suit_slot && (istype(suit_slot,/obj/item/clothing/suit/space/hardsuit)))
			defense_mod += 1
		if(T.is_shove_knockdown_blocked()) // riot armor and such
			defense_mod += 5
		if(T.is_holding_item_of_type(/obj/item/shield))
			defense_mod += 2

		if(islizard(T))
			if(!T.getorganslot(ORGAN_SLOT_TAIL)) // lizards without tails are off-balance
				defense_mod -= 1
			else if(T.dna.species.is_wagging_tail()) // lizard tail wagging is robust and can swat away assailants!
				defense_mod += 1

	// OF-FENSE
	var/mob/living/carbon/sacker = parent

	if(sacker.drunkenness > 60) // you're far too drunk to hold back!
		attack_mod += 1
	else if(sacker.drunkenness > 30) // if you're only a bit drunk though, you're just sloppy
		attack_mod -= 1
	if(HAS_TRAIT(sacker, TRAIT_CLUMSY))
		attack_mod -= 2
	if(HAS_TRAIT(sacker, TRAIT_DWARF))
		attack_mod -= 2
	if(HAS_TRAIT(sacker, TRAIT_GIANT))
		attack_mod += 2

	if(ishuman(target))
		var/mob/living/carbon/human/S = sacker

		var/suit_slot = S.get_item_by_slot(ITEM_SLOT_OCLOTHING)
		if(suit_slot && (istype(suit_slot,/obj/item/clothing/suit/armor/riot))) // tackling in riot armor is more effective, but tiring
			attack_mod += 2
			sacker.adjustStaminaLoss(20)

	var/r = rand(-3, 3) - defense_mod + attack_mod + skill_mod
	return r

/**
 * This is where we handle diving into dense atoms, generally with effects ranging from bad to REALLY bad. This works as a percentile roll that is modified in two steps as detailed below. The higher
 * the roll, the more severe the result.
 *
 * Mod 1: Speed-
 * * Base tackle speed is 1, which is what normal gripper gloves use. For other sources with higher speed tackles, like dolphin and ESPECIALLY rocket gloves, we obey Newton's laws and hit things harder.
 * * For every unit of speed above 1, move the lower bound of the roll up by 15. Unlike Mod 2, this only serves to raise the lower bound, so it can't be directly counteracted by anything you can control.
 *
 * Mod 2: Misc-
 * -Flat modifiers, these take whatever you rolled and add/subtract to it, with the end result capped between the minimum from Mod 1 and 100. Note that since we can't roll higher than 100 to start with,
 * wearing a helmet should be enough to remove any chance of permanently paralyzing yourself and dramatically lessen knocking yourself unconscious, even with rocket gloves. Will expand on maybe
 * * Wearing a helmet: -6
 * * Wearing riot armor: -6
 * * Clumsy: +6
 *
 * Effects: Below are the outcomes based off your roll, in order of increasing severity
 *
 * * 1-67: Knocked down for a few seconds and a bit of brute and stamina damage
 * * 68-85: Knocked silly, gain some confusion as well as the above
 * * 86-92: Cranial trauma, get a concussion and more confusion, plus more damage
 * * 93-96: Knocked unconscious, get a random mild brain trauma, as well as a fair amount of damage
 * * 97-98: Massive head damage, probably crack your skull open, random mild brain trauma
 * * 99-Infinity: Break your spinal cord, get paralyzed, take a bunch of damage too. Very unlucky!
*/
/datum/component/tackler/proc/splat(mob/living/carbon/user, atom/hit)
	if(istype(hit, /obj/machinery/vending)) // before we do anything else-
		var/obj/machinery/vending/darth_vendor = hit
		darth_vendor.tilt(user, TRUE)
		return
	else if(istype(hit, /obj/structure/window))
		var/obj/structure/window/W = hit
		splatWindow(user, W)
		if(QDELETED(W))
			return COMPONENT_MOVABLE_IMPACT_NEVERMIND
		return

	var/oopsie_mod = 0
	var/danger_zone = (speed - 1) * 13 // for every extra speed we have over 1, take away 13 of the safest chance
	danger_zone = max(min(danger_zone, 100), 1)

	if(ishuman(user))
		var/mob/living/carbon/human/S = user
		var/head_slot = S.get_item_by_slot(ITEM_SLOT_HEAD)
		var/suit_slot = S.get_item_by_slot(ITEM_SLOT_OCLOTHING)
		if(head_slot && (istype(head_slot,/obj/item/clothing/head/helmet) || istype(head_slot,/obj/item/clothing/head/hardhat)))
			oopsie_mod -= 6
		if(suit_slot && (istype(suit_slot,/obj/item/clothing/suit/armor/riot)))
			oopsie_mod -= 6

	if(HAS_TRAIT(user, TRAIT_CLUMSY))
		oopsie_mod += 6 //honk!

	var/oopsie = rand(danger_zone, 100)
	if(oopsie >= 94 && oopsie_mod < 0) // good job avoiding getting paralyzed! gold star!
		to_chat(user, span_notice("You're really glad you're wearing protection!"))
	oopsie += oopsie_mod

	switch(oopsie)
		if(99 to INFINITY)
			// can you imagine standing around minding your own business when all of the sudden some guy fucking launches himself into a wall at full speed and irreparably paralyzes himself?
			user.visible_message(span_danger("[user] slams face-first into [hit] at an awkward angle, severing [user.p_their()] spinal column with a sickening crack! Fucking shit!"), span_userdanger("You slam face-first into [hit] at an awkward angle, severing your spinal column with a sickening crack! Fucking shit!"))
			var/obj/item/bodypart/head/hed = user.get_bodypart(BODY_ZONE_HEAD)
			if(hed)
				hed.receive_damage(brute=40, updating_health=FALSE, wound_bonus = 40)
			else
				user.adjustBruteLoss(40, updating_health=FALSE)
			user.adjustStaminaLoss(30)
			playsound(user, 'sound/effects/blobattack.ogg', 60, TRUE)
			playsound(user, 'sound/effects/splat.ogg', 70, TRUE)
			playsound(user, 'sound/effects/wounds/crack2.ogg', 70, TRUE)
			user.emote("scream")
			user.gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) // oopsie indeed!
			shake_camera(user, 7, 7)
			user.flash_act(1, TRUE, TRUE, length = 4.5)

		if(97 to 98)
			user.visible_message(span_danger("[user] slams skull-first into [hit] with a sound like crumpled paper, revealing a horrifying breakage in [user.p_their()] cranium! Holy shit!"), span_userdanger("You slam skull-first into [hit] and your senses are filled with warm goo flooding across your face! Your skull is open!"))
			var/obj/item/bodypart/head/hed = user.get_bodypart(BODY_ZONE_HEAD)
			if(hed)
				hed.receive_damage(brute=30, updating_health=FALSE, wound_bonus = 25)
			else
				user.adjustBruteLoss(40, updating_health=FALSE)
			user.adjustStaminaLoss(30)
			user.gain_trauma_type(BRAIN_TRAUMA_MILD)
			playsound(user, 'sound/effects/blobattack.ogg', 60, TRUE)
			playsound(user, 'sound/effects/splat.ogg', 70, TRUE)
			user.emote("gurgle")
			shake_camera(user, 7, 7)
			user.flash_act(1, TRUE, TRUE, length = 4.5)

		if(93 to 96)
			user.visible_message(span_danger("[user] slams face-first into [hit] with a concerning squish, immediately going limp!"), span_userdanger("You slam face-first into [hit], and immediately lose consciousness!"))
			user.adjustStaminaLoss(30)
			user.adjustBruteLoss(30)
			user.Unconscious(100)
			user.gain_trauma_type(BRAIN_TRAUMA_MILD)
			user.playsound_local(get_turf(user), 'sound/weapons/flashbang.ogg', 100, TRUE, 8)
			shake_camera(user, 6, 6)
			user.flash_act(1, TRUE, TRUE, length = 3.5)

		if(86 to 92)
			user.visible_message(span_danger("[user] slams head-first into [hit], suffering major cranial trauma!"), span_userdanger("You slam head-first into [hit], and the world explodes around you!"))
			user.adjustStaminaLoss(30, updating_health=FALSE)
			user.adjustBruteLoss(30)
			user.add_confusion(15)
			if(prob(80))
				user.gain_trauma(/datum/brain_trauma/mild/concussion)
			user.playsound_local(get_turf(user), 'sound/weapons/flashbang.ogg', 100, TRUE, 8)
			user.Knockdown(40)
			shake_camera(user, 5, 5)
			user.flash_act(1, TRUE, TRUE, length = 2.5)

		if(68 to 85)
			user.visible_message(span_danger("[user] slams hard into [hit], knocking [user.p_them()] senseless!"), span_userdanger("You slam hard into [hit], knocking yourself senseless!"))
			user.adjustStaminaLoss(30, updating_health=FALSE)
			user.adjustBruteLoss(10)
			user.add_confusion(10)
			user.Knockdown(30)
			shake_camera(user, 3, 4)

		if(1 to 67)
			user.visible_message(span_danger("[user] slams into [hit]!"), span_userdanger("You slam into [hit]!"))
			user.adjustStaminaLoss(20, updating_health=FALSE)
			user.adjustBruteLoss(10)
			user.Knockdown(20)
			shake_camera(user, 2, 2)

	playsound(user, 'sound/weapons/smash.ogg', 70, TRUE)

/datum/component/tackler/proc/resetTackle()
	tackling = FALSE
	QDEL_NULL(tackle)
	UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)

///A special case for splatting for handling windows
/datum/component/tackler/proc/splatWindow(mob/living/carbon/user, obj/structure/window/W)
	playsound(user, 'sound/effects/Glasshit.ogg', 140, TRUE)

	if(W.type in list(/obj/structure/window, /obj/structure/window/fulltile, /obj/structure/window/unanchored, /obj/structure/window/fulltile/unanchored)) // boring unreinforced windows
		for(var/i = 0, i < speed, i++)
			var/obj/item/shard/shard = new /obj/item/shard(get_turf(user))
			shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult=3, pain_chance=5)
			shard.updateEmbedding()
			user.hitby(shard, skipcatch = TRUE, hitpush = FALSE)
			shard.embedding = null
			shard.updateEmbedding()
		W.atom_destruction()
		user.adjustStaminaLoss(10 * speed)
		user.Paralyze(30)
		user.visible_message(span_danger("[user] smacks into [W] and shatters it, shredding [user.p_them()]self with glass!"), span_userdanger("You smacks into [W] and shatter it, shredding yourself with glass!"))

	else
		user.visible_message(span_danger("[user] smacks into [W] like a bug!"), span_userdanger("You smacks into [W] like a bug!"))
		user.Paralyze(10)
		user.Knockdown(30)
		W.take_damage(30 * speed)
		user.adjustStaminaLoss(10 * speed, updating_health=FALSE)
		user.adjustBruteLoss(5 * speed)

/datum/component/tackler/proc/delayedSmash(obj/structure/window/W)
	if(W)
		W.atom_destruction()
		playsound(W, "shatter", 70, TRUE)

///Check to see if we hit a table, and if so, make a big mess!
/datum/component/tackler/proc/checkObstacle(mob/living/carbon/owner)
	SIGNAL_HANDLER

	if(!tackling)
		return

	var/turf/T = get_turf(owner)
	var/obj/structure/table/kevved = locate(/obj/structure/table) in T.contents
	if(!kevved)
		return

	var/list/messes = list()

	// we split the mess-making into two parts (check what we're gonna send flying, intermission for dealing with the tackler, then actually send stuff flying) for the benefit of making sure the face-slam text
	// comes before the list of stuff that goes flying, but can still adjust text + damage to how much of a mess it made
	for(var/obj/item/I in T.contents)
		if(!I.anchored)
			messes += I
			if(messes.len >= MAX_TABLE_MESSES)
				break

	// for telling HOW big of a mess we just made
	var/HOW_big_of_a_miss_did_we_just_make = ""
	if(messes.len)
		if(messes.len < MAX_TABLE_MESSES / 4)
			HOW_big_of_a_miss_did_we_just_make = ", making a mess"
		else if(messes.len < MAX_TABLE_MESSES / 2)
			HOW_big_of_a_miss_did_we_just_make = ", making a big mess"
		else if(messes.len < MAX_TABLE_MESSES)
			HOW_big_of_a_miss_did_we_just_make = ", making a giant mess"
		else
			HOW_big_of_a_miss_did_we_just_make = ", making a ginormous mess!" // an extra exclamation point!! for emphasis!!!

	owner.visible_message(span_danger("[owner] trips over [kevved] and slams into it face-first[HOW_big_of_a_miss_did_we_just_make]!"), span_userdanger("You trip over [kevved] and slam into it face-first[HOW_big_of_a_miss_did_we_just_make]!"))
	owner.adjustStaminaLoss(15 + messes.len * 2, FALSE)
	owner.adjustBruteLoss(8 + messes.len)
	owner.Paralyze(0.4 SECONDS * messes.len) // .4 seconds of paralyze for each thing you knock around
	owner.Knockdown(2 SECONDS + 0.4 SECONDS * messes.len) // 2 seconds of knockdown after the paralyze

	for(var/obj/item/I in messes)
		var/dist = rand(1, 3)
		var/sp = 2
		if(prob(25 * (src.speed - 1))) // if our tackle speed is higher than 1, with chance (speed - 1 * 25%), throw the thing at our tackle speed + 1
			sp = speed + 1
		I.throw_at(get_ranged_target_turf(I, pick(GLOB.alldirs), range = dist), range = dist, speed = sp)
		I.visible_message(span_danger("[I] goes flying[sp > 3 ? " dangerously fast" : ""]!")) // standard embed speed

	playsound(owner, 'sound/weapons/smash.ogg', 70, TRUE)
	tackle.finalize(hit=TRUE)
	resetTackle()

#undef MAX_TABLE_MESSES


code:
/datum/component/tactical
	var/allowed_slot

/datum/component/tactical/Initialize(allowed_slot)
	if(!isitem(parent))
		return COMPONENT_INCOMPATIBLE

	src.allowed_slot = allowed_slot

/datum/component/tactical/RegisterWithParent()
	RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/modify)
	RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/unmodify)

/datum/component/tactical/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
	unmodify()

/datum/component/tactical/Destroy()
	unmodify()
	return ..()

/datum/component/tactical/proc/modify(obj/item/source, mob/user, slot)
	SIGNAL_HANDLER

	if(allowed_slot && slot != allowed_slot)
		unmodify()
		return

	var/obj/item/master = parent
	var/image/I = image(icon = master.icon, icon_state = master.icon_state, loc = user)
	I.copy_overlays(master)
	I.override = TRUE
	source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I)
	I.layer = ABOVE_MOB_LAYER

/datum/component/tactical/proc/unmodify(obj/item/source, mob/user)
	SIGNAL_HANDLER

	var/obj/item/master = source || parent
	if(!user)
		if(!ismob(master.loc))
			return
		user = master.loc

	user.remove_alt_appearance("sneaking_mission")


code:
///This component lets you make specific mobs tameable by feeding them
/datum/component/tameable
	///Are we domesticated?
	var/tame = FALSE
	///What the mob eats, typically used for taming or animal husbandry.
	var/list/food_types
	///Starting success chance for taming.
	var/tame_chance
	///Added success chance after every failed tame attempt.
	var/bonus_tame_chance
	///For effects once soemthing is tamed
	var/datum/callback/after_tame

/datum/component/tameable/Initialize(food_types, tame_chance, bonus_tame_chance, datum/callback/after_tame)
	if(!isatom(parent)) //yes, you could make a tameable toolbox.
		return COMPONENT_INCOMPATIBLE

	if(food_types)
		src.food_types = food_types
	if(tame_chance)
		src.tame_chance = tame_chance
	if(bonus_tame_chance)
		src.bonus_tame_chance = bonus_tame_chance
	if(after_tame)
		src.after_tame = after_tame

	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/try_tame)
	RegisterSignal(parent, COMSIG_SIMPLEMOB_SENTIENCEPOTION, .proc/on_tame) //Instantly succeeds

/datum/component/tameable/proc/try_tame(datum/source, obj/item/food, mob/living/attacker, params)
	SIGNAL_HANDLER
	if(!is_type_in_list(food, food_types))
		return
	if(isliving(source))
		var/mob/living/potentially_dead_horse = source
		if(potentially_dead_horse.stat == DEAD)
			to_chat(attacker, span_warning("[parent] is dead!"))
			return COMPONENT_CANCEL_ATTACK_CHAIN

	attacker.visible_message(span_notice("[attacker] hand-feeds [food] to [parent]."), span_notice("You hand-feed [food] to [parent]."))
	qdel(food)
	if(tame)
		return COMPONENT_CANCEL_ATTACK_CHAIN
	if (prob(tame_chance)) //note: lack of feedback message is deliberate, keep them guessing!
		on_tame(attacker)
	else
		tame_chance += bonus_tame_chance
	return COMPONENT_CANCEL_ATTACK_CHAIN

///Ran once taming succeeds
/datum/component/tameable/proc/on_tame(mob/living/tamer)
	SIGNAL_HANDLER
	tame = TRUE

	after_tame?.Invoke(tamer)//Run custom behavior if needed

	if(ishostile(parent) && isliving(tamer)) //Kinda shit check but this only applies to hostiles atm
		var/mob/living/simple_animal/hostile/evil_but_now_not_evil = parent
		evil_but_now_not_evil.friends = tamer
		evil_but_now_not_evil.faction = tamer.faction.Copy()

	qdel(src)


code:
/**
 * ### engraved component!
 *
 * component for walls that applies an engraved overlay and lets you examine it to read a story (+ art element yay)
 * new creations will get a high art value, cross round scrawlings will get a low one.
 * MUST be a component, though it doesn't look like it. SSPersistence demandeth
 */
/datum/component/tattoo
	///the generated story string
	var/tattoo_description

/datum/component/tattoo/Initialize(tattoo_description)
	. = ..()
	if(!isbodypart(parent))
		return COMPONENT_INCOMPATIBLE
	var/obj/item/bodypart/tatted_limb = parent

	if(!tattoo_description)
		///okay, i need to add some way to saved tattoos
		return COMPONENT_INCOMPATIBLE
	src.tattoo_description = tattoo_description
	tatted_limb.AddElement(/datum/element/art/commoner, 15)

	if(tatted_limb.owner)
		setup_tatted_owner(tatted_limb.owner)

/datum/component/tattoo/Destroy(force, silent)
	. = ..()
	var/obj/item/bodypart/tatted_limb = parent
	if(tatted_limb.owner)
		clear_tatted_owner(tatted_limb.owner)
	parent.RemoveElement(/datum/element/art/commoner)

/datum/component/tattoo/RegisterWithParent()
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)

/datum/component/tattoo/UnregisterFromParent()
	UnregisterSignal(parent, COMSIG_PARENT_EXAMINE)

/datum/component/tattoo/proc/on_examine(datum/source, mob/user, list/examine_list)
	SIGNAL_HANDLER
	examine_list += span_boldnotice(tattoo_description)

/datum/component/tattoo/proc/setup_tatted_owner(mob/living/carbon/new_owner)
	RegisterSignal(new_owner, COMSIG_PARENT_EXAMINE, .proc/on_bodypart_owner_examine)

/datum/component/tattoo/proc/clear_tatted_owner(mob/living/carbon/old_owner)
	UnregisterSignal(old_owner, COMSIG_PARENT_EXAMINE)

/datum/component/tattoo/proc/on_bodypart_owner_examine(mob/living/carbon/bodypart_owner, mob/user, list/examine_list)
	SIGNAL_HANDLER

	var/obj/item/bodypart/tatted_limb = parent
	for(var/obj/item/clothing/possibly_blocking in bodypart_owner.get_equipped_items())
		if(possibly_blocking.body_parts_covered & tatted_limb.body_part) //check to see if something is obscuring their tattoo.
			return

	examine_list += span_notice("[tatted_limb] of [bodypart_owner] has a tattoo!")
	examine_list += span_boldnotice(tattoo_description)


code:
/// You can't use machines unless they are in a whitelist
/datum/component/technointrovert
	can_transfer = TRUE
	/// Typecache of allowed machinery
	var/list/whitelist
	/// Message presented when
	var/message = "That %TARGET is strange! Let's avoid it."

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

/datum/component/technointrovert/RegisterWithParent()
	RegisterSignal(parent, COMSIG_TRY_USE_MACHINE, .proc/on_try_use_machine)
	RegisterSignal(parent, COMSIG_TRY_WIRES_INTERACT, .proc/on_try_wires_interact)

/datum/component/technointrovert/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_TRY_USE_MACHINE, COMSIG_TRY_WIRES_INTERACT))

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

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

/datum/component/technointrovert/proc/is_in_whitelist(datum/source, obj/machinery/machine)
	if(!is_type_in_typecache(machine, whitelist))
		to_chat(source, span_warning("[replacetext(message, "%TARGET", machine)]"))
		return FALSE
	return TRUE

/datum/component/technointrovert/proc/on_try_use_machine(datum/source, obj/machinery/machine)
	SIGNAL_HANDLER
	if(!is_in_whitelist(source, machine))
		return COMPONENT_CANT_USE_MACHINE_INTERACT | COMPONENT_CANT_USE_MACHINE_TOOLS

/datum/component/technointrovert/proc/on_try_wires_interact(datum/source, atom/machine)
	SIGNAL_HANDLER
	if(ismachinery(machine) && !is_in_whitelist(source, machine))
		return COMPONENT_CANT_INTERACT_WIRES


code:
/// You can't use machines when they've been touched within the last [unused_duration], unless it was by a mob in [whitelist]
/datum/component/technoshy
	can_transfer = TRUE
	/// How long in deciseconds the machine can be untouched for
	var/unused_duration = (2 MINUTES)
	/// Typecache of allowed last_users
	var/list/whitelist
	/// Message presented if the machine was used too recently
	var/message = "The %TARGET is way too fresh dude. Since we're like, <b>super</b> retro, we gotta wait for it to exit the mainstream."

/datum/component/technoshy/Initialize(unused_duration, message, whitelist)
	if(!ismob(parent))
		return COMPONENT_INCOMPATIBLE
	if(unused_duration)
		src.unused_duration = unused_duration
	if(message)
		src.message = message
	if(!whitelist)
		whitelist = typecacheof(parent.type)
	src.whitelist = whitelist

/datum/component/technoshy/RegisterWithParent()
	RegisterSignal(parent, COMSIG_TRY_USE_MACHINE, .proc/on_try_use_machine)
	RegisterSignal(parent, COMSIG_TRY_WIRES_INTERACT, .proc/on_try_wires_interact)

/datum/component/technoshy/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_TRY_USE_MACHINE, COMSIG_TRY_WIRES_INTERACT))

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

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

/datum/component/technoshy/proc/is_not_touched(datum/source, obj/machinery/machine)
	var/time_since = world.time - machine.last_used_time
	if(time_since < unused_duration && !isnull(machine.last_user_mobtype) && !is_type_in_typecache(machine.last_user_mobtype, whitelist))
		to_chat(source, span_warning("[replacetext(message, "%TARGET", machine)]"))
		return TRUE

/datum/component/technoshy/proc/on_try_use_machine(datum/source, obj/machinery/machine)
	SIGNAL_HANDLER
	if(is_not_touched(source, machine))
		return COMPONENT_CANT_USE_MACHINE_INTERACT | COMPONENT_CANT_USE_MACHINE_TOOLS

/datum/component/technoshy/proc/on_try_wires_interact(datum/source, atom/machine)
	SIGNAL_HANDLER
	if(!ismachinery(machine))
		return
	else if(is_not_touched(source, machine))
		return COMPONENT_CANT_INTERACT_WIRES


code:
/datum/component/tether
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
	var/atom/tether_target
	var/max_dist
	var/tether_name

/datum/component/tether/Initialize(atom/tether_target, max_dist = 4, tether_name)
	if(!isliving(parent) || !istype(tether_target) || !tether_target.loc)
		return COMPONENT_INCOMPATIBLE
	src.tether_target = tether_target
	src.max_dist = max_dist
	if (ispath(tether_name, /atom))
		var/atom/tmp = tether_name
		src.tether_name = initial(tmp.name)
	else
		src.tether_name = tether_name
	RegisterSignal(parent, list(COMSIG_MOVABLE_PRE_MOVE), .proc/checkTether)

/datum/component/tether/proc/checkTether(mob/mover, newloc)
	SIGNAL_HANDLER

	if (get_dist(mover,newloc) > max_dist)
		to_chat(mover, span_userdanger("The [tether_name] runs out of slack and prevents you from moving!"))
		return COMPONENT_MOVABLE_BLOCK_PRE_MOVE

	var/atom/blocker
	out:
		for(var/turf/T in get_line(tether_target,newloc))
			if (T.density)
				blocker = T
				break out
			for(var/a in T)
				var/atom/A = a
				if(A.density && A != mover && A != tether_target)
					blocker = A
					break out
	if (blocker)
		to_chat(mover, span_userdanger("The [tether_name] catches on [blocker] and prevents you from moving!"))
		return COMPONENT_MOVABLE_BLOCK_PRE_MOVE


code:
/datum/component/thermite
	dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
	var/amount
	var/burn_require
	var/overlay

	var/static/list/blacklist = typecacheof(list(
		/turf/open/lava,
		/turf/open/space,
		/turf/open/water,
		/turf/open/chasm)
		)

	var/static/list/immunelist = typecacheof(list(
		/turf/closed/wall/mineral/diamond,
		/turf/closed/indestructible,
		/turf/open/indestructible)
		)

	var/static/list/resistlist = typecacheof(
		/turf/closed/wall/r_wall
		)

/datum/component/thermite/Initialize(_amount)
	if(!istype(parent, /turf) || blacklist[parent.type])
		return COMPONENT_INCOMPATIBLE

	if(immunelist[parent.type])
		amount = 0 //Yeah the overlay can still go on it and be cleaned but you arent burning down a diamond wall
	else
		amount = _amount
		if(resistlist[parent.type])
			burn_require = 50
		else
			burn_require = 30

	var/turf/master = parent
	overlay = mutable_appearance('icons/effects/effects.dmi', "thermite")
	master.add_overlay(overlay)

	RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react)
	RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby_react)
	RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, .proc/flame_react)

/datum/component/thermite/UnregisterFromParent()
	UnregisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT)
	UnregisterSignal(parent, COMSIG_PARENT_ATTACKBY)
	UnregisterSignal(parent, COMSIG_ATOM_FIRE_ACT)

/datum/component/thermite/Destroy()
	var/turf/master = parent
	master.cut_overlay(overlay)
	return ..()

/datum/component/thermite/InheritComponent(datum/component/thermite/newC, i_am_original, _amount)
	if(!i_am_original)
		return
	if(newC)
		amount += newC.amount
	else
		amount += _amount

/datum/component/thermite/proc/thermite_melt(mob/user)
	var/turf/master = parent
	master.cut_overlay(overlay)
	playsound(master, 'sound/items/welder.ogg', 100, TRUE)
	var/obj/effect/overlay/thermite/fakefire = new(master)
	addtimer(CALLBACK(src, .proc/burn_parent, fakefire, user), min(amount * 0.35 SECONDS, 20 SECONDS))
	UnregisterFromParent()

/datum/component/thermite/proc/burn_parent(datum/fakefire, mob/user)
	var/turf/master = parent
	if(!QDELETED(fakefire))
		qdel(fakefire)
	if(user)
		master.add_hiddenprint(user)
	if(amount >= burn_require)
		master = master.Melt()
		master.burn_tile()
	qdel(src)

/datum/component/thermite/proc/clean_react(datum/source, strength)
	SIGNAL_HANDLER

	//Thermite is just some loose powder, you could probably clean it with your hands. << todo?
	qdel(src)
	return COMPONENT_CLEANED

/datum/component/thermite/proc/flame_react(datum/source, exposed_temperature, exposed_volume)
	SIGNAL_HANDLER

	if(exposed_temperature > 1922) // This is roughly the real life requirement to ignite thermite
		thermite_melt()

/datum/component/thermite/proc/attackby_react(datum/source, obj/item/thing, mob/user, params)
	SIGNAL_HANDLER

	if(thing.get_temperature())
		thermite_melt(user)


code:
/*
 * Tippable component. For making mobs able to be tipped, like cows and medibots.
 */
/datum/component/tippable
	/// Time it takes to tip the mob. Can be 0, for instant tipping.
	var/tip_time = 3 SECONDS
	/// Time it takes to untip the mob. Can also be 0, for instant untip.
	var/untip_time = 1 SECONDS
	/// Time it takes for the mob to right itself. Can be 0 for instant self-righting, or null, to never self-right.
	var/self_right_time = 60 SECONDS
	/// Whether the mob is currently tipped.
	var/is_tipped = FALSE
	/// Callback to additional behavior before being tipped (on try_tip). Return anything from this callback to cancel the tip.
	var/datum/callback/pre_tipped_callback
	/// Callback to additional behavior after successfully tipping the mob.
	var/datum/callback/post_tipped_callback
	/// Callback to additional behavior after being untipped.
	var/datum/callback/post_untipped_callback

/datum/component/tippable/Initialize(
		tip_time = 3 SECONDS,
		untip_time = 1 SECONDS,
		self_right_time = 60 SECONDS,
		datum/callback/pre_tipped_callback,
		datum/callback/post_tipped_callback,
		datum/callback/post_untipped_callback)

	if(!isliving(parent))
		return COMPONENT_INCOMPATIBLE

	src.tip_time = tip_time
	src.untip_time = untip_time
	src.self_right_time = self_right_time
	src.pre_tipped_callback = pre_tipped_callback
	src.post_tipped_callback = post_tipped_callback
	src.post_untipped_callback = post_untipped_callback

/datum/component/tippable/RegisterWithParent()
	RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/interact_with_tippable)

/datum/component/tippable/UnregisterFromParent()
	UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND)

/datum/component/tippable/Destroy()
	if(pre_tipped_callback)
		QDEL_NULL(pre_tipped_callback)
	if(post_tipped_callback)
		QDEL_NULL(post_tipped_callback)
	if(post_untipped_callback)
		QDEL_NULL(post_untipped_callback)
	return ..()

/*
 * Attempt to interact with [source], either tipping it or helping it up.
 *
 * source - the mob being tipped over
 * user - the mob interacting with source
 * modifiers - list of on click modifiers (we only tip mobs over using right click!)
 */
/datum/component/tippable/proc/interact_with_tippable(mob/living/source, mob/user, modifiers)
	SIGNAL_HANDLER

	var/mob/living/living_user = user
	if(DOING_INTERACTION_WITH_TARGET(user, source))
		return
	if(istype(living_user) && !living_user.combat_mode)
		return

	if(is_tipped)
		INVOKE_ASYNC(src, .proc/try_untip, source, user)
	else if(LAZYACCESS(modifiers, RIGHT_CLICK))
		INVOKE_ASYNC(src, .proc/try_tip, source, user)

	return COMPONENT_CANCEL_ATTACK_CHAIN

/*
 * Try to tip over [tipped_mob].
 * If the mob is dead, or optional callback returns a value, or our do-after fails, we don't tip the mob.
 * Otherwise, upon completing of the do_after, tip over the mob.
 *
 * tipped_mob - the mob being tipped over
 * tipper - the mob tipping the tipped_mob
 */
/datum/component/tippable/proc/try_tip(mob/living/tipped_mob, mob/tipper)
	if(tipped_mob.stat != CONSCIOUS)
		return

	if(pre_tipped_callback?.Invoke(tipper))
		return

	if(tip_time > 0)
		to_chat(tipper, span_warning("You begin tipping over [tipped_mob]..."))
		tipped_mob.visible_message(
			span_warning("[tipper] begins tipping over [tipped_mob]."),
			span_userdanger("[tipper] begins tipping you over!"),
			ignored_mobs = tipper
		)

		if(!do_after(tipper, tip_time, target = tipped_mob))
			to_chat(tipper, span_danger("You fail to tip over [tipped_mob]."))
			return
	do_tip(tipped_mob, tipper)

/*
 * Actually tip over the mob, setting it to tipped.
 * Also invoking any callbacks we have, with the tipper as the argument,
 * and set a timer to right our self-right our tipped mob if we can.
 *
 * tipped_mob - the mob who was tipped
 * tipper - the mob who tipped the tipped_mob
 */
/datum/component/tippable/proc/do_tip(mob/living/tipped_mob, mob/tipper)
	if(QDELETED(tipped_mob))
		CRASH("Tippable component: do_tip() called with QDELETED tipped_mob!")

	to_chat(tipper, span_warning("You tip over [tipped_mob]."))
	tipped_mob.visible_message(
		span_warning("[tipper] tips over [tipped_mob]."),
		span_userdanger("You are tipped over by [tipper]!"),
		ignored_mobs = tipper
		)

	set_tipped_status(tipped_mob, TRUE)
	post_tipped_callback?.Invoke(tipper)
	if(isnull(self_right_time))
		return
	else if(self_right_time <= 0)
		right_self(tipped_mob)
	else
		addtimer(CALLBACK(src, .proc/right_self, tipped_mob), self_right_time)

/*
 * Try to untip a mob that has been tipped.
 * After a do-after is completed, we untip the mob.
 *
 * tipped_mob - the mob who is tipped
 * untipper - the mob who is untipping the tipped_mob
 */
/datum/component/tippable/proc/try_untip(mob/living/tipped_mob, mob/untipper)
	if(untip_time > 0)
		to_chat(untipper, span_notice("You begin righting [tipped_mob]..."))
		tipped_mob.visible_message(
			span_notice("[untipper] begins righting [tipped_mob]."),
			span_notice("[untipper] begins righting you."),
			ignored_mobs = untipper
		)

		if(!do_after(untipper, untip_time, target = tipped_mob))
			to_chat(untipper, span_warning("You fail to right [tipped_mob]."))
			return

	do_untip(tipped_mob, untipper)

/*
 * Actually untip over the mob, setting it to untipped.
 * Also invoke any untip callbacks we have, with the untipper as the argument.
 *
 * tipped_mob - the mob who was tipped
 * tipper - the mob who tipped the tipped_mob
 */
/datum/component/tippable/proc/do_untip(mob/living/tipped_mob, mob/untipper)
	if(QDELETED(tipped_mob))
		return

	to_chat(untipper, span_notice("You right [tipped_mob]."))
	tipped_mob.visible_message(
		span_notice("[untipper] rights [tipped_mob]."),
		span_notice("You are righted by [untipper]!"),
		ignored_mobs = untipper
		)

	set_tipped_status(tipped_mob, FALSE)
	post_untipped_callback?.Invoke(untipper)

/*
 * Proc called after a timer to have a tipped mob un-tip itself after a certain length of time.
 * Sets our mob to untipped and invokes the untipped callback without any arguments if we have one.
 *
 * tipped_mob - the mob who was tipped, and is freeing itself
 */
/datum/component/tippable/proc/right_self(mob/living/tipped_mob)
	if(!is_tipped || QDELETED(tipped_mob))
		return

	set_tipped_status(tipped_mob, FALSE)
	post_untipped_callback?.Invoke()

	tipped_mob.visible_message(
		span_notice("[tipped_mob] rights itself."),
		span_notice("You right yourself.")
		)

/*
 * Toggles our tipped status between tipped or untipped (TRUE or FALSE)
 * also handles rotating our mob and adding immobilization traits
 *
 * tipped_mob - the mob we're setting to tipped or untipped
 * new_status - the tipped status we're setting the mob to - TRUE for tipped, FALSE for untipped
 */
/datum/component/tippable/proc/set_tipped_status(mob/living/tipped_mob, new_status = FALSE)
	is_tipped = new_status
	if(is_tipped)
		tipped_mob.transform = turn(tipped_mob.transform, 180)
		ADD_TRAIT(tipped_mob, TRAIT_IMMOBILIZED, TIPPED_OVER)
	else
		tipped_mob.transform = turn(tipped_mob.transform, -180)
		REMOVE_TRAIT(tipped_mob, TRAIT_IMMOBILIZED, TIPPED_OVER)


code:
/*
 * Simple component for allowing a user to change an atom's appearance on alt-click.
 * When toggled, changes the atom's icon_state between [base_icon_state] and [base_icon_state]_t.
 */
/datum/component/toggle_icon
	/// Whether the icon is toggled
	var/toggled = FALSE
	/// The base icon state we do operations on.
	var/base_icon_state
	/// The noun of what was "toggled" displayed to the user. EX: "Toggled the item's [buttons]"
	var/toggle_noun

/datum/component/toggle_icon/Initialize(toggle_noun = "buttons")
	if(!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	var/atom/atom_parent = parent

	src.toggle_noun = toggle_noun
	src.base_icon_state = atom_parent.base_icon_state || atom_parent.icon_state

/datum/component/toggle_icon/RegisterWithParent()
	RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)

/datum/component/toggle_icon/UnregisterFromParent()
	UnregisterSignal(parent, list(COMSIG_CLICK_ALT, COMSIG_PARENT_EXAMINE))

/*
 * Signal proc for COMSIG_CLICK_ALT.
 * If the user is living, adjacent to the source,
 * and has arms / is not incapacitated, call do_icon_toggle.
 *
 * source - the atom being clicked on
 * user - the mob doing the click
 */
/datum/component/toggle_icon/proc/on_alt_click(atom/source, mob/user)
	SIGNAL_HANDLER

	if(!isliving(user))
		return

	var/mob/living/living_user = user

	if(!living_user.Adjacent(source))
		return

	if(living_user.incapacitated())
		source.balloon_alert(user, "you're incapacitated!")
		return

	if(living_user.usable_hands <= 0)
		source.balloon_alert(user, "you don't have hands!")
		return

	do_icon_toggle(source, living_user)

/*
 * Signal proc for COMSIG_PARENT_EXAMINE.
 * Lets the user know they can toggle the parent open or closed.
 *
 * source - the atom being examined (parent)
 * user - the mob examining it
 * examine_list - the list of things within the examine output
 */
/datum/component/toggle_icon/proc/on_examine(atom/source, mob/user, list/examine_list)
	SIGNAL_HANDLER

	examine_list += span_notice("Alt-click on [source] to toggle the [toggle_noun].")

/*
 * Actually do the toggle of the icon.
 * Swaps the icon from [base_icon_state] to [base_icon_state]_t.
 *
 * source - the atom being toggled
 * user - the mob doing the toggling
 */
/datum/component/toggle_icon/proc/do_icon_toggle(atom/source, mob/living/user)
	source.balloon_alert(user, "toggled [toggle_noun]")

	toggled = !toggled
	if(toggled)
		source.icon_state = "[base_icon_state]_t"
	else
		source.icon_state = base_icon_state

	if(isitem(source))
		var/obj/item/item_source = source
		item_source.update_slot_icon()