code:
/datum/wires/airlock/shell
	holder_type = /obj/machinery/door/airlock/shell
	proper_name = "Circuit Airlock"

/datum/wires/airlock/shell/on_cut(wire, mend)
	// Don't allow them to re-enable autoclose.
	if(wire == WIRE_TIMING)
		return
	return ..()

/obj/machinery/door/airlock/shell
	name = "circuit airlock"
	autoclose = FALSE

/obj/machinery/door/airlock/shell/Initialize(mapload)
	. = ..()
	AddComponent( \
		/datum/component/shell, \
		unremovable_circuit_components = list(new /obj/item/circuit_component/airlock, new /obj/item/circuit_component/airlock_access_event), \
		capacity = SHELL_CAPACITY_LARGE, \
		shell_flags = SHELL_FLAG_ALLOW_FAILURE_ACTION \
	)

/obj/machinery/door/airlock/shell/check_access(obj/item/I)
	return FALSE

/obj/machinery/door/airlock/shell/canAIControl(mob/user)
	return FALSE

/obj/machinery/door/airlock/shell/canAIHack(mob/user)
	return FALSE

/obj/machinery/door/airlock/shell/set_wires()
	return new /datum/wires/airlock/shell(src)

/obj/item/circuit_component/airlock
	display_name = "Airlock"
	desc = "The general interface with an airlock. Includes general statuses of the airlock"

	/// The shell, if it is an airlock.
	var/obj/machinery/door/airlock/attached_airlock

	/// Bolts the airlock (if possible)
	var/datum/port/input/bolt
	/// Unbolts the airlock (if possible)
	var/datum/port/input/unbolt
	/// Opens the airlock (if possible)
	var/datum/port/input/open
	/// Closes the airlock (if possible)
	var/datum/port/input/close

	/// Contains whether the airlock is open or not
	var/datum/port/output/is_open
	/// Contains whether the airlock is bolted or not
	var/datum/port/output/is_bolted

	/// Called when the airlock is opened.
	var/datum/port/output/opened
	/// Called when the airlock is closed
	var/datum/port/output/closed

	/// Called when the airlock is bolted
	var/datum/port/output/bolted
	/// Called when the airlock is unbolted
	var/datum/port/output/unbolted

/obj/item/circuit_component/airlock/populate_ports()
	// Input Signals
	bolt = add_input_port("Bolt", PORT_TYPE_SIGNAL)
	unbolt = add_input_port("Unbolt", PORT_TYPE_SIGNAL)
	open = add_input_port("Open", PORT_TYPE_SIGNAL)
	close = add_input_port("Close", PORT_TYPE_SIGNAL)
	// States
	is_open = add_output_port("Is Open", PORT_TYPE_NUMBER)
	is_bolted = add_output_port("Is Bolted", PORT_TYPE_NUMBER)
	// Output Signals
	opened = add_output_port("Opened", PORT_TYPE_SIGNAL)
	closed = add_output_port("Closed", PORT_TYPE_SIGNAL)
	bolted = add_output_port("Bolted", PORT_TYPE_SIGNAL)
	unbolted = add_output_port("Unbolted", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/airlock/register_shell(atom/movable/shell)
	. = ..()
	if(istype(shell, /obj/machinery/door/airlock))
		attached_airlock = shell
		RegisterSignal(shell, COMSIG_AIRLOCK_SET_BOLT, .proc/on_airlock_set_bolted)
		RegisterSignal(shell, COMSIG_AIRLOCK_OPEN, .proc/on_airlock_open)
		RegisterSignal(shell, COMSIG_AIRLOCK_CLOSE, .proc/on_airlock_closed)

/obj/item/circuit_component/airlock/unregister_shell(atom/movable/shell)
	attached_airlock = null
	UnregisterSignal(shell, list(
		COMSIG_AIRLOCK_SET_BOLT,
		COMSIG_AIRLOCK_OPEN,
		COMSIG_AIRLOCK_CLOSE,
	))
	return ..()

/obj/item/circuit_component/airlock/proc/on_airlock_set_bolted(datum/source, should_bolt)
	SIGNAL_HANDLER
	is_bolted.set_output(should_bolt)
	if(should_bolt)
		bolted.set_output(COMPONENT_SIGNAL)
	else
		unbolted.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/airlock/proc/on_airlock_open(datum/source, force)
	SIGNAL_HANDLER
	is_open.set_output(TRUE)
	opened.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/airlock/proc/on_airlock_closed(datum/source, forced)
	SIGNAL_HANDLER
	is_open.set_output(FALSE)
	closed.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/airlock/input_received(datum/port/input/port)

	if(!attached_airlock)
		return

	if(COMPONENT_TRIGGERED_BY(bolt, port))
		attached_airlock.bolt()
	if(COMPONENT_TRIGGERED_BY(unbolt, port))
		attached_airlock.unbolt()
	if(COMPONENT_TRIGGERED_BY(open, port) && attached_airlock.density)
		INVOKE_ASYNC(attached_airlock, /obj/machinery/door/airlock.proc/open)
	if(COMPONENT_TRIGGERED_BY(close, port) && !attached_airlock.density)
		INVOKE_ASYNC(attached_airlock, /obj/machinery/door/airlock.proc/close)

/obj/item/circuit_component/airlock_access_event
	display_name = "Airlock Access Event"
	desc = "An event that can be handled through circuit components to determine if the door should open or not for an entity that might be trying to access it."
	circuit_flags = CIRCUIT_FLAG_INSTANT

	/// The shell, if it is an airlock.
	var/obj/machinery/door/airlock/attached_airlock

	/// Tells the event to open the airlock.
	var/datum/port/input/open_airlock

	/// The person trying to open the airlock.
	var/datum/port/output/accessing_entity

	/// The signal sent when this event is triggered
	var/datum/port/output/event_triggered

/obj/item/circuit_component/airlock_access_event/register_shell(atom/movable/shell)
	. = ..()
	if(istype(shell, /obj/machinery/door/airlock))
		attached_airlock = shell
		RegisterSignal(shell, COMSIG_OBJ_ALLOWED, .proc/handle_allowed)

/obj/item/circuit_component/airlock_access_event/unregister_shell(atom/movable/shell)
	attached_airlock = null
	UnregisterSignal(shell, list(
		COMSIG_OBJ_ALLOWED,
	))
	return ..()

/obj/item/circuit_component/airlock_access_event/populate_ports()
	open_airlock = add_input_port("Should Open Airlock", PORT_TYPE_RESPONSE_SIGNAL, trigger = .proc/should_open_airlock)
	accessing_entity = add_output_port("Accessing Entity", PORT_TYPE_ATOM)
	event_triggered = add_output_port("Event Triggered", PORT_TYPE_INSTANT_SIGNAL)

/obj/item/circuit_component/airlock_access_event/proc/should_open_airlock(datum/port/input/port, list/return_values)
	CIRCUIT_TRIGGER
	if(!return_values)
		return
	return_values["should_open"] = TRUE

/obj/item/circuit_component/airlock_access_event/proc/handle_allowed(datum/source, mob/accesser)
	SIGNAL_HANDLER
	if(!attached_airlock)
		return

	SScircuit_component.queue_instant_run()
	accessing_entity.set_output(accesser)
	event_triggered.set_output(COMPONENT_SIGNAL)
	var/list/result = SScircuit_component.execute_instant_run()

	if(!result)
		attached_airlock.visible_message(span_warning("[attached_airlock]'s circuitry overheats!"))
		return

	if(result["should_open"])
		return COMPONENT_OBJ_ALLOW


code:
/**
 * # Bot
 *
 * Immobile (but not dense) shells that can interact with world.
 */
/obj/structure/bot
	name = "bot"
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_medium_box"

	density = FALSE
	light_system = MOVABLE_LIGHT
	light_on = FALSE

/obj/structure/bot/Initialize(mapload)
	. = ..()
	AddComponent( \
		/datum/component/shell, \
		unremovable_circuit_components = list(new /obj/item/circuit_component/bot), \
		capacity = SHELL_CAPACITY_LARGE, \
		shell_flags = SHELL_FLAG_USB_PORT, \
	)

/obj/item/circuit_component/bot
	display_name = "Bot"
	desc = "Triggers when someone interacts with the bot."

	/// Called when attack_hand is called on the shell.
	var/datum/port/output/signal
	/// The user who used the bot
	var/datum/port/output/entity

/obj/item/circuit_component/bot/populate_ports()
	entity = add_output_port("User", PORT_TYPE_ATOM)
	signal = add_output_port("Signal", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/bot/Destroy()
	signal = null
	entity = null
	return ..()

/obj/item/circuit_component/bot/register_shell(atom/movable/shell)
	RegisterSignal(shell, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)

/obj/item/circuit_component/bot/unregister_shell(atom/movable/shell)
	UnregisterSignal(shell, COMSIG_ATOM_ATTACK_HAND)

/obj/item/circuit_component/bot/proc/on_attack_hand(atom/source, mob/user)
	SIGNAL_HANDLER
	source.balloon_alert(user, "pushed button")
	playsound(source, get_sfx("terminal_type"), 25, FALSE)
	entity.set_output(user)
	signal.set_output(COMPONENT_SIGNAL)

repo_name: AnalWerewolf/sundown

license: mit


code:
/obj/item/organ/cyberimp/bci
	name = "brain-computer interface"
	desc = "An implant that can be placed in a user's head to control circuits using their brain."
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "bci"
	zone = BODY_ZONE_HEAD
	w_class = WEIGHT_CLASS_TINY

/obj/item/organ/cyberimp/bci/Initialize(mapload)
	. = ..()

	var/obj/item/integrated_circuit/circuit = new(src)
	circuit.add_component(new /obj/item/circuit_component/bci_action(null, "One"))

	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/bci_core,
	), SHELL_CAPACITY_SMALL, starting_circuit = circuit)

/obj/item/organ/cyberimp/bci/Insert(mob/living/carbon/reciever, special, drop_if_replaced)
	. = ..()

	// Organs are put in nullspace, but this breaks circuit interactions
	forceMove(reciever)

/obj/item/organ/cyberimp/bci/say(message, bubble_type, list/spans, sanitize, datum/language/language, ignore_spam, forced)
	if (owner)
		// Otherwise say_dead will be called.
		// It's intentional that a circuit for a dead person does not speak from the shell.
		if (owner.stat == DEAD)
			return

		owner.say(message, forced = "circuit speech")
	else
		return ..()

/obj/item/circuit_component/bci_action
	display_name = "BCI Action"
	desc = "Represents an action the user can take when implanted with the brain-computer interface."
	required_shells = list(/obj/item/organ/cyberimp/bci)

	/// The icon of the button
	var/datum/port/input/option/icon_options

	/// The name to use for the button
	var/datum/port/input/button_name

	/// Called when the user presses the button
	var/datum/port/output/signal

	/// A reference to the action button itself
	var/datum/action/innate/bci_action/bci_action

