code:
/obj/item/computer_hardware/network_card
	name = "network card"
	desc = "A basic wireless network card for usage with standard NTNet frequencies."
	power_usage = 50
	icon_state = "radio_mini"
	network_id = NETWORK_CARDS // Network we are on
	var/hardware_id = null // Identification ID. Technically MAC address of this device. Can't be changed by user.
	var/identification_string = "" // Identification string, technically nickname seen in the network. Can be set by user.
	var/long_range = 0
	var/ethernet = 0 // Hard-wired, therefore always on, ignores NTNet wireless checks.
	malfunction_probability = 1
	device_type = MC_NET

/obj/item/computer_hardware/network_card/diagnostics(mob/user)
	..()
	to_chat(user, "NIX Unique ID: [hardware_id]")
	to_chat(user, "NIX User Tag: [identification_string]")
	to_chat(user, "Supported protocols:")
	to_chat(user, "511.m SFS (Subspace) - Standard Frequency Spread")
	if(long_range)
		to_chat(user, "511.n WFS/HB (Subspace) - Wide Frequency Spread/High Bandiwdth")
	if(ethernet)
		to_chat(user, "OpenEth (Physical Connection) - Physical network connection port")

// Returns a string identifier of this network card
/obj/item/computer_hardware/network_card/proc/get_network_tag()
	return "[identification_string] (NID [hardware_id])"

// 0 - No signal, 1 - Low signal, 2 - High signal. 3 - Wired Connection
/obj/item/computer_hardware/network_card/proc/get_signal(specific_action = 0)
	if(!holder) // Hardware is not installed in anything. No signal. How did this even get called?
		return 0

	if(!check_functionality())
		return 0

	if(ethernet) // Computer is connected via wired connection.
		return 3

	if(!SSnetworks.station_network || !SSnetworks.station_network.check_function(specific_action)) // NTNet is down and we are not connected via wired connection. No signal.
		return 0

	if(holder)

		var/turf/T = get_turf(holder)
		if((T && istype(T)) && (is_station_level(T.z) || is_mining_level(T.z)))
			// Computer is on station. Low/High signal depending on what type of network card you have
			if(long_range)
				return 2
			else
				return 1

	if(long_range) // Computer is not on station, but it has upgraded network card. Low signal.
		return 1

	return 0 // Computer is not on station and does not have upgraded network card. No signal.

/obj/item/computer_hardware/network_card/advanced
	name = "advanced network card"
	desc = "An advanced network card for usage with standard NTNet frequencies. Its transmitter is strong enough to connect even off-station."
	long_range = 1
	power_usage = 100 // Better range but higher power usage.
	icon_state = "radio"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	w_class = WEIGHT_CLASS_TINY

/obj/item/computer_hardware/network_card/wired
	name = "wired network card"
	desc = "An advanced network card for usage with standard NTNet frequencies. This one also supports wired connection."
	ethernet = 1
	power_usage = 100 // Better range but higher power usage.
	icon_state = "net_wired"
	w_class = WEIGHT_CLASS_NORMAL

/obj/item/computer_hardware/network_card/integrated //Borg tablet version, only works while the borg has power and is not locked
	name = "cyborg data link"

/obj/item/computer_hardware/network_card/integrated/get_signal(specific_action = 0)
	var/obj/item/modular_computer/tablet/integrated/modularInterface = holder

	if(!modularInterface || !istype(modularInterface))
		return FALSE //wrong type of tablet

	if(!modularInterface.borgo)
		return FALSE //No borg found

	if(modularInterface.borgo.lockcharge)
		return FALSE //lockdown restricts borg networking

	if(!modularInterface.borgo.cell || modularInterface.borgo.cell.charge == 0)
		return FALSE //borg cell dying restricts borg networking

	return ..()


code:
/obj/item/computer_hardware/hard_drive/portable
	name = "data disk"
	desc = "Removable disk used to store data."
	power_usage = 10
	icon_state = "datadisk6"
	w_class = WEIGHT_CLASS_TINY
	critical = FALSE
	max_capacity = 16
	device_type = MC_SDD

/obj/item/computer_hardware/hard_drive/portable/on_remove(obj/item/modular_computer/remove_from, mob/user)
	return //this is a floppy disk, let's not shut the computer down when it gets pulled out.

/obj/item/computer_hardware/hard_drive/portable/install_default_programs()
	return // Empty by default

/obj/item/computer_hardware/hard_drive/portable/advanced
	name = "advanced data disk"
	power_usage = 20
	icon_state = "datadisk5"
	max_capacity = 64

/obj/item/computer_hardware/hard_drive/portable/super
	name = "super data disk"
	desc = "Removable disk used to store large amounts of data."
	power_usage = 40
	icon_state = "datadisk3"
	max_capacity = 256

repo_name: AnalWerewolf/sundown

license: mit


code:
/obj/item/computer_hardware/printer
	name = "printer"
	desc = "Computer-integrated printer with paper recycling module."
	power_usage = 100
	icon_state = "printer"
	w_class = WEIGHT_CLASS_NORMAL
	device_type = MC_PRINT
	expansion_hw = TRUE
	var/stored_paper = 20
	var/max_paper = 30

/obj/item/computer_hardware/printer/diagnostics(mob/living/user)
	..()
	to_chat(user, span_notice("Paper level: [stored_paper]/[max_paper]."))

/obj/item/computer_hardware/printer/examine(mob/user)
	. = ..()
	. += span_notice("Paper level: [stored_paper]/[max_paper].")

/obj/item/computer_hardware/printer/proc/print_text(text_to_print, paper_title = "")
	if(!stored_paper)
		return FALSE
	if(!check_functionality())
		return FALSE

	var/obj/item/paper/P = new/obj/item/paper(holder.drop_location())

	// Damaged printer causes the resulting paper to be somewhat harder to read.
	if(damage > damage_malfunction)
		P.info = stars(text_to_print, 100-malfunction_probability)
	else
		P.info = text_to_print
	if(paper_title)
		P.name = paper_title
	P.update_appearance()
	stored_paper--
	P = null
	return TRUE

/obj/item/computer_hardware/printer/try_insert(obj/item/I, mob/living/user = null)
	if(istype(I, /obj/item/paper))
		if(stored_paper >= max_paper)
			to_chat(user, span_warning("You try to add \the [I] into [src], but its paper bin is full!"))
			return FALSE

		if(user && !user.temporarilyRemoveItemFromInventory(I))
			return FALSE
		to_chat(user, span_notice("You insert \the [I] into [src]'s paper recycler."))
		qdel(I)
		stored_paper++
		return TRUE
	if(istype(I, /obj/item/paper_bin))
		var/obj/item/paper_bin/bin = I
		if(LAZYLEN(bin.papers))
			if(stored_paper >= max_paper)
				balloon_alert(user, "it's full!")
				return FALSE
			/// Number of sheets we're adding
			var/num_to_add = 0
			for(var/obj/item/paper/the_paper as anything in bin.papers) // Search for the first blank sheet of paper, then toss it in
				if(the_paper.get_info_length()) // Uh oh, paper has words!
					continue
				if(istype(the_paper, /obj/item/paper/carbon)) // Add both the carbon, and the copy
					var/obj/item/paper/carbon/carbon_paper = the_paper
					if(!carbon_paper.copied && ((max_paper - stored_paper) >= 2)) // See if there's room for both
						num_to_add = 2
				else
					num_to_add = 1
				LAZYREMOVE(bin.papers, the_paper)
				qdel(the_paper)
				stored_paper += num_to_add
				break // All full!
			bin.update_appearance()
			if(!num_to_add)
				balloon_alert(user, "everything is written on!")
			else
				balloon_alert(user, "pulled in [num_to_add] sheets\s of paper")
			return TRUE
		else
			balloon_alert(user, "the bin is empty!")
			return FALSE
	return FALSE

/obj/item/computer_hardware/printer/mini
	name = "miniprinter"
	desc = "A small printer with paper recycling module."
	power_usage = 50
	icon_state = "printer_mini"
	w_class = WEIGHT_CLASS_TINY
	stored_paper = 5
	max_paper = 15


code:
/obj/item/computer_hardware/recharger
	critical = 1
	enabled = 1
	var/charge_rate = 100
	device_type = MC_CHARGE

/obj/item/computer_hardware/recharger/proc/use_power(amount, charging=0)
	if(charging)
		return TRUE
	return FALSE

/obj/item/computer_hardware/recharger/process()
	..()
	var/obj/item/computer_hardware/battery/battery_module = holder.all_components[MC_CELL]
	if(!holder || !battery_module || !battery_module.battery)
		return

	var/obj/item/stock_parts/cell/cell = battery_module.battery
	if(cell.charge >= cell.maxcharge)
		return

	if(use_power(charge_rate, charging=1))
		holder.give_power(charge_rate JOULES)

/obj/item/computer_hardware/recharger/apc_recharger
	name = "area power connector"
	desc = "A device that wirelessly recharges connected device from nearby APC."
	icon_state = "charger_APC"
	w_class = WEIGHT_CLASS_SMALL // Can't be installed into tablets/PDAs

/obj/item/computer_hardware/recharger/apc_recharger/use_power(amount, charging=0)
	if(ismachinery(holder.physical))
		var/obj/machinery/M = holder.physical
		if(M.powered())
			M.use_power(amount)
			return TRUE

	else
		var/area/A = get_area(src)
		if(!istype(A))
			return FALSE

		if(A.powered(AREA_USAGE_EQUIP))
			A.use_power(amount, AREA_USAGE_EQUIP)
			return TRUE
	return FALSE

/obj/item/computer_hardware/recharger/wired
	name = "wired power connector"
	desc = "A power connector that recharges connected device from nearby power wire. Incompatible with portable computers."
	icon_state = "charger_wire"
	w_class = WEIGHT_CLASS_NORMAL

/obj/item/computer_hardware/recharger/wired/can_install(obj/item/modular_computer/install_into, mob/living/user = null)
	if(ismachinery(install_into.physical) && install_into.physical.anchored)
		return ..()
	to_chat(user, span_warning("\The [src] is incompatible with portable computers!"))
	return FALSE

/obj/item/computer_hardware/recharger/wired/use_power(amount, charging=0)
	if(ismachinery(holder.physical) && holder.physical.anchored)
		var/obj/machinery/M = holder.physical
		var/turf/T = M.loc
		if(!T || !istype(T))
			return FALSE

		var/obj/structure/cable/C = T.get_cable_node()
		if(!C || !C.powernet)
			return FALSE

		var/power_in_net = C.powernet.avail-C.powernet.load

		if(power_in_net && power_in_net > amount)
			C.powernet.load += amount
			return TRUE
	return FALSE

/// This recharger exists only in borg built-in tablets. I would have tied it to the borg's cell but
/// the program that displays laws should always be usable, and the exceptions were starting to pile.
/obj/item/computer_hardware/recharger/cyborg
	name = "modular interface power harness"
	desc = "A standard connection to power a small computer device from a cyborg's chassis."

/obj/item/computer_hardware/recharger/cyborg/use_power(amount, charging=0)
	return TRUE

// This is not intended to be obtainable in-game. Intended for adminbus and debugging purposes.
/obj/item/computer_hardware/recharger/lambda
	name = "lambda coil"
	desc = "A very complex device that draws power from its own bluespace dimension."
	icon_state = "charger_lambda"
	w_class = WEIGHT_CLASS_TINY
	charge_rate = 100000

/obj/item/computer_hardware/recharger/lambda/use_power(amount, charging=0)
	return 1


code:
//This item doesn't do much on its own, but is required by apps such as AtmoZphere.
/obj/item/computer_hardware/sensorpackage
	name = "sensor package"
	desc = "An integrated sensor package allowing a computer to take readings from the environment. Required by certain programs."
	icon_state = "servo"
	w_class = WEIGHT_CLASS_TINY
	device_type = MC_SENSORS
	expansion_hw = TRUE

/obj/item/computer_hardware/radio_card
	name = "integrated radio card"
	desc = "An integrated signaling assembly for computers to send an outgoing frequency signal. Required by certain programs."
	icon_state = "signal_card"
	w_class = WEIGHT_CLASS_TINY
	device_type = MC_SIGNALER
	expansion_hw = TRUE
	power_usage = 10


code:
// A vendor machine for modular computer portable devices - Laptops and Tablets

