code:
// TURBINE v2 AKA rev4407 Engine reborn!

// How to use it? - Mappers
//
// This is a very good power generating mechanism. All you need is a blast furnace with soaring flames and output.
// Not everything is included yet so the turbine can run out of fuel quiet quickly. The best thing about the turbine is that even
// though something is on fire that passes through it, it won't be on fire as it passes out of it. So the exhaust fumes can still
// containt unreacted fuel - plasma and oxygen that needs to be filtered out and re-routed back. This of course requires smart piping
// For a computer to work with the turbine the compressor requires a comp_id matching with the turbine computer's id. This will be
// subjected to a change in the near future mind you. Right now this method of generating power is a good backup but don't expect it
// become a main power source unless some work is done. Have fun. At 50k RPM it generates 60k power. So more than one turbine is needed!
//
// - Numbers
//
// Example setup  S - sparker
//  B - Blast doors into space for venting
// *BBB****BBB*  C - Compressor
// S    CT    *  T - Turbine
// * ^ *  * V *  D - Doors with firedoor
// **|***D**|**      ^ - Fuel feed (Not vent, but a gas outlet)
//   |      |        V - Suction vent (Like the ones in atmos
//

/obj/machinery/power/compressor
	name = "compressor"
	desc = "The compressor stage of a gas turbine generator."
	icon = 'icons/obj/atmospherics/pipes/simple.dmi'
	icon_state = "compressor"
	density = TRUE
	resistance_flags = FIRE_PROOF
	can_atmos_pass = ATMOS_PASS_DENSITY
	circuit = /obj/item/circuitboard/machine/power_compressor
	var/obj/machinery/power/turbine/turbine
	var/datum/gas_mixture/gas_contained
	var/turf/inturf
	var/starter = 0
	var/rpm = 0
	var/rpmtarget = 0
	var/capacity = 1e6
	var/comp_id = 0
	var/efficiency

/obj/machinery/power/compressor/Destroy()
	if (turbine && turbine.compressor == src)
		turbine.compressor = null
	turbine = null
	return ..()

/obj/machinery/power/turbine
	name = "gas turbine generator"
	desc = "A gas turbine used for backup power generation."
	icon = 'icons/obj/atmospherics/pipes/simple.dmi'
	icon_state = "turbine"
	density = TRUE
	resistance_flags = FIRE_PROOF
	can_atmos_pass = ATMOS_PASS_DENSITY
	circuit = /obj/item/circuitboard/machine/power_turbine
	var/opened = 0
	var/obj/machinery/power/compressor/compressor
	var/turf/outturf
	var/lastgen
	var/productivity = 1

/obj/machinery/power/turbine/Destroy()
	if (compressor && compressor.turbine == src)
		compressor.turbine = null
	compressor = null
	return ..()

// the inlet stage of the gas turbine electricity generator

/obj/machinery/power/compressor/Initialize(mapload)
	. = ..()
	// The inlet of the compressor is the direction it faces
	gas_contained = new
	inturf = get_step(src, dir)
	locate_machinery()
	if(!turbine)
		atom_break()

#define COMPFRICTION 5e5

/obj/machinery/power/compressor/locate_machinery()
	if(turbine)
		return
	turbine = locate() in get_step(src, get_dir(inturf, src))
	if(turbine)
		turbine.locate_machinery()

/obj/machinery/power/compressor/RefreshParts()
	var/E = 0
	for(var/obj/item/stock_parts/manipulator/M in component_parts)
		E += M.rating
	efficiency = E / 6

/obj/machinery/power/compressor/examine(mob/user)
	. = ..()
	if(in_range(user, src) || isobserver(user))
		. += span_notice("The status display reads: Efficiency at [efficiency*100]%.")

/obj/machinery/power/compressor/attackby(obj/item/I, mob/user, params)
	if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I))
		return

	if(default_change_direction_wrench(user, I))
		turbine = null
		inturf = get_step(src, dir)
		locate_machinery()
		if(turbine)
			to_chat(user, span_notice("Turbine connected."))
			set_machine_stat(machine_stat & ~BROKEN)
		else
			to_chat(user, span_alert("Turbine not connected."))
			atom_break()
		return

	default_deconstruction_crowbar(I)

/obj/machinery/power/compressor/process()
	if(!starter)
		return
	if(!turbine || (turbine.machine_stat & BROKEN))
		starter = FALSE
	if(machine_stat & BROKEN || panel_open)
		starter = FALSE
		return
	cut_overlays()

	if(istype(inturf, /turf/open))
		rpm = 0.9 * rpm + 0.1 * rpmtarget
		var/datum/gas_mixture/environment = inturf.return_air()

		// It's a simplified version taking only 1/10 of the moles from the turf nearby. It should be later changed into a better version

		var/transfer_moles = environment.total_moles()/10
		var/datum/gas_mixture/removed = inturf.remove_air(transfer_moles)
		gas_contained.merge(removed)
	else
		rpm = 0.9 * rpm // rpmtarget is basically 0, the intake is completely blocked with no airflow

// RPM function to include compression friction - be advised that too low/high of a compfriction value can make things screwy

	rpm = min(rpm, (COMPFRICTION*efficiency)/2)
	rpm = max(0, rpm - (rpm*rpm)/(COMPFRICTION*efficiency))

	if(starter && !(machine_stat & NOPOWER))
		use_power(2800)
		if(rpm<1000)
			rpmtarget = 1000
	else
		if(rpm<1000)
			rpmtarget = 0

	if(rpm>50000)
		add_overlay(mutable_appearance(icon, "comp-o4", FLY_LAYER))
	else if(rpm>10000)
		add_overlay(mutable_appearance(icon, "comp-o3", FLY_LAYER))
	else if(rpm>2000)
		add_overlay(mutable_appearance(icon, "comp-o2", FLY_LAYER))
	else if(rpm>500)
		add_overlay(mutable_appearance(icon, "comp-o1", FLY_LAYER))
	//TODO: DEFERRED

// These are crucial to working of a turbine - the stats modify the power output. TurbGenQ modifies how much raw energy can you get from
// rpms, TurbGenG modifies the shape of the curve - the lower the value the less straight the curve is.

#define TURBGENQ 100000
#define TURBGENG 0.5

/obj/machinery/power/turbine/Initialize(mapload)
	. = ..()
// The outlet is pointed at the direction of the turbine component
	outturf = get_step(src, dir)
	locate_machinery()
	if(!compressor)
		atom_break()
	connect_to_network()

/obj/machinery/power/turbine/RefreshParts()
	var/P = 0
	for(var/obj/item/stock_parts/capacitor/C in component_parts)
		P += C.rating
	productivity = P / 6

/obj/machinery/power/turbine/examine(mob/user)
	. = ..()
	if(in_range(user, src) || isobserver(user))
		. += span_notice("The status display reads: Productivity at [productivity*100]%.")

/obj/machinery/power/turbine/locate_machinery()
	if(compressor)
		return
	compressor = locate() in get_step(src, get_dir(outturf, src))
	if(compressor)
		compressor.locate_machinery()

/obj/machinery/power/turbine/process()

	if(!compressor)
		set_machine_stat(BROKEN)

	if((machine_stat & BROKEN) || panel_open)
		return
	if(!compressor.starter)
		return
	cut_overlays()

	// This is the power generation function. If anything is needed it's good to plot it in EXCEL before modifying
	// the TURBGENQ and TURBGENG values

	lastgen = ((compressor.rpm / TURBGENQ)**TURBGENG) * TURBGENQ * productivity

	add_avail(lastgen)

	// Weird function but it works. Should be something else...

	var/newrpm = ((compressor.gas_contained.temperature) * compressor.gas_contained.total_moles())/4

	newrpm = max(0, newrpm)

	if(!compressor.starter || newrpm > 1000)
		compressor.rpmtarget = newrpm

	if(compressor.gas_contained.total_moles()>0)
		var/oamount = min(compressor.gas_contained.total_moles(), (compressor.rpm+100)/35000*compressor.capacity)
		var/datum/gas_mixture/removed = compressor.gas_contained.remove(oamount)
		outturf.assume_air(removed)

// If it works, put an overlay that it works!

	if(lastgen > 100)
		add_overlay(mutable_appearance(icon, "turb-o", FLY_LAYER))

/obj/machinery/power/turbine/attackby(obj/item/I, mob/user, params)
	if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I))
		return

	if(default_change_direction_wrench(user, I))
		compressor = null
		outturf = get_step(src, dir)
		locate_machinery()
		if(compressor)
			to_chat(user, span_notice("Compressor connected."))
			set_machine_stat(machine_stat & ~BROKEN)
		else
			to_chat(user, span_alert("Compressor not connected."))
			atom_break()
		return

	default_deconstruction_crowbar(I)

/obj/machinery/power/turbine/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "TurbineComputer", name)
		ui.open()

/obj/machinery/power/turbine/ui_data(mob/user)
	var/list/data = list()
	data["compressor"] = compressor ? TRUE : FALSE
	data["compressor_broke"] = (!compressor || (compressor.machine_stat & BROKEN)) ? TRUE : FALSE
	data["turbine"] = compressor?.turbine ? TRUE : FALSE
	data["turbine_broke"] = (!compressor || !compressor.turbine || (compressor.turbine.machine_stat & BROKEN)) ? TRUE : FALSE
	data["online"] = compressor?.starter
	data["power"] = display_power(compressor?.turbine?.lastgen)
	data["rpm"] = compressor?.rpm
	data["temp"] = compressor?.gas_contained.temperature
	return data

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

	switch(action)
		if("toggle_power")
			if(compressor?.turbine)
				compressor.starter = !compressor.starter
				. = TRUE
		if("reconnect")
			locate_machinery()
			. = TRUE

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// COMPUTER NEEDS A SERIOUS REWRITE.

/obj/machinery/computer/turbine_computer
	name = "gas turbine control computer"
	desc = "A computer to remotely control a gas turbine."
	icon_screen = "turbinecomp"
	icon_keyboard = "tech_key"
	circuit = /obj/item/circuitboard/computer/turbine_computer
	var/obj/machinery/power/compressor/compressor
	var/id = 0

/obj/machinery/computer/turbine_computer/Initialize(mapload)
	. = ..()
	return INITIALIZE_HINT_LATELOAD

/obj/machinery/computer/turbine_computer/LateInitialize()
	locate_machinery()

/obj/machinery/computer/turbine_computer/locate_machinery()
	if(id)
		for(var/obj/machinery/power/compressor/C in GLOB.machines)
			if(C.comp_id == id)
				compressor = C
				return
	else
		compressor = locate(/obj/machinery/power/compressor) in range(7, src)

/obj/machinery/computer/turbine_computer/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "TurbineComputer", name)
		ui.open()

/obj/machinery/computer/turbine_computer/ui_data(mob/user)
	var/list/data = list()
	data["compressor"] = compressor ? TRUE : FALSE
	data["compressor_broke"] = (!compressor || (compressor.machine_stat & BROKEN)) ? TRUE : FALSE
	data["turbine"] = compressor?.turbine ? TRUE : FALSE
	data["turbine_broke"] = (!compressor || !compressor.turbine || (compressor.turbine.machine_stat & BROKEN)) ? TRUE : FALSE
	data["online"] = compressor?.starter
	data["power"] = display_power(compressor?.turbine?.lastgen)
	data["rpm"] = compressor?.rpm
	data["temp"] = compressor?.gas_contained.temperature
	return data

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

	switch(action)
		if("toggle_power")
			if(compressor?.turbine)
				compressor.starter = !compressor.starter
				. = TRUE
		if("reconnect")
			locate_machinery()
			. = TRUE

#undef COMPFRICTION
#undef TURBGENQ
#undef TURBGENG


code:
by RemieRichards

//////////////////////////////
// CODER INFORMATIVE README //
//////////////////////////////
(See below for Mapper Friendly Readme)

mapGenerator:
	Desc: a mapGenerator is a master datum that collects
	and syncs all mapGeneratorModules in it's modules list

	defineRegion(var/turf/Start, turf/End, replace = 0)
		Example: defineRegion(locate(1,1,1),locate(5,5,5),0)
		Desc: Sets the bounds of the mapGenerator's "map"

	defineCircularRegion(var/turf/Start, turf/End, replace = 0)
		Example: defineCircularRegion(locate(1,1,1),locate(5,5,5),0)
		Desc: Sets the mapGenerator's "map" as a circle, with center in the middle of Start and End's X,Y,Z coordinates

	undefineRegion()
		Example: undefineRegion()
		Desc: Empties the map generator list

	checkRegion(var/turf/Start, turf/End)
		Example: checkRegion(locate(1,1,1), locate(5,5,5))
		Desc: Checks if a rectangle between Start's coords and End's coords is valid
		Existing Calls: mapGenerator/defineRegion(), mapGenerator/defineCircularRegion()

	generate()
		Example: generate()
		Desc: Orders all mapGeneratorModules in the modules list to generate()

	generateOneTurf(var/turf/T)
		Example: generateOneTurf(locate(1,1,1))
		Desc: Orders all mapGeneratorModules in the modules list to place(T) on this turf

	initialiseModules()
		Example: initialiseModules()
		Desc: Replaces all typepaths in the modules list with actual /datum/map_generator/Module types
		Existing Calls: mapGenerator/New()

	syncModules()
		Example: syncModules()
		Desc: Sets the Mother variable on all mapGeneratorModules in the modules list to this mapGenerator
		Existing Calls: initialiseModules(),generate(),generateOneTurf()

mapGeneratorModule
	Desc: a mapGeneratorModule has spawnableAtoms and spawnableTurfs lists
	which it will generate on turfs in it's mother's map based on cluster variables

	sync(var/datum/map_generator/mum)
		Example: sync(a_mapGenerator_as_a_variable)
		Desc: Sets the Mother variable to the mum argument
		Existing Calls: mapGenerator/syncModules()

	generate()
		Example: generate()
		Desc: Calls place(T) on all turfs in it's mother's map
		Existing Calls: mapGenerator/generate()

	place(var/turf/T)
		Example: place(locate(1,1,1))
		Desc: Run this mapGeneratorModule's effects on this turf (Spawning atoms, Changing turfs)
		Existing Calls: mapGenerator/generate(), mapGenerator/generateOneTurf()

	checkPlaceAtom(var/turf/T)
		Example: checkPlace(locate(1,1,1))
		Desc: Checks if the turf is valid for placing atoms
		Existing Calls: place()