/obj/item/circuit_component/bci_action/Initialize(mapload, default_icon)
	. = ..()

	if (!isnull(default_icon))
		icon_options.set_input(default_icon)

	button_name = add_input_port("Name", PORT_TYPE_STRING)

	signal = add_output_port("Signal", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/bci_action/Destroy()
	QDEL_NULL(bci_action)
	return ..()

/obj/item/circuit_component/bci_action/populate_options()
	var/static/action_options = list(
		"Blank",

		"One",
		"Two",
		"Three",
		"Four",
		"Five",

		"Blood",
		"Bomb",
		"Brain",
		"Brain Damage",
		"Cross",
		"Electricity",
		"Exclamation",
		"Heart",
		"Id",
		"Info",
		"Injection",
		"Magnetism",
		"Minus",
		"Network",
		"Plus",
		"Power",
		"Question",
		"Radioactive",
		"Reaction",
		"Repair",
		"Say",
		"Scan",
		"Shield",
		"Skull",
		"Sleep",
		"Wireless",
	)

	icon_options = add_option_port("Icon", action_options)

/obj/item/circuit_component/bci_action/register_shell(atom/movable/shell)
	var/obj/item/organ/cyberimp/bci/bci = shell

	bci_action = new(src)
	update_action()

	bci.actions += list(bci_action)

/obj/item/circuit_component/bci_action/unregister_shell(atom/movable/shell)
	var/obj/item/organ/cyberimp/bci/bci = shell

	bci.actions -= bci_action
	QDEL_NULL(bci_action)

/obj/item/circuit_component/bci_action/input_received(datum/port/input/port)
	if (!isnull(bci_action))
		update_action()

/obj/item/circuit_component/bci_action/proc/update_action()
	bci_action.name = button_name.value
	bci_action.button_icon_state = "bci_[replacetextEx(lowertext(icon_options.value), " ", "_")]"

/datum/action/innate/bci_action
	name = "Action"
	icon_icon = 'icons/mob/actions/actions_items.dmi'
	check_flags = AB_CHECK_CONSCIOUS
	button_icon_state = "bci_power"

	var/obj/item/circuit_component/bci_action/circuit_component

/datum/action/innate/bci_action/New(obj/item/circuit_component/bci_action/circuit_component)
	..()

	src.circuit_component = circuit_component

/datum/action/innate/bci_action/Destroy()
	circuit_component.bci_action = null
	circuit_component = null

	return ..()

/datum/action/innate/bci_action/Activate()
	circuit_component.signal.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/bci_core
	display_name = "BCI Core"
	desc = "Controls the core operations of the BCI."

	/// A reference to the action button to look at charge/get info
	var/datum/action/innate/bci_charge_action/charge_action

	var/datum/port/input/message
	var/datum/port/input/send_message_signal

	var/datum/port/output/user_port

	var/datum/weakref/user

/obj/item/circuit_component/bci_core/populate_ports()

	message = add_input_port("Message", PORT_TYPE_STRING)
	send_message_signal = add_input_port("Send Message", PORT_TYPE_SIGNAL)

	user_port = add_output_port("User", PORT_TYPE_ATOM)

/obj/item/circuit_component/bci_core/Destroy()
	QDEL_NULL(charge_action)
	return ..()

/obj/item/circuit_component/bci_core/register_shell(atom/movable/shell)
	var/obj/item/organ/cyberimp/bci/bci = shell

	charge_action = new(src)
	bci.actions += list(charge_action)

	RegisterSignal(shell, COMSIG_ORGAN_IMPLANTED, .proc/on_organ_implanted)
	RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed)

/obj/item/circuit_component/bci_core/unregister_shell(atom/movable/shell)
	var/obj/item/organ/cyberimp/bci/bci = shell

	bci.actions -= charge_action
	QDEL_NULL(charge_action)

	UnregisterSignal(shell, list(
		COMSIG_ORGAN_IMPLANTED,
		COMSIG_ORGAN_REMOVED,
	))

/obj/item/circuit_component/bci_core/should_receive_input(datum/port/input/port)
	if (!COMPONENT_TRIGGERED_BY(send_message_signal, port))
		return FALSE
	return ..()

/obj/item/circuit_component/bci_core/input_received(datum/port/input/port)
	var/sent_message = trim(message.value)
	if (!sent_message)
		return

	var/mob/living/carbon/resolved_owner = user?.resolve()
	if (isnull(resolved_owner))
		return

	if (resolved_owner.stat == DEAD)
		return

	to_chat(resolved_owner, "You hear a strange, robotic voice in your head... \"[span_robot("[html_encode(sent_message)]")]\"")

/obj/item/circuit_component/bci_core/proc/on_organ_implanted(datum/source, mob/living/carbon/owner)
	SIGNAL_HANDLER

	user_port.set_output(owner)
	user = WEAKREF(owner)

	RegisterSignal(owner, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/on_borg_charge)
	RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_electrocute)

/obj/item/circuit_component/bci_core/proc/on_organ_removed(datum/source, mob/living/carbon/owner)
	SIGNAL_HANDLER

	user_port.set_output(null)
	user = null

	UnregisterSignal(owner, list(
		COMSIG_PARENT_EXAMINE,
		COMSIG_PROCESS_BORGCHARGER_OCCUPANT,
		COMSIG_LIVING_ELECTROCUTE_ACT,
	))

/obj/item/circuit_component/bci_core/proc/on_borg_charge(datum/source, amount)
	SIGNAL_HANDLER

	if (isnull(parent.cell))
		return

	parent.cell.give(amount)

/obj/item/circuit_component/bci_core/proc/on_electrocute(datum/source, shock_damage, siemens_coefficient, flags)
	SIGNAL_HANDLER

	if (isnull(parent.cell))
		return

	if (flags & SHOCK_ILLUSION)
		return

	parent.cell.give(shock_damage * 2)
	to_chat(source, span_notice("You absorb some of the shock into your [parent.name]!"))

/obj/item/circuit_component/bci_core/proc/on_examine(datum/source, mob/mob, list/examine_text)
	SIGNAL_HANDLER

	if (isobserver(mob))
		examine_text += span_notice("[source.p_they(capitalized = TRUE)] [source.p_have()] \a [parent] implanted in [source.p_them()].")

/obj/item/circuit_component/bci_core/Topic(href, list/href_list)
	..()

	if (!isobserver(usr))
		return

	if (href_list["open_bci"])
		parent.attack_ghost(usr)

/datum/action/innate/bci_charge_action
	name = "Check BCI Charge"
	check_flags = NONE
	icon_icon = 'icons/obj/power.dmi'
	button_icon_state = "cell"

	var/obj/item/circuit_component/bci_core/circuit_component

/datum/action/innate/bci_charge_action/New(obj/item/circuit_component/bci_core/circuit_component)
	..()

	src.circuit_component = circuit_component

	button.maptext_x = 2
	button.maptext_y = 0
	update_maptext()

	START_PROCESSING(SSobj, src)

/datum/action/innate/bci_charge_action/Destroy()
	circuit_component.charge_action = null
	circuit_component = null

	STOP_PROCESSING(SSobj, src)

	return ..()

/datum/action/innate/bci_charge_action/Trigger()
	var/obj/item/stock_parts/cell/cell = circuit_component.parent.cell

	if (isnull(cell))
		to_chat(owner, span_boldwarning("[circuit_component.parent] has no power cell."))
	else
		to_chat(owner, span_info("[circuit_component.parent]'s [cell.name] has [cell.percent()]% charge left."))
		to_chat(owner, span_info("You can recharge it by using a cyborg recharging station."))

/datum/action/innate/bci_charge_action/process(delta_time)
	update_maptext()

/datum/action/innate/bci_charge_action/proc/update_maptext()
	var/obj/item/stock_parts/cell/cell = circuit_component.parent.cell
	button.maptext = cell ? MAPTEXT("[cell.percent()]%") : ""

/obj/machinery/bci_implanter
	name = "brain-computer interface manipulation chamber"
	desc = "A machine that, when given a brain-computer interface, will implant it into an occupant. Otherwise, will remove any brain-computer interfaces they already have."
	circuit = /obj/item/circuitboard/machine/bci_implanter
	icon = 'icons/obj/machines/bci_implanter.dmi'
	icon_state = "bci_implanter"
	base_icon_state = "bci_implanter"
	layer = ABOVE_WINDOW_LAYER
	use_power = IDLE_POWER_USE
	anchored = TRUE
	density = TRUE
	obj_flags = NO_BUILD // Becomes undense when the door is open
	idle_power_usage = 50
	active_power_usage = 300

	var/busy = FALSE
	var/busy_icon_state
	var/locked = FALSE

	var/datum/weakref/bci_to_implant

	COOLDOWN_DECLARE(message_cooldown)

/obj/machinery/bci_implanter/Initialize(mapload)
	. = ..()
	occupant_typecache = typecacheof(/mob/living/carbon)

/obj/machinery/bci_implanter/Destroy()
	QDEL_NULL(bci_to_implant)
	return ..()

/obj/machinery/bci_implanter/examine(mob/user)
	. = ..()

	if (isnull(bci_to_implant?.resolve()))
		. += span_notice("There is no BCI inserted.")
	else
		. += span_notice("Right-click to remove current BCI.")

/obj/machinery/bci_implanter/proc/set_busy(status, working_icon)
	busy = status
	busy_icon_state = working_icon
	update_appearance()

/obj/machinery/bci_implanter/update_icon_state()
	if (occupant)
		icon_state = busy ? busy_icon_state : "[base_icon_state]_occupied"
		return ..()
	icon_state = "[base_icon_state][state_open ? "_open" : null]"
	return ..()

/obj/machinery/bci_implanter/update_overlays()
	var/list/overlays = ..()

	if ((machine_stat & MAINT) || panel_open)
		overlays += "maint"
		return overlays

	if (machine_stat & (NOPOWER|BROKEN))
		return overlays

	if (busy || locked)
		overlays += "red"
		if (locked)
			overlays += "bolted"
		return overlays

	overlays += "green"

	return overlays

/obj/machinery/bci_implanter/attack_hand_secondary(mob/user, list/modifiers)
	. = ..()
	if (. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
		return .

	if(!user.Adjacent(src))
		return

	if (locked)
		balloon_alert(user, "it's locked!")
		return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN

	var/obj/item/organ/cyberimp/bci/bci_to_implant_resolved = bci_to_implant?.resolve()
	if (isnull(bci_to_implant_resolved))
		balloon_alert(user, "no bci inserted!")
	else
		user.put_in_hands(bci_to_implant_resolved)
		balloon_alert(user, "ejected bci")

	bci_to_implant = null

	return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN

/obj/machinery/bci_implanter/attackby(obj/item/weapon, mob/user, params)
	var/obj/item/organ/cyberimp/bci/new_bci = weapon
	if (istype(new_bci))
		if (!(locate(/obj/item/integrated_circuit) in new_bci))
			balloon_alert(user, "bci has no circuit!")
			return

		var/obj/item/organ/cyberimp/bci/previous_bci_to_implant = bci_to_implant?.resolve()

		bci_to_implant = WEAKREF(weapon)
		weapon.forceMove(src)

		if (isnull(previous_bci_to_implant))
			balloon_alert(user, "inserted bci")
		else
			balloon_alert(user, "swapped bci")
			user.put_in_hands(previous_bci_to_implant)

		return

	return ..()

/obj/machinery/bci_implanter/attackby_secondary(obj/item/weapon, mob/user, params)
	if (!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, weapon))
		update_appearance()
		return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN

	if (default_pry_open(weapon))
		return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN

	if (default_deconstruction_crowbar(weapon))
		return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN

	return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN

/obj/machinery/bci_implanter/proc/start_process()
	if (machine_stat & (NOPOWER|BROKEN))
		return
	if ((machine_stat & MAINT) || panel_open)
		return
	if (!occupant || busy)
		return

	var/locked_state = locked
	locked = TRUE

	set_busy(TRUE, "[initial(icon_state)]_raising")
	addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_active"), 1 SECONDS)
	addtimer(CALLBACK(src, .proc/set_busy, TRUE, "[initial(icon_state)]_falling"), 2 SECONDS)
	addtimer(CALLBACK(src, .proc/complete_process, locked_state), 3 SECONDS)

/obj/machinery/bci_implanter/proc/complete_process(locked_state)
	locked = locked_state
	set_busy(FALSE)

	var/mob/living/carbon/carbon_occupant = occupant
	if (!istype(carbon_occupant))
		return

	playsound(loc, 'sound/machines/ping.ogg', 30, FALSE)

	var/obj/item/organ/cyberimp/bci/bci_organ = carbon_occupant.getorgan(/obj/item/organ/cyberimp/bci)
	var/obj/item/organ/cyberimp/bci/bci_to_implant_resolved = bci_to_implant?.resolve()

	if (bci_organ)
		bci_organ.Remove(carbon_occupant)

		if (isnull(bci_to_implant_resolved))
			say("Occupant's previous brain-computer interface has been transferred to internal storage unit.")
			bci_organ.forceMove(src)
			bci_to_implant = WEAKREF(bci_organ)
		else
			say("Occupant's previous brain-computer interface has been ejected.")
			bci_organ.forceMove(drop_location())
	else if (!isnull(bci_to_implant_resolved))
		say("Occupant has been injected with [bci_to_implant_resolved].")
		bci_to_implant_resolved.Insert(carbon_occupant)
		bci_to_implant = null

/obj/machinery/bci_implanter/open_machine()
	if(state_open)
		return FALSE

	..()

	return TRUE