/obj/machinery/lapvend
	name = "computer vendor"
	desc = "A vending machine with microfabricator capable of dispensing various NT-branded computers."
	icon = 'icons/obj/vending.dmi'
	icon_state = "robotics"
	layer = 2.9
	density = TRUE

	// The actual laptop/tablet
	var/obj/item/modular_computer/laptop/fabricated_laptop = null
	var/obj/item/modular_computer/tablet/fabricated_tablet = null

	// Utility vars
	var/state = 0 // 0: Select device type, 1: Select loadout, 2: Payment, 3: Thankyou screen
	var/devtype = 0 // 0: None(unselected), 1: Laptop, 2: Tablet
	var/total_price = 0 // Price of currently vended device.
	var/credits = 0

	// Device loadout
	var/dev_cpu = 1 // 1: Default, 2: Upgraded
	var/dev_battery = 1 // 1: Default, 2: Upgraded, 3: Advanced
	var/dev_disk = 1 // 1: Default, 2: Upgraded, 3: Advanced
	var/dev_netcard = 0 // 0: None, 1: Basic, 2: Long-Range
	var/dev_apc_recharger = 0 // 0: None, 1: Standard (LAPTOP ONLY)
	var/dev_printer = 0 // 0: None, 1: Standard
	var/dev_card = 0 // 0: None, 1: Standard

// Removes all traces of old order and allows you to begin configuration from scratch.
/obj/machinery/lapvend/proc/reset_order()
	state = 0
	devtype = 0
	if(fabricated_laptop)
		qdel(fabricated_laptop)
		fabricated_laptop = null
	if(fabricated_tablet)
		qdel(fabricated_tablet)
		fabricated_tablet = null
	dev_cpu = 1
	dev_battery = 1
	dev_disk = 1
	dev_netcard = 0
	dev_apc_recharger = 0
	dev_printer = 0
	dev_card = 0

// Recalculates the price and optionally even fabricates the device.
/obj/machinery/lapvend/proc/fabricate_and_recalc_price(fabricate = FALSE)
	total_price = 0
	if(devtype == 1) // Laptop, generally cheaper to make it accessible for most station roles
		var/obj/item/computer_hardware/battery/battery_module = null
		if(fabricate)
			fabricated_laptop = new /obj/item/modular_computer/laptop/buildable(src)
			fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot)
			fabricated_laptop.install_component(new /obj/item/computer_hardware/battery)
			battery_module = fabricated_laptop.all_components[MC_CELL]
		total_price = 99
		switch(dev_cpu)
			if(1)
				if(fabricate)
					fabricated_laptop.install_component(new /obj/item/computer_hardware/processor_unit/small)
			if(2)
				if(fabricate)
					fabricated_laptop.install_component(new /obj/item/computer_hardware/processor_unit)
				total_price += 299
		switch(dev_battery)
			if(1) // Basic(750C)
				if(fabricate)
					battery_module.try_insert(new /obj/item/stock_parts/cell/computer)
			if(2) // Upgraded(1100C)
				if(fabricate)
					battery_module.try_insert(new /obj/item/stock_parts/cell/computer/advanced)
				total_price += 199
			if(3) // Advanced(1500C)
				if(fabricate)
					battery_module.try_insert(new /obj/item/stock_parts/cell/computer/super)
				total_price += 499
		switch(dev_disk)
			if(1) // Basic(128GQ)
				if(fabricate)
					fabricated_laptop.install_component(new /obj/item/computer_hardware/hard_drive)
			if(2) // Upgraded(256GQ)
				if(fabricate)
					fabricated_laptop.install_component(new /obj/item/computer_hardware/hard_drive/advanced)
				total_price += 99
			if(3) // Advanced(512GQ)
				if(fabricate)
					fabricated_laptop.install_component(new /obj/item/computer_hardware/hard_drive/super)
				total_price += 299
		switch(dev_netcard)
			if(1) // Basic(Short-Range)
				if(fabricate)
					fabricated_laptop.install_component(new /obj/item/computer_hardware/network_card)
				total_price += 99
			if(2) // Advanced (Long Range)
				if(fabricate)
					fabricated_laptop.install_component(new /obj/item/computer_hardware/network_card/advanced)
				total_price += 299
		if(dev_apc_recharger)
			total_price += 399
			if(fabricate)
				fabricated_laptop.install_component(new /obj/item/computer_hardware/recharger/apc_recharger)
		if(dev_printer)
			total_price += 99
			if(fabricate)
				fabricated_laptop.install_component(new /obj/item/computer_hardware/printer/mini)
		if(dev_card)
			total_price += 199
			if(fabricate)
				fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot/secondary)

		return total_price
	else if(devtype == 2) // Tablet, more expensive, not everyone could probably afford this.
		var/obj/item/computer_hardware/battery/battery_module = null
		if(fabricate)
			fabricated_tablet = new(src)
			fabricated_tablet.install_component(new /obj/item/computer_hardware/battery)
			fabricated_tablet.install_component(new /obj/item/computer_hardware/processor_unit/small)
			fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot)
			battery_module = fabricated_tablet.all_components[MC_CELL]
		total_price = 199
		switch(dev_battery)
			if(1) // Basic(300C)
				if(fabricate)
					battery_module.try_insert(new /obj/item/stock_parts/cell/computer/nano)
			if(2) // Upgraded(500C)
				if(fabricate)
					battery_module.try_insert(new /obj/item/stock_parts/cell/computer/micro)
				total_price += 199
			if(3) // Advanced(750C)
				if(fabricate)
					battery_module.try_insert(new /obj/item/stock_parts/cell/computer)
				total_price += 499
		switch(dev_disk)
			if(1) // Basic(32GQ)
				if(fabricate)
					fabricated_tablet.install_component(new /obj/item/computer_hardware/hard_drive/micro)
			if(2) // Upgraded(64GQ)
				if(fabricate)
					fabricated_tablet.install_component(new /obj/item/computer_hardware/hard_drive/small)
				total_price += 99
			if(3) // Advanced(128GQ)
				if(fabricate)
					fabricated_tablet.install_component(new /obj/item/computer_hardware/hard_drive)
				total_price += 299
		switch(dev_netcard)
			if(1) // Basic(Short-Range)
				if(fabricate)
					fabricated_tablet.install_component(new/obj/item/computer_hardware/network_card)
				total_price += 99
			if(2) // Advanced (Long Range)
				if(fabricate)
					fabricated_tablet.install_component(new/obj/item/computer_hardware/network_card/advanced)
				total_price += 299
		if(dev_printer)
			total_price += 99
			if(fabricate)
				fabricated_tablet.install_component(new/obj/item/computer_hardware/printer/mini)
		if(dev_card)
			total_price += 199
			if(fabricate)
				fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot/secondary)
		return total_price
	return FALSE

/obj/machinery/lapvend/ui_act(action, params)
	. = ..()
	if(.)
		return

	switch(action)
		if("pick_device")
			if(state) // We've already picked a device type
				return FALSE
			devtype = text2num(params["pick"])
			state = 1
			fabricate_and_recalc_price(FALSE)
			return TRUE
		if("clean_order")
			reset_order()
			return TRUE
		if("purchase")
			try_purchase()
			return TRUE
	if((state != 1) && devtype) // Following IFs should only be usable when in the Select Loadout mode
		return FALSE
	switch(action)
		if("confirm_order")
			state = 2 // Wait for ID swipe for payment processing
			fabricate_and_recalc_price(FALSE)
			return TRUE
		if("hw_cpu")
			dev_cpu = text2num(params["cpu"])
			fabricate_and_recalc_price(FALSE)
			return TRUE
		if("hw_battery")
			dev_battery = text2num(params["battery"])
			fabricate_and_recalc_price(FALSE)
			return TRUE
		if("hw_disk")
			dev_disk = text2num(params["disk"])
			fabricate_and_recalc_price(FALSE)
			return TRUE
		if("hw_netcard")
			dev_netcard = text2num(params["netcard"])
			fabricate_and_recalc_price(FALSE)
			return TRUE
		if("hw_tesla")
			dev_apc_recharger = text2num(params["tesla"])
			fabricate_and_recalc_price(FALSE)
			return TRUE
		if("hw_nanoprint")
			dev_printer = text2num(params["print"])
			fabricate_and_recalc_price(FALSE)
			return TRUE
		if("hw_card")
			dev_card = text2num(params["card"])
			fabricate_and_recalc_price(FALSE)
			return TRUE
	return FALSE

/obj/machinery/lapvend/ui_interact(mob/user, datum/tgui/ui)
	if(machine_stat & (BROKEN | NOPOWER | MAINT))
		if(ui)
			ui.close()
		return FALSE

	ui = SStgui.try_update_ui(user, src, ui)
	if (!ui)
		ui = new(user, src, "ComputerFabricator")
		ui.open()

/obj/machinery/lapvend/attackby(obj/item/I, mob/user)
	if(istype(I, /obj/item/stack/spacecash))
		var/obj/item/stack/spacecash/c = I
		if(!user.temporarilyRemoveItemFromInventory(c))
			return
		credits += c.value
		visible_message(span_info("[span_name("[user]")] inserts [c.value] cr into [src]."))
		qdel(c)
		return
	else if(istype(I, /obj/item/holochip))
		var/obj/item/holochip/HC = I
		credits += HC.credits
		visible_message(span_info("[user] inserts a [HC.credits] cr holocredit chip into [src]."))
		qdel(HC)
		return
	else if(istype(I, /obj/item/card/id))
		if(state != 2)
			return
		var/obj/item/card/id/ID = I
		var/datum/bank_account/account = ID.registered_account
		var/target_credits = total_price - credits
		if(!account.adjust_money(-target_credits))
			say("Insufficient credits on card to purchase!")
			return
		credits += target_credits
		say("[target_credits] cr have been withdrawn from your account.")
		return
	return ..()

// Simplified payment processing, returns 1 on success.
/obj/machinery/lapvend/proc/process_payment()
	if(total_price > credits)
		say("Insufficient credits.")
		return FALSE
	else
		return TRUE

/obj/machinery/lapvend/ui_data(mob/user)

	var/list/data = list()
	data["state"] = state
	if(state == 1)
		data["devtype"] = devtype
		data["hw_battery"] = dev_battery
		data["hw_disk"] = dev_disk
		data["hw_netcard"] = dev_netcard
		data["hw_tesla"] = dev_apc_recharger
		data["hw_nanoprint"] = dev_printer
		data["hw_card"] = dev_card
		data["hw_cpu"] = dev_cpu
	if(state == 1 || state == 2)
		data["totalprice"] = total_price
		data["credits"] = credits

	return data

/obj/machinery/lapvend/proc/try_purchase()
	// Awaiting payment state
	if(state == 2)
		if(process_payment())
			fabricate_and_recalc_price(1)
			if((devtype == 1) && fabricated_laptop)
				fabricated_laptop.forceMove(src.loc)
				fabricated_laptop = null
			else if((devtype == 2) && fabricated_tablet)
				fabricated_tablet.forceMove(src.loc)
				fabricated_tablet = null
			credits -= total_price
			say("Enjoy your new product!")
			state = 3
			addtimer(CALLBACK(src, .proc/reset_order), 100)
			return TRUE
		return FALSE


code:
/*! Movespeed modification datums.

	How move speed for mobs works

Move speed is now calculated by using modifier datums which are added to mobs. Some of them (nonvariable ones) are globally cached, the variable ones are instanced and changed based on need.

This gives us the ability to have multiple sources of movespeed, reliabily keep them applied and remove them when they should be

THey can have unique sources and a bunch of extra fancy flags that control behaviour

Previously trying to update move speed was a shot in the dark that usually meant mobs got stuck going faster or slower

Movespeed modification list is a simple key = datum system. Key will be the datum's ID if it is overridden to not be null, or type if it is not.

DO NOT override datum IDs unless you are going to have multiple types that must overwrite each other. It's more efficient to use types, ID functionality is only kept for cases where dynamic creation of modifiers need to be done.

When update movespeed is called, the list of items is iterated, according to flags priority and a bunch of conditions
this spits out a final calculated value which is used as a modifer to last_move + modifier for calculating when a mob
can next move

Key procs
* [add_movespeed_modifier](mob.html#proc/add_movespeed_modifier)
* [remove_movespeed_modifier](mob.html#proc/remove_movespeed_modifier)
* [has_movespeed_modifier](mob.html#proc/has_movespeed_modifier)
* [update_movespeed](mob.html#proc/update_movespeed)
*/

/datum/movespeed_modifier
	/// Whether or not this is a variable modifier. Variable modifiers can NOT be ever auto-cached. ONLY CHECKED VIA INITIAL(), EFFECTIVELY READ ONLY (and for very good reason)
	var/variable = FALSE

	/// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID.
	var/id

	/// Higher ones override lower priorities. This is NOT used for ID, ID must be unique, if it isn't unique the newer one overwrites automatically if overriding.
	var/priority = 0
	var/flags = NONE

	/// Multiplicative slowdown
	var/multiplicative_slowdown = 0

	/// Movetypes this applies to
	var/movetypes = ALL

	/// Movetypes this never applies to
	var/blacklisted_movetypes = NONE

	/// Other modification datums this conflicts with.
	var/conflicts_with