////////////////////////////
// MAPPER FRIENDLY README //
////////////////////////////

Simple Workflow:

	1. Define a/some mapGeneratorModule(s) to your liking, choosing atoms and turfs to spawn
	 #Note: I chose to split Turfs and Atoms off into separate modules, but this is NOT required.
	 #Note: A mapGeneratorModule may have turfs AND atoms, so long as each is in it's appropriate list

	2. Define a mapGenerator type who's modules list contains the typepath(s) of all the module(s) you wish to use
	 #Note: The order of the typepaths in the modules list is the order they will happen in, this is important for clusterCheckFlags.

	3. Take notes of the Bottom Left and Top Right turfs of your rectangular "map"'s coordinates
	 #Note: X,Y AND Z, Yes you can created 3D "maps" by having differing Z coords

	4. Create the mapGenerator type you created

	5. Call yourMapGeneratorType.defineRegion(locate(X,Y,Z), locate(X,Y,Z))
	 #Note: The above X/Y/Zs are the coordinates of the start and end turfs, the locate() simply finds the turf for the code

	6. Call yourMapGeneratorType.generate(), this will cause all the modules in the generator to build within the map bounds

Option Suggestions:

	* Have separate modules for Turfs and Atoms, this is not enforced, but it is how I have structured my nature example.
	* If your map doesn't look quite to your liking, simply jiggle with the variables on your modules and the type probabilities
	* You can mix and map premade areas with the procedural generation, for example mapping an entire flat land but having code generate just the grass tufts

Using the Modules list

	Simply think of it like each module is a layer in a graphics editing program!
	To help you do this templates such as /mapGeneratorModule/bottomLayer have been provided with appropriate default settings.
	These are located near the bottom of mapGeneratorModule.dm
	you would order your list left to right, top to bottom, e.g:
	modules = list(bottomLayer,nextLayer,nextNextLayer) etc.

Variable Breakdown (For Mappers):

	mapGenerator
		map - INTERNAL, do not touch
		modules - A list of typepaths of mapGeneratorModules

	mapGeneratorModule
		mother - INTERNAL, do not touch
		spawnableAtoms - A list of typepaths and their probability to spawn, eg: spawnableAtoms = list(/obj/structure/flora/tree/pine = 30)
		spawnableTurfs - A list of typepaths and their probability to spawn, eg: spawnableTurfs = list(/turf/unsimulated/floor/grass = 100)
		clusterMax - The max range to check for something being "too close" for this atom/turf to spawn, the true value is random between clusterMin and clusterMax
		clusterMin - The min range to check for something being "too close" for this atom/turf to spawn, the true value is random between clusterMin and clusterMax
		clusterCheckFlags - A Bitfield that controls how the cluster checks work, All based on clusterMin and clusterMax guides
		allowAtomsOnSpace - A Boolean for if we allow atoms to spawn on space tiles

		clusterCheckFlags flags:
			CLUSTER_CHECK_NONE	0 			   //No checks are done, cluster as much as possible
			CLUSTER_CHECK_DIFFERENT_TURFS	2  //Don't let turfs of DIFFERENT types cluster
			CLUSTER_CHECK_DIFFERENT_ATOMS	4  //Don't let atoms of DIFFERENT types cluster
			CLUSTER_CHECK_SAME_TURFS		8  //Don't let turfs of the SAME type cluster
			CLUSTER_CHECK_SAME_ATOMS		16 //Don't let atoms of the SAME type cluster

			CLUSTER_CHECK_SAMES				24 //Don't let any of the same type cluster
			CLUSTER_CHECK_DIFFERENTS		6  //Don't let any different types cluster
			CLUSTER_CHECK_ALL_TURFS			10 //Don't let ANY turfs cluster same and different types
			CLUSTER_CHECK_ALL_ATOMS			20 //Don't let ANY atoms cluster same and different types

			CLUSTER_CHECK_ALL				30 //Don't let anything cluster, like, at all

repo_name: AnalWerewolf/sundown

license: mit


code:
//clusterCheckFlags defines
//All based on clusterMin and clusterMax as guides

//Individual defines
#define CLUSTER_CHECK_NONE 0 //No checks are done, cluster as much as possible
#define CLUSTER_CHECK_DIFFERENT_TURFS (1<<1)  //Don't let turfs of DIFFERENT types cluster
#define CLUSTER_CHECK_DIFFERENT_ATOMS (1<<2)  //Don't let atoms of DIFFERENT types cluster
#define CLUSTER_CHECK_SAME_TURFS (1<<3)  //Don't let turfs of the SAME type cluster
#define CLUSTER_CHECK_SAME_ATOMS (1<<4) //Don't let atoms of the SAME type cluster

//Combined defines
#define CLUSTER_CHECK_SAMES 24 //Don't let any of the same type cluster
#define CLUSTER_CHECK_DIFFERENTS 6  //Don't let any of different types cluster
#define CLUSTER_CHECK_ALL_TURFS 10 //Don't let ANY turfs cluster same and different types
#define CLUSTER_CHECK_ALL_ATOMS 20 //Don't let ANY atoms cluster same and different types

//All
#define CLUSTER_CHECK_ALL 30 //Don't let anything cluster, like, at all

/datum/map_generator

	//Map information
	var/list/map = list()

	//mapGeneratorModule information
	var/list/modules = list()

	var/buildmode_name = "Undocumented"

/datum/map_generator/New()
	..()
	if(buildmode_name == "Undocumented")
		buildmode_name = copytext_char("[type]", 20) // / d a t u m / m a p g e n e r a t o r / = 20 characters.
	initialiseModules()

//Defines the region the map represents, sets map
//Returns the map
/datum/map_generator/proc/defineRegion(turf/Start, turf/End, replace = 0)
	if(!checkRegion(Start, End))
		return 0

	if(replace)
		undefineRegion()
	map |= block(Start,End)
	return map

//Defines the region the map represents, as a CIRCLE!, sets map
//Returns the map
/datum/map_generator/proc/defineCircularRegion(turf/Start, turf/End, replace = 0)
	if(!checkRegion(Start, End))
		return 0

	var/centerX = max(abs((End.x+Start.x)/2),1)
	var/centerY = max(abs((End.y+Start.y)/2),1)

	var/lilZ = min(Start.z,End.z)
	var/bigZ = max(Start.z,End.z)

	var/sphereMagic = max(abs(bigZ-(lilZ/2)),1) //Spherical maps! woo!

	var/radius = abs(max(centerX,centerY)) //take the biggest displacement as the radius

	if(replace)
		undefineRegion()

	//Even sphere correction engage
	var/offByOneOffset = 1
	if(bigZ % 2 == 0)
		offByOneOffset = 0

	for(var/i = lilZ, i <= bigZ+offByOneOffset, i++)
		var/theRadius = radius
		if(i != sphereMagic)
			theRadius = max(radius/max((2*abs(sphereMagic-i)),1),1)

		map |= circle_range(locate(centerX,centerY,i),theRadius)

	return map

//Empties the map list, he's dead jim.
/datum/map_generator/proc/undefineRegion()
	map = list() //bai bai

//Checks for and Rejects bad region coordinates
//Returns 1/0
/datum/map_generator/proc/checkRegion(turf/Start, turf/End)
	if(!Start || !End)
		return FALSE //Just bail

	if(Start.x > world.maxx || End.x > world.maxx)
		return FALSE
	if(Start.y > world.maxy || End.y > world.maxy)
		return FALSE
	if(Start.z > world.maxz || End.z > world.maxz)
		return FALSE
	return TRUE

//Requests the mapGeneratorModule(s) to (re)generate
/datum/map_generator/proc/generate()
	syncModules()
	if(!modules || !modules.len)
		return
	for(var/datum/map_generator_module/mod in modules)
		INVOKE_ASYNC(mod, /datum/map_generator_module.proc/generate)

//Requests the mapGeneratorModule(s) to (re)generate this one turf
/datum/map_generator/proc/generateOneTurf(turf/T)
	if(!T)
		return
	syncModules()
	if(!modules || !modules.len)
		return
	for(var/datum/map_generator_module/mod in modules)
		INVOKE_ASYNC(mod, /datum/map_generator_module.proc/place, T)

//Replaces all paths in the module list with actual module datums
/datum/map_generator/proc/initialiseModules()
	for(var/path in modules)
		if(ispath(path))
			modules.Remove(path)
			modules |= new path
	syncModules()

//Sync mapGeneratorModule(s) to mapGenerator
/datum/map_generator/proc/syncModules()
	for(var/datum/map_generator_module/mod in modules)
		mod.sync(src)

///////////////////////////
// HERE BE DEBUG DRAGONS //
///////////////////////////