/obj/machinery/bci_implanter/close_machine(mob/living/carbon/user)
	if(!state_open)
		return FALSE

	..()

	var/mob/living/carbon/carbon_occupant = occupant
	if (istype(occupant))
		var/obj/item/organ/cyberimp/bci/existing_bci_organ = carbon_occupant.getorgan(/obj/item/organ/cyberimp/bci)
		if (isnull(existing_bci_organ) && isnull(bci_to_implant?.resolve()))
			say("No brain-computer interface inserted, and occupant does not have one. Insert a BCI to implant one.")
			playsound(src, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
			return FALSE

	addtimer(CALLBACK(src, .proc/start_process), 1 SECONDS)
	return TRUE

/obj/machinery/bci_implanter/relaymove(mob/living/user, direction)
	var/message

	if (locked)
		message = "it won't budge!"
	else if (user.stat != CONSCIOUS)
		message = "you don't have the energy!"

	if (!isnull(message))
		if (COOLDOWN_FINISHED(src, message_cooldown))
			COOLDOWN_START(src, message_cooldown, 5 SECONDS)
			balloon_alert(user, message)

		return

	open_machine()

/obj/machinery/bci_implanter/interact(mob/user)
	if (state_open)
		close_machine(null, user)
		return
	else if (locked)
		balloon_alert(user, "it's locked!")
		return

	open_machine()

/obj/item/circuitboard/machine/bci_implanter
	name = "Brain-Computer Interface Manipulation Chamber (Machine Board)"
	greyscale_colors = CIRCUIT_COLOR_SCIENCE
	build_path = /obj/machinery/bci_implanter
	req_components = list(
		/obj/item/stock_parts/micro_laser = 2,
		/obj/item/stock_parts/manipulator = 1,
	)


code:
/**
 * # Compact Remote
 *
 * A handheld device with one big button.
 */
/obj/item/compact_remote
	name = "compact remote"
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_small_simple"
	inhand_icon_state = "electronic"
	worn_icon_state = "electronic"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	light_system = MOVABLE_LIGHT_DIRECTIONAL
	light_on = FALSE

/obj/item/compact_remote/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/compact_remote()
	), SHELL_CAPACITY_SMALL)

/obj/item/circuit_component/compact_remote
	display_name = "Compact Remote"
	desc = "Used to receive inputs from the compact remote shell. Use the shell in hand to trigger the output signal."

	/// Called when attack_self is called on the shell.
	var/datum/port/output/signal
	/// The user who used the bot
	var/datum/port/output/entity

/obj/item/circuit_component/compact_remote/populate_ports()
	entity = add_output_port("User", PORT_TYPE_ATOM)
	signal = add_output_port("Signal", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/compact_remote/register_shell(atom/movable/shell)
	RegisterSignal(shell, COMSIG_ITEM_ATTACK_SELF, .proc/send_trigger)

/obj/item/circuit_component/compact_remote/unregister_shell(atom/movable/shell)
	UnregisterSignal(shell, COMSIG_ITEM_ATTACK_SELF)

/**
 * Called when the shell item is used in hand.
 */
/obj/item/circuit_component/compact_remote/proc/send_trigger(atom/source, mob/user)
	SIGNAL_HANDLER
	source.balloon_alert(user, "clicked primary button")
	playsound(source, get_sfx("terminal_type"), 25, FALSE)
	entity.set_output(user)
	signal.set_output(COMPONENT_SIGNAL)


code:
/**
 * # Compact Remote
 *
 * A handheld device with several buttons.
 * In game, this translates to having different signals for normal usage, alt-clicking, and ctrl-clicking when in your hand.
 */
/obj/item/controller
	name = "controller"
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_small_calc"
	inhand_icon_state = "electronic"
	worn_icon_state = "electronic"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	light_system = MOVABLE_LIGHT_DIRECTIONAL
	light_on = FALSE

/obj/item/controller/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/controller()
	), SHELL_CAPACITY_MEDIUM)

/obj/item/circuit_component/controller
	display_name = "Controller"
	desc = "Used to receive inputs from the controller shell. Use the shell in hand to trigger the output signal. Alt-click for the alternate signal. Right click for the extra signal."

	/// The three separate buttons that are called in attack_hand on the shell.
	var/datum/port/output/signal
	var/datum/port/output/alt
	var/datum/port/output/right

	/// The entity output
	var/datum/port/output/entity

/obj/item/circuit_component/controller/populate_ports()
	entity = add_output_port("User", PORT_TYPE_ATOM)
	signal = add_output_port("Signal", PORT_TYPE_SIGNAL)
	alt = add_output_port("Alternate Signal", PORT_TYPE_SIGNAL)
	right = add_output_port("Extra Signal", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/controller/register_shell(atom/movable/shell)
	RegisterSignal(shell, COMSIG_ITEM_ATTACK_SELF, .proc/send_trigger)
	RegisterSignal(shell, COMSIG_CLICK_ALT, .proc/send_alternate_signal)
	RegisterSignal(shell, COMSIG_ITEM_ATTACK_SELF_SECONDARY, .proc/send_right_signal)

/obj/item/circuit_component/controller/unregister_shell(atom/movable/shell)
	UnregisterSignal(shell, list(
		COMSIG_ITEM_ATTACK_SELF,
		COMSIG_ITEM_ATTACK_SELF_SECONDARY,
		COMSIG_CLICK_ALT,
	))

/**
 * Called when the shell item is used in hand
 */
/obj/item/circuit_component/controller/proc/send_trigger(atom/source, mob/user)
	SIGNAL_HANDLER
	if(!user.Adjacent(source))
		return
	source.balloon_alert(user, "clicked primary button")
	playsound(source, get_sfx("terminal_type"), 25, FALSE)
	entity.set_output(user)
	signal.set_output(COMPONENT_SIGNAL)

/**
 * Called when the shell item is alt-clicked
 */
/obj/item/circuit_component/controller/proc/send_alternate_signal(atom/source, mob/user)
	SIGNAL_HANDLER
	if(!user.Adjacent(source))
		return
	source.balloon_alert(user, "clicked alternate button")
	playsound(source, get_sfx("terminal_type"), 25, FALSE)
	entity.set_output(user)
	alt.set_output(COMPONENT_SIGNAL)

/**
 * Called when the shell item is right-clicked in active hand
 */
/obj/item/circuit_component/controller/proc/send_right_signal(atom/source, mob/user)
	SIGNAL_HANDLER
	if(!user.Adjacent(source))
		return
	source.balloon_alert(user, "clicked extra button")
	playsound(source, get_sfx("terminal_type"), 25, FALSE)
	entity.set_output(user)
	right.set_output(COMPONENT_SIGNAL)


code:
/**
 * # Dispenser
 *
 * Immobile (but not dense) shell that can receive and dispense items.
 */
/obj/structure/dispenser_bot
	name = "dispenser"
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_drone_arms"

	density = FALSE
	light_system = MOVABLE_LIGHT
	light_on = FALSE

	var/max_weight = WEIGHT_CLASS_NORMAL
	var/capacity = 20

	var/list/obj/item/stored_items = list()
	var/locked = FALSE

/obj/structure/dispenser_bot/deconstruct(disassembled)
	for(var/obj/item/stored_item as anything in stored_items)
		remove_item(stored_item)
	return ..()

/obj/structure/dispenser_bot/Destroy()
	QDEL_LIST(stored_items)
	return ..()

/obj/structure/dispenser_bot/proc/add_item(obj/item/to_add)
	stored_items += to_add
	to_add.forceMove(src)
	RegisterSignal(to_add, COMSIG_MOVABLE_MOVED, .proc/handle_stored_item_moved)
	RegisterSignal(to_add, COMSIG_PARENT_QDELETING, .proc/handle_stored_item_deleted)
	SEND_SIGNAL(src, COMSIG_DISPENSERBOT_ADD_ITEM, to_add)

/obj/structure/dispenser_bot/proc/handle_stored_item_moved(obj/item/moving_item, atom/location)
	SIGNAL_HANDLER
	if(location != src)
		remove_item(moving_item)

/obj/structure/dispenser_bot/proc/handle_stored_item_deleted(obj/item/deleting_item)
	SIGNAL_HANDLER
	remove_item(deleting_item)

/obj/structure/dispenser_bot/proc/remove_item(obj/item/to_remove)
	UnregisterSignal(to_remove, list(
		COMSIG_MOVABLE_MOVED,
		COMSIG_PARENT_QDELETING,
	))
	to_remove.forceMove(drop_location())
	stored_items -= to_remove
	SEND_SIGNAL(src, COMSIG_DISPENSERBOT_REMOVE_ITEM, to_remove)

/obj/structure/dispenser_bot/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/dispenser_bot()
	), SHELL_CAPACITY_LARGE)

/obj/structure/dispenser_bot/attackby(obj/item/item, mob/living/user, params)
	. = ..()
	if(user.combat_mode || .)
		return

	if(item.w_class > max_weight)
		balloon_alert(user, "item too big!")
		return

	if(length(stored_items) >= capacity)
		balloon_alert(user, "at maximum capacity!")
		return

	add_item(item)

/obj/structure/dispenser_bot/wrench_act(mob/living/user, obj/item/tool)
	if(locked)
		return
	set_anchored(!anchored)
	tool.play_tool_sound(src)
	balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
	return TRUE

/obj/item/circuit_component/dispenser_bot
	display_name = "Dispenser"
	desc = "A dispenser bot that can dispense items "

	/// The list of items
	var/datum/port/output/item_list
	/// The item that was added/removed.
	var/datum/port/output/item
	/// Called when an item is added.
	var/datum/port/output/on_item_added
	/// Called when an item is removed.
	var/datum/port/output/on_item_removed

	ui_buttons = list(
		"plus" = "add_vend_component",
	)

	/// Vendor components attached to this dispenser bot
	var/list/obj/item/circuit_component/vendor_component/vendor_components = list()

	var/max_vendor_components = 20

/obj/item/circuit_component/dispenser_bot/populate_ports()
	item_list = add_output_port("Items", PORT_TYPE_LIST(PORT_TYPE_ATOM))

	item = add_output_port("Item", PORT_TYPE_ATOM)
	on_item_added = add_output_port("On Item Added", PORT_TYPE_SIGNAL)
	on_item_removed = add_output_port("On Item Removed", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/dispenser_bot/register_shell(atom/movable/shell)
	. = ..()
	RegisterSignal(shell, COMSIG_DISPENSERBOT_ADD_ITEM, .proc/on_shell_add_item)
	RegisterSignal(shell, COMSIG_DISPENSERBOT_REMOVE_ITEM, .proc/on_shell_remove_item)

/obj/item/circuit_component/dispenser_bot/unregister_shell(atom/movable/shell)
	UnregisterSignal(shell, list(
		COMSIG_DISPENSERBOT_ADD_ITEM,
		COMSIG_DISPENSERBOT_REMOVE_ITEM,
	))
	return ..()

/obj/item/circuit_component/dispenser_bot/proc/on_shell_add_item(obj/structure/dispenser_bot/source, obj/item/added_item)
	SIGNAL_HANDLER
	item.set_output(added_item)
	item_list.set_output(source.stored_items)
	on_item_added.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/dispenser_bot/proc/on_shell_remove_item(obj/structure/dispenser_bot/source, obj/item/added_item)
	SIGNAL_HANDLER
	item.set_output(added_item)
	item_list.set_output(source.stored_items)
	on_item_added.set_output(COMPONENT_SIGNAL)

/obj/item/circuit_component/dispenser_bot/proc/remove_vendor_component(obj/item/circuit_component/vendor_component/vendor_component)
	SIGNAL_HANDLER
	UnregisterSignal(vendor_component, list(
		COMSIG_PARENT_QDELETING,
		COMSIG_CIRCUIT_COMPONENT_REMOVED,
	))
	if(!QDELING(vendor_component))
		qdel(vendor_component)
	vendor_components -= vendor_component

/obj/item/circuit_component/dispenser_bot/ui_perform_action(mob/user, action)
	switch(action)
		if("add_vend_component")
			if(length(vendor_components) >= max_vendor_components)
				balloon_alert(user, "you have hit vendor component limit!")
				return
			var/obj/item/circuit_component/vendor_component/vendor_component = new(parent)
			parent.add_component(vendor_component, user)
			vendor_components += vendor_component
			RegisterSignal(vendor_component, list(
				COMSIG_PARENT_QDELETING,
				COMSIG_CIRCUIT_COMPONENT_REMOVED,
			), .proc/remove_vendor_component)

/obj/item/circuit_component/vendor_component
	display_name = "Vend"
	desc = "A component used to vend out specific objects from the dispenser bot."

	circuit_flags = CIRCUIT_FLAG_OUTPUT_SIGNAL

	var/obj/structure/dispenser_bot/attached_bot

	/// The item this vendor component should vend
	var/datum/port/input/item_to_vend
	/// Used to vend the item
	var/datum/port/input/vend_item

	circuit_size = 0

/obj/item/circuit_component/vendor_component/register_shell(atom/movable/shell)
	. = ..()
	if(istype(shell, /obj/structure/dispenser_bot))
		attached_bot = shell

/obj/item/circuit_component/vendor_component/unregister_shell(atom/movable/shell)
	attached_bot = null
	return ..()

/obj/item/circuit_component/vendor_component/populate_ports()
	item_to_vend = add_input_port("Item", PORT_TYPE_ATOM, trigger = null)
	vend_item = add_input_port("Vend Item", PORT_TYPE_SIGNAL, trigger = .proc/vend_item)

/obj/item/circuit_component/vendor_component/proc/vend_item(datum/port/input/port, list/return_values)
	CIRCUIT_TRIGGER
	if(!attached_bot)
		return

	var/obj/item/vending_item = locate(item_to_vend.value) in attached_bot.stored_items

	if(!vending_item)
		return

	attached_bot.remove_item(vending_item)


code:
/**
 * # Drone
 *
 * A movable mob that can be fed inputs on which direction to travel.
 */
/mob/living/circuit_drone
	name = "drone"
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_medium_med"
	living_flags = 0
	light_system = MOVABLE_LIGHT_DIRECTIONAL
	light_on = FALSE

/mob/living/circuit_drone/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/bot_circuit()
	), SHELL_CAPACITY_LARGE)