/datum/movespeed_modifier/New()
	. = ..()
	if(!id)
		id = "[type]" //We turn the path into a string.

GLOBAL_LIST_EMPTY(movespeed_modification_cache)

/// Grabs a STATIC MODIFIER datum from cache. YOU MUST NEVER EDIT THESE DATUMS, OR IT WILL AFFECT ANYTHING ELSE USING IT TOO!
/proc/get_cached_movespeed_modifier(modtype)
	if(!ispath(modtype, /datum/movespeed_modifier))
		CRASH("[modtype] is not a movespeed modification typepath.")
	var/datum/movespeed_modifier/M = modtype
	if(initial(M.variable))
		CRASH("[modtype] is a variable modifier, and can never be cached.")
	M = GLOB.movespeed_modification_cache[modtype]
	if(!M)
		M = GLOB.movespeed_modification_cache[modtype] = new modtype
	return M

///Add a move speed modifier to a mob. If a variable subtype is passed in as the first argument, it will make a new datum. If ID conflicts, it will overwrite the old ID.
/mob/proc/add_movespeed_modifier(datum/movespeed_modifier/type_or_datum, update = TRUE)
	if(ispath(type_or_datum))
		if(!initial(type_or_datum.variable))
			type_or_datum = get_cached_movespeed_modifier(type_or_datum)
		else
			type_or_datum = new type_or_datum
	var/datum/movespeed_modifier/existing = LAZYACCESS(movespeed_modification, type_or_datum.id)
	if(existing)
		if(existing == type_or_datum) //same thing don't need to touch
			return TRUE
		remove_movespeed_modifier(existing, FALSE)
	if(length(movespeed_modification))
		BINARY_INSERT(type_or_datum.id, movespeed_modification, /datum/movespeed_modifier, type_or_datum, priority, COMPARE_VALUE)
	LAZYSET(movespeed_modification, type_or_datum.id, type_or_datum)
	if(update)
		update_movespeed()
	return TRUE

/// Remove a move speed modifier from a mob, whether static or variable.
/mob/proc/remove_movespeed_modifier(datum/movespeed_modifier/type_id_datum, update = TRUE)
	var/key
	if(ispath(type_id_datum))
		key = initial(type_id_datum.id) || "[type_id_datum]" //id if set, path set to string if not.
	else if(!istext(type_id_datum)) //if it isn't text it has to be a datum, as it isn't a type.
		key = type_id_datum.id
	else //assume it's an id
		key = type_id_datum
	if(!LAZYACCESS(movespeed_modification, key))
		return FALSE
	LAZYREMOVE(movespeed_modification, key)
	if(update)
		update_movespeed(FALSE)
	return TRUE

/*! Used for variable slowdowns like hunger/health loss/etc, works somewhat like the old list-based modification adds. Returns the modifier datum if successful
	How this SHOULD work is:
	1. Ensures type_id_datum one way or another refers to a /variable datum. This makes sure it can't be cached. This includes if it's already in the modification list.
	2. Instantiate a new datum if type_id_datum isn't already instantiated + in the list, using the type. Obviously, wouldn't work for ID only.
	3. Add the datum if necessary using the regular add proc
	4. If any of the rest of the args are not null (see: multiplicative slowdown), modify the datum
	5. Update if necessary
*/
/mob/proc/add_or_update_variable_movespeed_modifier(datum/movespeed_modifier/type_id_datum, update = TRUE, multiplicative_slowdown)
	var/modified = FALSE
	var/inject = FALSE
	var/datum/movespeed_modifier/final
	if(istext(type_id_datum))
		final = LAZYACCESS(movespeed_modification, type_id_datum)
		if(!final)
			CRASH("Couldn't find existing modification when provided a text ID.")
	else if(ispath(type_id_datum))
		if(!initial(type_id_datum.variable))
			CRASH("Not a variable modifier")
		final = LAZYACCESS(movespeed_modification, initial(type_id_datum.id) || "[type_id_datum]")
		if(!final)
			final = new type_id_datum
			inject = TRUE
			modified = TRUE
	else
		if(!initial(type_id_datum.variable))
			CRASH("Not a variable modifier")
		final = type_id_datum
		if(!LAZYACCESS(movespeed_modification, final.id))
			inject = TRUE
			modified = TRUE
	if(!isnull(multiplicative_slowdown))
		final.multiplicative_slowdown = multiplicative_slowdown
		modified = TRUE
	if(inject)
		add_movespeed_modifier(final, FALSE)
	if(update && modified)
		update_movespeed(TRUE)
	return final

///Is there a movespeed modifier for this mob
/mob/proc/has_movespeed_modifier(datum/movespeed_modifier/datum_type_id)
	var/key
	if(ispath(datum_type_id))
		key = initial(datum_type_id.id) || "[datum_type_id]"
	else if(istext(datum_type_id))
		key = datum_type_id
	else
		key = datum_type_id.id
	return LAZYACCESS(movespeed_modification, key)

/// Set or update the global movespeed config on a mob
/mob/proc/update_config_movespeed()
	add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/mob_config_speedmod, multiplicative_slowdown = get_config_multiplicative_speed())

/// Get the global config movespeed of a mob by type
/mob/proc/get_config_multiplicative_speed()
	if(!islist(GLOB.mob_config_movespeed_type_lookup) || !GLOB.mob_config_movespeed_type_lookup[type])
		return 0
	else
		return GLOB.mob_config_movespeed_type_lookup[type]

/// Go through the list of movespeed modifiers and calculate a final movespeed. ANY ADD/REMOVE DONE IN UPDATE_MOVESPEED MUST HAVE THE UPDATE ARGUMENT SET AS FALSE!
/mob/proc/update_movespeed()
	. = 0
	var/list/conflict_tracker = list()
	for(var/key in get_movespeed_modifiers())
		var/datum/movespeed_modifier/M = movespeed_modification[key]
		if(!(M.movetypes & movement_type)) // We don't affect any of these move types, skip
			continue
		if(M.blacklisted_movetypes & movement_type) // There's a movetype here that disables this modifier, skip
			continue
		var/conflict = M.conflicts_with
		var/amt = M.multiplicative_slowdown
		if(conflict)
			// Conflicting modifiers prioritize the larger slowdown or the larger speedup
			// We purposefuly don't handle mixing speedups and slowdowns on the same id
			if(abs(conflict_tracker[conflict]) < abs(amt))
				conflict_tracker[conflict] = amt
			else
				continue
		. += amt
	cached_multiplicative_slowdown = .
	SEND_SIGNAL(src, COMSIG_MOB_MOVESPEED_UPDATED)

/// Get the move speed modifiers list of the mob
/mob/proc/get_movespeed_modifiers()
	. = LAZYCOPY(movespeed_modification)
	for(var/id in movespeed_mod_immunities)
		. -= id

/// Calculate the total slowdown of all movespeed modifiers
/mob/proc/total_multiplicative_slowdown()
	. = 0
	for(var/id in get_movespeed_modifiers())
		var/datum/movespeed_modifier/M = movespeed_modification[id]
		. += M.multiplicative_slowdown

/// Checks if a move speed modifier is valid and not missing any data
/proc/movespeed_data_null_check(datum/movespeed_modifier/M) //Determines if a data list is not meaningful and should be discarded.
	. = TRUE
	if(M.multiplicative_slowdown)
		. = FALSE


code:
/datum/movespeed_modifier/shrink_ray
	movetypes = GROUND
	multiplicative_slowdown = 4
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/snail_crawl
	multiplicative_slowdown = -7
	movetypes = GROUND

/datum/movespeed_modifier/tenacious
	multiplicative_slowdown = -0.7
	movetypes = GROUND

/datum/movespeed_modifier/sanity
	id = MOVESPEED_ID_SANITY
	movetypes = (~FLYING)

/datum/movespeed_modifier/sanity/insane
	multiplicative_slowdown = 1

/datum/movespeed_modifier/sanity/crazy
	multiplicative_slowdown = 0.5

/datum/movespeed_modifier/sanity/disturbed
	multiplicative_slowdown = 0.25


code:
/datum/movespeed_modifier/stimulants
	movetypes = GROUND
	multiplicative_slowdown = 1
	id = MOVESPEED_ID_STIMULANTS


code:
/datum/movespeed_modifier/strained_muscles
	multiplicative_slowdown = -0.55
	blacklisted_movetypes = (FLYING|FLOATING)

/datum/movespeed_modifier/pai_spacewalk
	multiplicative_slowdown = 2
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/species
	movetypes = ~FLYING
	variable = TRUE

/datum/movespeed_modifier/dna_vault_speedup
	blacklisted_movetypes = (FLYING|FLOATING)
	multiplicative_slowdown = -0.4


code:
/datum/movespeed_modifier/jetpack
	conflicts_with = MOVE_CONFLICT_JETPACK
	movetypes = FLOATING

/datum/movespeed_modifier/jetpack/cybernetic
	multiplicative_slowdown = -0.5

/datum/movespeed_modifier/jetpack/fullspeed
	multiplicative_slowdown = -0.5

/datum/movespeed_modifier/die_of_fate
	multiplicative_slowdown = 1

/datum/movespeed_modifier/berserk
	multiplicative_slowdown = -0.2


code:
/datum/movespeed_modifier/admin_varedit
	variable = TRUE

/datum/movespeed_modifier/yellow_orb
	multiplicative_slowdown = -0.65
	blacklisted_movetypes = (FLYING|FLOATING)


code:
/datum/movespeed_modifier/obesity
	multiplicative_slowdown = 1.5

/datum/movespeed_modifier/monkey_reagent_speedmod
	variable = TRUE

/datum/movespeed_modifier/monkey_health_speedmod
	variable = TRUE

/datum/movespeed_modifier/monkey_temperature_speedmod
	variable = TRUE

/datum/movespeed_modifier/hunger
	variable = TRUE

/datum/movespeed_modifier/slaughter
	multiplicative_slowdown = -1

/datum/movespeed_modifier/resonance
	multiplicative_slowdown = 0.75

/datum/movespeed_modifier/damage_slowdown
	blacklisted_movetypes = FLOATING|FLYING
	variable = TRUE

/datum/movespeed_modifier/damage_slowdown_flying
	movetypes = FLYING
	variable = TRUE

/datum/movespeed_modifier/equipment_speedmod
	variable = TRUE
	blacklisted_movetypes = FLOATING

/datum/movespeed_modifier/grab_slowdown
	id = MOVESPEED_ID_MOB_GRAB_STATE
	blacklisted_movetypes = FLOATING

/datum/movespeed_modifier/grab_slowdown/aggressive
	multiplicative_slowdown = 3

/datum/movespeed_modifier/grab_slowdown/neck
	multiplicative_slowdown = 6

/datum/movespeed_modifier/grab_slowdown/kill
	multiplicative_slowdown = 9

/datum/movespeed_modifier/slime_reagentmod
	variable = TRUE

/datum/movespeed_modifier/slime_healthmod
	variable = TRUE

/datum/movespeed_modifier/config_walk_run
	multiplicative_slowdown = 1
	id = MOVESPEED_ID_MOB_WALK_RUN
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/config_walk_run/proc/sync()

/datum/movespeed_modifier/config_walk_run/walk/sync()
	var/mod = CONFIG_GET(number/movedelay/walk_delay)
	multiplicative_slowdown = isnum(mod)? mod : initial(multiplicative_slowdown)

/datum/movespeed_modifier/config_walk_run/run/sync()
	var/mod = CONFIG_GET(number/movedelay/run_delay)
	multiplicative_slowdown = isnum(mod)? mod : initial(multiplicative_slowdown)

/datum/movespeed_modifier/turf_slowdown
	movetypes = GROUND
	blacklisted_movetypes = (FLYING|FLOATING)
	variable = TRUE

/datum/movespeed_modifier/bulky_drag
	variable = TRUE

/datum/movespeed_modifier/cold
	blacklisted_movetypes = FLOATING
	variable = TRUE

/datum/movespeed_modifier/shove
	multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH

/datum/movespeed_modifier/human_carry
	multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN

/datum/movespeed_modifier/limbless
	variable = TRUE
	movetypes = GROUND
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/simplemob_varspeed
	variable = TRUE
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/tarantula_web
	multiplicative_slowdown = 5

/datum/movespeed_modifier/gravity
	blacklisted_movetypes = FLOATING
	variable = TRUE
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/carbon_softcrit
	multiplicative_slowdown = SOFTCRIT_ADD_SLOWDOWN
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/slime_tempmod
	variable = TRUE

/datum/movespeed_modifier/carbon_crawling
	multiplicative_slowdown = CRAWLING_ADD_SLOWDOWN
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/mob_config_speedmod
	variable = TRUE
	flags = IGNORE_NOSLOW