/client/proc/debugNatureMapGenerator()
	set name = "Test Nature Map Generator"
	set category = "Debug"

	var/datum/map_generator/nature/N = new()
	var/startInput = input(usr,"Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text|null

	if (isnull(startInput))
		return

	var/endInput = input(usr,"End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text|null

	if (isnull(endInput))
		return

	//maxx maxy and current z so that if you fuck up, you only fuck up one entire z level instead of the entire universe
	if(!startInput || !endInput)
		to_chat(src, "Missing Input")
		return

	var/list/startCoords = splittext(startInput, ";")
	var/list/endCoords = splittext(endInput, ";")
	if(!startCoords || !endCoords)
		to_chat(src, "Invalid Coords")
		to_chat(src, "Start Input: [startInput]")
		to_chat(src, "End Input: [endInput]")
		return

	var/turf/Start = locate(text2num(startCoords[1]),text2num(startCoords[2]),text2num(startCoords[3]))
	var/turf/End = locate(text2num(endCoords[1]),text2num(endCoords[2]),text2num(endCoords[3]))
	if(!Start || !End)
		to_chat(src, "Invalid Turfs")
		to_chat(src, "Start Coords: [startCoords[1]] - [startCoords[2]] - [startCoords[3]]")
		to_chat(src, "End Coords: [endCoords[1]] - [endCoords[2]] - [endCoords[3]]")
		return

	var/list/clusters = list("None"=CLUSTER_CHECK_NONE,"All"=CLUSTER_CHECK_ALL,"Sames"=CLUSTER_CHECK_SAMES,"Differents"=CLUSTER_CHECK_DIFFERENTS, \
	"Same turfs"=CLUSTER_CHECK_SAME_TURFS, "Same atoms"=CLUSTER_CHECK_SAME_ATOMS, "Different turfs"=CLUSTER_CHECK_DIFFERENT_TURFS, \
	"Different atoms"=CLUSTER_CHECK_DIFFERENT_ATOMS, "All turfs"=CLUSTER_CHECK_ALL_TURFS,"All atoms"=CLUSTER_CHECK_ALL_ATOMS)

	var/moduleClusters = input("Cluster Flags (Cancel to leave unchanged from defaults)","Map Gen Settings") as null|anything in clusters
	//null for default

	var/theCluster = 0
	if(moduleClusters != "None")
		if(!clusters[moduleClusters])
			to_chat(src, "Invalid Cluster Flags")
			return
		theCluster = clusters[moduleClusters]
	else
		theCluster =  CLUSTER_CHECK_NONE

	if(theCluster)
		for(var/datum/map_generator_module/M in N.modules)
			M.clusterCheckFlags = theCluster

	to_chat(src, "Defining Region")
	N.defineRegion(Start, End)
	to_chat(src, "Region Defined")
	to_chat(src, "Generating Region")
	N.generate()
	to_chat(src, "Generated Region")


code:
/datum/map_generator_module
	var/datum/map_generator/mother = null
	var/list/spawnableAtoms = list()
	var/list/spawnableTurfs = list()
	var/clusterMax = 5
	var/clusterMin = 1
	var/clusterCheckFlags = CLUSTER_CHECK_SAME_ATOMS
	var/allowAtomsOnSpace = FALSE

//Syncs the module up with its mother
/datum/map_generator_module/proc/sync(datum/map_generator/mum)
	mother = null
	if(mum)
		mother = mum

//Generates its spawnable atoms and turfs
/datum/map_generator_module/proc/generate()
	if(!mother)
		return
	var/list/map = mother.map
	for(var/turf/T in map)
		place(T)

//Place a spawnable atom or turf on this turf
/datum/map_generator_module/proc/place(turf/T)
	if(!T)
		return 0

	var/clustering = 0
	var/skipLoopIteration = FALSE

	//Turfs don't care whether atoms can be placed here
	for(var/turfPath in spawnableTurfs)

		//Clustering!
		if(clusterMax && clusterMin)

			//You're the same as me? I hate you I'm going home
			if(clusterCheckFlags & CLUSTER_CHECK_SAME_TURFS)
				clustering = rand(clusterMin,clusterMax)
				for(var/turf/F in RANGE_TURFS(clustering,T))
					if(istype(F,turfPath))
						skipLoopIteration = TRUE
						break
				if(skipLoopIteration)
					skipLoopIteration = FALSE
					continue

			//You're DIFFERENT to me? I hate you I'm going home
			if(clusterCheckFlags & CLUSTER_CHECK_DIFFERENT_TURFS)
				clustering = rand(clusterMin,clusterMax)
				for(var/turf/F in RANGE_TURFS(clustering,T))
					if(!(istype(F,turfPath)))
						skipLoopIteration = TRUE
						break
				if(skipLoopIteration)
					skipLoopIteration = FALSE
					continue

		//Success!
		if(prob(spawnableTurfs[turfPath]))
			T.ChangeTurf(turfPath)

	//Atoms DO care whether atoms can be placed here
	if(checkPlaceAtom(T))

		for(var/atomPath in spawnableAtoms)

			//Clustering!
			if(clusterMax && clusterMin)

				//You're the same as me? I hate you I'm going home
				if(clusterCheckFlags & CLUSTER_CHECK_SAME_ATOMS)
					clustering = rand(clusterMin, clusterMax)
					for(var/atom/movable/M in range(clustering,T))
						if(istype(M,atomPath))
							skipLoopIteration = TRUE
							break
					if(skipLoopIteration)
						skipLoopIteration = FALSE
						continue

				//You're DIFFERENT from me? I hate you I'm going home
				if(clusterCheckFlags & CLUSTER_CHECK_DIFFERENT_ATOMS)
					clustering = rand(clusterMin, clusterMax)
					for(var/atom/movable/M in range(clustering,T))
						if(!(istype(M,atomPath)))
							skipLoopIteration = TRUE
							break
					if(skipLoopIteration)
						skipLoopIteration = FALSE
						continue

			//Success!
			if(prob(spawnableAtoms[atomPath]))
				new atomPath(T)

	. = 1

//Checks and Rejects dense turfs
/datum/map_generator_module/proc/checkPlaceAtom(turf/T)
	if(!T)
		return FALSE
	if(T.density)
		return FALSE
	for(var/atom/A in T)
		if(A.density)
			return FALSE
	if(!allowAtomsOnSpace && (isspaceturf(T)))
		return FALSE
	return TRUE

///////////////////////////////////////////////////////////
//                 PREMADE BASE TEMPLATES                //
//           Appropriate settings for usable types       //
// Not usable types themselves, use them as parent types //
// Seriously, don't use these on their own, just parents //
///////////////////////////////////////////////////////////
//The /atom and /turf examples are just so these compile, replace those with your typepaths in your subtypes.

//Settings appropriate for a turf that covers the entire map region, eg a fill colour on a bottom layer in a graphics program.
//Should only have one of these in your mapGenerator unless you want to waste CPU
/datum/map_generator_module/bottom_layer
	clusterCheckFlags = CLUSTER_CHECK_NONE
	spawnableAtoms = list()//Recommended: No atoms.
	spawnableTurfs = list(/turf = 100)

//Settings appropriate for turfs/atoms that cover SOME of the map region, sometimes referred to as a splatter layer.
/datum/map_generator_module/splatter_layer
	clusterCheckFlags = CLUSTER_CHECK_ALL
	spawnableAtoms = list(/atom = 30)
	spawnableTurfs = list(/turf = 30)

//Settings appropriate for turfs/atoms that cover a lot of the map region, eg a dense forest.
/datum/map_generator_module/dense_layer
	clusterCheckFlags = CLUSTER_CHECK_NONE
	spawnableAtoms = list(/atom = 75)
	spawnableTurfs = list(/turf = 75)


code:
//Helper Modules

// Helper to repressurize the area in case it was run in space
/datum/map_generator_module/bottom_layer/repressurize
	spawnableAtoms = list()
	spawnableTurfs = list()

/datum/map_generator_module/bottom_layer/repressurize/generate()
	if(!mother)
		return
	var/list/map = mother.map
	for(var/turf/T in map)
		SSair.remove_from_active(T)
	for(var/turf/open/T in map)
		if(T.air)
			T.air.copy_from_turf(T)
		SSair.add_to_active(T, TRUE)

/datum/map_generator_module/bottom_layer/massdelete
	spawnableAtoms = list()
	spawnableTurfs = list()
	var/deleteturfs = TRUE //separate var for the empty type.
	var/list/ignore_typecache

/datum/map_generator_module/bottom_layer/massdelete/generate()
	if(!mother)
		return
	for(var/V in mother.map)
		var/turf/T = V
		T.empty(deleteturfs? null : T.type, null, ignore_typecache, CHANGETURF_FORCEOP)

/datum/map_generator_module/bottom_layer/massdelete/no_delete_mobs/New()
	..()
	ignore_typecache = GLOB.typecache_mob

/datum/map_generator_module/bottom_layer/massdelete/leave_turfs
	deleteturfs = FALSE

/datum/map_generator_module/bottom_layer/massdelete/regeneration_delete
	deleteturfs = FALSE

/datum/map_generator_module/bottom_layer/massdelete/regeneration_delete/New()
	..()
	ignore_typecache = GLOB.typecache_mob

//Only places atoms/turfs on area borders
/datum/map_generator_module/border
	clusterCheckFlags = CLUSTER_CHECK_NONE

/datum/map_generator_module/border/generate()
	if(!mother)
		return
	var/list/map = mother.map
	for(var/turf/T in map)
		if(is_border(T))
			place(T)

/datum/map_generator_module/border/proc/is_border(turf/T)
	for(var/direction in list(SOUTH,EAST,WEST,NORTH))
		if (get_step(T,direction) in mother.map)
			continue
		return 1
	return 0

/datum/map_generator/repressurize
	modules = list(/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Block: Restore Roundstart Air Contents"

/datum/map_generator/massdelete
	modules = list(/datum/map_generator_module/bottom_layer/massdelete)
	buildmode_name = "Block: Full Mass Deletion"

/datum/map_generator/massdelete/nomob
	modules = list(/datum/map_generator_module/bottom_layer/massdelete/no_delete_mobs)
	buildmode_name = "Block: Mass Deletion - Leave Mobs"

/datum/map_generator/massdelete/noturf
	modules = list(/datum/map_generator_module/bottom_layer/massdelete/leave_turfs)
	buildmode_name = "Block: Mass Deletion - Leave Turfs"

/datum/map_generator/massdelete/regen
	modules = list(/datum/map_generator_module/bottom_layer/massdelete/regeneration_delete)
	buildmode_name = "Block: Mass Deletion - Leave Mobs and Turfs"


code:
//Contents exist primarily for the nature generator test type.

//Pine Trees
/datum/map_generator_module/pine_trees
	spawnableAtoms = list(/obj/structure/flora/tree/pine = 30)

//Dead Trees
/datum/map_generator_module/dead_trees
	spawnableAtoms = list(/obj/structure/flora/tree/dead = 10)

//Random assortment of bushes
/datum/map_generator_module/rand_bushes
	spawnableAtoms = list()

/datum/map_generator_module/rand_bushes/New()
	..()
	spawnableAtoms = typesof(/obj/structure/flora/ausbushes)
	for(var/i in spawnableAtoms)
		spawnableAtoms[i] = 20

//Random assortment of rocks and rockpiles
/datum/map_generator_module/rand_rocks
	spawnableAtoms = list(/obj/structure/flora/rock = 40, /obj/structure/flora/rock/pile = 20)

//Grass turfs
/datum/map_generator_module/bottom_layer/grass_turfs
	spawnableTurfs = list(/turf/open/floor/grass = 100)

//Grass tufts with a high spawn chance
/datum/map_generator_module/dense_layer/grass_tufts
	spawnableTurfs = list()
	spawnableAtoms = list(/obj/structure/flora/ausbushes/grassybush = 75)


code:
/obj/effect/landmark/map_generator
	var/startTurfX = 0
	var/startTurfY = 0
	var/startTurfZ = -1
	var/endTurfX = 0
	var/endTurfY = 0
	var/endTurfZ = -1
	var/mapGeneratorType = /datum/map_generator/nature
	var/datum/map_generator/mapGenerator

/obj/effect/landmark/map_generator/New()
	..()
	if(startTurfZ < 0)
		startTurfZ = z
	if(endTurfZ < 0)
		endTurfZ = z
	mapGenerator = new mapGeneratorType()
	mapGenerator.defineRegion(locate(startTurfX,startTurfY,startTurfZ), locate(endTurfX,endTurfY,endTurfZ))
	mapGenerator.generate()


code:
//Asteroid turfs
/datum/map_generator_module/bottom_layer/asteroid_turfs
	spawnableTurfs = list(/turf/open/floor/plating/asteroid = 100)

/datum/map_generator_module/bottom_layer/asteroid_walls
	spawnableTurfs = list(/turf/closed/mineral = 100)

//Border walls
/datum/map_generator_module/border/asteroid_walls
	spawnableAtoms = list()
	spawnableTurfs = list(/turf/closed/mineral = 100)

//Random walls
/datum/map_generator_module/splatter_layer/asteroid_walls
	clusterCheckFlags = CLUSTER_CHECK_NONE
	spawnableAtoms = list()
	spawnableTurfs = list(/turf/closed/mineral = 30)

//Monsters
/datum/map_generator_module/splatter_layer/asteroid_monsters
	spawnableTurfs = list()
	spawnableAtoms = list(/mob/living/simple_animal/hostile/asteroid/basilisk = 10, \
		/mob/living/simple_animal/hostile/asteroid/hivelord = 10, \
		/mob/living/simple_animal/hostile/asteroid/goliath = 10)

// GENERATORS

/datum/map_generator/asteroid/hollow
	modules = list(/datum/map_generator_module/bottom_layer/asteroid_turfs, \
		/datum/map_generator_module/border/asteroid_walls)
	buildmode_name = "Pattern: Asteroid Room \[AIRLESS!\]"

/datum/map_generator/asteroid/hollow/random
	modules = list(/datum/map_generator_module/bottom_layer/asteroid_turfs, \
		/datum/map_generator_module/border/asteroid_walls, \
		/datum/map_generator_module/splatter_layer/asteroid_walls)
	buildmode_name = "Pattern: Asteroid Room: Splatter Walls \[AIRLESS!\]"

/datum/map_generator/asteroid/hollow/random/monsters
	modules = list(/datum/map_generator_module/bottom_layer/asteroid_turfs, \
		/datum/map_generator_module/border/asteroid_walls, \
		/datum/map_generator_module/splatter_layer/asteroid_walls, \
		/datum/map_generator_module/splatter_layer/asteroid_monsters)
	buildmode_name = "Pattern: Asteroid Room: Splatter Walls + Monsters \[AIRLESS!\]"

/datum/map_generator/asteroid/filled
	modules = list(/datum/map_generator_module/bottom_layer/asteroid_walls)
	buildmode_name = "Block: Asteroid Walls"


code:
// Very Simple Cellular Automata generators
// Mostly for better caves
// Should probably move these up and refactor modules so these can be mixed with other ones

/datum/map_generator/ca
	var/list/b_rule = list() // 0 -> 1, 1 -> 1
	var/list/s_rule = list() // 0 -> 0, 1 -> 1
	var/iterations = 1
	var/loop_edges = 0
	var/edge_value = 1 //if loop_edges = 0
	var/list/old_state
	var/list/current_state
	var/width = 10
	var/height = 10
	var/list/type_map = list(/turf/open/floor/plating, /turf/closed/wall)
	var/turf/start = null

/datum/map_generator/ca/defineRegion(turf/Start, turf/End, replace = 0)
	. = ..()

	var/min_x = min(Start.x,End.x)
	var/max_x = max(Start.x,End.x)
	var/min_y = min(Start.y,End.y)
	var/max_y = max(Start.y,End.y)
	width = max_x - min_x
	height = max_y - min_y

	//We assume 2D everywhere anyway
	start = locate(min_x,min_y,Start.z)

/datum/map_generator/ca/proc/initialize()
	old_state = new/list(width)
	for(var/i = 1,i<=width,i++)
		old_state[i] = new/list(height)
		for(var/j = 1,j<=height,j++)
			old_state[i][j] = rand(0,1)

	current_state = old_state.Copy()

/datum/map_generator/ca/generate()
	//Abandon all hope for efficency all who enter here
	//Maybe some less basic implemetation later, but this is just simple admin tool
	initialize()

	for(var/generation = 0,generation<iterations,generation++)
		for(var/i = 1,i<=width,i++)
			for(var/j = 1,j<=height,j++)
				current_state[i][j] = apply_rule(i,j)
		//copy state over
		old_state = current_state.Copy()

	for(var/i=1,i<=width,i++)
		for(var/j=1,j<=height,j++)
			var/turf/T = locate(start.x+i-1,start.y+j-1,start.z)
			if(T)
				T.ChangeTurf(type_map[current_state[i][j]+1])

/datum/map_generator/ca/proc/apply_rule(i,j)
	var/value = 0
	for(var/dx=-1,dx<=1,dx++)
		for(var/dy=-1,dy<=1,dy++)
			var/n_x = i+dx
			var/n_y = j+dy
			if(n_x < 1 || n_x > width || n_y <1 || n_y > height)
				if(loop_edges)
					if(n_x < 1)
						n_x = width
					else if(n_x > width)
						n_x = 1
					if(n_y < 1)
						n_y = height
					else if(n_y > height)
						n_y = 1
				else
					value += edge_value
					continue
			value += old_state[n_x][n_y]
	value -= old_state[i][j]

	if(value in b_rule)
		return 1
	if(value in s_rule)
		return old_state[i][j]
	return 0

/datum/map_generator/ca/caves
	b_rule = list(5,6,7,8)
	s_rule = list(4)
	type_map = list(/turf/open/floor/plating/asteroid/basalt, /turf/closed/mineral/volcanic)
	iterations = 5

/datum/map_generator/ca/maze
	b_rule = list(3)
	s_rule = list(1,2,3,4,5)
	iterations = 20
	edge_value = 0


code:
/datum/map_generator_module/bottom_layer/cult_floor
	spawnableTurfs = list(/turf/open/floor/engine/cult = 100)

/datum/map_generator_module/border/cult_walls
	spawnableTurfs = list(/turf/closed/wall/mineral/cult = 100)

/datum/map_generator/cult //walls and floor only
	modules = list(/datum/map_generator_module/bottom_layer/cult_floor, \
		/datum/map_generator_module/border/cult_walls, \
		/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Pattern: Cult Room"

/datum/map_generator/cult/floor //floors only
	modules = list(/datum/map_generator_module/bottom_layer/cult_floor, \
		/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Block: Cult Floor"


code:
/datum/map_generator/lavaland
	var/start_z
	var/min_x = 0
	var/min_y = 0
	var/max_x = 0
	var/max_y = 0
	modules = list(/datum/map_generator_module/river)
	buildmode_name = "Pattern: Lava Rivers"

/datum/map_generator/lavaland/defineRegion(turf/Start, turf/End, replace = 0)
	start_z = Start.z
	min_x = min(Start.x,End.x)
	min_y = min(Start.y,End.y)
	max_x = max(Start.x,End.x)
	max_y = max(Start.y,End.y)
	..()

/datum/map_generator_module/river
	var/river_type = /turf/open/lava/smooth
	var/river_nodes = 4

/datum/map_generator_module/river/generate()
	var/datum/map_generator/lavaland/L = mother
	if(!istype(L))
		return
	spawn_rivers(L.start_z, river_nodes, river_type, min_x = L.min_x, min_y = L.min_y, max_x = L.max_x, max_y = L.max_y)


code:
/datum/map_generator_module/bottom_layer/lavaland_default
	spawnableTurfs = list(/turf/open/floor/plating/asteroid/basalt/lava_land_surface = 100)

/datum/map_generator_module/bottom_layer/lavaland_mineral
	spawnableTurfs = list(/turf/closed/mineral/random/volcanic = 100)

/datum/map_generator_module/bottom_layer/lavaland_mineral/dense
	spawnableTurfs = list(/turf/closed/mineral/random/high_chance/volcanic = 100)

/datum/map_generator_module/splatter_layer/lavaland_monsters
	spawnableTurfs = list()
	spawnableAtoms = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast = 10,
	/mob/living/simple_animal/hostile/asteroid/hivelord/legion = 10,
	/mob/living/simple_animal/hostile/asteroid/basilisk/watcher = 10)

/datum/map_generator_module/splatter_layer/lavaland_tendrils
	spawnableTurfs = list()
	spawnableAtoms = list(/obj/structure/spawner/lavaland = 5,
	/obj/structure/spawner/lavaland/legion = 5,
	/obj/structure/spawner/lavaland/goliath = 5)

/datum/map_generator/lavaland/ground_only
	modules = list(/datum/map_generator_module/bottom_layer/lavaland_default)
	buildmode_name = "Block: Lavaland Floor"

/datum/map_generator/lavaland/dense_ores
	modules = list(/datum/map_generator_module/bottom_layer/lavaland_mineral/dense)
	buildmode_name = "Block: Lavaland Ores: Dense"

/datum/map_generator/lavaland/normal_ores
	modules = list(/datum/map_generator_module/bottom_layer/lavaland_mineral)
	buildmode_name = "Block: Lavaland Ores"


code:
//Exists primarily as a test type.

/datum/map_generator/nature
	modules = list(/datum/map_generator_module/bottom_layer/grass_turfs, \
	/datum/map_generator_module/pine_trees, \
	/datum/map_generator_module/dead_trees, \
	/datum/map_generator_module/rand_bushes, \
	/datum/map_generator_module/rand_rocks, \
	/datum/map_generator_module/dense_layer/grass_tufts)
	buildmode_name = "Pattern: Nature"


code:
/datum/map_generator_module/bottom_layer/repair_floor_plasteel
	spawnableTurfs = list(/turf/open/floor/iron = 100)
	var/ignore_wall = FALSE
	allowAtomsOnSpace = TRUE

/datum/map_generator_module/bottom_layer/repair_floor_plasteel/place(turf/T)
	if(isclosedturf(T) && !ignore_wall)
		return FALSE
	return ..()

/datum/map_generator_module/bottom_layer/repair_floor_plasteel/flatten
	ignore_wall = TRUE

/datum/map_generator_module/border/normal_walls
	spawnableAtoms = list()
	spawnableTurfs = list(/turf/closed/wall = 100)
	allowAtomsOnSpace = TRUE

/datum/map_generator_module/reload_station_map/generate()
	if(!istype(mother, /datum/map_generator/repair/reload_station_map))
		return
	var/datum/map_generator/repair/reload_station_map/mother1 = mother
	GLOB.reloading_map = TRUE
	// This is kind of finicky on multi-Z maps but the reader would need to be
	// changed to allow Z cropping and that's a mess
	var/z_offset = SSmapping.station_start
	var/list/bounds
	for (var/path in SSmapping.config.GetFullMapPaths())
		var/datum/parsed_map/parsed = load_map(file(path), 1, 1, z_offset, measureOnly = FALSE, no_changeturf = FALSE, cropMap=TRUE, x_lower = mother1.x_low, y_lower = mother1.y_low, x_upper = mother1.x_high, y_upper = mother1.y_high)
		bounds = parsed?.bounds
		z_offset += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1

	var/list/obj/machinery/atmospherics/atmos_machines = list()
	var/list/obj/structure/cable/cables = list()
	var/list/atom/atoms = list()

	repopulate_sorted_areas()

	for(var/L in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], SSmapping.station_start),
						locate(bounds[MAP_MAXX], bounds[MAP_MAXY], z_offset - 1)))
		set waitfor = FALSE
		var/turf/B = L
		atoms += B
		for(var/A in B)
			atoms += A
			if(istype(A,/obj/structure/cable))
				cables += A
				continue
			if(istype(A,/obj/machinery/atmospherics))
				atmos_machines += A

	SSatoms.InitializeAtoms(atoms)
	SSmachines.setup_template_powernets(cables)
	SSair.setup_template_machinery(atmos_machines)
	GLOB.reloading_map = FALSE

/datum/map_generator/repair
	modules = list(/datum/map_generator_module/bottom_layer/repair_floor_plasteel,
	/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Repair: Floor"

/datum/map_generator/repair/delete_walls
	modules = list(/datum/map_generator_module/bottom_layer/repair_floor_plasteel/flatten,
	/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Repair: Floor: Flatten Walls"

/datum/map_generator/repair/enclose_room
	modules = list(/datum/map_generator_module/bottom_layer/repair_floor_plasteel/flatten,
	/datum/map_generator_module/border/normal_walls,
	/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Repair: Generate Aired Room"

/datum/map_generator/repair/reload_station_map
	modules = list(/datum/map_generator_module/bottom_layer/massdelete/no_delete_mobs)
	var/x_low = 0
	var/x_high = 0
	var/y_low = 0
	var/y_high = 0
	var/z = 0
	var/cleanload = FALSE
	var/datum/map_generator_module/reload_station_map/loader
	buildmode_name = "Repair: Reload Block \[DO NOT USE\]"

/datum/map_generator/repair/reload_station_map/clean
	buildmode_name = "Repair: Reload Block - Mass Delete"
	cleanload = TRUE

/datum/map_generator/repair/reload_station_map/clean/in_place
	modules = list(/datum/map_generator_module/bottom_layer/massdelete/regeneration_delete)
	buildmode_name = "Repair: Reload Block - Mass Delete - In Place"

/datum/map_generator/repair/reload_station_map/defineRegion(turf/start, turf/end)
	. = ..()
	if(!is_station_level(start.z) || !is_station_level(end.z))
		return
	x_low = min(start.x, end.x)
	y_low = min(start.y, end.y)
	x_high = max(start.x, end.x)
	y_high = max(start.y, end.y)
	z = SSmapping.station_start

GLOBAL_VAR_INIT(reloading_map, FALSE)

/datum/map_generator/repair/reload_station_map/generate(clean = cleanload)
	if(!loader)
		loader = new
	if(cleanload)
		..() //Trigger mass deletion.
	modules |= loader
	syncModules()
	loader.generate()


code:
/datum/map_generator_module/bottom_layer/shuttle_floor
	spawnableTurfs = list(/turf/open/floor/mineral/titanium = 100)

/datum/map_generator_module/border/shuttle_walls
	spawnableTurfs = list(/turf/closed/wall/mineral/titanium = 100)
// Generators

/datum/map_generator/shuttle/full
	modules = list(/datum/map_generator_module/bottom_layer/shuttle_floor, \
		/datum/map_generator_module/border/shuttle_walls,\
		/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Pattern: Shuttle Room"

/datum/map_generator/shuttle/floor
	modules = list(/datum/map_generator_module/bottom_layer/shuttle_floor)
	buildmode_name = "Block: Shuttle Floor"


code:
// Modules

/datum/map_generator_module/bottom_layer/syndie_floor
	spawnableTurfs = list(/turf/open/floor/mineral/plastitanium/red = 100)

/datum/map_generator_module/border/syndie_walls
	spawnableAtoms = list()
	spawnableTurfs = list(/turf/closed/wall/r_wall/syndicate = 100)

/datum/map_generator_module/syndie_furniture
	clusterCheckFlags = CLUSTER_CHECK_ALL
	spawnableTurfs = list()
	spawnableAtoms = list(/obj/structure/table = 20,/obj/structure/chair = 15,/obj/structure/chair/stool = 10, \
		/obj/structure/frame/computer = 15, /obj/item/storage/toolbox/syndicate = 15 ,\
		/obj/structure/closet/syndicate = 25, /obj/machinery/suit_storage_unit/syndicate = 15)

/datum/map_generator_module/splatter_layer/syndie_mobs
	spawnableAtoms = list(/mob/living/simple_animal/hostile/syndicate = 30, \
		/mob/living/simple_animal/hostile/syndicate/melee = 20, \
		/mob/living/simple_animal/hostile/syndicate/ranged = 20, \
		/mob/living/simple_animal/hostile/viscerator = 30)
	spawnableTurfs = list()

// Generators

/datum/map_generator/syndicate/empty //walls and floor only
	modules = list(/datum/map_generator_module/bottom_layer/syndie_floor, \
		/datum/map_generator_module/border/syndie_walls,\
		/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Pattern: Shuttle Room: Syndicate"

/datum/map_generator/syndicate/mobsonly
	modules = list(/datum/map_generator_module/bottom_layer/syndie_floor, \
		/datum/map_generator_module/border/syndie_walls,\
		/datum/map_generator_module/splatter_layer/syndie_mobs, \
		/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Pattern: Shuttle Room: Syndicate: Mobs"

/datum/map_generator/syndicate/furniture
	modules = list(/datum/map_generator_module/bottom_layer/syndie_floor, \
		/datum/map_generator_module/border/syndie_walls,\
		/datum/map_generator_module/syndie_furniture, \
		/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Pattern: Shuttle Room: Syndicate: Furniture"

/datum/map_generator/syndicate/full
	modules = list(/datum/map_generator_module/bottom_layer/syndie_floor, \
		/datum/map_generator_module/border/syndie_walls,\
		/datum/map_generator_module/syndie_furniture, \
		/datum/map_generator_module/splatter_layer/syndie_mobs, \
		/datum/map_generator_module/bottom_layer/repressurize)
	buildmode_name = "Pattern: Shuttle Room: Syndicate: All"


code:
/obj/item/ammo_casing
	name = "bullet casing"
	desc = "A bullet casing."
	icon = 'icons/obj/guns/ammo.dmi'
	icon_state = "s-casing"
	worn_icon_state = "bullet"
	flags_1 = CONDUCT_1
	slot_flags = ITEM_SLOT_BELT
	throwforce = 0
	w_class = WEIGHT_CLASS_TINY
	custom_materials = list(/datum/material/iron = 500)
	override_notes = TRUE
	///What sound should play when this ammo is fired
	var/fire_sound = null
	///Which kind of guns it can be loaded into
	var/caliber = null
	///The bullet type to create when New() is called
	var/projectile_type = null
	///the loaded projectile in this ammo casing
	var/obj/projectile/loaded_projectile = null
	///Pellets for spreadshot
	var/pellets = 1
	///Variance for inaccuracy fundamental to the casing
	var/variance = 0
	///Randomspread for automatics
	var/randomspread = 0
	///Delay for energy weapons
	var/delay = 0
	///Override this to make your gun have a faster fire rate, in tenths of a second. 4 is the default gun cooldown.
	var/click_cooldown_override = 0
	///the visual effect appearing when the ammo is fired.
	var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect
	var/heavy_metal = TRUE
	///pacifism check for boolet, set to FALSE if bullet is non-lethal
	var/harmful = TRUE

/obj/item/ammo_casing/spent
	name = "spent bullet casing"
	loaded_projectile = null

/obj/item/ammo_casing/Initialize(mapload)
	. = ..()
	if(projectile_type)
		loaded_projectile = new projectile_type(src)
	pixel_x = base_pixel_x + rand(-10, 10)
	pixel_y = base_pixel_y + rand(-10, 10)
	setDir(pick(GLOB.alldirs))
	update_appearance()

/obj/item/ammo_casing/Destroy()
	var/turf/T = get_turf(src)
	if(T && !loaded_projectile && is_station_level(T.z))
		SSblackbox.record_feedback("tally", "station_mess_destroyed", 1, name)
	QDEL_NULL(loaded_projectile)
	return ..()

/obj/item/ammo_casing/add_weapon_description()
	AddElement(/datum/element/weapon_description, attached_proc = .proc/add_notes_ammo)

/**
 *
 * Outputs type-specific weapon stats for ammunition based on the projectile loaded inside the casing.
 * Distinguishes between critting and stam-critting in separate lines
 *
 */
/obj/item/ammo_casing/proc/add_notes_ammo()
	// Try to get a projectile to derive stats from
	var/obj/projectile/exam_proj = GLOB.proj_by_path_key[projectile_type]
	if(!istype(exam_proj) || pellets == 0)
		return

	var/list/readout = list()
	// No dividing by 0
	if(exam_proj.damage > 0)
		readout += "Most monkeys our legal team subjected to these [span_warning(caliber)] rounds succumbed to their wounds after [span_warning("[HITS_TO_CRIT(exam_proj.damage * pellets)] shot\s")] at point-blank, taking [span_warning("[pellets] shot\s")] per round"
	if(exam_proj.stamina > 0)
		readout += "[!readout.len ? "Most monkeys" : "More fortunate monkeys"] collapsed from exhaustion after [span_warning("[HITS_TO_CRIT(exam_proj.stamina * pellets)] impact\s")] of these [span_warning("[caliber]")] rounds"
	if(!readout.len) // Everything else failed, give generic text
		return "Our legal team has determined the offensive nature of these [span_warning(caliber)] rounds to be esoteric"
	return readout.Join("\n") // Sending over a single string, rather than the whole list

/obj/item/ammo_casing/update_icon_state()
	icon_state = "[initial(icon_state)][loaded_projectile ? "-live" : null]"
	return ..()

/obj/item/ammo_casing/update_desc()
	desc = "[initial(desc)][loaded_projectile ? null : " This one is spent."]"
	return ..()

/*
 * On accidental consumption, 'spend' the ammo, and add in some gunpowder
 */
/obj/item/ammo_casing/on_accidental_consumption(mob/living/carbon/victim, mob/living/carbon/user, obj/item/source_item,  discover_after = TRUE)
	if(loaded_projectile)
		loaded_projectile = null
		update_appearance()
		victim.reagents?.add_reagent(/datum/reagent/gunpowder, 3)
		source_item?.reagents?.add_reagent(/datum/reagent/gunpowder, source_item.reagents.total_volume*(2/3))

	return ..()

//proc to magically refill a casing with a new projectile
/obj/item/ammo_casing/proc/newshot() //For energy weapons, syringe gun, shotgun shells and wands (!).
	if(!loaded_projectile)
		loaded_projectile = new projectile_type(src, src)

/obj/item/ammo_casing/attackby(obj/item/I, mob/user, params)
	//SEPTIC EDIT BEGIN
	/* This is the best way I can think of without replacing the entire proc.
	if(istype(I, /obj/item/ammo_box))
	*/
	if(istype(I, /obj/item/ammo_box) && !istype(I, /obj/item/ammo_box/magazine/ammo_stack))
	//SEPTIC EDIT END
		var/obj/item/ammo_box/box = I
		if(isturf(loc))
			var/boolets = 0
			for(var/obj/item/ammo_casing/bullet in loc)
				if (box.stored_ammo.len >= box.max_ammo)
					break
				if (bullet.loaded_projectile)
					if (box.give_round(bullet, 0))
						boolets++
				else
					continue
			if (boolets > 0)
				box.update_appearance()
				to_chat(user, span_notice("You collect [boolets] shell\s. [box] now contains [box.stored_ammo.len] shell\s."))
			else
				to_chat(user, span_warning("You fail to collect anything!"))
	else
		return ..()

/obj/item/ammo_casing/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
	bounce_away(FALSE, NONE)
	return ..()

/obj/item/ammo_casing/proc/bounce_away(still_warm = FALSE, bounce_delay = 3)
	if(!heavy_metal)
		return
	update_appearance()
	SpinAnimation(10, 1)
	var/turf/T = get_turf(src)
	if(still_warm && T?.bullet_sizzle)
		addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/items/welder.ogg', 20, 1), bounce_delay) //If the turf is made of water and the shell casing is still hot, make a sizzling sound when it's ejected.
	else if(T?.bullet_bounce_sound)
		addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, T.bullet_bounce_sound, 20, 1), bounce_delay) //Soft / non-solid turfs that shouldn't make a sound when a shell casing is ejected over them.


code:
/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
	distro += variance
	var/targloc = get_turf(target)
	ready_proj(target, user, quiet, zone_override, fired_from)
	if(pellets == 1)
		if(distro) //We have to spread a pixel-precision bullet. throw_proj was called before so angles should exist by now...
			if(randomspread)
				spread = round((rand() - 0.5) * distro)
			else //Smart spread
				spread = round(1 - 0.5) * distro
		if(!throw_proj(target, targloc, user, params, spread))
			return FALSE
	else
		if(isnull(loaded_projectile))
			return FALSE
		AddComponent(/datum/component/pellet_cloud, projectile_type, pellets)
		SEND_SIGNAL(src, COMSIG_PELLET_CLOUD_INIT, target, user, fired_from, randomspread, spread, zone_override, params, distro)

	if(click_cooldown_override)
		user.changeNext_move(click_cooldown_override)
	else
		user.changeNext_move(CLICK_CD_RANGE)
	user.newtonian_move(get_dir(target, user))
	update_appearance()
	return TRUE

/obj/item/ammo_casing/proc/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", atom/fired_from)
	if (!loaded_projectile)
		return
	loaded_projectile.original = target
	loaded_projectile.firer = user
	loaded_projectile.fired_from = fired_from
	loaded_projectile.hit_prone_targets = user.combat_mode
	if (zone_override)
		loaded_projectile.def_zone = zone_override
	else
		loaded_projectile.def_zone = user.zone_selected
	loaded_projectile.suppressed = quiet

	if(isgun(fired_from))
		var/obj/item/gun/G = fired_from
		loaded_projectile.damage *= G.projectile_damage_multiplier
		loaded_projectile.stamina *= G.projectile_damage_multiplier

	if(reagents && loaded_projectile.reagents)
		reagents.trans_to(loaded_projectile, reagents.total_volume, transfered_by = user) //For chemical darts/bullets
		qdel(reagents)

/obj/item/ammo_casing/proc/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread)
	var/turf/curloc = get_turf(user)
	if (!istype(targloc) || !istype(curloc) || !loaded_projectile)
		return FALSE

	var/firing_dir
	if(loaded_projectile.firer)
		firing_dir = loaded_projectile.firer.dir
	if(!loaded_projectile.suppressed && firing_effect_type)
		new firing_effect_type(get_turf(src), firing_dir)

	var/direct_target
	if(targloc == curloc)
		if(target) //if the target is right on our location we'll skip the travelling code in the proj's fire()
			direct_target = target
	if(!direct_target)
		var/modifiers = params2list(params)
		loaded_projectile.preparePixelProjectile(target, user, modifiers, spread)
	var/obj/projectile/loaded_projectile_cache = loaded_projectile
	loaded_projectile = null
	loaded_projectile_cache.fire(null, direct_target)
	return TRUE

/obj/item/ammo_casing/proc/spread(turf/target, turf/current, distro)
	var/dx = abs(target.x - current.x)
	var/dy = abs(target.y - current.y)
	return locate(target.x + round(gaussian(0, distro) * (dy+2)/8, 1), target.y + round(gaussian(0, distro) * (dx+2)/8, 1), target.z)


code:
// 7.12x82mm (SAW)

/obj/item/ammo_casing/mm712x82
	name = "7.12x82mm bullet casing"
	desc = "A 7.12x82mm bullet casing."
	icon_state = "762-casing"
	caliber = CALIBER_712X82MM
	projectile_type = /obj/projectile/bullet/mm712x82

/obj/item/ammo_casing/mm712x82/ap
	name = "7.12x82mm armor-piercing bullet casing"
	desc = "A 7.12x82mm bullet casing designed with a hardened-tipped core to help penetrate armored targets."
	projectile_type = /obj/projectile/bullet/mm712x82/ap

/obj/item/ammo_casing/mm712x82/hollow
	name = "7.12x82mm hollow-point bullet casing"
	desc = "A 7.12x82mm bullet casing designed to cause more damage to unarmored targets."
	projectile_type = /obj/projectile/bullet/mm712x82/hp

/obj/item/ammo_casing/mm712x82/incen
	name = "7.12x82mm incendiary bullet casing"
	desc = "A 7.12x82mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames."
	projectile_type = /obj/projectile/bullet/incendiary/mm712x82

/obj/item/ammo_casing/mm712x82/match
	name = "7.12x82mm match bullet casing"
	desc = "A 7.12x82mm bullet casing manufactured to unfailingly high standards, you could pull off some cool trickshots with this."
	projectile_type = /obj/projectile/bullet/mm712x82/match

/obj/item/ammo_casing/mm712x82/bouncy
	name = "7.12x82mm rubber bullet casing"
	desc = "A 7.12x82mm rubber bullet casing manufactured to unfailingly disastrous standards, you could piss off a lot of people spraying this down a hallway."
	projectile_type = /obj/projectile/bullet/mm712x82/bouncy


code:
// 10mm

/obj/item/ammo_casing/c10mm
	name = "10mm bullet casing"
	desc = "A 10mm bullet casing."
	caliber = CALIBER_10MM
	projectile_type = /obj/projectile/bullet/c10mm

/obj/item/ammo_casing/c10mm/ap
	name = "10mm armor-piercing bullet casing"
	desc = "A 10mm armor-piercing bullet casing."
	projectile_type = /obj/projectile/bullet/c10mm/ap

/obj/item/ammo_casing/c10mm/hp
	name = "10mm hollow-point bullet casing"
	desc = "A 10mm hollow-point bullet casing."
	projectile_type = /obj/projectile/bullet/c10mm/hp

/obj/item/ammo_casing/c10mm/fire
	name = "10mm incendiary bullet casing"
	desc = "A 10mm incendiary bullet casing."
	projectile_type = /obj/projectile/bullet/incendiary/c10mm

// 9mm (Makarov, Stechkin APS, PP-95)

/obj/item/ammo_casing/c9mm
	name = "9mm bullet casing"
	desc = "A 9mm bullet casing."
	caliber = CALIBER_9MM
	projectile_type = /obj/projectile/bullet/c9mm

/obj/item/ammo_casing/c9mm/ap
	name = "9mm armor-piercing bullet casing"
	desc = "A 9mm armor-piercing bullet casing."
	projectile_type =/obj/projectile/bullet/c9mm/ap

/obj/item/ammo_casing/c9mm/hp
	name = "9mm hollow-point bullet casing"
	desc = "A 9mm hollow-point bullet casing."
	projectile_type = /obj/projectile/bullet/c9mm/hp

/obj/item/ammo_casing/c9mm/fire
	name = "9mm incendiary bullet casing"
	desc = "A 9mm incendiary bullet casing."
	projectile_type = /obj/projectile/bullet/incendiary/c9mm

// .50AE (Desert Eagle)

/obj/item/ammo_casing/a50ae
	name = ".50AE bullet casing"
	desc = "A .50AE bullet casing."
	caliber = CALIBER_50
	projectile_type = /obj/projectile/bullet/a50ae


code:
// .357 (Syndie Revolver)

/obj/item/ammo_casing/a357
	name = ".357 bullet casing"
	desc = "A .357 bullet casing."
	caliber = CALIBER_357
	projectile_type = /obj/projectile/bullet/a357

/obj/item/ammo_casing/a357/match
	name = ".357 match bullet casing"
	desc = "A .357 bullet casing, manufactured to exceedingly high standards."
	projectile_type = /obj/projectile/bullet/a357/match

// 7.62x38mmR (Nagant Revolver)

/obj/item/ammo_casing/n762
	name = "7.62x38mmR bullet casing"
	desc = "A 7.62x38mmR bullet casing."
	caliber = CALIBER_N762
	projectile_type = /obj/projectile/bullet/n762

// .38 (Detective's Gun)

/obj/item/ammo_casing/c38
	name = ".38 bullet casing"
	desc = "A .38 bullet casing."
	caliber = CALIBER_38
	projectile_type = /obj/projectile/bullet/c38

/obj/item/ammo_casing/c38/trac
	name = ".38 TRAC bullet casing"
	desc = "A .38 \"TRAC\" bullet casing."
	projectile_type = /obj/projectile/bullet/c38/trac

/obj/item/ammo_casing/c38/match
	name = ".38 Match bullet casing"
	desc = "A .38 bullet casing, manufactured to exceedingly high standards."
	projectile_type = /obj/projectile/bullet/c38/match

/obj/item/ammo_casing/c38/match/bouncy
	name = ".38 Rubber bullet casing"
	desc = "A .38 rubber bullet casing, manufactured to exceedingly bouncy standards."
	projectile_type = /obj/projectile/bullet/c38/match/bouncy

/obj/item/ammo_casing/c38/dumdum
	name = ".38 DumDum bullet casing"
	desc = "A .38 DumDum bullet casing."
	projectile_type = /obj/projectile/bullet/c38/dumdum

/obj/item/ammo_casing/c38/hotshot
	name = ".38 Hot Shot bullet casing"
	desc = "A .38 Hot Shot bullet casing."
	projectile_type = /obj/projectile/bullet/c38/hotshot

/obj/item/ammo_casing/c38/iceblox
	name = ".38 Iceblox bullet casing"
	desc = "A .38 Iceblox bullet casing."
	projectile_type = /obj/projectile/bullet/c38/iceblox


code:
// 7.62 (Nagant Rifle)

/obj/item/ammo_casing/a762
	name = "7.62 bullet casing"
	desc = "A 7.62 bullet casing."
	icon_state = "762-casing"
	caliber = CALIBER_A762
	projectile_type = /obj/projectile/bullet/a762

/obj/item/ammo_casing/a762/enchanted
	projectile_type = /obj/projectile/bullet/a762/enchanted

// 5.56mm (M-90gl Carbine)

/obj/item/ammo_casing/a556
	name = "5.56mm bullet casing"
	desc = "A 5.56mm bullet casing."
	caliber = CALIBER_A556
	projectile_type = /obj/projectile/bullet/a556

/obj/item/ammo_casing/a556/phasic
	name = "5.56mm phasic bullet casing"
	desc = "A 5.56mm phasic bullet casing."
	projectile_type = /obj/projectile/bullet/a556/phasic

// 40mm (Grenade Launcher)

/obj/item/ammo_casing/a40mm
	name = "40mm HE shell"
	desc = "A cased high explosive grenade that can only be activated once fired out of a grenade launcher."
	caliber = CALIBER_40MM
	icon_state = "40mmHE"
	projectile_type = /obj/projectile/bullet/a40mm


code:
// Shotgun

/obj/item/ammo_casing/shotgun
	name = "shotgun slug"
	desc = "A 12 gauge lead slug."
	icon_state = "blshell"
	worn_icon_state = "shell"
	caliber = CALIBER_SHOTGUN
	custom_materials = list(/datum/material/iron=4000)
	projectile_type = /obj/projectile/bullet/shotgun_slug

/obj/item/ammo_casing/shotgun/executioner
	name = "executioner slug"
	desc = "A 12 gauge lead slug purpose built to annihilate flesh on impact."
	icon_state = "stunshell"
	projectile_type = /obj/projectile/bullet/shotgun_slug/executioner

/obj/item/ammo_casing/shotgun/pulverizer
	name = "pulverizer slug"
	desc = "A 12 gauge lead slug purpose built to annihilate bones on impact."
	icon_state = "stunshell"
	projectile_type = /obj/projectile/bullet/shotgun_slug/pulverizer

/obj/item/ammo_casing/shotgun/beanbag
	name = "beanbag slug"
	desc = "A weak beanbag slug for riot control."
	icon_state = "bshell"
	custom_materials = list(/datum/material/iron=250)
	projectile_type = /obj/projectile/bullet/shotgun_beanbag

/obj/item/ammo_casing/shotgun/incendiary
	name = "incendiary slug"
	desc = "An incendiary-coated shotgun slug."
	icon_state = "ishell"
	projectile_type = /obj/projectile/bullet/incendiary/shotgun

/obj/item/ammo_casing/shotgun/dragonsbreath
	name = "dragonsbreath shell"
	desc = "A shotgun shell which fires a spread of incendiary pellets."
	icon_state = "ishell2"
	projectile_type = /obj/projectile/bullet/incendiary/shotgun/dragonsbreath
	pellets = 4
	variance = 35

/obj/item/ammo_casing/shotgun/stunslug
	name = "taser slug"
	desc = "A stunning taser slug."
	icon_state = "stunshell"
	projectile_type = /obj/projectile/bullet/shotgun_stunslug
	custom_materials = list(/datum/material/iron=250)

/obj/item/ammo_casing/shotgun/meteorslug
	name = "meteorslug shell"
	desc = "A shotgun shell rigged with CMC technology, which launches a massive slug when fired."
	icon_state = "mshell"
	projectile_type = /obj/projectile/bullet/shotgun_meteorslug

/obj/item/ammo_casing/shotgun/pulseslug
	name = "pulse slug"
	desc = "A delicate device which can be loaded into a shotgun. The primer acts as a button which triggers the gain medium and fires a powerful \
	energy blast. While the heat and power drain limit it to one use, it can still allow an operator to engage targets that ballistic ammunition \
	would have difficulty with."
	icon_state = "pshell"
	projectile_type = /obj/projectile/beam/pulse/shotgun

/obj/item/ammo_casing/shotgun/frag12
	name = "FRAG-12 slug"
	desc = "A high explosive breaching round for a 12 gauge shotgun."
	icon_state = "heshell"
	projectile_type = /obj/projectile/bullet/shotgun_frag12

/obj/item/ammo_casing/shotgun/buckshot
	name = "buckshot shell"
	desc = "A 12 gauge buckshot shell."
	icon_state = "gshell"
	projectile_type = /obj/projectile/bullet/pellet/shotgun_buckshot
	pellets = 6
	variance = 25

/obj/item/ammo_casing/shotgun/rubbershot
	name = "rubber shot"
	desc = "A shotgun casing filled with densely-packed rubber balls, used to incapacitate crowds from a distance."
	icon_state = "bshell"
	projectile_type = /obj/projectile/bullet/pellet/shotgun_rubbershot
	pellets = 6
	variance = 25
	custom_materials = list(/datum/material/iron=4000)

/obj/item/ammo_casing/shotgun/incapacitate
	name = "custom incapacitating shot"
	desc = "A shotgun casing filled with... something. used to incapacitate targets."
	icon_state = "bountyshell"
	projectile_type = /obj/projectile/bullet/pellet/shotgun_incapacitate
	pellets = 12//double the pellets, but half the stun power of each, which makes this best for just dumping right in someone's face.
	variance = 25
	custom_materials = list(/datum/material/iron=4000)

/obj/item/ammo_casing/shotgun/improvised
	name = "improvised shell"
	desc = "An extremely weak shotgun shell with multiple small pellets made out of metal shards."
	icon_state = "improvshell"
	projectile_type = /obj/projectile/bullet/pellet/shotgun_improvised
	custom_materials = list(/datum/material/iron=250)
	pellets = 10
	variance = 25

/obj/item/ammo_casing/shotgun/ion
	name = "ion shell"
	desc = "An advanced shotgun shell which uses a subspace ansible crystal to produce an effect similar to a standard ion rifle. \
	The unique properties of the crystal split the pulse into a spread of individually weaker bolts."
	icon_state = "ionshell"
	projectile_type = /obj/projectile/ion/weak
	pellets = 4
	variance = 35

/obj/item/ammo_casing/shotgun/laserslug
	name = "scatter laser shell"
	desc = "An advanced shotgun shell that uses a micro laser to replicate the effects of a scatter laser weapon in a ballistic package."
	icon_state = "lshell"
	projectile_type = /obj/projectile/beam/weak
	pellets = 6
	variance = 35

/obj/item/ammo_casing/shotgun/techshell
	name = "unloaded technological shell"
	desc = "A high-tech shotgun shell which can be loaded with materials to produce unique effects."
	icon_state = "cshell"
	projectile_type = null

/obj/item/ammo_casing/shotgun/dart
	name = "shotgun dart"
	desc = "A dart for use in shotguns. Can be injected with up to 30 units of any chemical."
	icon_state = "cshell"
	projectile_type = /obj/projectile/bullet/dart
	var/reagent_amount = 30

/obj/item/ammo_casing/shotgun/dart/Initialize(mapload)
	. = ..()
	create_reagents(reagent_amount, OPENCONTAINER)

/obj/item/ammo_casing/shotgun/dart/attackby()
	return

/obj/item/ammo_casing/shotgun/dart/bioterror
	desc = "A shotgun dart filled with deadly toxins."

/obj/item/ammo_casing/shotgun/dart/bioterror/Initialize(mapload)
	. = ..()
	reagents.add_reagent(/datum/reagent/consumable/ethanol/neurotoxin, 6)
	reagents.add_reagent(/datum/reagent/toxin/spore, 6)
	reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 6) //;HELP OPS IN MAINT
	reagents.add_reagent(/datum/reagent/toxin/coniine, 6)
	reagents.add_reagent(/datum/reagent/toxin/sodium_thiopental, 6)


code:
// 4.6x30mm (Autorifles)

/obj/item/ammo_casing/c46x30mm
	name = "4.6x30mm bullet casing"
	desc = "A 4.6x30mm bullet casing."
	caliber = CALIBER_46X30MM
	projectile_type = /obj/projectile/bullet/c46x30mm

/obj/item/ammo_casing/c46x30mm/ap
	name = "4.6x30mm armor-piercing bullet casing"
	desc = "A 4.6x30mm armor-piercing bullet casing."
	projectile_type = /obj/projectile/bullet/c46x30mm/ap

/obj/item/ammo_casing/c46x30mm/inc
	name = "4.6x30mm incendiary bullet casing"
	desc = "A 4.6x30mm incendiary bullet casing."
	projectile_type = /obj/projectile/bullet/incendiary/c46x30mm

// .45 (M1911 + C20r)

/obj/item/ammo_casing/c45
	name = ".45 bullet casing"
	desc = "A .45 bullet casing."
	caliber = CALIBER_45
	projectile_type = /obj/projectile/bullet/c45

/obj/item/ammo_casing/c45/ap
	name = ".45 armor-piercing bullet casing"
	desc = "A .45 bullet casing."
	projectile_type = /obj/projectile/bullet/c45/ap

/obj/item/ammo_casing/c45/inc
	name = ".45 incendiary bullet casing"
	desc = "A .45 bullet casing."
	projectile_type = /obj/projectile/bullet/incendiary/c45


code:
// .50 (Sniper)

/obj/item/ammo_casing/p50
	name = ".50 bullet casing"
	desc = "A .50 bullet casing."
	caliber = CALIBER_50
	projectile_type = /obj/projectile/bullet/p50
	icon_state = ".50"

/obj/item/ammo_casing/p50/soporific
	name = ".50 soporific bullet casing"
	desc = "A .50 bullet casing, specialised in sending the target to sleep, instead of hell."
	projectile_type = /obj/projectile/bullet/p50/soporific
	icon_state = "sleeper"
	harmful = FALSE

/obj/item/ammo_casing/p50/penetrator
	name = ".50 penetrator round bullet casing"
	desc = "A .50 caliber penetrator round casing."
	projectile_type = /obj/projectile/bullet/p50/penetrator


code:
/obj/item/ammo_casing/caseless
	desc = "A caseless bullet casing."
	firing_effect_type = null
	heavy_metal = FALSE

/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
	if (!..()) //failed firing
		return FALSE
	if(istype(fired_from, /obj/item/gun))
		var/obj/item/gun/shot_from = fired_from
		if(shot_from.chambered == src)
			shot_from.chambered = null //Nuke it. Nuke it now.
	qdel(src)
	return TRUE

/obj/item/ammo_casing/caseless/update_icon_state()
	. = ..()
	icon_state = "[initial(icon_state)]"


code:
/obj/item/ammo_casing/caseless/laser
	name = "laser casing"
	desc = "You shouldn't be seeing this."
	caliber = CALIBER_LASER
	icon_state = "s-casing-live"
	slot_flags = null
	projectile_type = /obj/projectile/beam
	fire_sound = 'sound/weapons/laser.ogg'
	firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy


code:
/obj/item/ammo_casing/caseless/foam_dart
	name = "foam dart"
	desc = "It's Donk or Don't! Ages 8 and up."
	projectile_type = /obj/projectile/bullet/reusable/foam_dart
	caliber = CALIBER_FOAM
	icon = 'icons/obj/guns/toy.dmi'
	icon_state = "foamdart"
	base_icon_state = "foamdart"
	custom_materials = list(/datum/material/iron = 11.25)
	harmful = FALSE
	var/modified = FALSE

/obj/item/ammo_casing/caseless/foam_dart/update_icon_state()
	. = ..()
	if(modified)
		icon_state = "[base_icon_state]_empty"
		loaded_projectile?.icon_state = "[base_icon_state]_empty"
		return
	icon_state = "[base_icon_state]"
	loaded_projectile?.icon_state = "[loaded_projectile.base_icon_state]"

/obj/item/ammo_casing/caseless/foam_dart/update_desc()
	. = ..()
	desc = "It's Donk or Don't! [modified ? "... Although, this one doesn't look too safe." : "Ages 8 and up."]"

/obj/item/ammo_casing/caseless/foam_dart/attackby(obj/item/A, mob/user, params)
	var/obj/projectile/bullet/reusable/foam_dart/FD = loaded_projectile
	if (A.tool_behaviour == TOOL_SCREWDRIVER && !modified)
		modified = TRUE
		FD.modified = TRUE
		FD.damage_type = BRUTE
		to_chat(user, span_notice("You pop the safety cap off [src]."))
		update_appearance()
	else if (istype(A, /obj/item/pen))
		if(modified)
			if(!FD.pen)
				harmful = TRUE
				if(!user.transferItemToLoc(A, FD))
					return
				FD.pen = A
				FD.damage = 5
				FD.nodamage = FALSE
				to_chat(user, span_notice("You insert [A] into [src]."))
			else
				to_chat(user, span_warning("There's already something in [src]."))
		else
			to_chat(user, span_warning("The safety cap prevents you from inserting [A] into [src]."))
	else
		return ..()

/obj/item/ammo_casing/caseless/foam_dart/attack_self(mob/living/user)
	var/obj/projectile/bullet/reusable/foam_dart/FD = loaded_projectile
	if(FD.pen)
		FD.damage = initial(FD.damage)
		FD.nodamage = initial(FD.nodamage)
		user.put_in_hands(FD.pen)
		to_chat(user, span_notice("You remove [FD.pen] from [src]."))
		FD.pen = null

/obj/item/ammo_casing/caseless/foam_dart/riot
	name = "riot foam dart"
	desc = "Whose smart idea was it to use toys as crowd control? Ages 18 and up."
	projectile_type = /obj/projectile/bullet/reusable/foam_dart/riot
	icon_state = "foamdart_riot"
	base_icon_state = "foamdart_riot"
	custom_materials = list(/datum/material/iron = 1125)


code:
/obj/item/ammo_casing/caseless/harpoon
	name = "harpoon"
	caliber = CALIBER_HARPOON
	icon_state = "magspear"
	projectile_type = /obj/projectile/bullet/harpoon


code:
/obj/item/ammo_casing/caseless/rocket
	name = "\improper PM-9HE"
	desc = "An 84mm High Explosive rocket. Fire at people and pray."
	caliber = CALIBER_84MM
	icon_state = "srm-8"
	projectile_type = /obj/projectile/bullet/a84mm/he

/obj/item/ammo_casing/caseless/rocket/hedp
	name = "\improper PM-9HEDP"
	desc = "An 84mm High Explosive Dual Purpose rocket. Pointy end toward mechs."
	icon_state = "84mm-hedp"
	projectile_type = /obj/projectile/bullet/a84mm

/obj/item/ammo_casing/caseless/rocket/weak
	name = "\improper PM-9HE Low-Yield"
	desc = "An 84mm High Explosive rocket. This one isn't quite as devastating."
	projectile_type = /obj/projectile/bullet/a84mm/weak

/obj/item/ammo_casing/caseless/a75
	desc = "A .75 bullet casing."
	caliber = CALIBER_75
	icon_state = "s-casing-live"
	projectile_type = /obj/projectile/bullet/gyro


code:
/obj/item/ammo_casing/energy
	name = "energy weapon lens"
	desc = "The part of the gun that makes the laser go pew."
	caliber = ENERGY
	projectile_type = /obj/projectile/energy
	slot_flags = null
	var/e_cost = 100 //The amount of energy a cell needs to expend to create this shot.
	var/select_name = CALIBER_ENERGY
	fire_sound = 'sound/weapons/laser.ogg'
	firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy
	heavy_metal = FALSE


code:
/obj/item/ammo_casing/energy/bolt
	projectile_type = /obj/projectile/energy/bolt
	select_name = "bolt"
	e_cost = 500
	fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // Even for non-suppressed crossbows, this is the most appropriate sound

/obj/item/ammo_casing/energy/bolt/halloween
	projectile_type = /obj/projectile/energy/bolt/halloween

/obj/item/ammo_casing/energy/bolt/large
	projectile_type = /obj/projectile/energy/bolt/large
	select_name = "heavy bolt"


code:
/obj/item/ammo_casing/energy/gravity
	e_cost = 0
	fire_sound = 'sound/weapons/wave.ogg'
	select_name = "gravity"
	delay = 50
	var/obj/item/gun/energy/gravity_gun/gun

/obj/item/ammo_casing/energy/gravity/Initialize(mapload)
	if(istype(loc,/obj/item/gun/energy/gravity_gun))
		gun = loc
	. = ..()

/obj/item/ammo_casing/energy/gravity/Destroy()
	gun = null
	. = ..()

/obj/item/ammo_casing/energy/gravity/repulse
	projectile_type = /obj/projectile/gravityrepulse
	select_name = "repulse"

/obj/item/ammo_casing/energy/gravity/attract
	projectile_type = /obj/projectile/gravityattract
	select_name = "attract"

/obj/item/ammo_casing/energy/gravity/chaos
	projectile_type = /obj/projectile/gravitychaos
	select_name = "chaos"


code:
/obj/item/ammo_casing/energy/laser
	projectile_type = /obj/projectile/beam/laser
	e_cost = 83
	select_name = "kill"

/obj/item/ammo_casing/energy/laser/hellfire
	projectile_type = /obj/projectile/beam/laser/hellfire
	e_cost = 100
	select_name = "maim"

/obj/item/ammo_casing/energy/laser/hellfire/antique
	e_cost = 100

/obj/item/ammo_casing/energy/lasergun
	projectile_type = /obj/projectile/beam/laser
	e_cost = 62.5
	select_name = "kill"

/obj/item/ammo_casing/energy/lasergun/old
	projectile_type = /obj/projectile/beam/laser
	e_cost = 200
	select_name = "kill"

/obj/item/ammo_casing/energy/laser/hos
	e_cost = 120

/obj/item/ammo_casing/energy/laser/practice
	projectile_type = /obj/projectile/beam/practice
	select_name = "practice"
	harmful = FALSE

/obj/item/ammo_casing/energy/laser/scatter
	projectile_type = /obj/projectile/beam/scatter
	pellets = 5
	variance = 25
	select_name = "scatter"

/obj/item/ammo_casing/energy/laser/scatter/disabler
	projectile_type = /obj/projectile/beam/disabler
	pellets = 3
	variance = 15
	harmful = FALSE

/obj/item/ammo_casing/energy/laser/heavy
	projectile_type = /obj/projectile/beam/laser/heavylaser
	select_name = "anti-vehicle"
	fire_sound = 'sound/weapons/lasercannonfire.ogg'

/obj/item/ammo_casing/energy/laser/pulse
	projectile_type = /obj/projectile/beam/pulse
	e_cost = 200
	select_name = "DESTROY"
	fire_sound = 'sound/weapons/pulse.ogg'

/obj/item/ammo_casing/energy/laser/bluetag
	projectile_type = /obj/projectile/beam/lasertag/bluetag
	select_name = "bluetag"
	harmful = FALSE

/obj/item/ammo_casing/energy/laser/bluetag/hitscan
	projectile_type = /obj/projectile/beam/lasertag/bluetag/hitscan

/obj/item/ammo_casing/energy/laser/redtag
	projectile_type = /obj/projectile/beam/lasertag/redtag
	select_name = "redtag"
	harmful = FALSE

/obj/item/ammo_casing/energy/laser/redtag/hitscan
	projectile_type = /obj/projectile/beam/lasertag/redtag/hitscan

/obj/item/ammo_casing/energy/xray
	projectile_type = /obj/projectile/beam/xray
	e_cost = 50
	fire_sound = 'sound/weapons/laser3.ogg'

/obj/item/ammo_casing/energy/mindflayer
	projectile_type = /obj/projectile/beam/mindflayer
	select_name = "MINDFUCK"
	fire_sound = 'sound/weapons/laser.ogg'

/obj/item/ammo_casing/energy/laser/minigun
	select_name = "kill"
	projectile_type = /obj/projectile/beam/weak/penetrator
	variance = 0.8


code:
/obj/item/ammo_casing/energy/c3dbullet
	projectile_type = /obj/projectile/bullet/c3d
	select_name = "spraydown"
	fire_sound = 'sound/weapons/gun/smg/shot.ogg'
	e_cost = 20
	firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect


code:
/obj/item/ammo_casing/energy/plasma
	projectile_type = /obj/projectile/plasma
	select_name = "plasma burst"
	fire_sound = 'sound/weapons/plasma_cutter.ogg'
	delay = 15
	e_cost = 25

/obj/item/ammo_casing/energy/plasma/adv
	projectile_type = /obj/projectile/plasma/adv
	delay = 10
	e_cost = 10


code:
/obj/item/ammo_casing/energy/wormhole
	projectile_type = /obj/projectile/beam/wormhole
	e_cost = 0
	harmful = FALSE
	fire_sound = 'sound/weapons/pulse3.ogg'
	select_name = "blue"
	//Weakref to the gun that shot us
	var/datum/weakref/gun

/obj/item/ammo_casing/energy/wormhole/orange
	projectile_type = /obj/projectile/beam/wormhole/orange
	select_name = "orange"

/obj/item/ammo_casing/energy/wormhole/Initialize(mapload, obj/item/gun/energy/wormhole_projector/wh)
	. = ..()
	gun = WEAKREF(wh)

/obj/item/ammo_casing/energy/wormhole/throw_proj()
	. = ..()
	if(istype(loaded_projectile, /obj/projectile/beam/wormhole))
		var/obj/projectile/beam/wormhole/WH = loaded_projectile
		WH.gun = gun


code:
/obj/item/ammo_casing/energy/ion
	projectile_type = /obj/projectile/ion
	select_name = "ion"
	fire_sound = 'sound/weapons/ionrifle.ogg'

/obj/item/ammo_casing/energy/ion/hos
	projectile_type = /obj/projectile/ion/weak
	e_cost = 300

/obj/item/ammo_casing/energy/declone
	projectile_type = /obj/projectile/energy/declone
	select_name = "declone"
	fire_sound = 'sound/weapons/pulse3.ogg'

/obj/item/ammo_casing/energy/declone/weak
	projectile_type = /obj/projectile/energy/declone/weak

/obj/item/ammo_casing/energy/flora
	fire_sound = 'sound/effects/stealthoff.ogg'
	harmful = FALSE

/obj/item/ammo_casing/energy/flora/yield
	projectile_type = /obj/projectile/energy/florayield
	select_name = "yield"

/obj/item/ammo_casing/energy/flora/mut
	projectile_type = /obj/projectile/energy/floramut
	select_name = "mutation"

/obj/item/ammo_casing/energy/flora/revolution
	projectile_type = /obj/projectile/energy/florarevolution
	select_name = "revolution"
	e_cost = 250

/obj/item/ammo_casing/energy/temp
	projectile_type = /obj/projectile/temp
	select_name = "freeze"
	e_cost = 250
	fire_sound = 'sound/weapons/pulse3.ogg'

/obj/item/ammo_casing/energy/temp/hot
	projectile_type = /obj/projectile/temp/hot
	select_name = "bake"

/obj/item/ammo_casing/energy/meteor
	projectile_type = /obj/projectile/meteor
	select_name = "goddamn meteor"

/obj/item/ammo_casing/energy/net
	projectile_type = /obj/projectile/energy/net
	select_name = "netting"
	pellets = 6
	variance = 40
	harmful = FALSE

/obj/item/ammo_casing/energy/trap
	projectile_type = /obj/projectile/energy/trap
	select_name = "snare"
	harmful = FALSE

/obj/item/ammo_casing/energy/tesla_cannon
	fire_sound = 'sound/magic/lightningshock.ogg'
	e_cost = 30
	select_name = "shock"
	projectile_type = /obj/projectile/energy/tesla_cannon

/obj/item/ammo_casing/energy/shrink
	projectile_type = /obj/projectile/beam/shrink
	select_name = "shrink ray"
	e_cost = 200


code:
/obj/item/ammo_casing/energy/electrode
	projectile_type = /obj/projectile/energy/electrode
	select_name = "stun"
	fire_sound = 'sound/weapons/taser.ogg'
	e_cost = 200
	harmful = FALSE

/obj/item/ammo_casing/energy/electrode/spec
	e_cost = 100

/obj/item/ammo_casing/energy/electrode/gun
	fire_sound = 'sound/weapons/gun/pistol/shot.ogg'
	e_cost = 100

/obj/item/ammo_casing/energy/electrode/old
	e_cost = 1000

/obj/item/ammo_casing/energy/disabler
	projectile_type = /obj/projectile/beam/disabler
	select_name  = "disable"
	e_cost = 50
	fire_sound = 'sound/weapons/taser2.ogg'
	harmful = FALSE

/obj/item/ammo_casing/energy/disabler/hos
	e_cost = 60


code:
/obj/item/ammo_casing/magic
	name = "magic casing"
	desc = "I didn't even know magic needed ammo..."
	slot_flags = null
	projectile_type = /obj/projectile/magic
	firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/magic
	heavy_metal = FALSE

/obj/item/ammo_casing/magic/change
	projectile_type = /obj/projectile/magic/change

/obj/item/ammo_casing/magic/animate
	projectile_type = /obj/projectile/magic/animate

/obj/item/ammo_casing/magic/heal
	projectile_type = /obj/projectile/magic/resurrection
	harmful = FALSE

/obj/item/ammo_casing/magic/death
	projectile_type = /obj/projectile/magic/death

/obj/item/ammo_casing/magic/teleport
	projectile_type = /obj/projectile/magic/teleport
	harmful = FALSE

/obj/item/ammo_casing/magic/safety
	projectile_type = /obj/projectile/magic/safety
	harmful = FALSE

/obj/item/ammo_casing/magic/door
	projectile_type = /obj/projectile/magic/door
	harmful = FALSE

/obj/item/ammo_casing/magic/fireball
	projectile_type = /obj/projectile/magic/aoe/fireball

/obj/item/ammo_casing/magic/chaos
	projectile_type = /obj/projectile/magic

/obj/item/ammo_casing/magic/spellblade
	projectile_type = /obj/projectile/magic/spellblade

/obj/item/ammo_casing/magic/arcane_barrage
	projectile_type = /obj/projectile/magic/arcane_barrage

/obj/item/ammo_casing/magic/honk
	projectile_type = /obj/projectile/bullet/honker

/obj/item/ammo_casing/magic/locker
	projectile_type = /obj/projectile/magic/locker

/obj/item/ammo_casing/magic/flying
	projectile_type = /obj/projectile/magic/flying

/obj/item/ammo_casing/magic/bounty
	projectile_type = /obj/projectile/magic/bounty

/obj/item/ammo_casing/magic/antimagic
	projectile_type = /obj/projectile/magic/antimagic

/obj/item/ammo_casing/magic/sapping
	projectile_type = /obj/projectile/magic/sapping

/obj/item/ammo_casing/magic/necropotence
	projectile_type = /obj/projectile/magic/necropotence

/obj/item/ammo_casing/magic/wipe
	projectile_type = /obj/projectile/magic/wipe

/obj/item/ammo_casing/magic/nothing
	projectile_type = /obj/projectile/magic/nothing
	harmful = FALSE


code:
/obj/item/ammo_casing/syringegun
	name = "syringe gun spring"
	desc = "A high-power spring that throws syringes."
	slot_flags = null
	projectile_type = /obj/projectile/bullet/dart/syringe
	firing_effect_type = null

/obj/item/ammo_casing/syringegun/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
	if(!loaded_projectile)
		return
	if(istype(loc, /obj/item/gun/syringe))
		var/obj/item/gun/syringe/SG = loc
		if(!SG.syringes.len)
			return

		var/obj/item/reagent_containers/syringe/S = SG.syringes[1]

		S.reagents.trans_to(loaded_projectile, S.reagents.total_volume, transfered_by = user)
		loaded_projectile.name = S.name
		var/obj/projectile/bullet/dart/D = loaded_projectile
		D.piercing = S.proj_piercing
		SG.syringes.Remove(S)
		qdel(S)
	else if(istype(loc, /obj/item/mecha_parts/mecha_equipment/medical/syringe_gun))
		var/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/syringe_gun = loc
		var/obj/item/reagent_containers/syringe/loaded_syringe = syringe_gun.syringes[1]
		var/obj/projectile/bullet/dart/shot_dart = loaded_projectile
		syringe_gun.reagents.trans_to(shot_dart, min(loaded_syringe.volume, syringe_gun.reagents.total_volume), transfered_by = user)
		shot_dart.name = loaded_syringe.name
		shot_dart.piercing = loaded_syringe.proj_piercing
		LAZYREMOVE(syringe_gun.syringes, loaded_syringe)
		qdel(loaded_syringe)
	return ..()

/obj/item/ammo_casing/chemgun
	name = "dart synthesiser"
	desc = "A high-power spring, linked to an energy-based piercing dart synthesiser."
	projectile_type = /obj/projectile/bullet/dart/piercing
	firing_effect_type = null

/obj/item/ammo_casing/chemgun/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
	if(!loaded_projectile)
		return
	if(istype(loc, /obj/item/gun/chem))
		var/obj/item/gun/chem/CG = loc
		if(CG.syringes_left <= 0)
			return
		CG.reagents.trans_to(loaded_projectile, 15, transfered_by = user)
		loaded_projectile.name = "piercing chemical dart"
		CG.syringes_left--
	return ..()

/obj/item/ammo_casing/dnainjector
	name = "rigged syringe gun spring"
	desc = "A high-power spring that throws DNA injectors."
	projectile_type = /obj/projectile/bullet/dnainjector
	firing_effect_type = null

/obj/item/ammo_casing/dnainjector/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
	if(!loaded_projectile)
		return
	if(istype(loc, /obj/item/gun/syringe/dna))
		var/obj/item/gun/syringe/dna/SG = loc
		if(!SG.syringes.len)
			return

		var/obj/item/dnainjector/S = popleft(SG.syringes)
		var/obj/projectile/bullet/dnainjector/D = loaded_projectile
		S.forceMove(D)
		D.injector = S
	return ..()


code:
//Boxes of ammo
/obj/item/ammo_box
	name = "ammo box (null_reference_exception)"
	desc = "A box of ammo."
	icon = 'icons/obj/guns/ammo.dmi'
	flags_1 = CONDUCT_1
	slot_flags = ITEM_SLOT_BELT
	inhand_icon_state = "syringe_kit"
	worn_icon_state = "ammobox"
	lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
	custom_materials = list(/datum/material/iron = 30000)
	throwforce = 2
	w_class = WEIGHT_CLASS_TINY
	throw_speed = 3
	throw_range = 7
	override_notes = TRUE
	///list containing the actual ammo within the magazine
	var/list/stored_ammo = list()
	///type that the magazine will be searching for, rejects if not a subtype of
	var/ammo_type = /obj/item/ammo_casing
	///maximum amount of ammo in the magazine
	var/max_ammo = 7
	///Controls how sprites are updated for the ammo box; see defines in combat.dm: AMMO_BOX_ONE_SPRITE; AMMO_BOX_PER_BULLET; AMMO_BOX_FULL_EMPTY
	var/multiple_sprites = AMMO_BOX_ONE_SPRITE
	///String, used for checking if ammo of different types but still fits can fit inside it; generally used for magazines
	var/caliber
	///Allows multiple bullets to be loaded in from one click of another box/magazine
	var/multiload = TRUE
	///Whether the magazine should start with nothing in it
	var/start_empty = FALSE
	///cost of all the bullets in the magazine/box
	var/list/bullet_cost
	///cost of the materials in the magazine/box itself
	var/list/base_cost

/obj/item/ammo_box/Initialize(mapload)
	. = ..()
	if(!bullet_cost)
		base_cost = SSmaterials.FindOrCreateMaterialCombo(custom_materials, 0.1)
		bullet_cost = SSmaterials.FindOrCreateMaterialCombo(custom_materials, 0.9 / max_ammo)
	if(!start_empty)
		top_off(starting=TRUE)

/obj/item/ammo_box/add_weapon_description()
	AddElement(/datum/element/weapon_description, attached_proc = .proc/add_notes_box)

/obj/item/ammo_box/proc/add_notes_box()
	var/list/readout = list()

	if(caliber && max_ammo) // Text references a 'magazine' as only magazines generally have the caliber variable initialized
		readout += "Up to [span_warning("[max_ammo] [caliber] rounds")] can be found within this magazine. \
		\nAccidentally discharging any of these projectiles may void your insurance contract."

	var/obj/item/ammo_casing/mag_ammo = get_round(TRUE)

	if(istype(mag_ammo))
		readout += "\n[mag_ammo.add_notes_ammo()]"

	return readout.Join("\n")

/**
 * top_off is used to refill the magazine to max, in case you want to increase the size of a magazine with VV then refill it at once
 *
 * Arguments:
 * * load_type - if you want to specify a specific ammo casing type to load, enter the path here, otherwise it'll use the basic [/obj/item/ammo_box/var/ammo_type]. Must be a compatible round
 * * starting - Relevant for revolver cylinders, if FALSE then we mind the nulls that represent the empty cylinders (since those nulls don't exist yet if we haven't initialized when this is TRUE)
 */
/obj/item/ammo_box/proc/top_off(load_type, starting=FALSE)
	if(!load_type) //this check comes first so not defining an argument means we just go with default ammo
		load_type = ammo_type

	var/obj/item/ammo_casing/round_check = load_type
	if(!starting && !(caliber ? (caliber == initial(round_check.caliber)) : (ammo_type == load_type)))
		stack_trace("Tried loading unsupported ammocasing type [load_type] into ammo box [type].")
		return

	for(var/i = max(1, stored_ammo.len), i <= max_ammo, i++)
		stored_ammo += new round_check(src)
	update_ammo_count()

///gets a round from the magazine, if keep is TRUE the round will stay in the gun
/obj/item/ammo_box/proc/get_round(keep = FALSE)
	if (!stored_ammo.len)
		return null
	else
		var/b = stored_ammo[stored_ammo.len]
		stored_ammo -= b
		if (keep)
			stored_ammo.Insert(1,b)
		return b

///puts a round into the magazine
/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0)
	// Boxes don't have a caliber type, magazines do. Not sure if it's intended or not, but if we fail to find a caliber, then we fall back to ammo_type.
	if(!R || !(caliber ? (caliber == R.caliber) : (ammo_type == R.type)))
		return FALSE

	if (stored_ammo.len < max_ammo)
		stored_ammo += R
		R.forceMove(src)
		return TRUE

	//for accessibles magazines (e.g internal ones) when full, start replacing spent ammo
	else if(replace_spent)
		for(var/obj/item/ammo_casing/AC in stored_ammo)
			if(!AC.loaded_projectile)//found a spent ammo
				stored_ammo -= AC
				AC.forceMove(get_turf(src.loc))

				stored_ammo += R
				R.forceMove(src)
				return TRUE
	return FALSE

///Whether or not the box can be loaded, used in overrides
/obj/item/ammo_box/proc/can_load(mob/user)
	return TRUE

/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = FALSE, replace_spent = 0)
	var/num_loaded = 0
	if(!can_load(user))
		return
	if(istype(A, /obj/item/ammo_box))
		var/obj/item/ammo_box/AM = A
		for(var/obj/item/ammo_casing/AC in AM.stored_ammo)
			var/did_load = give_round(AC, replace_spent)
			if(did_load)
				AM.stored_ammo -= AC
				num_loaded++
			if(!did_load || !multiload)
				break
		if(num_loaded)
			AM.update_ammo_count()
	if(istype(A, /obj/item/ammo_casing))
		var/obj/item/ammo_casing/AC = A
		if(give_round(AC, replace_spent))
			user.transferItemToLoc(AC, src, TRUE)
			num_loaded++
			AC.update_appearance()

	if(num_loaded)
		if(!silent)
			to_chat(user, span_notice("You load [num_loaded] shell\s into \the [src]!"))
			playsound(src, 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE)
		update_ammo_count()

	return num_loaded

/obj/item/ammo_box/attack_self(mob/user)
	var/obj/item/ammo_casing/A = get_round()
	if(!A)
		return

	A.forceMove(drop_location())
	if(!user.is_holding(src) || !user.put_in_hands(A)) //incase they're using TK
		A.bounce_away(FALSE, NONE)
	playsound(src, 'sound/weapons/gun/general/mag_bullet_insert.ogg', 60, TRUE)
	to_chat(user, span_notice("You remove a round from [src]!"))
	update_ammo_count()

/// Updates the materials and appearance of this ammo box
/obj/item/ammo_box/proc/update_ammo_count()
	update_custom_materials()
	update_appearance()

/obj/item/ammo_box/update_desc(updates)
	. = ..()
	var/shells_left = LAZYLEN(stored_ammo)
	desc = "[initial(desc)] There [(shells_left == 1) ? "is" : "are"] [shells_left] shell\s left!"

/obj/item/ammo_box/update_icon_state()
	var/shells_left = LAZYLEN(stored_ammo)
	switch(multiple_sprites)
		if(AMMO_BOX_PER_BULLET)
			icon_state = "[initial(icon_state)]-[shells_left]"
		if(AMMO_BOX_FULL_EMPTY)
			icon_state = "[initial(icon_state)]-[shells_left ? "[max_ammo]" : "0"]"
	return ..()

/// Updates the amount of material in this ammo box according to how many bullets are left in it.
/obj/item/ammo_box/proc/update_custom_materials()
	var/temp_materials = custom_materials.Copy()
	for(var/material in bullet_cost)
		temp_materials[material] = (bullet_cost[material] * stored_ammo.len) + base_cost[material]
	set_custom_materials(temp_materials)

///Count of number of bullets in the magazine
/obj/item/ammo_box/magazine/proc/ammo_count(countempties = TRUE)
	var/boolets = 0
	for(var/obj/item/ammo_casing/bullet in stored_ammo)
		if(bullet && (bullet.loaded_projectile || countempties))
			boolets++
	return boolets

///list of every bullet in the magazine
/obj/item/ammo_box/magazine/proc/ammo_list(drop_list = FALSE)
	var/list/L = stored_ammo.Copy()
	if(drop_list)
		stored_ammo.Cut()
	return L

///drops the entire contents of the magazine on the floor
/obj/item/ammo_box/magazine/proc/empty_magazine()
	var/turf_mag = get_turf(src)
	for(var/obj/item/ammo in stored_ammo)
		ammo.forceMove(turf_mag)
		stored_ammo -= ammo

/obj/item/ammo_box/magazine/handle_atom_del(atom/A)
	stored_ammo -= A
	update_ammo_count()


code:
/obj/item/ammo_box/a357
	name = "speed loader (.357)"
	desc = "Designed to quickly reload revolvers."
	icon_state = "357"
	ammo_type = /obj/item/ammo_casing/a357
	max_ammo = 7
	multiple_sprites = AMMO_BOX_PER_BULLET
	item_flags = NO_MAT_REDEMPTION

/obj/item/ammo_box/a357/match
	name = "speed loader (.357 Match)"
	desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
	ammo_type = /obj/item/ammo_casing/a357/match

/obj/item/ammo_box/c38
	name = "speed loader (.38)"
	desc = "Designed to quickly reload revolvers."
	icon_state = "38"
	ammo_type = /obj/item/ammo_casing/c38
	max_ammo = 6
	multiple_sprites = AMMO_BOX_PER_BULLET
	custom_materials = list(/datum/material/iron = 20000)

/obj/item/ammo_box/c38/trac
	name = "speed loader (.38 TRAC)"
	desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body."
	ammo_type = /obj/item/ammo_casing/c38/trac

/obj/item/ammo_box/c38/match
	name = "speed loader (.38 Match)"
	desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
	ammo_type = /obj/item/ammo_casing/c38/match

/obj/item/ammo_box/c38/match/bouncy
	name = "speed loader (.38 Rubber)"
	desc = "Designed to quickly reload revolvers. These rounds are incredibly bouncy and MOSTLY nonlethal, making them great to show off trickshots with."
	ammo_type = /obj/item/ammo_casing/c38/match/bouncy

/obj/item/ammo_box/c38/dumdum
	name = "speed loader (.38 DumDum)"
	desc = "Designed to quickly reload revolvers. These rounds expand on impact, allowing them to shred the target and cause massive bleeding. Very weak against armor and distant targets."
	ammo_type = /obj/item/ammo_casing/c38/dumdum

/obj/item/ammo_box/c38/hotshot
	name = "speed loader (.38 Hot Shot)"
	desc = "Designed to quickly reload revolvers. Hot Shot bullets contain an incendiary payload."
	ammo_type = /obj/item/ammo_casing/c38/hotshot

/obj/item/ammo_box/c38/iceblox
	name = "speed loader (.38 Iceblox)"
	desc = "Designed to quickly reload revolvers. Iceblox bullets contain a cryogenic payload."
	ammo_type = /obj/item/ammo_casing/c38/iceblox

/obj/item/ammo_box/c9mm
	name = "ammo box (9mm)"
	icon_state = "9mmbox"
	ammo_type = /obj/item/ammo_casing/c9mm
	max_ammo = 30

/obj/item/ammo_box/c10mm
	name = "ammo box (10mm)"
	icon_state = "10mmbox"
	ammo_type = /obj/item/ammo_casing/c10mm
	max_ammo = 20

/obj/item/ammo_box/c45
	name = "ammo box (.45)"
	icon_state = "45box"
	ammo_type = /obj/item/ammo_casing/c45
	max_ammo = 20

/obj/item/ammo_box/a40mm
	name = "ammo box (40mm grenades)"
	icon_state = "40mm"
	ammo_type = /obj/item/ammo_casing/a40mm
	max_ammo = 4
	multiple_sprites = AMMO_BOX_PER_BULLET

/obj/item/ammo_box/a762
	name = "stripper clip (7.62mm)"
	desc = "A stripper clip."
	icon_state = "762"
	ammo_type = /obj/item/ammo_casing/a762
	max_ammo = 5
	multiple_sprites = AMMO_BOX_PER_BULLET

/obj/item/ammo_box/n762
	name = "ammo box (7.62x38mmR)"
	icon_state = "10mmbox"
	ammo_type = /obj/item/ammo_casing/n762
	max_ammo = 14

/obj/item/ammo_box/foambox
	name = "ammo box (Foam Darts)"
	icon = 'icons/obj/guns/toy.dmi'
	icon_state = "foambox"
	ammo_type = /obj/item/ammo_casing/caseless/foam_dart
	max_ammo = 40
	custom_materials = list(/datum/material/iron = 500)

/obj/item/ammo_box/foambox/riot
	icon_state = "foambox_riot"
	ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot
	custom_materials = list(/datum/material/iron = 50000)


code:
/obj/item/ammo_box/magazine/m75
	name = "specialized magazine (.75)"
	icon_state = "75"
	ammo_type = /obj/item/ammo_casing/caseless/a75
	caliber = CALIBER_75
	multiple_sprites = AMMO_BOX_FULL_EMPTY
	max_ammo = 8


code:
/obj/item/ammo_box/magazine/mm712x82
	name = "box magazine (7.12x82mm)"
	icon_state = "a762-50"
	ammo_type = /obj/item/ammo_casing/mm712x82
	caliber = CALIBER_712X82MM
	max_ammo = 50

/obj/item/ammo_box/magazine/mm712x82/hollow
	name = "box magazine (Hollow-Point 7.12x82mm)"
	ammo_type = /obj/item/ammo_casing/mm712x82/hollow

/obj/item/ammo_box/magazine/mm712x82/ap
	name = "box magazine (Armor Penetrating 7.12x82mm)"
	ammo_type = /obj/item/ammo_casing/mm712x82/ap

/obj/item/ammo_box/magazine/mm712x82/incen
	name = "box magazine (Incendiary 7.12x82mm)"
	ammo_type = /obj/item/ammo_casing/mm712x82/incen

/obj/item/ammo_box/magazine/mm712x82/match
	name = "box magazine (Match 7.12x82mm)"
	ammo_type = /obj/item/ammo_casing/mm712x82/match

/obj/item/ammo_box/magazine/mm712x82/bouncy
	name = "box magazine (Rubber 7.12x82mm)"
	ammo_type = /obj/item/ammo_casing/mm712x82/bouncy

/obj/item/ammo_box/magazine/mm712x82/bouncy/hicap
	name = "hi-cap box magazine (Rubber 7.12x82mm)"
	max_ammo = 150

/obj/item/ammo_box/magazine/mm712x82/update_icon_state()
	. = ..()
	icon_state = "a762-[round(ammo_count(), 10)]"