/mob/living/circuit_drone/updatehealth()
	. = ..()
	if(health < 0)
		gib(no_brain = TRUE, no_organs = TRUE, no_bodyparts = TRUE)

/mob/living/circuit_drone/spawn_gibs()
	new /obj/effect/gibspawner/robot(drop_location(), src, get_static_viruses())

/obj/item/circuit_component/bot_circuit
	display_name = "Drone"
	desc = "Used to send movement output signals to the drone shell."

	/// The inputs to allow for the drone to move
	var/datum/port/input/north
	var/datum/port/input/east
	var/datum/port/input/south
	var/datum/port/input/west

	// Done like this so that travelling diagonally is more simple
	COOLDOWN_DECLARE(north_delay)
	COOLDOWN_DECLARE(east_delay)
	COOLDOWN_DECLARE(south_delay)
	COOLDOWN_DECLARE(west_delay)

	/// Delay between each movement
	var/move_delay = 0.2 SECONDS

/obj/item/circuit_component/bot_circuit/populate_ports()
	north = add_input_port("Move North", PORT_TYPE_SIGNAL)
	east = add_input_port("Move East", PORT_TYPE_SIGNAL)
	south = add_input_port("Move South", PORT_TYPE_SIGNAL)
	west = add_input_port("Move West", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/bot_circuit/input_received(datum/port/input/port)

	var/mob/living/shell = parent.shell
	if(!istype(shell) || shell.stat)
		return

	var/direction

	if(COMPONENT_TRIGGERED_BY(north, port) && COOLDOWN_FINISHED(src, north_delay))
		direction = NORTH
		COOLDOWN_START(src, north_delay, move_delay)
	else if(COMPONENT_TRIGGERED_BY(east, port) && COOLDOWN_FINISHED(src, east_delay))
		direction = EAST
		COOLDOWN_START(src, east_delay, move_delay)
	else if(COMPONENT_TRIGGERED_BY(south, port) && COOLDOWN_FINISHED(src, south_delay))
		direction = SOUTH
		COOLDOWN_START(src, south_delay, move_delay)
	else if(COMPONENT_TRIGGERED_BY(west, port) && COOLDOWN_FINISHED(src, west_delay))
		direction = WEST
		COOLDOWN_START(src, west_delay, move_delay)

	if(!direction)
		return

	if(shell.Process_Spacemove(direction))
		shell.Move(get_step(shell, direction), direction)


code:
/**
 * # Circuit Gun
 *
 * A gun that lets you fire projectiles to enact circuitry.
 */
/obj/item/gun/energy/wiremod_gun
	name = "circuit gun"
	desc = "A gun that fires projectiles able to control circuitry. It can recharge using power from an attached circuit."
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_gun"
	ammo_type = list(/obj/item/ammo_casing/energy/wiremod_gun)
	cell_type = /obj/item/stock_parts/cell/emproof/wiremod_gun
	item_flags = NONE
	light_system = MOVABLE_LIGHT_DIRECTIONAL
	light_on = FALSE
	automatic_charge_overlays = FALSE
	trigger_guard = TRIGGER_GUARD_ALLOW_ALL

/obj/item/ammo_casing/energy/wiremod_gun
	projectile_type = /obj/projectile/energy/wiremod_gun
	harmful = FALSE
	select_name = "circuit"
	fire_sound = 'sound/weapons/blaster.ogg'

/obj/projectile/energy/wiremod_gun
	name = "scanning beam"
	icon_state = "energy"
	nodamage = TRUE
	damage = 0
	range = 7

/obj/item/stock_parts/cell/emproof/wiremod_gun
	maxcharge = 100

/obj/item/gun/energy/wiremod_gun/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/wiremod_gun()
	), SHELL_CAPACITY_MEDIUM)

/obj/item/circuit_component/wiremod_gun
	display_name = "Gun"
	desc = "Used to receive entities hit by projectiles from a gun."
	/// Called when a projectile hits
	var/datum/port/output/signal
	/// The shooter
	var/datum/port/output/shooter
	/// The entity being shot
	var/datum/port/output/shot

/obj/item/circuit_component/wiremod_gun/Initialize(mapload)
	. = ..()
	shooter = add_output_port("Shooter", PORT_TYPE_ATOM)
	shot = add_output_port("Shot Entity", PORT_TYPE_ATOM)
	signal = add_output_port("Shot", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/wiremod_gun/register_shell(atom/movable/shell)
	RegisterSignal(shell, COMSIG_PROJECTILE_ON_HIT, .proc/handle_shot)
	if(istype(shell, /obj/item/gun/energy))
		RegisterSignal(shell, COMSIG_GUN_CHAMBER_PROCESSED, .proc/handle_chamber)

/obj/item/circuit_component/wiremod_gun/unregister_shell(atom/movable/shell)
	UnregisterSignal(shell, list(COMSIG_PROJECTILE_ON_HIT, COMSIG_GUN_CHAMBER_PROCESSED))

/**
 * Called when the shell item shoots something
 */
/obj/item/circuit_component/wiremod_gun/proc/handle_shot(atom/source, mob/firer, atom/target, angle)
	SIGNAL_HANDLER

	playsound(source, get_sfx("terminal_type"), 25, FALSE)
	shooter.set_output(firer)
	shot.set_output(target)
	signal.set_output(COMPONENT_SIGNAL)

/**
 * Called when the shell item processes a new chamber
 */
/obj/item/circuit_component/wiremod_gun/proc/handle_chamber(atom/source)
	SIGNAL_HANDLER

	if(!parent?.cell)
		return
	var/obj/item/gun/energy/fired_gun = source
	var/totransfer = min(100, parent.cell.charge)
	var/transferred = fired_gun.cell.give(totransfer)
	parent.cell.use(transferred)


code:
/**
 * # Money Bot
 *
 * Immobile (but not dense) shell that can receive and dispense money.
 */
/obj/structure/money_bot
	name = "money bot"
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_large"

	density = FALSE
	light_system = MOVABLE_LIGHT
	light_on = FALSE

	var/stored_money = 0
	var/locked = FALSE

/obj/structure/money_bot/deconstruct(disassembled)
	new /obj/item/holochip(drop_location(), stored_money)
	return ..()

/obj/structure/money_bot/proc/add_money(to_add)
	stored_money += to_add
	SEND_SIGNAL(src, COMSIG_MONEYBOT_ADD_MONEY, to_add)

/obj/structure/money_bot/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/money_bot(),
		new /obj/item/circuit_component/money_dispenser()
	), SHELL_CAPACITY_LARGE)

/obj/structure/money_bot/wrench_act(mob/living/user, obj/item/tool)
	if(locked)
		return
	set_anchored(!anchored)
	tool.play_tool_sound(src)
	balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
	return TRUE

/obj/item/circuit_component/money_dispenser
	display_name = "Money Dispenser"
	desc = "Used to dispense money from the money bot. Money is taken from the internal storage of money."
	circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL

	/// The amount of money to dispense
	var/datum/port/input/dispense_amount

	/// Outputs a signal when it fails to output any money.
	var/datum/port/output/on_fail

	var/obj/structure/money_bot/attached_bot

/obj/item/circuit_component/money_dispenser/populate_ports()
	dispense_amount = add_input_port("Amount", PORT_TYPE_NUMBER)
	on_fail = add_output_port("On Failed", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/money_dispenser/register_shell(atom/movable/shell)
	. = ..()
	if(istype(shell, /obj/structure/money_bot))
		attached_bot = shell

/obj/item/circuit_component/money_dispenser/unregister_shell(atom/movable/shell)
	attached_bot = null
	return ..()

/obj/item/circuit_component/money_dispenser/input_received(datum/port/input/port)

	if(!attached_bot)
		return

	var/to_dispense = clamp(dispense_amount.value, 0, attached_bot.stored_money)
	if(!to_dispense)
		on_fail.set_output(COMPONENT_SIGNAL)
		return

	attached_bot.add_money(-to_dispense)
	new /obj/item/holochip(drop_location(), to_dispense)

/obj/item/circuit_component/money_bot
	display_name = "Money Bot"
	var/obj/structure/money_bot/attached_bot
	desc = "Used to receive input signals when money is inserted into the money bot shell and also keep track of the total money in the shell."

	/// Total money in the shell
	var/datum/port/output/total_money
	/// Amount of the last money inputted into the shell
	var/datum/port/output/money_input
	/// Trigger for when money is inputted into the shell
	var/datum/port/output/money_trigger
	/// The person who input the money
	var/datum/port/output/entity

/obj/item/circuit_component/money_bot/populate_ports()
	total_money = add_output_port("Total Money", PORT_TYPE_NUMBER)
	money_input = add_output_port("Last Input Money", PORT_TYPE_NUMBER)
	entity = add_output_port("User", PORT_TYPE_ATOM)
	money_trigger = add_output_port("Money Input", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/money_bot/register_shell(atom/movable/shell)
	. = ..()
	if(istype(shell, /obj/structure/money_bot))
		attached_bot = shell
		total_money.set_output(attached_bot.stored_money)
		RegisterSignal(shell, COMSIG_PARENT_ATTACKBY, .proc/handle_money_insert)
		RegisterSignal(shell, COMSIG_MONEYBOT_ADD_MONEY, .proc/handle_money_update)
		RegisterSignal(parent, COMSIG_CIRCUIT_SET_LOCKED, .proc/on_set_locked)
		attached_bot.locked = parent.locked

/obj/item/circuit_component/money_bot/unregister_shell(atom/movable/shell)
	UnregisterSignal(shell, list(
		COMSIG_PARENT_ATTACKBY,
		COMSIG_MONEYBOT_ADD_MONEY,
	))
	total_money.set_output(null)
	if(attached_bot)
		attached_bot.locked = FALSE
		UnregisterSignal(parent, COMSIG_CIRCUIT_SET_LOCKED)
	attached_bot = null
	return ..()

/obj/item/circuit_component/money_bot/proc/handle_money_insert(atom/source, obj/item/item, mob/living/attacker)
	SIGNAL_HANDLER
	if(!attached_bot || !iscash(item))
		return

	var/amount_to_insert = item.get_item_credit_value()
	if(!amount_to_insert)
		balloon_alert(attacker, "this has no value!")
		return

	attached_bot.add_money(amount_to_insert)
	balloon_alert(attacker, "inserted [amount_to_insert] credits.")
	money_input.set_output(amount_to_insert)
	entity.set_output(attacker)
	money_trigger.set_output(COMPONENT_SIGNAL)
	qdel(item)

/obj/item/circuit_component/money_bot/proc/handle_money_update(atom/source)
	SIGNAL_HANDLER
	if(attached_bot)
		total_money.set_output(attached_bot.stored_money)

/**
 * Locks the attached bot when the circuit is locked.
 *
 * Arguments:
 * * new_value - A boolean that determines if the circuit is locked or not.
 **/
/obj/item/circuit_component/money_bot/proc/on_set_locked(datum/source, new_value)
	SIGNAL_HANDLER
	attached_bot.locked = new_value


code:
/**
 * # Scanner
 *
 * A handheld device that lets you flash it over people.
 */
/obj/item/wiremod_scanner
	name = "scanner"
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_small"
	inhand_icon_state = "electronic"
	worn_icon_state = "electronic"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	light_system = MOVABLE_LIGHT_DIRECTIONAL
	light_on = FALSE

/obj/item/wiremod_scanner/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/wiremod_scanner()
	), SHELL_CAPACITY_SMALL)