/datum/movespeed_modifier/metabolicboost
	multiplicative_slowdown = -1.5

/datum/movespeed_modifier/dragon_rage
	multiplicative_slowdown = -0.5

/datum/movespeed_modifier/dragon_depression
	multiplicative_slowdown = 5

/datum/movespeed_modifier/morph_disguised
	multiplicative_slowdown = 1


code:
/datum/movespeed_modifier/reagent
	blacklisted_movetypes = (FLYING|FLOATING)

/datum/movespeed_modifier/reagent/stimulants
	multiplicative_slowdown = -0.55

/datum/movespeed_modifier/reagent/ephedrine
	multiplicative_slowdown = -0.5

/datum/movespeed_modifier/reagent/pepperspray
	multiplicative_slowdown = 0.25

/datum/movespeed_modifier/reagent/monkey_energy
	multiplicative_slowdown = -0.35

/datum/movespeed_modifier/reagent/changelinghaste
	multiplicative_slowdown = -0.8

/datum/movespeed_modifier/reagent/methamphetamine
	multiplicative_slowdown = -0.65

/datum/movespeed_modifier/reagent/nitryl
	multiplicative_slowdown = -0.65

/datum/movespeed_modifier/reagent/cannabis
	multiplicative_slowdown = 0.4

/datum/movespeed_modifier/reagent/freon
	multiplicative_slowdown = 1.6

/datum/movespeed_modifier/reagent/halon
	multiplicative_slowdown = 1.8

/datum/movespeed_modifier/reagent/lenturi
	multiplicative_slowdown = 1.5

/datum/movespeed_modifier/reagent/nuka_cola
	multiplicative_slowdown = -0.35

/datum/movespeed_modifier/reagent/nooartrium
	multiplicative_slowdown = 2


code:
/datum/movespeed_modifier/status_effect/bloodchill
	multiplicative_slowdown = 3

/datum/movespeed_modifier/status_effect/bonechill
	multiplicative_slowdown = 3

/datum/movespeed_modifier/status_effect/lightpink
	multiplicative_slowdown = -0.5
	blacklisted_movetypes = (FLYING|FLOATING)

/datum/movespeed_modifier/status_effect/tarfoot
	multiplicative_slowdown = 0.5
	blacklisted_movetypes = (FLYING|FLOATING)

/datum/movespeed_modifier/status_effect/sepia
	variable = TRUE
	blacklisted_movetypes = (FLYING|FLOATING)


code:
//ninjacost() specificCheck defines
#define N_STEALTH_CANCEL 1
#define N_ADRENALINE 2

//ninjaDrainAct() defines for non numerical returns
#define INVALID_DRAIN "INVALID" //This one is if the drain proc needs to cancel, eg missing variables, etc, it's important.
#define DRAIN_RD_HACK_FAILED "RDHACKFAIL"
#define DRAIN_MOB_SHOCK "MOBSHOCK"
#define DRAIN_MOB_SHOCK_FAILED "MOBSHOCKFAIL"

//Tells whether or not someone is a space ninja
#define IS_SPACE_NINJA(ninja) (ninja.mind && ninja.mind.has_antag_datum(/datum/antagonist/ninja))

//Defines for the suit's unique abilities
#define IS_NINJA_SUIT_INITIALIZATION(action) (istype(action, /datum/action/item_action/initialize_ninja_suit))
#define IS_NINJA_SUIT_STATUS(action) (istype(action, /datum/action/item_action/ninjastatus))
#define IS_NINJA_SUIT_BOOST(action) (istype(action, /datum/action/item_action/ninjaboost))
#define IS_NINJA_SUIT_EMP(action) (istype(action, /datum/action/item_action/ninjapulse))
#define IS_NINJA_SUIT_STAR_CREATION(action) (istype(action, /datum/action/item_action/ninjastar))
#define IS_NINJA_SUIT_NET_CREATION(action) (istype(action, /datum/action/item_action/ninjanet))
#define IS_NINJA_SUIT_SWORD_RECALL(action) (istype(action, /datum/action/item_action/ninja_sword_recall))
#define IS_NINJA_SUIT_STEALTH(action) (istype(action, /datum/action/item_action/ninja_stealth))


code:
/**
 * # Energy Katana
 *
 * The space ninja's katana.
 *
 * The katana that only space ninja spawns with.  Comes with 30 force and throwforce, along with a signature special jaunting system.
 * Upon clicking on a tile when right clicking, the user will teleport to that tile, assuming their target was not dense.
 * The katana has 3 dashes stored at maximum, and upon using the dash, it will return 20 seconds after it was used.
 * It also has a special feature where if it is tossed at a space ninja who owns it (determined by the ninja suit), the ninja will catch the katana instead of being hit by it.
 *
 */
/obj/item/energy_katana
	name = "energy katana"
	desc = "A katana infused with strong energy. Right-click to dash."
	icon_state = "energy_katana"
	inhand_icon_state = "energy_katana"
	worn_icon_state = "energy_katana"
	lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
	force = 30
	throwforce = 30
	block_chance = 50
	armour_penetration = 50
	w_class = WEIGHT_CLASS_NORMAL
	hitsound = 'sound/weapons/bladeslice.ogg'
	attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts")
	attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut")
	slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT
	sharpness = SHARP_EDGED
	max_integrity = 200
	resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
	var/datum/effect_system/spark_spread/spark_system
	var/datum/action/innate/dash/ninja/jaunt

/obj/item/energy_katana/Initialize(mapload)
	. = ..()
	jaunt = new(src)
	spark_system = new /datum/effect_system/spark_spread()
	spark_system.set_up(5, 0, src)
	spark_system.attach(src)

/obj/item/energy_katana/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
	. = ..()

	var/list/modifiers = params2list(click_parameters)

	if(LAZYACCESS(modifiers, RIGHT_CLICK) && !target.density)
		jaunt.Teleport(user, target)