/obj/item/circuit_component/wiremod_scanner
	display_name = "Scanner"
	desc = "Used to receive scanned entities from the scanner."

	/// Called when afterattack is called on the shell.
	var/datum/port/output/signal

	/// The attacker
	var/datum/port/output/attacker

	/// The entity being attacked
	var/datum/port/output/attacking

/obj/item/circuit_component/wiremod_scanner/populate_ports()
	attacker = add_output_port("Scanner", PORT_TYPE_ATOM)
	attacking = add_output_port("Scanned Entity", PORT_TYPE_ATOM)
	signal = add_output_port("Scanned", PORT_TYPE_SIGNAL)

/obj/item/circuit_component/wiremod_scanner/register_shell(atom/movable/shell)
	RegisterSignal(shell, COMSIG_ITEM_AFTERATTACK, .proc/handle_afterattack)

/obj/item/circuit_component/wiremod_scanner/unregister_shell(atom/movable/shell)
	UnregisterSignal(shell, COMSIG_ITEM_AFTERATTACK)

/**
 * Called when the shell item attacks something
 */
/obj/item/circuit_component/wiremod_scanner/proc/handle_afterattack(atom/source, atom/target, mob/user, proximity_flag)
	SIGNAL_HANDLER
	if(!proximity_flag)
		return
	source.balloon_alert(user, "scanned object")
	playsound(source, get_sfx("terminal_type"), 25, FALSE)
	attacker.set_output(user)
	attacking.set_output(target)
	signal.set_output(COMPONENT_SIGNAL)


code:
/obj/structure/scanner_gate_shell
	name = "circuit scanner gate"
	desc = "A gate able to perform mid-depth scans on any organisms who pass under it."
	icon = 'icons/obj/machines/scangate.dmi'
	icon_state = "scangate_black"
	var/locked = FALSE

/obj/structure/scanner_gate_shell/Initialize(mapload)
	. = ..()
	set_scanline("passive")
	var/static/list/loc_connections = list(
		COMSIG_ATOM_ENTERED = .proc/on_entered,
	)
	AddElement(/datum/element/connect_loc, loc_connections)

	AddComponent(/datum/component/shell, list(
		new /obj/item/circuit_component/scanner_gate()
	), SHELL_CAPACITY_LARGE, SHELL_FLAG_REQUIRE_ANCHOR)

/obj/structure/scanner_gate_shell/wrench_act(mob/living/user, obj/item/tool)
	if(locked)
		return
	set_anchored(!anchored)
	tool.play_tool_sound(src)
	balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
	return TRUE

/obj/structure/scanner_gate_shell/proc/on_entered(datum/source, atom/movable/AM)
	SIGNAL_HANDLER
	set_scanline("scanning", 10)
	SEND_SIGNAL(src, COMSIG_SCANGATE_SHELL_PASS, AM)

/obj/structure/scanner_gate_shell/proc/set_scanline(type, duration)
	cut_overlays()
	add_overlay(type)
	if(duration)
		addtimer(CALLBACK(src, .proc/set_scanline, "passive"), duration, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE)

/obj/item/circuit_component/scanner_gate
	display_name = "Scanner Gate"
	desc = "A gate able to perform mid-depth scans on any object that pass through it."

	var/datum/port/output/scanned

	var/obj/structure/scanner_gate_shell/attached_gate

/obj/item/circuit_component/scanner_gate/populate_ports()
	scanned = add_output_port("Scanned Object", PORT_TYPE_ATOM)
	trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL, order = 2)

/obj/item/circuit_component/scanner_gate/register_shell(atom/movable/shell)
	. = ..()
	if(istype(shell, /obj/structure/scanner_gate_shell))
		attached_gate = shell
		RegisterSignal(attached_gate, COMSIG_SCANGATE_SHELL_PASS, .proc/on_trigger)
		RegisterSignal(parent, COMSIG_CIRCUIT_SET_LOCKED, .proc/on_set_locked)
		attached_gate.locked = parent.locked

/obj/item/circuit_component/scanner_gate/unregister_shell(atom/movable/shell)
	UnregisterSignal(attached_gate, COMSIG_SCANGATE_SHELL_PASS)
	if(attached_gate)
		attached_gate.locked = FALSE
		UnregisterSignal(parent, COMSIG_CIRCUIT_SET_LOCKED)
	attached_gate = null
	return ..()

/obj/item/circuit_component/scanner_gate/proc/on_trigger(datum/source, atom/movable/passed)
	SIGNAL_HANDLER
	scanned.set_output(passed)
	trigger_output.set_output(COMPONENT_SIGNAL)

/**
 * Locks the attached bot when the circuit is locked.
 *
 * Arguments:
 * * new_value - A boolean that determines if the circuit is locked or not.
 **/
/obj/item/circuit_component/scanner_gate/proc/on_set_locked(datum/source, new_value)
	SIGNAL_HANDLER
	attached_gate.locked = new_value


code:
/**
 * # Server
 *
 * Immobile (but not dense) shells that can interact with
 * world.
 */
/obj/structure/server
	name = "server"
	icon = 'icons/obj/wiremod.dmi'
	icon_state = "setup_stationary"

	density = TRUE
	light_system = MOVABLE_LIGHT
	light_on = FALSE

/obj/structure/server/Initialize(mapload)
	. = ..()
	AddComponent(/datum/component/shell, null, SHELL_CAPACITY_VERY_LARGE, SHELL_FLAG_REQUIRE_ANCHOR|SHELL_FLAG_USB_PORT)

/obj/structure/server/wrench_act(mob/living/user, obj/item/tool)
	set_anchored(!anchored)
	tool.play_tool_sound(src)
	balloon_alert(user, "You [anchored?"secure":"unsecure"] [src].")
	return TRUE


code:
/**
 * # Shell Item
 *
 * Printed out by protolathes. Screwdriver to complete the shell.
 */
/obj/item/shell
	name = "assembly"
	desc = "A shell assembly that can be completed by screwdrivering it."
	icon = 'icons/obj/wiremod.dmi'
	var/shell_to_spawn
	var/screw_delay = 3 SECONDS

/obj/item/shell/screwdriver_act(mob/living/user, obj/item/tool)
	user.visible_message(span_notice("[user] begins finishing [src]."), span_notice("You begin finishing [src]."))
	tool.play_tool_sound(src)
	if(!do_after(user, screw_delay, src))
		return
	user.visible_message(span_notice("[user] finishes [src]."), span_notice("You finish [src]."))

	var/turf/drop_loc = drop_location()

	qdel(src)
	if(drop_loc)
		new shell_to_spawn(drop_loc)

	return TRUE

/obj/item/shell/bot
	name = "bot assembly"
	icon_state = "setup_medium_box-open"
	shell_to_spawn = /obj/structure/bot

/obj/item/shell/money_bot
	name = "money bot assembly"
	icon_state = "setup_large-open"
	shell_to_spawn = /obj/structure/money_bot

/obj/item/shell/drone
	name = "drone assembly"
	icon_state = "setup_medium_med-open"
	shell_to_spawn = /mob/living/circuit_drone

/obj/item/shell/server
	name = "server assembly"
	icon_state = "setup_stationary-open"
	shell_to_spawn = /obj/structure/server
	screw_delay = 10 SECONDS

/obj/item/shell/airlock
	name = "circuit airlock assembly"
	icon = 'icons/obj/doors/airlocks/station/public.dmi'
	icon_state = "construction"
	shell_to_spawn = /obj/machinery/door/airlock/shell
	screw_delay = 10 SECONDS

/obj/item/shell/dispenser
	name = "circuit dispenser assembly"
	icon_state = "setup_drone_arms-open"
	shell_to_spawn = /obj/structure/dispenser_bot

/obj/item/shell/bci
	name = "brain-computer interface assembly"
	icon_state = "bci-open"
	shell_to_spawn = /obj/item/organ/cyberimp/bci

/obj/item/shell/scanner_gate
	name = "scanner gate assembly"
	icon = 'icons/obj/machines/scangate.dmi'
	icon_state = "scangate_black_open"
	shell_to_spawn = /obj/structure/scanner_gate_shell


code:
/obj/item/zombie_hand
	name = "zombie claw"
	desc = "A zombie's claw is its primary tool, capable of infecting \
		humans, butchering all other living things to \
		sustain the zombie, smashing open airlock doors and opening \
		child-safe caps on bottles."
	item_flags = ABSTRACT | DROPDEL
	resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
	icon = 'icons/effects/blood.dmi'
	icon_state = "bloodhand_left"
	var/icon_left = "bloodhand_left"
	var/icon_right = "bloodhand_right"
	hitsound = 'sound/hallucinations/growl1.ogg'
	force = 21 // Just enough to break airlocks with melee attacks
	sharpness = SHARP_EDGED
	wound_bonus = -30
	bare_wound_bonus = 15
	damtype = BRUTE

/obj/item/zombie_hand/Initialize(mapload)
	. = ..()
	ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)

/obj/item/zombie_hand/visual_equipped(mob/user, slot)
	. = ..()
	//these are intentionally inverted
	var/i = user.get_held_index_of_item(src)
	if(!(i % 2))
		icon_state = icon_left
	else
		icon_state = icon_right

/obj/item/zombie_hand/afterattack(atom/target, mob/user, proximity_flag)
	. = ..()
	if(!proximity_flag)
		return
	else if(isliving(target))
		if(ishuman(target))
			try_to_zombie_infect(target)
		else
			check_feast(target, user)

/proc/try_to_zombie_infect(mob/living/carbon/human/target)
	CHECK_DNA_AND_SPECIES(target)

	if(NOZOMBIE in target.dna.species.species_traits)
		// cannot infect any NOZOMBIE subspecies (such as high functioning
		// zombies)
		return

	var/obj/item/organ/zombie_infection/infection
	infection = target.getorganslot(ORGAN_SLOT_ZOMBIE)
	if(!infection)
		infection = new()
		infection.Insert(target)

/obj/item/zombie_hand/suicide_act(mob/user)
	user.visible_message(span_suicide("[user] is ripping [user.p_their()] brains out! It looks like [user.p_theyre()] trying to commit suicide!"))
	if(isliving(user))
		var/mob/living/L = user
		var/obj/item/bodypart/O = L.get_bodypart(BODY_ZONE_HEAD)
		if(O)
			O.dismember()
	return (BRUTELOSS)

/obj/item/zombie_hand/proc/check_feast(mob/living/target, mob/living/user)
	if(target.stat == DEAD)
		var/hp_gained = target.maxHealth
		target.gib()
		// zero as argument for no instant health update
		user.adjustBruteLoss(-hp_gained, 0)
		user.adjustToxLoss(-hp_gained, 0)
		user.adjustFireLoss(-hp_gained, 0)
		user.adjustCloneLoss(-hp_gained, 0)
		user.updatehealth()
		user.adjustOrganLoss(ORGAN_SLOT_BRAIN, -hp_gained) // Zom Bee gibbers "BRAAAAISNSs!1!"
		user.set_nutrition(min(user.nutrition + hp_gained, NUTRITION_LEVEL_FULL))


code:
/obj/item/organ/zombie_infection
	name = "festering ooze"
	desc = "A black web of pus and viscera."
	zone = BODY_ZONE_HEAD
	/* SEPTIC EDIT REMOVAL
	slot = ORGAN_SLOT_ZOMBIE
	*/
	//SEPTIC EDIT BEGIN
	organ_efficiency = list(ORGAN_SLOT_ZOMBIE = 100)
	//SEPTIC EDIT END
	icon_state = "blacktumor"
	var/causes_damage = TRUE
	var/datum/species/old_species = /datum/species/human
	var/living_transformation_time = 30
	var/converts_living = FALSE

	var/revive_time_min = 450
	var/revive_time_max = 700
	var/timer_id

/obj/item/organ/zombie_infection/Initialize(mapload)
	. = ..()
	if(iscarbon(loc))
		Insert(loc)
	GLOB.zombie_infection_list += src

/obj/item/organ/zombie_infection/Destroy()
	GLOB.zombie_infection_list -= src
	. = ..()

/obj/item/organ/zombie_infection/Insert(mob/living/carbon/M, special = 0)
	. = ..()
	START_PROCESSING(SSobj, src)

/obj/item/organ/zombie_infection/Remove(mob/living/carbon/M, special = 0)
	. = ..()
	STOP_PROCESSING(SSobj, src)
	if(iszombie(M) && old_species && !QDELETED(M))
		M.set_species(old_species)
	if(timer_id)
		deltimer(timer_id)

/obj/item/organ/zombie_infection/on_find(mob/living/finder)
	to_chat(finder, "Inside the head is a disgusting black \
		web of pus and viscera, bound tightly around the brain like some \
		biological harness.")

/obj/item/organ/zombie_infection/process(delta_time)
	if(!owner)
		return
	if(!(src in owner.internal_organs))
		Remove(owner)
	if(owner.mob_biotypes & MOB_MINERAL)//does not process in inorganic things
		return
	if (causes_damage && !iszombie(owner) && owner.stat != DEAD)
		owner.adjustToxLoss(0.5 * delta_time)
		if(DT_PROB(5, delta_time))
			to_chat(owner, span_danger("You feel sick..."))
	if(timer_id)
		return
	if(owner.suiciding)
		return
	if(owner.stat != DEAD && !converts_living)
		return
	if(!owner.getorgan(/obj/item/organ/brain))
		return
	if(!iszombie(owner))
		to_chat(owner, "You can feel your heart stopping, but something isn't right... \
		life has not abandoned your broken form. You can only feel a deep and immutable hunger that \
		not even death can stop, you will rise again!")
	var/revive_time = rand(revive_time_min, revive_time_max)
	var/flags = TIMER_STOPPABLE
	timer_id = addtimer(CALLBACK(src, .proc/zombify), revive_time, flags)

/obj/item/organ/zombie_infection/proc/zombify()
	timer_id = null

	if(!converts_living && owner.stat != DEAD)
		return

	if(!iszombie(owner))
		old_species = owner.dna.species.type
		owner.set_species(/datum/species/zombie/infectious)

	var/stand_up = (owner.stat == DEAD) || (owner.stat == UNCONSCIOUS)

	//Fully heal the zombie's damage the first time they rise
	owner.setToxLoss(0, 0)
	owner.setOxyLoss(0, 0)
	owner.heal_overall_damage(INFINITY, INFINITY, INFINITY, null, TRUE)

	if(!owner.revive(full_heal = FALSE, admin_revive = FALSE))
		return

	owner.grab_ghost()
	owner.visible_message(span_danger("[owner] suddenly convulses, as [owner.p_they()][stand_up ? " stagger to [owner.p_their()] feet and" : ""] gain a ravenous hunger in [owner.p_their()] eyes!"), span_alien("You HUNGER!"))
	playsound(owner.loc, 'sound/hallucinations/far_noise.ogg', 50, TRUE)
	owner.do_jitter_animation(living_transformation_time)
	owner.Stun(living_transformation_time)
	to_chat(owner, span_alertalien("You are now a zombie! Do not seek to be cured, do not help any non-zombies in any way, do not harm your zombie brethren and spread the disease by killing others. You are a creature of hunger and violence."))

/obj/item/organ/zombie_infection/nodamage
	causes_damage = FALSE


code:
//This file is just for the necessary /world definition
//Try looking in game/world.dm

/**
 * # World
 *
 * Two possibilities exist: either we are alone in the Universe or we are not. Both are equally terrifying. ~ Arthur C. Clarke
 *
 * The byond world object stores some basic byond level config, and has a few hub specific procs for managing hub visiblity
 *
 * The world /New() is the root of where a round itself begins
 */
/world
	mob = /mob/dead/new_player
	turf = /turf/open/space/basic
	area = /area/space
	view = "15x15"
	hub = "Exadv1.spacestation13"
	hub_password = "kMZy3U5jJHSiBQjr"
	name = "/tg/ Station 13"
	fps = 20
#ifdef FIND_REF_NO_CHECK_TICK
	loop_checks = FALSE
#endif


ranks:
Dedmin
Sir Madam Headmin

names:
Badmin
Spanmin


code:
#Admin Rank format is as follows:
#
#Name = Game Admin
#Include = @ ADMIN BAN SOUND
#Exclude = FUN
#Edit =
#
#Name will match anything after '=' and must be identical to an admin's rank in admins.txt to be linked but otherwise has no formatting restrictions.
#A rank's permissions are defined with keywords that control access to groups of verbs and abilities, they are case-sensitive and separated by a space with no prefix.
#To define no permissions for a type, leave it empty.
#There are three types of permissions:
#Include will give a keyword to a rank.
#Exclude removes a keyword and takes precedence over Include.
#Edit will allow an admin to edit these permissions on other ranks or change an admin's rank to another if they can edit all the permissions it has.
#Edit is only used when SQL-based admin loading is enabled.
#If SQL-based admin loading is enabled, ranks and their keywords listed here will be loaded first and override any with the same name loaded from the database.
#
#The following are valid permission keywords:
#ADMIN = general admin tools, verbs etc.
#FUN = events, other event-orientated actions. Access to the fun secrets in the secrets panel.
#BAN = the ability to ban and unban.
#STEALTH = the ability to stealthmin (make yourself appear with a fake name to everyone but other admins.
#POSSESS = the ability to possess objects.
#POLL = the ability to create in game server polls (requires DB).
#BUILD = the ability to use buildmode.
#SERVER = the ability to restart the server, change the game mode or force a round to start/end.
#DEBUG = debug tools used for diagnosing and fixing problems. It's useful to give this to coders so they can investigate problems on a live server.
#VAREDIT = everyone may view viewvars/debugvars/whatever you call it. This keyword allows you to actually EDIT those variables.
#PERMISSIONS = allows you to promote and/or demote people.
#SOUND = allows you to upload and play SOUND.
#SPAWN = mob transformations, spawning of most atoms including mobs (high-risk atoms, e.g. blackholes, will require the +FUN flag too).
#AUTOADMIN = admin gains powers upon connect. This defaults to on, you can exclude AUTOADMIN to make a role require using the readmin verb to gain powers (this does not effect the admin's ability to walk past bans or other on-connect limitations like panic bunker or pop limit).
#DBRANKS = when sql-based admin loading is enabled, allows for non-temporary changes in the permissions panel to be saved (requires DB).
#EVERYTHING = Simply gives you everything without having to type every flag.
#@ = special keyword for the current permission type that adds all the keywords that the preceding rank has of the same type.

Name = Admin Observer
Include =
Exclude = AUTOADMIN
Edit =

Name = Moderator
Include = ADMIN
Exclude =
Edit =

Name = Admin Candidate
Include = @
Exclude =
Edit =

Name = Trial Admin
Include = @ SPAWN VAREDIT BAN
Exclude =
Edit =

Name = Badmin
Include = @ POSSESS POLL BUILD SERVER FUN
Exclude =
Edit =

Name = Game Admin
Include = @ STEALTH SOUND DEBUG
Exclude =
Edit =

Name = Game Master
Include = EVERYTHING
Exclude =
Edit = EVERYTHING

Name = Lazy Master
Include = EVERYTHING
Exclude = AUTOADMIN
Edit = EVERYTHING

Name = Host
Include = EVERYTHING
Exclude =
Edit = EVERYTHING

Name = Doge
Include = EVERYTHING
Exclude =
Edit = EVERYTHING

Name = Janny
Include = EVERYTHING
Exclude =
Edit = EVERYTHING

Name = Coder
Include = DEBUG VAREDIT SERVER SPAWN POLL
Exclude = AUTOADMIN
Edit =


code:
#List users here to be made admins with the format: ckey = rank name.
#Ckey will be converted to ckey format, lowercase with no punctuation except '@'.
#Rank name will match anything after ' = ' and has not format requirements.
#Ranks will match to those with the same name in admin_ranks.txt, if a match isn't found the user won't be adminned.
#If SQL-based admin loading is enabled, admins listed here will always be loaded first and will override any duplicate entries in the database.

ChaoticAgent = Doge
BedrockDigger2 = Doge
Bomberman64 = Doge
Remis12 = Janny
SoulVapor = Janny
Lilith_Rofocale = Janny
EvelynnCannotCode = Janny
Null-FFFFFF = Janny


code:
Add admin arena dmms here.

**These are fully cached so keep this directory empty by default.**


code:
Add away mission dmms here.

**These are fully cached so keep this directory empty by default.**


code:
#List the potential random Z-levels here.
#Maps must be the full path to them
#Maps should be 255x255 or smaller and be bounded.  Falling off the edge of the map will result in undefined behavior.
#SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START

#!!IMPORTANT NOTES FOR HOSTING AWAY MISSIONS!!:
#Do NOT tick the maps during compile -- the game uses this list to decide which map to load. Ticking the maps will result in them ALL being loaded at once.
#DO tick the associated code file for the away mission you are enabling. Otherwise, the map will be trying to reference objects which do not exist, which will cause runtime errors!

#_maps/RandomZLevels/blackmarketpackers.dmm
#_maps/RandomZLevels/spacebattle.dmm
#_maps/RandomZLevels/TheBeach.dmm
#_maps/RandomZLevels/Academy.dmm
#_maps/RandomZLevels/wildwest.dmm
#_maps/RandomZLevels/challenge.dmm
#_maps/RandomZLevels/centcomAway.dmm
#_maps/RandomZLevels/moonoutpost19.dmm
#_maps/RandomZLevels/undergroundoutpost45.dmm
#_maps/RandomZLevels/caves.dmm
#_maps/RandomZLevels/snowdin.dmm
#_maps/RandomZLevels/research.dmm
#_maps/RandomZLevels/SnowCabin.dmm
_maps/RandomZLevels/VR/snowdin_VR.dmm


code:
## Communication key for receiving data through world/Topic(), you don't want to give this out.
## Must be set to something other than 'default_pwd' and at least 7 characters long to be valid.
#COMMS_KEY default_pwd

## World address and port for server receiving cross server messages
## Use '+' to denote spaces in ServerName
## Repeat this entry to add more servers
#CROSS_SERVER ServerName byond:\\address:port

## Name that the server calls itself in communications
#CROSS_COMMS_NAME

## Network-name used for cross-server broadcasts made from communication consoles.
## Servers that do not match this network-name will have their messages discarded.
## Leaving this commented will allow all messages through, regardless of network.
#CROSS_COMMS_NETWORK default_network


code:
# You can use the "$include" directive to split your configs however you want

$include game_options.txt
$include dbconfig.txt
$include comms.txt
$include resources.txt
$include interviews.txt
$include septic/septic_config.txt

# You can use the @ character at the beginning of a config option to lock it from being edited in-game
# Example usage:
# @SERVERNAME tgstation
# Which sets the SERVERNAME, and disallows admins from being able to change it using View Variables.
# @LOG_TWITTER 0
# Which explicitly disables LOG_TWITTER, as well as locking it.
# There are various options which are hard-locked for security reasons.

## Server name: This appears at the top of the screen in-game and in the BYOND hub. Uncomment and replace 'tgstation' with the name of your choice.
SERVERNAME Septic Shock