/obj/item/energy_katana/pickup(mob/living/user)
	. = ..()
	jaunt.Grant(user, src)
	user.update_icons()
	playsound(src, 'sound/items/unsheath.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)

/obj/item/energy_katana/dropped(mob/user)
	. = ..()
	jaunt.Remove(user)
	user.update_icons()

//If we hit the Ninja who owns this Katana, they catch it.
//Works for if the Ninja throws it or it throws itself or someone tries
//To throw it at the ninja
/obj/item/energy_katana/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
	if(ishuman(hit_atom))
		var/mob/living/carbon/human/hit_human = hit_atom
		if(istype(hit_human.wear_suit, /obj/item/clothing/suit/space/space_ninja))
			var/obj/item/clothing/suit/space/space_ninja/ninja_suit = hit_human.wear_suit
			if(ninja_suit.energyKatana == src)
				returnToOwner(hit_human, 0, 1)
				return

	..()

/obj/item/energy_katana/Destroy()
	QDEL_NULL(spark_system)
	QDEL_NULL(jaunt)
	return ..()

/**
 * Proc called when the katana is recalled to its space ninja.
 *
 * Proc called when space ninja is hit with its suit's katana or the recall ability is used.
 * Arguments:
 * * user - To whom the katana is returning to.
 * * doSpark - whether or not the katana will spark when it returns.
 * * caught - boolean for whether or not the katana was caught or was teleported back.
 */
/obj/item/energy_katana/proc/returnToOwner(mob/living/carbon/human/user, doSpark = TRUE, caught = FALSE)
	if(!istype(user))
		return
	forceMove(get_turf(user))

	if(doSpark)
		spark_system.start()
		playsound(get_turf(src), "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)

	var/msg = ""

	if(user.put_in_hands(src))
		msg = "Your Energy Katana teleports into your hand!"
	else if(user.equip_to_slot_if_possible(src, ITEM_SLOT_BELT, 0, 1, 1))
		msg = "Your Energy Katana teleports back to you, sheathing itself as it does so!</span>"
	else
		msg = "Your Energy Katana teleports to your location!"

	if(caught)
		if(loc == user)
			msg = "You catch your Energy Katana!"
		else
			msg = "Your Energy Katana lands at your feet!"

	if(msg)
		to_chat(user, span_notice("[msg]"))

/datum/action/innate/dash/ninja
	current_charges = 3
	max_charges = 3
	charge_rate = 200
	recharge_sound = null


code:
/**
 * # Spider Charge
 *
 * A unique version of c4 possessed only by the space ninja.  Has a stronger blast radius.
 * Can only be detonated by space ninjas with the bombing objective.  Can only be set up where the objective says it can.
 * When it primes, the space ninja responsible will have their objective set to complete.
 *
 */
/obj/item/grenade/c4/ninja
	name = "spider charge"
	desc = "A modified C-4 charge supplied to you by the Spider Clan.  Its explosive power has been juiced up, but only works in one specific area."
	boom_sizes = list(4, 8, 12)
	var/mob/detonator = null

/obj/item/grenade/c4/ninja/afterattack(atom/movable/AM, mob/user, flag)
	var/datum/antagonist/ninja/ninja_antag = user.mind.has_antag_datum(/datum/antagonist/ninja)
	if(!ninja_antag)
		to_chat(user, span_notice("While it appears normal, you can't seem to detonate the charge."))
		return
	var/datum/objective/plant_explosive/objective = locate() in ninja_antag.objectives
	if(!objective)
		to_chat(user, span_notice("You can't seem to activate the charge.  It's location-locked, but you don't know where to detonate it."))
		return
	if(objective.detonation_location != get_area(user))
		to_chat(user, span_notice("This isn't the location you're supposed to use this!"))
		return
	detonator = user
	return ..()

/obj/item/grenade/c4/ninja/detonate(mob/living/lanced_by)
	. = ..()
	//Since we already did the checks in afterattack, the denonator must be a ninja with the bomb objective.
	if(!detonator)
		return
	var/datum/antagonist/ninja/ninja_antag = detonator.mind.has_antag_datum(/datum/antagonist/ninja)
	var/datum/objective/plant_explosive/objective = locate() in ninja_antag.objectives
	objective.completed = TRUE


code:
/datum/outfit/ninja
	name = "Space Ninja"
	uniform = /obj/item/clothing/under/color/black
	suit = /obj/item/clothing/suit/space/space_ninja
	glasses = /obj/item/clothing/glasses/night
	mask = /obj/item/clothing/mask/gas/space_ninja
	head = /obj/item/clothing/head/helmet/space/space_ninja
	ears = /obj/item/radio/headset
	shoes = /obj/item/clothing/shoes/space_ninja
	gloves = /obj/item/clothing/gloves/space_ninja
	l_pocket = /obj/item/grenade/c4/ninja
	r_pocket = /obj/item/tank/internals/emergency_oxygen
	internals_slot = ITEM_SLOT_RPOCKET
	belt = /obj/item/energy_katana
	implants = list(/obj/item/implant/explosive)

/datum/outfit/ninja/post_equip(mob/living/carbon/human/H)
	if(istype(H.wear_suit, suit))
		var/obj/item/clothing/suit/space/space_ninja/S = H.wear_suit
		if(istype(H.belt, belt))
			S.energyKatana = H.belt


code:
/**
 * # Ninja Gloves
 *
 * Space ninja's gloves.  Gives access to a number of special interactions.
 *
 * Gloves only found from space ninjas.  Allows the wearer to access special interactions with various objects.
 * These interactions are detailed in ninjaDrainAct.dm in the suit file.
 * These interactions are toggled by an action tied to the gloves.  The interactions will not activate if the user is also not wearing a ninja suit.
 *
 */
/obj/item/clothing/gloves/space_ninja
	desc = "These nano-enhanced gloves insulate from electricity and provide fire resistance."
	name = "ninja gloves"
	icon_state = "black"
	inhand_icon_state = "s-ninjan"
	siemens_coefficient = 0
	cold_protection = HANDS
	min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
	heat_protection = HANDS
	max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
	strip_delay = 120
	resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
	///Whether or not we're currently draining something
	var/draining = FALSE
	///Minimum amount of power we can drain in a single drain action
	var/mindrain = 200
	///Maximum amount of power we can drain in a single drain action
	var/maxdrain = 400
	///Whether or not the communication console hack was used to summon another antagonist
	var/communication_console_hack_success = FALSE
	///How many times the gloves have been used to force open doors.
	var/door_hack_counter = 0

/obj/item/clothing/gloves/space_ninja/Touch(atom/A,proximity,modifiers)
	if(!LAZYACCESS(modifiers, RIGHT_CLICK) || draining)
		return FALSE
	if(!ishuman(loc))
		return FALSE //Only works while worn

	var/mob/living/carbon/human/wearer = loc

	var/obj/item/clothing/suit/space/space_ninja/suit = wearer.wear_suit
	if(!istype(suit))
		return FALSE
	if(isturf(A))
		return FALSE

	if(!proximity)
		return FALSE

	A.add_fingerprint(wearer)

	draining = TRUE
	. = A.ninjadrain_act(suit,wearer,src)
	draining = FALSE

	if(isnum(.)) //Numerical values of drained handle their feedback here, Alpha values handle it themselves (Research hacking)
		if(.)
			to_chat(wearer, span_notice("Gained <B>[display_energy(.)]</B> of energy from [A]."))
		else
			to_chat(wearer, span_danger("\The [A] has run dry of energy, you must find another source!"))
	else
		. = FALSE //as to not cancel attack_hand()

/obj/item/clothing/gloves/space_ninja/examine(mob/user)
	. = ..() + "[p_their(TRUE)] energy drain mechanism is activated by touching objects in a disarming manner."


code:
/**
 * # Ninja Hood
 *
 * Space ninja's hood.  Provides armor and blocks AI tracking.
 *
 * A hood that only exists as a part of space ninja's starting kit.  Provides armor equal of space ninja's suit and disallows an AI to track the wearer.
 *
 */
/obj/item/clothing/head/helmet/space/space_ninja
	desc = "What may appear to be a simple black garment is in fact a highly sophisticated nano-weave helmet. Standard issue ninja gear."
	name = "ninja hood"
	icon_state = "s-ninja"
	inhand_icon_state = "s-ninja_mask"
	armor = list(MELEE = 40, BULLET = 30, LASER = 20,ENERGY = 15, BOMB = 30, BIO = 30, RAD = 25, FIRE = 100, ACID = 100)
	strip_delay = 12
	resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
	blockTracking = TRUE//Roughly the only unique thing about this helmet.
	flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR


code:
/**
 * # Ninja Mask
 *
 * Space ninja's mask.  Other than looking cool, doesn't do anything.
 *
 * A mask which only spawns as a part of space ninja's starting kit.  Functions as a gas mask.
 *
 */
/obj/item/clothing/mask/gas/space_ninja
	name = "ninja mask"
	desc = "A close-fitting mask that acts both as an air filter and a post-modern fashion statement."
	icon_state = "s-ninja"
	inhand_icon_state = "s-ninja_mask"
	strip_delay = 120
	resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF


code:
/**
 * Atom level proc for space ninja's glove interactions.
 *
 * Proc which only occurs when space ninja uses his gloves on an atom.
 * Does nothing by default, but effects will vary.
 * Arguments:
 * * ninja_suit - The offending space ninja's suit.
 * * ninja - The human mob wearing the suit.
 * * ninja_gloves - The offending space ninja's gloves.
 */
/atom/proc/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	return INVALID_DRAIN

//APC//
/obj/machinery/power/apc/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	var/maxcapacity = FALSE //Safety check for batteries
	var/drain = 0 //Drain amount from batteries
	var/drain_total = 0

	if(cell?.charge)
		var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
		spark_system.set_up(5, 0, loc)

		while(cell.charge> 0 && !maxcapacity)
			drain = rand(ninja_gloves.mindrain, ninja_gloves.maxdrain)

			if(cell.charge < drain)
				drain = cell.charge

			if(ninja_suit.cell.charge + drain > ninja_suit.cell.maxcharge)
				drain = ninja_suit.cell.maxcharge - ninja_suit.cell.charge
				maxcapacity = TRUE//Reached maximum battery capacity.

			if (do_after(ninja ,10, target = src))
				spark_system.start()
				playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
				cell.use(drain)
				ninja_suit.cell.give(drain)
				drain_total += drain
			else
				break

		if(!(obj_flags & EMAGGED))
			flick("apc-spark", ninja_gloves)
			playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
			obj_flags |= EMAGGED
			locked = FALSE
			update_appearance()

	return drain_total

//SMES//
/obj/machinery/power/smes/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	var/maxcapacity = FALSE //Safety check for batteries
	var/drain = 0 //Drain amount from batteries
	var/drain_total = 0

	if(charge)
		var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
		spark_system.set_up(5, 0, loc)

		while(charge > 0 && !maxcapacity)
			drain = rand(ninja_gloves.mindrain, ninja_gloves.maxdrain)

			if(charge < drain)
				drain = charge

			if(ninja_suit.cell.charge + drain > ninja_suit.cell.maxcharge)
				drain = ninja_suit.cell.maxcharge - ninja_suit.cell.charge
				maxcapacity = TRUE

			if (do_after(ninja,10, target = src))
				spark_system.start()
				playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
				charge -= drain
				ninja_suit.cell.give(drain)
				drain_total += drain

			else
				break

	return drain_total

//CELL//
/obj/item/stock_parts/cell/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	var/drain_total = 0

	if(charge)
		if(do_after(ninja, 30, target = src))
			drain_total = charge
			if(ninja_suit.cell.charge + charge > ninja_suit.cell.maxcharge)
				ninja_suit.cell.charge = ninja_suit.cell.maxcharge
			else
				ninja_suit.cell.give(charge)
			charge = 0
			corrupt()
			update_appearance()

	return drain_total

//RD SERVER//
/obj/machinery/rnd/server/master/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	. = DRAIN_RD_HACK_FAILED

	// If the traitor theft objective is still present, this will destroy it...
	if(!source_code_hdd)
		return ..()

	to_chat(ninja, span_notice("Hacking \the [src]..."))
	AI_notify_hack()
	to_chat(ninja, span_notice("Encrypted source code detected. Overloading storage device..."))
	if(do_after(ninja, 30 SECONDS, target = src))
		overload_source_code_hdd()
		to_chat(ninja, span_notice("Sabotage complete. Storage device overloaded."))
		var/datum/antagonist/ninja/ninja_antag = ninja.mind.has_antag_datum(/datum/antagonist/ninja)
		if(!ninja_antag)
			return
		var/datum/objective/research_secrets/objective = locate() in ninja_antag.objectives
		if(objective)
			objective.completed = TRUE

/obj/machinery/rnd/server/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	. = DRAIN_RD_HACK_FAILED

	to_chat(ninja, span_notice("Research notes detected. Corrupting data..."))

	if(!do_after(ninja, 30 SECONDS, target = src))
		return

	SSresearch.science_tech.modify_points_all(0)
	to_chat(ninja, span_notice("Sabotage complete. Research notes corrupted."))
	var/datum/antagonist/ninja/ninja_antag = ninja.mind.has_antag_datum(/datum/antagonist/ninja)
	if(!ninja_antag)
		return
	var/datum/objective/research_secrets/objective = locate() in ninja_antag.objectives
	if(objective)
		objective.completed = TRUE

//SECURITY CONSOLE//
/obj/machinery/computer/secure_data/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN
	AI_notify_hack()
	if(do_after(ninja, 200))
		for(var/datum/data/record/rec in sort_record(GLOB.data_core.general, sortBy, order))
			for(var/datum/data/record/security_record in GLOB.data_core.security)
				security_record.fields["criminal"] = "*Arrest*"
		var/datum/antagonist/ninja/ninja_antag = ninja.mind.has_antag_datum(/datum/antagonist/ninja)
		if(!ninja_antag)
			return
		var/datum/objective/security_scramble/objective = locate() in ninja_antag.objectives
		if(objective)
			objective.completed = TRUE

//COMMUNICATIONS CONSOLE//
/obj/machinery/computer/communications/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN
	if(ninja_gloves.communication_console_hack_success)
		return
	AI_notify_hack()
	if(do_after(ninja, 300))
		var/announcement_pick = rand(0, 1)
		switch(announcement_pick)
			if(0)
				priority_announce("Attention crew, it appears that someone on your station has made unexpected communication with an alien device in nearby space.", "[command_name()] High-Priority Update")
				var/datum/round_event_control/spawn_swarmer/swarmer_event = new/datum/round_event_control/spawn_swarmer
				swarmer_event.runEvent()
			if(1)
				priority_announce("Attention crew, it appears that someone on your station has made unexpected communication with a syndicate ship in nearby space.", "[command_name()] High-Priority Update")
				var/datum/round_event_control/pirates/pirate_event = new/datum/round_event_control/pirates
				pirate_event.runEvent()
		ninja_gloves.communication_console_hack_success = TRUE
		var/datum/antagonist/ninja/ninja_antag = ninja.mind.has_antag_datum(/datum/antagonist/ninja)
		if(!ninja_antag)
			return
		var/datum/objective/terror_message/objective = locate() in ninja_antag.objectives
		if(objective)
			objective.completed = TRUE

//AIRLOCK//
/obj/machinery/door/airlock/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	if(!operating && density && hasPower() && !(obj_flags & EMAGGED))
		emag_act()
		ninja_gloves.door_hack_counter++
		var/datum/antagonist/ninja/ninja_antag = ninja.mind.has_antag_datum(/datum/antagonist/ninja)
		if(!ninja_antag)
			return
		var/datum/objective/door_jack/objective = locate() in ninja_antag.objectives
		if(objective && objective.doors_required <= ninja_gloves.door_hack_counter)
			objective.completed = TRUE

//WIRE//
/obj/structure/cable/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	var/maxcapacity = FALSE //Safety check
	var/drain = 0 //Drain amount

	var/drain_total = 0

	var/datum/powernet/wire_powernet = powernet
	while(!maxcapacity && src)
		drain = (round((rand(ninja_gloves.mindrain, ninja_gloves.maxdrain))/2))
		var/drained = 0
		if(wire_powernet && do_after(ninja ,10, target = src))
			drained = min(drain, delayed_surplus())
			add_delayedload(drained)
			if(drained < drain)//if no power on net, drain apcs
				for(var/obj/machinery/power/terminal/affected_terminal in wire_powernet.nodes)
					if(istype(affected_terminal.master, /obj/machinery/power/apc))
						var/obj/machinery/power/apc/AP = affected_terminal.master
						if(AP.operating && AP.cell && AP.cell.charge > 0)
							AP.cell.charge = max(0, AP.cell.charge - 5)
							drained += 5
		else
			break

		ninja_suit.cell.give(drain)
		if(ninja_suit.cell.charge > ninja_suit.cell.maxcharge)
			drain_total += (drained-(ninja_suit.cell.charge - ninja_suit.cell.maxcharge))
			ninja_suit.cell.charge = ninja_suit.cell.maxcharge
			maxcapacity = TRUE
		else
			drain_total += drained
		ninja_suit.spark_system.start()

	return drain_total

//MECH//
/obj/vehicle/sealed/mecha/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	var/maxcapacity = FALSE //Safety check
	var/drain = 0 //Drain amount
	var/drain_total = 0

	to_chat(occupants, "[icon2html(src, occupants)][span_danger("Warning: Unauthorized access through sub-route 4, block H, detected.")]")
	if(get_charge())
		while(cell.charge > 0 && !maxcapacity)
			drain = rand(ninja_gloves.mindrain, ninja_gloves.maxdrain)
			if(cell.charge < drain)
				drain = cell.charge
			if(ninja_suit.cell.charge + drain > ninja_suit.cell.maxcharge)
				drain = ninja_suit.cell.maxcharge - ninja_suit.cell.charge
				maxcapacity = TRUE
			if (do_after(ninja, 10, target = src))
				spark_system.start()
				playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
				cell.use(drain)
				ninja_suit.cell.give(drain)
				drain_total += drain
			else
				break

	return drain_total

//BORG//
/mob/living/silicon/robot/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves || (ROLE_NINJA in faction))
		return INVALID_DRAIN

	to_chat(src, span_danger("Warni-***BZZZZZZZZZRT*** UPLOADING SPYDERPATCHER VERSION 9.5.2..."))
	if (do_after(ninja, 60, target = src))
		spark_system.start()
		playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
		to_chat(src, span_danger("UPLOAD COMPLETE. NEW CYBORG MODEL DETECTED.  INSTALLING..."))
		faction = list(ROLE_NINJA)
		bubble_icon = "syndibot"
		UnlinkSelf()
		ionpulse = TRUE
		laws = new /datum/ai_laws/ninja_override()
		model.transform_to(pick(/obj/item/robot_model/syndicate, /obj/item/robot_model/syndicate_medical, /obj/item/robot_model/saboteur))

		var/datum/antagonist/ninja/ninja_antag = ninja.mind.has_antag_datum(/datum/antagonist/ninja)
		if(!ninja_antag)
			return
		var/datum/objective/cyborg_hijack/objective = locate() in ninja_antag.objectives
		if(objective)
			objective.completed = TRUE

//CARBON MOBS//
/mob/living/carbon/ninjadrain_act(obj/item/clothing/suit/space/space_ninja/ninja_suit, mob/living/carbon/human/ninja, obj/item/clothing/gloves/space_ninja/ninja_gloves)
	if(!ninja_suit || !ninja || !ninja_gloves)
		return INVALID_DRAIN

	. = DRAIN_MOB_SHOCK_FAILED

	//Default cell = 10,000 charge, 10,000/1000 = 10 uses without charging/upgrading
	if(ninja_suit.cell?.charge && ninja_suit.cell.use(1000))
		. = DRAIN_MOB_SHOCK
		//Got that electric touch
		var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread()
		spark_system.set_up(5, 0, loc)
		playsound(src, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
		visible_message(span_danger("[ninja] electrocutes [src] with [ninja.p_their()] touch!"), span_userdanger("[ninja] electrocutes you with [ninja.p_their()] touch!"))
		Knockdown(3 SECONDS)


code:
/**
 * # Energy Net
 *
 * Energy net which ensnares prey until it is destroyed.  Used by space ninjas.
 *
 * Energy net which keeps its target from moving until it is destroyed.  Used to send
 * players to a holding area in which they could never leave, but such feature has since
 * been removed.
 */
/obj/structure/energy_net
	name = "energy net"
	desc = "It's a net made of green energy."
	icon = 'icons/effects/effects.dmi'
	icon_state = "energynet"

	density = TRUE//Can't pass through.
	opacity = FALSE //Can see through.
	mouse_opacity = MOUSE_OPACITY_ICON//So you can hit it with stuff.
	anchored = TRUE//Can't drag/grab the net.
	layer = ABOVE_ALL_MOB_LAYER
	max_integrity = 60 //How much health it has.
	can_buckle = 1
	buckle_lying = 0
	buckle_prevents_pull = TRUE
	///The creature currently caught in the net
	var/mob/living/affecting

/obj/structure/energy_net/play_attack_sound(damage, damage_type = BRUTE, damage_flag = 0)
	if(damage_type == BRUTE || damage_type == BURN)
		playsound(src, 'sound/weapons/slash.ogg', 80, TRUE)

/obj/structure/energy_net/Destroy()
	if(!QDELETED(affecting))
		affecting.visible_message(span_notice("[affecting.name] is recovered from the energy net!"), span_notice("You are recovered from the energy net!"), span_hear("You hear a grunt."))
	affecting = null
	return ..()

/obj/structure/energy_net/attack_paw(mob/user, list/modifiers)
	return attack_hand(user, modifiers)

/obj/structure/energy_net/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE)
	return//We only want our target to be buckled

/obj/structure/energy_net/user_unbuckle_mob(mob/living/buckled_mob, mob/living/user)
	return//The net must be destroyed to free the target


code:
//Wakes the user so they are able to do their thing. Also injects a decent dose of radium.
//Movement impairing would indicate drugs and the like.

/datum/action/item_action/ninjaboost
	check_flags = NONE
	name = "Adrenaline Boost"
	desc = "Inject a secret chemical that will counteract all movement-impairing effect."
	button_icon_state = "repulse"
	icon_icon = 'icons/mob/actions/actions_spells.dmi'

/**
 * Proc called to activate space ninja's adrenaline.
 *
 * Proc called to use space ninja's adrenaline.  Gets the ninja out of almost any stun.
 * Also makes them shout MGS references when used.  After a bit, it injects the user with
 * radium by calling a different proc.
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninjaboost()
	if(ninjacost(0,N_ADRENALINE))
		return
	var/mob/living/carbon/human/ninja = affecting
	ninja.SetUnconscious(0)
	ninja.SetStun(0)
	ninja.SetKnockdown(0)
	ninja.SetImmobilized(0)
	ninja.SetParalyzed(0)
	ninja.adjustStaminaLoss(-200)
	ninja.stuttering = 0
	ninja.reagents.add_reagent(/datum/reagent/medicine/stimulants, 5)
	ninja.say(pick("A CORNERED FOX IS MORE DANGEROUS THAN A JACKAL!","HURT ME MOOORRREEE!","IMPRESSIVE!"), forced = "ninjaboost")
	a_boost = FALSE
	to_chat(ninja, span_notice("You have used the adrenaline boost."))
	s_coold = 6
	addtimer(CALLBACK(src, .proc/ninjaboost_after), 70)

/**
 * Proc called to inject the ninja with radium.
 *
 * Used after 7 seconds of using the ninja's adrenaline.
 * Injects the user with how much radium the suit needs to refill an adrenaline boost.
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninjaboost_after()
	var/mob/living/carbon/human/ninja = affecting
	ninja.reagents.add_reagent(/datum/reagent/uranium/radium, a_transfer * 0.25)
	to_chat(ninja, span_danger("You are beginning to feel the after-effect of the injection."))


code:
/**
 * Proc called to check if the ninja can afford an ability's cost.
 *
 * Proc which determine whether or not a space ninja can afford to use a specific ability.
 * It can also cancel stealth if the ability requested it.
 * Arguments:
 * * cost - the energy cost of the ability
 * * specificCheck - Determines if the check is a normal one, an adrenaline one, or a stealth cancel check.
 * * Returns TRUE or the current cooldown timer if we can't perform the ability, and FALSE if we can.
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninjacost(cost = 0, specificCheck = 0)
	var/mob/living/carbon/human/ninja = affecting
	var/actualCost = cost*10
	if(cost && cell.charge < actualCost)
		to_chat(ninja, span_warning("Not enough energy!"))
		return TRUE
	else
		//This shit used to be handled individually on every proc.. why even bother with a universal check proc then?
		cell.charge-=(actualCost)

	switch(specificCheck)
		if(N_STEALTH_CANCEL)
			cancel_stealth()//Get rid of it.
		if(N_ADRENALINE)
			if(!a_boost)
				to_chat(ninja, span_warning("You do not have any more adrenaline boosters!"))
				return TRUE
	return (s_coold)//Returns the value of the variable which counts down to zero.


code:
//Disables nearby tech equipment.

/datum/action/item_action/ninjapulse
	name = "EM Burst (50E)"
	desc = "Disable any nearby technology with an electro-magnetic pulse."
	button_icon_state = "emp"
	icon_icon = 'icons/mob/actions/actions_spells.dmi'

/**
 * Proc called to allow the ninja to EMP the nearby area.
 *
 * Proc called to allow the ninja to EMP the nearby area.  By default, costs 500E, which is half of the default battery's max charge.
 * Also affects the ninja as well.
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninjapulse()
	if(ninjacost(500,N_STEALTH_CANCEL))
		return
	var/mob/living/carbon/human/H = affecting
	playsound(H.loc, 'sound/effects/empulse.ogg', 60, 2)
	empulse(H, 4, 6) //Procs sure are nice. Slightly weaker than wizard's disable tch.
	s_coold = 4


code:
/datum/action/item_action/ninjanet
	name = "Energy Net (40E)"
	desc = "Captures a fallen opponent in a net of energy."
	button_icon_state = "energynet"
	icon_icon = 'icons/effects/effects.dmi'

/**
 * Proc called to ensnare a person in a energy net.
 *
 * Used to ensnare a target in an energy net, preventing them from moving until the net is broken.
 * Costs 40E, which is 40% of the default battery's max charge.  Intended as a means of reliably locking down an opponent when ninja stars won't suffice.
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninjanet()
	var/mob/living/carbon/human/ninja = affecting
	var/mob/living/net_target = input("Select who to capture:","Capture who?",null) as null|mob in sort_names(oview(ninja))

	if(QDELETED(net_target)||!(net_target in oview(ninja)))
		return

	if(locate(/obj/structure/energy_net) in get_turf(net_target))//Check if they are already being affected by an energy net.
		to_chat(ninja, span_warning("[net_target.p_they(TRUE)] are already trapped inside an energy net!"))
		return
	for(var/turf/between_turf in get_line(get_turf(ninja), get_turf(net_target)))
		if(between_turf.density)//Don't want them shooting nets through walls. It's kind of cheesy.
			to_chat(ninja, span_warning("You may not use an energy net through solid obstacles!"))
			return
	if(!ninjacost(400,N_STEALTH_CANCEL))
		ninja.Beam(net_target, "n_beam", time = 15)
		ninja.say("Get over here!", forced = "ninja net")
		var/obj/structure/energy_net/net = new /obj/structure/energy_net(net_target.drop_location())
		net.affecting = net_target
		ninja.visible_message(span_danger("[ninja] caught [net_target] with an energy net!"),span_notice("You caught [net_target] with an energy net!"))

		if(net_target.buckled)
			net_target.buckled.unbuckle_mob(affecting,TRUE)
		net.buckle_mob(net_target, TRUE) //No moving for you!


code:
/datum/action/item_action/ninjastar
	name = "Create Throwing Stars (1E)"
	desc = "Creates a throwing star in your hand, if possible."
	button_icon_state = "throwingstar"
	icon_icon = 'icons/obj/items_and_weapons.dmi'

/**
 * Proc called to create a ninja star in the ninja's hands.
 *
 * Called to create a ninja star in the wearer's hand.  The ninja
 * star doesn't do much up-front damage, but deals stamina damage
 * as the target moves around, forcing a finish or flee scenario.
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninjastar()
	if(ninjacost(10))
		return
	var/mob/living/carbon/human/ninja = affecting
	var/obj/item/throwing_star/stamina/ninja/ninja_star = new(ninja)
	if(ninja.put_in_hands(ninja_star))
		to_chat(ninja, span_notice("A throwing star has been created in your hand!"))
	else
		qdel(ninja_star)
		to_chat(ninja, span_notice("You can't create a throwing star, your hands are full!"))

/**
 * # Ninja Throwing Star
 *
 * a throwing star which specifically makes sure you know it came from a real ninja.
 *
 * The most important item in the entire codebase, as without it we would all cease to exist.
 * Inherits everything that makes it interesting the stamina throwing star, but the most
 * important change made is that its name specifically has the prefix, 'ninja' in it.
 * This provides the detective role with information to play off of by ensuring that his
 * assumption that a space ninja is aboard the ship to be true when he find 20 of these in
 * the captain's back.  Along with this, its throwforce is 10 instead of the 5 of the stamina
 * throwing star, meaning it'll do a little more damage than the stamina throwing star does as well.
 * Changes to this item need to be approved by all maintainers, so if you do change it, make sure
 * you go through the proper channels, lest you get permabanned.  Do I make myself clear?
 */
/obj/item/throwing_star/stamina/ninja
	name = "ninja throwing star"
	throwforce = 10


code:
/datum/action/item_action/ninjastatus
	check_flags = NONE
	name = "Status Readout"
	desc = "Gives a detailed readout about your current status."
	button_icon_state = "health"
	icon_icon = 'icons/obj/device.dmi'

/**
 * Proc called to put a status readout to the ninja in chat.
 *
 * Called put some information about the ninja's current status into chat.
 * This information used to be displayed constantly on the status tab screen
 * when the suit was on, but was turned into this as to remove the code from
 * human.dm
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninjastatus()
	var/mob/living/carbon/human/ninja = affecting
	var/list/info_list = list()
	info_list += "[span_info("SpiderOS Status: [s_initialized ? "Initialized" : "Disabled"]")]\n"
	info_list += "[span_info("Current Time: [station_time_timestamp()]")]\n"
	//Ninja status
	info_list += "[span_info("Fingerprints: [md5(ninja.dna.unique_identity)]")]\n"
	info_list += "[span_info("Unique Identity: [ninja.dna.unique_enzymes]")]\n"
	info_list += "[span_info("Overall Status: [ninja.stat > 1 ? "dead" : "[ninja.health]% healthy"]")]\n"
	info_list += "[span_info("Nutrition Status: [ninja.nutrition]")]\n"
	info_list += "[span_info("Oxygen Loss: [ninja.getOxyLoss()]")]\n"
	info_list += "[span_info("Toxin Levels: [ninja.getToxLoss()]")]\n"
	info_list += "[span_info("Burn Severity: [ninja.getFireLoss()]")]\n"
	info_list += "[span_info("Brute Trauma: [ninja.getBruteLoss()]")]\n"
	info_list += "[span_info("Radiation Levels: [ninja.radiation] rad")]\n"
	info_list += "[span_info("Body Temperature: [ninja.bodytemperature-T0C] degrees C ([ninja.bodytemperature*1.8-459.67] degrees F)")]\n"

	//Diseases
	if(length(ninja.diseases))
		info_list += "Viruses:"
		for(var/datum/disease/ninja_disease in ninja.diseases)
			info_list += "[span_info("* [ninja_disease.name], Type: [ninja_disease.spread_text], Stage: [ninja_disease.stage]/[ninja_disease.max_stages], Possible Cure: [ninja_disease.cure_text]")]\n"

	to_chat(ninja, "[info_list.Join()]")


code:
/datum/action/item_action/ninja_stealth
	name = "Toggle Stealth"
	desc = "Toggles stealth mode on and off."
	button_icon_state = "ninja_cloak"
	icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'

/**
 * Proc called to toggle ninja stealth.
 *
 * Proc called to toggle whether or not the ninja is in stealth mode.
 * If cancelling, calls a separate proc in case something else needs to quickly cancel stealth.
 */
/obj/item/clothing/suit/space/space_ninja/proc/toggle_stealth()
	var/mob/living/carbon/human/ninja = affecting
	if(!ninja)
		return
	if(stealth)
		cancel_stealth()
	else
		if(cell.charge <= 0)
			to_chat(ninja, span_warning("You don't have enough power to enable Stealth!"))
			return
		stealth = !stealth
		animate(ninja, alpha = 20,time = 12)
		ninja.visible_message(span_warning("[ninja.name] vanishes into thin air!"), \
						span_notice("You are now mostly invisible to normal detection."))

/**
 * Proc called to cancel stealth.
 *
 * Called to cancel the stealth effect if it is ongoing.
 * Does nothing otherwise.
 * Arguments:
 * * Returns false if either the ninja no longer exists or is already visible, returns true if we successfully made the ninja visible.
 */
/obj/item/clothing/suit/space/space_ninja/proc/cancel_stealth()
	var/mob/living/carbon/human/ninja = affecting
	if(!ninja)
		return FALSE
	if(stealth)
		stealth = !stealth
		animate(ninja, alpha = 255, time = 12)
		ninja.visible_message(span_warning("[ninja.name] appears from thin air!"), \
						span_notice("You are now visible."))
		return TRUE
	return FALSE


code:
#define NINJA_LOCK_PHASE 1
#define NINJA_ICON_GENERATE_PHASE 3
#define NINJA_COMPLETE_PHASE 6
#define NINJA_DEINIT_LOGOFF_PHASE 1
#define NINJA_DEINIT_STEALTH_PHASE 5

GLOBAL_LIST_INIT(ninja_initialize_messages, list(
	"Now initializing...",
	"Securing external locking mechanism...\nNeural-net established.",
	"Extending neural-net interface...\nNow monitoring brain wave pattern...",
	"Linking neural-net interface...\nPattern GREEN, continuing operation.",
	"VOID-shift device status: ONLINE.\nCLOAK-tech device status: ONLINE.",
	"Primary system status: ONLINE.\nBackup system status: ONLINE.\nCurrent energy capacity: ",
	"All systems operational. Welcome to SpiderOS, "
))

GLOBAL_LIST_INIT(ninja_deinitialize_messages, list(
	"Now de-initializing...",
	"Shutting down SpiderOS.",
	"Primary system status: OFFLINE.\nBackup system status: OFFLINE.",
	"VOID-shift device status: OFFLINE.\nCLOAK-tech device status: OFFLINE.",
	"Disconnecting neural-net interface...Success.",
	"Disengaging neural-net interface... Success.",
	"Unsecuring external locking mechanism...\nNeural-net abolished.\nOperation status: FINISHED."
))

/datum/action/item_action/initialize_ninja_suit
	name = "Toggle Ninja Suit"

/**
 * Toggles the ninja suit on/off
 *
 * Attempts to initialize or deinitialize the ninja suit
 */
/obj/item/clothing/suit/space/space_ninja/proc/toggle_on_off()
	. = TRUE
	if(s_busy)
		to_chat(loc, "[span_warning("ERROR")]: You cannot use this function at this time.")
		return FALSE
	s_busy = TRUE
	if(s_initialized)
		deinitialize()
	else
		ninitialize()

/**
 * Initializes the ninja suit
 *
 * Initializes the ninja suit through seven phases, each of which calls this proc with an incremented phase
 * Arguments:
 * * delay - The delay between each phase of initialization
 * * ninja - The human who is being affected by the suit
 * * phase - The phase of initialization
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninitialize(delay = s_delay, mob/living/carbon/human/ninja = loc, phase = 0)
	if(!ninja || !ninja.mind)
		s_busy = FALSE
		return
	if (phase > NINJA_LOCK_PHASE && (ninja.stat == DEAD || ninja.health <= 0))
		to_chat(ninja, span_danger("FÄAL ï¿½Rrï¿½R: 344--93#ï¿½&&21 BRï¿½ï¿½N |/|/aVï¿½ PATT$RN RED\nA-A-aBï¿½rTï¿½NG..."))
		unlock_suit(ninja)
		s_busy = FALSE
		return

	var/message = GLOB.ninja_initialize_messages[phase + 1]
	switch(phase)
		if (NINJA_LOCK_PHASE)
			if(!lock_suit(ninja))//To lock the suit onto wearer.
				s_busy = FALSE
				return
		if (NINJA_ICON_GENERATE_PHASE)
			lockIcons(ninja)//Check for icons.
			ninja.regenerate_icons()
		if (NINJA_COMPLETE_PHASE - 1)
			message += "[display_energy(cell.charge)]."
		if (NINJA_COMPLETE_PHASE)
			message += "[ninja.real_name]."
			s_initialized = TRUE
			s_busy = FALSE

	to_chat(ninja, span_notice("[message]"))
	playsound(ninja, 'sound/effects/sparks1.ogg', 10, TRUE)

	if (phase < NINJA_COMPLETE_PHASE)
		addtimer(CALLBACK(src, .proc/ninitialize, delay, ninja, phase + 1), delay)

/**
 * Deinitializes the ninja suit
 *
 * Deinitializes the ninja suit through eight phases, each of which calls this proc with an incremented phase
 * Arguments:
 * * delay - The delay between each phase of deinitialization
 * * ninja - The human who is being affected by the suit
 * * phase - The phase of deinitialization
 */
/obj/item/clothing/suit/space/space_ninja/proc/deinitialize(delay = s_delay, mob/living/carbon/human/ninja = affecting == loc ? affecting : null, phase = 0)
	if (!ninja || !ninja.mind)
		s_busy = FALSE
		return
	if (phase == 0 && tgui_alert(usr, "Are you certain you wish to remove the suit? This will take time and remove all abilities.",,list("Yes","No")) == "No")
		s_busy = FALSE
		return

	var/message = GLOB.ninja_deinitialize_messages[phase + 1]
	switch(phase)
		if(NINJA_DEINIT_LOGOFF_PHASE)
			message = "Logging off, [ninja.real_name]. " + message
		if(NINJA_DEINIT_STEALTH_PHASE)
			cancel_stealth()
	to_chat(ninja, span_notice("[message]"))
	playsound(ninja, 'sound/items/deconstruct.ogg', 10, TRUE)

	if (phase < NINJA_COMPLETE_PHASE)
		addtimer(CALLBACK(src, .proc/deinitialize, delay, ninja, phase + 1), delay)
	else
		unlock_suit(ninja)
		ninja.regenerate_icons()
		s_initialized = FALSE
		s_busy = FALSE

#undef NINJA_LOCK_PHASE
#undef NINJA_ICON_GENERATE_PHASE
#undef NINJA_COMPLETE_PHASE
#undef NINJA_DEINIT_LOGOFF_PHASE
#undef NINJA_DEINIT_STEALTH_PHASE


code:
/datum/action/item_action/ninja_sword_recall
	name = "Recall Energy Katana (Variable Cost)"
	desc = "Teleports the Energy Katana linked to this suit to its wearer, cost based on distance."
	button_icon_state = "energy_katana"
	icon_icon = 'icons/obj/items_and_weapons.dmi'

/**
 * Proc called to recall the ninja's sword.
 *
 * Called to summon the ninja's katana back to them
 * If the katana can see the ninja, it will throw itself towards them.
 * If not, the katana will teleport itself to the ninja.
 */
/obj/item/clothing/suit/space/space_ninja/proc/ninja_sword_recall()
	var/mob/living/carbon/human/ninja = affecting
	var/cost = 0
	var/inview = TRUE

	if(!energyKatana)
		to_chat(ninja, span_warning("Could not locate Energy Katana!"))
		return

	if(energyKatana in ninja)
		return

	var/distance = get_dist(ninja,energyKatana)

	if(!(energyKatana in view(ninja)))
		cost = distance //Actual cost is cost x 10, so 5 turfs is 50 cost.
		inview = FALSE

	if(!ninjacost(cost))
		if(iscarbon(energyKatana.loc))
			var/mob/living/carbon/sword_holder = energyKatana.loc
			sword_holder.transferItemToLoc(energyKatana, get_turf(energyKatana), TRUE)

		else
			energyKatana.forceMove(get_turf(energyKatana))

		if(inview) //If we can see the katana, throw it towards ourselves, damaging people as we go.
			energyKatana.spark_system.start()
			playsound(ninja, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
			ninja.visible_message(span_danger("\the [energyKatana] flies towards [ninja]!"),span_warning("You hold out your hand and \the [energyKatana] flies towards you!"))
			energyKatana.throw_at(ninja, distance+1, energyKatana.throw_speed, ninja)

		else //Else just TP it to us.
			energyKatana.returnToOwner(ninja, 1)


code:
/**
 * # Ninja Shoes
 *
 * Space ninja's shoes.  Gives him armor on his feet.
 *
 * Space ninja's ninja shoes.  How mousey.  Gives him slip protection and protection against attacks.
 * Also are temperature resistant.
 *
 */
/obj/item/clothing/shoes/space_ninja
	name = "ninja shoes"
	desc = "A pair of running shoes. Excellent for running and even better for smashing skulls."
	icon_state = "s-ninja"
	inhand_icon_state = "secshoes"
	permeability_coefficient = 0.01
	clothing_flags = NOSLIP
	resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
	armor = list(MELEE = 40, BULLET = 30, LASER = 20,ENERGY = 15, BOMB = 30, BIO = 30, RAD = 30, FIRE = 100, ACID = 100)
	strip_delay = 120
	cold_protection = FEET
	min_cold_protection_temperature = SHOES_MIN_TEMP_PROTECT
	heat_protection = FEET
	max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT
	slowdown = -1


code:
/**
 * # Ninja Suit
 *
 * Space ninja's suit.  Provides him with most of his powers.
 *
 * Space ninja's suit.  Gives space ninja all his iconic powers, which are mostly kept in
 * the folder ninja_equipment_actions.  Has a lot of unique stuff going on, so make sure to check
 * the variables.  Check suit_attackby to see radium interaction, disk copying, and cell replacement.
 *
 */
/obj/item/clothing/suit/space/space_ninja
	name = "ninja suit"
	desc = "A unique, vacuum-proof suit of nano-enhanced armor designed specifically for Spider Clan assassins."
	icon_state = "s-ninja"
	inhand_icon_state = "s-ninja_suit"
	allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/stock_parts/cell)
	resistance_flags = LAVA_PROOF | ACID_PROOF
	armor = list(MELEE = 40, BULLET = 30, LASER = 20,ENERGY = 30, BOMB = 30, BIO = 30, RAD = 30, FIRE = 100, ACID = 100)
	strip_delay = 12
	min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
	actions_types = list(/datum/action/item_action/initialize_ninja_suit, /datum/action/item_action/ninjastatus, /datum/action/item_action/ninjaboost, /datum/action/item_action/ninjapulse, /datum/action/item_action/ninjastar, /datum/action/item_action/ninjanet, /datum/action/item_action/ninja_sword_recall, /datum/action/item_action/ninja_stealth)

	///The person wearing the suit
	var/mob/living/carbon/human/affecting = null
	///The suit's spark system, used for... sparking.
	var/datum/effect_system/spark_spread/spark_system
	///The suit's stored research.  Used for the research objective (see antagonist file)
	var/datum/techweb/stored_research
	///The katana registered with the suit, used for recalling and catching the katana.  Set when the ninja outfit is created.
	var/obj/item/energy_katana/energyKatana

	///The space ninja's hood.
	var/obj/item/clothing/head/helmet/space/space_ninja/n_hood
	///The space ninja's shoes.
	var/obj/item/clothing/shoes/space_ninja/n_shoes
	///The space ninja's gloves.
	var/obj/item/clothing/gloves/space_ninja/n_gloves
	///The space ninja's mask.
	var/obj/item/clothing/mask/gas/space_ninja/n_mask

	///Whether or not the suit is currently booted up.  Starts off.
	var/s_initialized = FALSE//Suit starts off.
	///The suit's current cooldown.  If not 0, blocks usage of most abilities, and decrements its value by 1 every process
	var/s_coold = 0
	///How much energy the suit expends in a single process
	var/s_cost = 1
	///Additional energy cost for cloaking per process
	var/s_acost = 4
	///How fast the suit is at certain actions, like draining power from things
	var/s_delay = 40
	///Units of radium required to refill the adrenaline boost
	var/a_transfer = 20//How much radium is required to refill the adrenaline boost.
	///Whether or not the suit is currently in stealth mode.
	var/stealth = FALSE//Stealth off.
	///Whether or not the wearer is in the middle of an action, like hacking.
	var/s_busy = FALSE
	///Whether or not the adrenaline boost ability is available
	var/a_boost = TRUE

/obj/item/clothing/suit/space/space_ninja/examine(mob/user)
	. = ..()
	if(!s_initialized)
		return
	if(!user == affecting)
		return
	. += "All systems operational. Current energy capacity: [display_energy(cell.charge)].\n"+\
	"The CLOAK-tech device is [stealth?"active":"inactive"].\n"+\
	"[a_boost?"An adrenaline boost is available to use.":"There is no adrenaline boost available.  Try refilling the suit with 20 units of radium."]"

/obj/item/clothing/suit/space/space_ninja/Initialize(mapload)
	. = ..()

	//Spark Init
	spark_system = new
	spark_system.set_up(5, 0, src)
	spark_system.attach(src)

	//Research Init
	stored_research = new()

	//Cell Init
	cell = new/obj/item/stock_parts/cell/high
	cell.charge = 9000
	cell.name = "black power cell"
	cell.icon_state = "bscell"

/obj/item/clothing/suit/space/space_ninja/Destroy()
	QDEL_NULL(spark_system)
	QDEL_NULL(cell)
	return ..()

// seal the cell in the ninja outfit
/obj/item/clothing/suit/space/space_ninja/toggle_spacesuit_cell(mob/user)
	return

// Space Suit temperature regulation and power usage
/obj/item/clothing/suit/space/space_ninja/process(delta_time)
	var/mob/living/carbon/human/user = src.loc
	if(!user || !ishuman(user) || !(user.wear_suit == src))
		return

	// Check for energy usage
	if(s_initialized)
		if(!affecting)
			terminate() // Kills the suit and attached objects.
		else if(cell.charge > 0)
			if(s_coold > 0)
				s_coold = max(s_coold - delta_time, 0) // Checks for ability s_cooldown first.
			cell.charge -= s_cost * delta_time // s_cost is the default energy cost each ntick, usually 5.
			if(stealth) // If stealth is active.
				cell.charge -= s_acost * delta_time
		else
			cell.charge = 0
			cancel_stealth()

	user.adjust_bodytemperature(BODYTEMP_NORMAL - user.bodytemperature)

/obj/item/clothing/suit/space/space_ninja/ui_action_click(mob/user, action)
	if(IS_NINJA_SUIT_INITIALIZATION(action))
		toggle_on_off()
		return TRUE
	if(!s_initialized)
		to_chat(user, span_warning("ERROR: suit offline. Please activate suit."))
		return FALSE
	if(s_coold > 0)
		to_chat(user, span_warning("ERROR: suit is on cooldown."))
		return FALSE
	if(IS_NINJA_SUIT_STATUS(action))
		ninjastatus()
		return TRUE
	if(IS_NINJA_SUIT_BOOST(action))
		ninjaboost()
		return TRUE
	if(IS_NINJA_SUIT_EMP(action))
		ninjapulse()
		return TRUE
	if(IS_NINJA_SUIT_STAR_CREATION(action))
		ninjastar()
		return TRUE
	if(IS_NINJA_SUIT_NET_CREATION(action))
		ninjanet()
		return TRUE
	if(IS_NINJA_SUIT_SWORD_RECALL(action))
		ninja_sword_recall()
		return TRUE
	if(IS_NINJA_SUIT_STEALTH(action))
		toggle_stealth()
		return TRUE
	return FALSE

/obj/item/clothing/suit/space/space_ninja/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
	. = ..()
	if(stealth)
		cancel_stealth()
		s_coold = 5

/**
 * Proc for changing the suit's appearance upon locking.
 *
 * Proc for when space ninja's suit locks.  If the user selects Original, gives it glowing lights, along with having an alternate sprite for female body types.
 * Yes, we do have nipLEDs, how could you tell?
 * If the user selects New Age, it applies new sprites to all the gear.
 * Arguments:
 * * ninja - The person wearing the suit.
 */
/obj/item/clothing/suit/space/space_ninja/proc/lockIcons(mob/living/carbon/human/ninja)
	var/design_choice = tgui_alert(ninja, "Please choose your desired suit design.",,list("Original","New Age"))
	switch(design_choice)
		if("Original")
			icon_state = ninja.body_type == "female" ? "s-ninjanf" : "s-ninjan"
			ninja.gloves.icon_state = "s-ninjan"
			ninja.gloves.inhand_icon_state = "s-ninjan"
		if("New Age")
			icon_state ="ninja_new"
			n_hood.icon_state = "ninja_newcowl"
			n_gloves.icon_state = "ninja_new"
			if(n_mask)
				n_mask.icon_state = "ninja_new"

/**
 * Proc called to lock the important gear pieces onto space ninja's body.
 *
 * Called during the suit startup to lock all gear pieces onto space ninja.
 * Terminates if a gear piece is not being worn.  Also gives the ninja the inability to use firearms.
 * If the person in the suit isn't a ninja when this is called, this proc just gibs them instead.
 * Arguments:
 * * ninja - The person wearing the suit.
 * * Returns false if the locking fails due to lack of all suit parts, and true if it succeeds.
 */
/obj/item/clothing/suit/space/space_ninja/proc/lock_suit(mob/living/carbon/human/ninja)
	if(!istype(ninja))
		return FALSE
	if(!IS_SPACE_NINJA(ninja))
		to_chat(ninja, span_danger("fÄTaL ÈÈRRoR: 382200-*#00CÖDE RED\nUNAUHORIZED USÈ DETÈCeD\nCoMMÈNCING SUB-R0UIN3 13...\nTÈRMInATING U-U-USÈR..."))
		ninja.gib()
		return FALSE
	if(!istype(ninja.head, /obj/item/clothing/head/helmet/space/space_ninja))
		to_chat(ninja, "[span_userdanger("ERROR")]: 100113 UNABLE TO LOCATE HEAD GEAR\nABORTING...")
		return FALSE
	if(!istype(ninja.shoes, /obj/item/clothing/shoes/space_ninja))
		to_chat(ninja, "[span_userdanger("ERROR")]: 122011 UNABLE TO LOCATE FOOT GEAR\nABORTING...")
		return FALSE
	if(!istype(ninja.gloves, /obj/item/clothing/gloves/space_ninja))
		to_chat(ninja, "[span_userdanger("ERROR")]: 110223 UNABLE TO LOCATE HAND GEAR\nABORTING...")
		return FALSE
	affecting = ninja
	ADD_TRAIT(src, TRAIT_NODROP, NINJA_SUIT_TRAIT)
	n_hood = ninja.head
	ADD_TRAIT(n_hood, TRAIT_NODROP, NINJA_SUIT_TRAIT)
	n_shoes = ninja.shoes
	ADD_TRAIT(n_shoes, TRAIT_NODROP, NINJA_SUIT_TRAIT)
	n_gloves = ninja.gloves
	ADD_TRAIT(n_gloves, TRAIT_NODROP, NINJA_SUIT_TRAIT)
	n_mask = ninja.wear_mask

	ADD_TRAIT(ninja, TRAIT_NOGUNS, NINJA_SUIT_TRAIT)
	return TRUE

/**
 * Proc called to unlock all the gear off space ninja's body.
 *
 * Proc which is essentially the opposite of lock_suit.  Lets you take off all the suit parts.
 * Also gets rid of the objection to using firearms from the wearer.
 * Arguments:
 * * ninja - The person wearing the suit.
 */
/obj/item/clothing/suit/space/space_ninja/proc/unlock_suit(mob/living/carbon/human/ninja)
	affecting = null
	REMOVE_TRAIT(src, TRAIT_NODROP, NINJA_SUIT_TRAIT)
	icon_state = "s-ninja"
	if(n_hood)//Should be attached, might not be attached.
		REMOVE_TRAIT(n_hood, TRAIT_NODROP, NINJA_SUIT_TRAIT)
		n_hood.icon_state = "s-ninja"
	if(n_shoes)
		REMOVE_TRAIT(n_shoes, TRAIT_NODROP, NINJA_SUIT_TRAIT)
	if(n_gloves)
		n_gloves.icon_state = "black"
		REMOVE_TRAIT(n_gloves, TRAIT_NODROP, NINJA_SUIT_TRAIT)
		n_gloves.draining = FALSE

		REMOVE_TRAIT(ninja, TRAIT_NOGUNS, NINJA_SUIT_TRAIT)
	if(n_mask)
		n_mask.icon_state = "s-ninja"

/**
 * Proc used to delete all the attachments and itself.
 *
 * Can be called to entire rid of the suit pieces and the suit itself.
 */
/obj/item/clothing/suit/space/space_ninja/proc/terminate()
	QDEL_NULL(n_hood)
	QDEL_NULL(n_gloves)
	QDEL_NULL(n_shoes)
	QDEL_NULL(src)


code:
/obj/item/clothing/suit/space/space_ninja/attackby(obj/item/I, mob/ninja, params)
	if(ninja!=affecting)//Safety, in case you try doing this without wearing the suit/being the person with the suit.
		return ..()

	if(istype(I, /obj/item/reagent_containers/glass) && I.reagents.has_reagent(/datum/reagent/uranium/radium, a_transfer) && a_boost != TRUE)//If it's a glass beaker, and what we're transferring is radium.
		I.reagents.remove_reagent(/datum/reagent/uranium/radium, a_transfer)
		a_boost = TRUE;
		to_chat(ninja, span_notice("The suit's adrenaline boost is now reloaded."))
		return

	else if(istype(I, /obj/item/stock_parts/cell))
		var/obj/item/stock_parts/cell/CELL = I
		if(CELL.maxcharge > cell.maxcharge)
			to_chat(ninja, span_notice("Higher maximum capacity detected.\nUpgrading..."))
			if (do_after(ninja,s_delay, target = src))
				ninja.transferItemToLoc(CELL, src)
				CELL.charge = min(CELL.charge+cell.charge, CELL.maxcharge)
				var/obj/item/stock_parts/cell/old_cell = cell
				old_cell.charge = 0
				ninja.put_in_hands(old_cell)
				old_cell.add_fingerprint(ninja)
				old_cell.corrupt()
				old_cell.update_appearance()
				cell = CELL
				to_chat(ninja, span_notice("Upgrade complete. Maximum capacity: <b>[round(cell.maxcharge/100)]</b>%"))
			else
				to_chat(ninja, span_danger("Procedure interrupted. Protocol terminated."))
		return

	else if(istype(I, /obj/item/disk/tech_disk))//If it's a data disk, we want to copy the research on to the suit.
		var/obj/item/disk/tech_disk/TD = I
		var/has_research = FALSE
		for(var/node in TD.stored_research.researched_nodes)
			if(!stored_research.researched_nodes[node])
				has_research = TRUE
				break
		if(has_research)//If it has something on it.
			to_chat(ninja, span_notice("Research information detected, processing..."))
			if(do_after(ninja,s_delay, target = src))
				TD.stored_research.copy_research_to(stored_research)
				qdel(TD.stored_research)
				TD.stored_research = new
				to_chat(ninja, span_notice("Data analyzed and updated. Disk erased."))
			else
				to_chat(ninja, "[span_userdanger("ERROR")]: Procedure interrupted. Process terminated.")
		else
			to_chat(ninja, span_notice("No new research information detected."))
		return
	return ..()


code:
/obj/item/paper/carbon
	name = "sheet of carbon"
	icon_state = "paper_stack"
	inhand_icon_state = "paper"
	show_written_words = FALSE
	var/copied = FALSE

/obj/item/paper/carbon/update_icon_state()
	if(copied)
		icon_state = "paper"
	else
		icon_state = "paper_stack"
	if(info || add_info)
		icon_state = "[icon_state]_words"
	return ..()

/obj/item/paper/carbon/examine()
	. = ..()
	if(copied)
		return
	. += span_notice("Right-click to tear off the carbon-copy (you must use both hands).")

/obj/item/paper/carbon/proc/removecopy(mob/living/user)
	if(copied)
		to_chat(user, span_notice("There are no more carbon copies attached to this paper!"))
		return

	var/obj/item/paper/carbon/copy = copy(/obj/item/paper/carbon_copy, loc.drop_location(), FALSE)
	copy.name = "\improper Copy - [name]"
	to_chat(user, span_notice("You tear off the carbon-copy!"))
	copied = TRUE
	update_icon_state()
	user.put_in_hands(copy)

/obj/item/paper/carbon/attack_hand_secondary(mob/user, list/modifiers)
	. = ..()
	if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
		return

	if(loc == user && user.is_holding(src))
		removecopy(user)
		return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN

/obj/item/paper/carbon_copy
	icon_state = "cpaper"