## Server SQL name: This is the name used to identify the server to the SQL DB, distinct from SERVERNAME as it must be at most 32 characters.
# SERVERSQLNAME tgstation

## Station name: The name of the station as it is referred to in-game. If commented out, the game will generate a random name instead.
STATIONNAME Baluarte Fortress

## Put on byond hub: Uncomment this to put your server on the byond hub.
HUB

## Lobby time: This is the amount of time between rounds that players have to setup their characters and be ready.
LOBBY_COUNTDOWN 120

## Round End Time: This is the amount of time after the round ends that players have to murder death kill each other.
ROUND_END_COUNTDOWN 90

## Comment this out if you want to use the SQL based admin system, the legacy system uses admins.txt.
## You need to set up your database to use the SQL based system.
## This flag is automatically enabled if SQL_ENABLED isn't
ADMIN_LEGACY_SYSTEM

##Uncomment this to stop any admins loaded by the legacy system from having their rank edited by the permissions panel
#PROTECT_LEGACY_ADMINS

##Uncomment this to stop any ranks loaded by the legacy system from having their flags edited by the permissions panel
#PROTECT_LEGACY_RANKS

##Uncomment this to have admin ranks only loaded from the legacy admin_ranks.txt
##If enabled, each time admins are loaded ranks the database will be updated with the current ranks and their flags
#LOAD_LEGACY_RANKS_ONLY

## Comment this out to stop locally connected clients from being given the almost full access !localhost! admin rank
ENABLE_LOCALHOST_RANK

## Uncomment this entry to have certain jobs require your account to be at least a certain number of days old to select. You can configure the exact age requirement for different jobs by editing
## the minimal_player_age variable in the files in folder /code/game/jobs/job/.. for the job you want to edit. Set minimal_player_age to 0 to disable age requirement for that job.
## REQUIRES the database set up to work. Keep it hashed if you don't have a database set up.
## NOTE: If you have just set-up the database keep this DISABLED, as player age is determined from the first time they connect to the server with the database up. If you just set it up, it means
## you have noone older than 0 days, since noone has been logged yet. Only turn this on once you have had the database up for 30 days.
#USE_AGE_RESTRICTION_FOR_JOBS

## Uncomment this to have the job system use the player's account creation date, rather than the when they first joined the server for job timers.
#USE_ACCOUNT_AGE_FOR_JOBS

## Unhash this to track player playtime in the database. Requires database to be enabled.
#USE_EXP_TRACKING
## Unhash this to enable playtime requirements for head jobs.
#USE_EXP_RESTRICTIONS_HEADS
## Unhash this to override head jobs' playtime requirements with this number of hours.
## Leave this commented out to use the values defined in the job datums. Values in the datums are stored as minutes.
#USE_EXP_RESTRICTIONS_HEADS_HOURS 3
## Unhash this to change head jobs' playtime requirements so that they're based on department playtime, rather than crew playtime.
#USE_EXP_RESTRICTIONS_HEADS_DEPARTMENT
## Unhash this to enable playtime requirements for certain non-head jobs, like Engineer and Scientist.
#USE_EXP_RESTRICTIONS_OTHER
## Allows admins to bypass job playtime requirements.
#USE_EXP_RESTRICTIONS_ADMIN_BYPASS
## Unhash this to have intern tags automatically added to ID cards for station roles depending on the living hours of the player holding them.
#USE_LOW_LIVING_HOUR_INTERN
## If USE_LOW_LIVING_HOUR_INTERN is unhashed, players under this number of living hours have [Intern] added to their ID card. If this isn't set, uses USE_EXP_RESTRICTIONS_HEADS_HOURS instead. If that isn't set, it finally defaults to a hardcoded fallback of 15 hours.
#USE_LOW_LIVING_HOUR_INTERN_HOURS 50

## log OOC channel
LOG_OOC

## log client Say
LOG_SAY

## log admin actions
LOG_ADMIN

## log admin chat
LOG_ADMINCHAT

## log asset debug logs
#LOG_ASSET

## log client access (logon/logoff)
LOG_ACCESS

## Enables log entries for logins that failed due to suspicious circumstances (banned player, CID randomiser, spoofed BYOND version, etc.) to a dedicated file.
LOG_SUSPICIOUS_LOGIN

## log game actions (start of round, results, etc.)
LOG_GAME

## log player votes
LOG_VOTE

## log client Whisper
LOG_WHISPER

## log emotes
LOG_EMOTE

## log attack messages
LOG_ATTACK

## log pda messages
LOG_PDA

## log uplink purchases
LOG_UPLINK

## log telecomms messages
LOG_TELECOMMS

## log prayers
LOG_PRAYER

## log lawchanges
LOG_LAW

## log economy actions
LOG_ECON

## log crew manifest to seperate file
LOG_MANIFEST

## log job divide debugging information
#LOG_JOB_DEBUG

## log all world.Topic() calls
# LOG_WORLD_TOPIC

## enables use of the proc twitterize() that lets you take a large list of strings and turn it into a JSON file of tweet sized strings.
## As an example of how this could be """useful""" look towards Poly (https://twitter.com/Poly_the_Parrot)
# LOG_TWITTER

## Enable logging pictures
LOG_PICTURES

## log mecha actions
# LOG_MECHA

## log virus and actions
LOG_VIRUS

## log cloning actions
LOG_CLONING

## log shuttle actions
LOG_SHUTTLE

## Log all timers on timer auto reset
# LOG_TIMERS_ON_BUCKET_RESET

##Log camera pictures - Must have picture logging enabled
PICTURE_LOGGING_CAMERA

## period of time in seconds for players to be considered inactive
# INACTIVITY_PERIOD 300

## period of time in seconds for players to be considered afk and kickable
# AFK_PERIOD 600

## disconnect players who are considered afk
# KICK_INACTIVE

## Comment this out to stop admins being able to choose their personal ooccolor
ALLOW_ADMIN_OOCCOLOR

## Comment this out to stop admins being able to choose their personal asay color
ALLOW_ADMIN_ASAYCOLOR

## Job slot open/close by identification consoles delay in seconds
ID_CONSOLE_JOBSLOT_DELAY 30

## allow players to initiate a restart vote
#ALLOW_VOTE_RESTART

## allow players to initiate a mode-change vote
#ALLOW_VOTE_MODE

## allow players to initiate a map-change vote
#ALLOW_VOTE_MAP

## min delay (deciseconds) between voting sessions (default 10 minutes)
VOTE_DELAY 6000

## time period (deciseconds) which voting session will last (default 1 minute)
VOTE_PERIOD 600

## prevents dead players from voting or starting votes
# NO_DEAD_VOTE

## players' votes default to "No vote" (otherwise,  default to "No change")
# DEFAULT_NO_VOTE

## disable abandon mob
NORESPAWN

## set a hosted by name for unix platforms
HOSTEDBY Atlas

## Uncomment this to stop people connecting to your server without a registered ckey. (i.e. guest-* are all blocked from connecting)
GUEST_BAN

## Comment this out to disable checking for the cid randomizer dll. (disabled if database isn't enabled or connected)
CHECK_RANDOMIZER

## IPINTEL:
## This allows you to detect likely proxies by checking ips against getipintel.net
## Rating to warn at: (0.9 is good, 1 is 100% likely to be a spammer/proxy, 0.8 is 80%, etc) anything equal to or higher then this number triggers an admin warning
#IPINTEL_RATING_BAD 0.9
## Contact email, (required to use the service, leaving blank or default disables IPINTEL)
#IPINTEL_EMAIL ch@nge.me
## How long to save good matches (ipintel rate limits to 15 per minute and 500 per day. so this shouldn't be too low, getipintel.net suggests 6 hours, time is in hours) (Your ip will get banned if you go over 500 a day too many times)
#IPINTEL_SAVE_GOOD 12
## How long to save bad matches (these numbers can change as ips change hands, best not to save these for too long in case somebody gets a new ip used by a spammer/proxy before.)
#IPINTEL_SAVE_BAD 3
## Domain name to query (leave commented out for the default, only needed if you pay getipintel.net for more querys)
#IPINTEL_DOMAIN check.getipintel.net

## Uncomment to allow web client connections
#ALLOW_WEBCLIENT

## Uncomment to restrict web client connections to byond members
## This makes for a nice pay gate to cut down on ban evading, as the webclient's cid system isn't that great
## byond membership starts at $10 for 3 months, so to use the webclient to evade, they would have sink 10 bucks in each evade.
#WEBCLIENT_ONLY_BYOND_MEMBERS

## Set to prevent anyone but those ckeys listed in config/whitelist.txt and config/admins.txt from joining your server
#USEWHITELIST

## set a server location for world reboot. Don't include the byond://, just give the address and port.
## Don't set this to the same server, BYOND will automatically restart players to the server when it has restarted.
# SERVER ss13.example.com:2506

## forum address
FORUMURL https://discord.gg/e7JkxvRtvX

## Wiki address
WIKIURL https://discord.gg/e7JkxvRtvX

## Rules address
RULESURL https://discord.gg/e7JkxvRtvX

## Github address
GITHUBURL https://gitgud.io/bobstation/bobstation-reignited

## Discord bot command prefix, if the discord bot is used
# DISCORDBOTCOMMANDPREFIX ?

## Round specific stats address
## Link to round specific parsed logs; IE statbus. It is appended with the RoundID automatically by ticker/Reboot()
## This will take priority over the game logs address during reboot.
## Example: https://atlantaned.space/statbus/round.php?round=
# ROUNDSTATSURL

## Game Logs address
## Incase you don't have a fancy parsing system, but still want players to be able to find where you keep your server's logs.
## Example: https://tgstation13.org/parsed-logs/basil/data/logs/
# GAMELOGURL

## Ban appeals URL - usually for a forum or wherever people should go to contact your admins.
BANAPPEALS https://www.youtube.com/watch?v=aC69fRVXeac

## System command that invokes youtube-dl, used by Play Internet Sound.
## You can install youtube-dl with
## "pip install youtube-dl" if you have pip installed
## from https://github.com/rg3/youtube-dl/releases
## or your package manager
## The default value assumes youtube-dl is in your system PATH
# INVOKE_YOUTUBEDL youtube-dl

## In-game features
##Toggle for having jobs load up from the .txt
# LOAD_JOBS_FROM_TXT

## Uncomment this to forbid admins from possessing the singularity.
#FORBID_SINGULO_POSSESSION

## Uncomment to give admins the ability to send a maptext popup to players.
## Only fires when an admin requests it, not every ahelp.
POPUP_ADMIN_PM

## Uncomment to allow special 'Easter-egg' events on special holidays such as seasonal holidays and stuff like 'Talk Like a Pirate Day' :3 YAARRR
ALLOW_HOLIDAYS

## Uncomment to show the names of the admin sending a pm from IRC instead of showing as a stealthmin.
#SHOW_IRC_NAME

## Defines the ticklimit for subsystem initialization (In percents of a byond tick). Lower makes world start smoother. Higher makes it faster.
##This is currently a testing optimized setting. A good value for production would be 98.
TICK_LIMIT_MC_INIT 500

##Defines the ticklag for the world. Ticklag is the amount of time between game ticks (aka byond ticks) (in 1/10ths of a second).
##	This also controls the client network update rate, as well as the default client fps
TICKLAG 0.5

##Can also be set as per-second value, the following value is identical to the above.
#FPS 20

## Comment this out to disable automuting
#AUTOMUTE_ON

## Uncomment this to let players see their own notes (they can still be set by admins only)
#SEE_OWN_NOTES

### Comment these two out to prevent notes fading out over time for admins.
## Notes older then this will start fading out.
NOTE_FRESH_DAYS 91.31055
## Notes older then this will be completely faded out.
NOTE_STALE_DAYS 365.2422

## Uncomment to allow drastic performence enhancemet measures to turn on automatically once there are equal or more clients than the configured amount (will also prompt admin for veto)
#AUTO_LAG_SWITCH_POP 75

##Note: all population caps can be used with each other if desired.

## Uncomment for 'soft' population caps, players will be warned while joining if the living crew exceeds the listed number.
#SOFT_POPCAP 100

## Message for soft cap
SOFT_POPCAP_MESSAGE Be warned that the server is currently serving a high number of users, consider using alternative game servers.

## Uncomment for 'hard' population caps, players will not be allowed to spawn if the living crew exceeds the listed number, though they may still observe or wait for the living crew to decrease in size.
#HARD_POPCAP 150

## Message for hard cap
HARD_POPCAP_MESSAGE The server is currently serving a high number of users, You cannot currently join. You may wait for the number of living crew to decline, observe, or find alternative servers.

## Uncomment for 'extreme' population caps, players will not be allowed to join the server if living crew exceeds the listed number.
#EXTREME_POPCAP 200

## Message for extreme cap
EXTREME_POPCAP_MESSAGE The server is currently serving a high number of users, find alternative servers.

## Uncomment to allow BYOND subscribers to bypassing server connection population cap. (Living player population cap still applies.)
BYOND_MEMBER_BYPASS_POPCAP

## Notify admins when a new player connects for the first x days a player's been around. (0 for first connection only, -1 for never)
##	Requres database
NOTIFY_NEW_PLAYER_AGE 0

## Notify admins when a player connects if their byond account was created in the last X days
## Requires database
NOTIFY_NEW_PLAYER_ACCOUNT_AGE 1

## Notify the irc channel when a new player makes their first connection
##	Requres database
#IRC_FIRST_CONNECTION_ALERT

## Deny all new connections by ckeys we haven't seen before (exempts admins and only denies the connection if the database is enabled and connected)
##	Requires database
PANIC_BUNKER

## Uncomment to enable the interview system for players who are below the living hour requirement instead of explicitly blocking them from the server
## Note this requires the PANIC_BUNKER to be enabled to do anything.
#PANIC_BUNKER_INTERVIEW

## If a player connects during a bunker with less then or this amount of living time (Minutes), we deny the connection
PANIC_BUNKER_LIVING 5

## The message the Panic Bunker gives when someone is rejected by it
## %minutes% is replaced with PANIC_BUNKER_LIVING on runtime, remove it if you don't want this
PANIC_BUNKER_MESSAGE You are not invited.

## If panic bunker is on and a player is rejected (see above), attempt to send them to this connected server (see below) instead.
##	You probably want this to be the same as CROSS_SERVER_ADDRESS
#PANIC_SERVER_ADDRESS byond://address:port

##Name of the place to send people rejected by the bunker
#PANIC_SERVER_NAME [Put the name here]

## Uncomment to have the changelog file automatically open when a user connects and hasn't seen the latest changelog
#AGGRESSIVE_CHANGELOG

## Comment this out if you've used the mass conversion sql proc for notes or want to stop converting notes
AUTOCONVERT_NOTES

## Comment this out to stop admin messages sent anytime an admin disconnects from a round in play, you can edit the messages in admin.dm
ANNOUNCE_ADMIN_LOGOUT

## Uncomment to have an admin message sent anytime an admin connects to a round in play, you can edit the messages in admin.dm
#ANNOUNCE_ADMIN_LOGIN

## Map rotation
## You should edit maps.txt to match your configuration when you enable this.
MAPROTATION

## Map voting
## Allows players to vote with their preffered map setting
## When it's set to zero, the map will be randomly picked each round
PREFERENCE_MAP_VOTING 1

## AUTOADMIN
## The default admin rank
AUTOADMIN_RANK Game Master

## Uncomment to automatically give that admin rank to all players
#AUTOADMIN

## Uncomment to automatically deadmin players when the game starts.
#AUTO_DEADMIN_PLAYERS

## Uncomment to automatically deadmin antagonists when they gain the role.
#AUTO_DEADMIN_ANTAGONISTS

## Uncomment to automatically deadmin specific role sets when a player joins the game.
#AUTO_DEADMIN_HEADS
#AUTO_DEADMIN_SECURITY
#AUTO_DEADMIN_SILICONS

## CLIENT VERSION CONTROL
## This allows you to configure the minimum required client version, as well as a warning version, and message for both.
## These trigger for any version below (non-inclusive) the given version, so 510 triggers on 509 or lower.
## These messages will be followed by one stating the clients current version and the required version for clarity.
## If CLIENT_WARN_POPUP is uncommented a popup window with the message will be displayed instead
#CLIENT_WARN_VERSION 511
#CLIENT_WARN_POPUP
#CLIENT_WARN_MESSAGE Byond released 511 as the stable release. You can set the framerate your client runs at, which makes the game feel very different and cool. Shortly after its release we will end up using 511 client features and you will be forced to update.
CLIENT_ERROR_VERSION 511
CLIENT_ERROR_MESSAGE Your version of byond is not supported. Please upgrade.
## The minimum build needed for joining the server, if using 512, a good minimum build would be 1421 as that disables the Middle Mouse Button exploit.
CLIENT_ERROR_BUILD 1421

## TOPIC RATE LIMITING
## This allows you to limit how many topic calls (clicking on an interface window) the client can do in any given game second and/or game minute.
## Admins are exempt from these limits.
## Hitting the minute limit notifies admins.
## Set to 0 or comment out to disable.
SECOND_TOPIC_LIMIT 10

MINUTE_TOPIC_LIMIT 100

## CLICK RATE LIMITING
## Same as above, but applies to clicking on objects in the game window.
## This should be a higher then the interface limit to allow for the spam clickly nature of most battles.
## Admins are exempt from these limits.
## Hitting the minute limit notifies admins.
## Set to 0 to disable.
SECOND_CLICK_LIMIT 15

MINUTE_CLICK_LIMIT 400

##Error handling related options
## The "cooldown" time for each occurence of a unique error
#ERROR_COOLDOWN 600
## How many occurences before the next will silence them
#ERROR_LIMIT 90
## How long a unique error will be silenced for
#ERROR_SILENCE_TIME 6000
##How long to wait between messaging admins about occurences of a unique error
#ERROR_MSG_DELAY 50

## Game Chat Message Options
## Various messages to be sent to connected chat channels.
## Uncommenting these will enable them, by default they will be broadcast to Game chat channels on TGS3 or non-admin channels on TGS4.
## If using TGS4, the string option can be set as one of more chat channel tags (separated by ','s) to limit the message to channels with that tag name (case-sensitive). This will have no effect on TGS3.
## i.e. CHAT_ANNOUNCE_NEW_GAME chat_channel_tag

## Send a message with the station name starting a new game.
#CHAT_ANNOUNCE_NEW_GAME

## Ping users who use the `notify` command when a new game starts.
#CHAT_NEW_GAME_NOTIFICATIONS

## Allow admin hrefs that don't use the new token system, will eventually be removed
#DEBUG_ADMIN_HREFS

###Master Controller High Pop Mode###

##The Master Controller(MC) is the primary system controlling timed tasks and events in SS13 (lobby timer, game checks, lighting updates, atmos, etc)
##Default base MC tick rate (1 = process every "byond tick" (see: tick_lag/fps config settings), 2 = process every 2 byond ticks, etc)
##	Setting this to 0 will prevent the Master Controller from ticking
BASE_MC_TICK_RATE 1

##High population MC tick rate
##	Byond rounds timer values UP, but the tick rate is modified with heuristics during lag spites so setting this to something like 2
##	will make it run every 2 byond ticks, but will also double the effect of anti-lag heuristics. You can instead set it to something like
##	1.1 to make it run every 2 byond ticks, but only increase the effect of anti-lag heuristics by 10%. or 1.5 for 50%.
##	(As an aside, you could in theory also reduce the effect of anti-lag heuristics in the base tick rate by setting it to something like 0.5)
HIGH_POP_MC_TICK_RATE 1.1

##Engage high pop mode if player count raises above this (Player in this context means any connected user. Lobby, ghost or in-game all count)
HIGH_POP_MC_MODE_AMOUNT 65

##Disengage high pop mode if player count drops below this
DISABLE_HIGH_POP_MC_MODE_AMOUNT 60

## Uncomment to prevent the world from sleeping while no players are connected after initializations
#RESUME_AFTER_INITIALIZATIONS

## Uncomment to set the number of /world/Reboot()s before the DreamDaemon restarts itself. 0 means restart every round. Requires tgstation server tools.
#ROUNDS_UNTIL_HARD_RESTART 10

##Default view size, in tiles.
##	By default, this is 15x15, which gets simplified to 7 by byond
##  15x15 would be the standard square view. 21x15 is what goonstation uses for widescreen.
##  Setting this to something different from DEFAULT_VIEW_SQUARE will enable widescreen toggles
##	Do note that changing this value will affect the title screen. The title screen will have to be updated manually if this is changed.
DEFAULT_VIEW 22x16

##Default view size, in tiles. Should *always* be square.
## The alternative square viewport size if you're using a widescreen view size
## You probably shouldn't ever be changing this, but it's here if you want to.
DEFAULT_VIEW_SQUARE 15x15

## Enable automatic profiling - Byond 513.1506 and newer only.
#AUTO_PROFILE

## Uncomment to enable global ban DB using the provided URL. The API should expect to receive a ckey at the end of the URL.
## More API details can be found here: https://centcom.melonmesa.com
#CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search
## Uncomment to enable source whitelisting, a comma-separated list (no spaces) of CentCom sources (sourceName).
## If enabled, only bans from these servers will be shown to admins using CentCom. The default sources list is an example.
#CENTCOM_SOURCE_WHITELIST Beestation MRP,TGMC,FTL13

## If uncommented, this will enable admin 2FA (two-factor authentication).
## The URL admins will be asked to go to will be here, with %ID% being the ID
## of their connection attempt.
#ADMIN_2FA_URL https://example.com/id/%ID%

#### DISCORD STUFFS ####
## MAKE SURE ALL SECTIONS OF THIS ARE FILLED OUT BEFORE ENABLING
## Discord IDs can be obtained by following this guide: https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-

## Uncomment to enable discord auto-roling when users link their BYOND and Discord accounts
#ENABLE_DISCORD_AUTOROLE

## Add your discord bot token here. Make sure it has the ability to manage roles
#DISCORD_TOKEN someDiscordToken

## Add the ID of your guild (server) here
#DISCORD_GUILDID 000000000000000000

## Add the ID of the role you want assigning here
#DISCORD_ROLEID 000000000000000000

## How long in seconds after which a hard delete is treated as causing lag. This can be a float and supports a precision as low as nanoseconds.
#HARD_DELETES_OVERRUN_THRESHOLD 0.5

## Once a typepath causes overrun from hard deletes this many times, stop hard deleting it on garbage collection failures. (set to 0 to disable)
#HARD_DELETES_OVERRUN_LIMIT 0

## Sets an MOTD of the server.
## You can use this multiple times, and the MOTDs will be appended in order.
## Based on config directory, so "motd.txt" points to "config/motd.txt"
MOTD motd.txt
#MOTD motd_extra.txt


code:
## MySQL Connection Configuration
## This is used for stats, feedback gathering,
## administration, and the in game library.

## Should SQL be enabled? Uncomment to enable
#SQL_ENABLED

## Server the MySQL database can be found at.
## Examples: localhost, 200.135.5.43, www.mysqldb.com, etc.
ADDRESS localhost

## MySQL server port (default is 3306).
PORT 3306

## Database for all SQL functions, not just feedback.
FEEDBACK_DATABASE feedback

## Prefix to be added to the name of every table, older databases will require this be set to erro_
## Note, this does not change the table names in the database, you will have to do that yourself.
##IE:
##	FEEDBACK_TABLEPREFIX
##	FEEDBACK_TABLEPREFIX SS13_
## Remove "SS13_" if you are using the standard schema file.
FEEDBACK_TABLEPREFIX SS13_

## Username/Login used to access the database.
FEEDBACK_LOGIN username

## Password used to access the database.
FEEDBACK_PASSWORD password

## Time in seconds for asynchronous queries to timeout
## Set to 0 for infinite
ASYNC_QUERY_TIMEOUT 10

## Time in seconds for blocking queries to execute before slow query timeout
## Set to 0 for infinite
## Must be less than or equal to ASYNC_QUERY_TIMEOUT
BLOCKING_QUERY_TIMEOUT 5

## The maximum number of additional threads BSQL is allowed to run at once
BSQL_THREAD_LIMIT 50

## The maximum number of concurrent asynchronous queries that can run at one time, handled by DM.
MAX_CONCURRENT_QUERIES 25