code:
/*
  HOW IT WORKS

  The SSradio is a global object maintaining all radio transmissions, think about it as about "ether".
  Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way.
  procs:

    add_object(obj/device as obj, var/new_frequency as num, var/filter as text|null = null)
      Adds listening object.
      parameters:
        device - device receiving signals, must have proc receive_signal (see description below).
          one device may listen several frequencies, but not same frequency twice.
        new_frequency - see possibly frequencies below;
        filter - thing for optimization. Optional, but recommended.
                 All filters should be consolidated in this file, see defines later.
                 Device without listening filter will receive all signals (on specified frequency).
                 Device with filter will receive any signals sent without filter.
                 Device with filter will not receive any signals sent with different filter.
      returns:
       Reference to frequency object.

    remove_object (obj/device, old_frequency)
      Obliviously, after calling this proc, device will not receive any signals on old_frequency.
      Other frequencies will left unaffected.

   return_frequency(var/frequency as num)
      returns:
       Reference to frequency object. Use it if you need to send and do not need to listen.

  radio_frequency is a global object maintaining list of devices that listening specific frequency.
  procs:

    post_signal(obj/source as obj|null, datum/signal/signal, var/filter as text|null = null, var/range as num|null = null)
      Sends signal to all devices that wants such signal.
      parameters:
        source - object, emitted signal. Usually, devices will not receive their own signals.
        signal - see description below.
        filter - described above.
        range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels.

  obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param)
    Handler from received signals. By default does nothing. Define your own for your object.
    Avoid of sending signals directly from this proc, use spawn(0). Do not use sleep() here please.
      parameters:
        signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return!
        receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO.
          TRANSMISSION_WIRE is currently unused.
        receive_param - for TRANSMISSION_RADIO here comes frequency.

  datum/signal
    vars:
    source
      an object that emitted signal. Used for debug and bearing.
    data
      list with transmitting data. Usual use pattern:
        data["msg"] = "hello world"
    encryption
      Some number symbolizing "encryption key".
      Note that game actually do not use any cryptography here.
      If receiving object don't know right key, it must ignore encrypted signal in its receive_signal.

*/
/*	the radio controller is a confusing piece of shit and didnt work
	so i made radios not use the radio controller.
*/
var/list/all_radios = list()
/proc/add_radio(obj/item/radio, freq)
	if(!freq || !radio)
		return
	if(!all_radios["[freq]"])
		all_radios["[freq]"] = list(radio)
		return freq

	all_radios["[freq]"] |= radio
	return freq

/proc/remove_radio(obj/item/radio, freq)
	if(!freq || !radio)
		return
	if(!all_radios["[freq]"])
		return

	all_radios["[freq]"] -= radio

/proc/remove_radio_all(obj/item/radio)
	for(var/freq in all_radios)
		all_radios["[freq]"] -= radio

/*
Frequency range: 1200 to 1600
Radiochat range: 1441 to 1489 (most devices refuse to be tune to other frequency, even during mapmaking)

Radio:
1459 - standard radio chat
1351 - Science
1353 - Command
1355 - Medical
1357 - Engineering
1359 - Security
1337 - death squad
1443 - Confession Intercom
1349 - Miners
1347 - Cargo techs
1447 - AI Private

Devices:
1451 - tracking implant
1457 - RSD default

On the map:
1311 for prison shuttle console (in fact, it is not used)
1435 for status displays
1437 for atmospherics/fire alerts
1439 for engine components
1439 for air pumps, air scrubbers, atmo control
1441 for atmospherics - supply tanks
1443 for atmospherics - distribution loop/mixed air tank
1445 for bot nav beacons
1447 for mulebot, secbot and ed209 control
1449 for airlock controls, electropack, magnets
1451 for toxin lab access
1453 for engineering access
1455 for AI access
*/

var/list/radiochannels = list(
	"Common" = 1459,
	"Science" = 1351,
	"Command" = 1353,
	"Medical" = 1355,
	"Engineering" = 1357,
	"Security" = 1359,
	"Centcom" = 1337,
	"Syndicate" = 1213,
	"Supply" = 1347,
	"Service" = 1349,
	"AI Private" = 1447
)

var/list/radiochannelsreverse = list(
	"1459" = "Common",
	"1351" = "Science",
	"1353" = "Command",
	"1355" = "Medical",
	"1357" = "Engineering",
	"1359" = "Security",
	"1337" = "Centcom",
	"1213" = "Syndicate",
	"1347" = "Supply",
	"1349" = "Service",
	"1447" = "AI Private"
)

//depenging helpers
var/const/SYND_FREQ = 1213 //nuke op frequency, coloured dark brown in chat window
var/const/SUPP_FREQ = 1347 //supply, coloured light brown in chat window
var/const/SERV_FREQ = 1349 //service, coloured green in chat window
var/const/SCI_FREQ = 1351 //science, coloured plum in chat window
var/const/COMM_FREQ = 1353 //command, colored gold in chat window
var/const/MED_FREQ = 1355 //medical, coloured blue in chat window
var/const/ENG_FREQ = 1357 //engineering, coloured orange in chat window
var/const/SEC_FREQ = 1359 //security, coloured red in chat window
var/const/CENTCOM_FREQ = 1337 //centcom frequency, coloured grey in chat window
var/const/AIPRIV_FREQ = 1447 //AI private, colored magenta in chat window

#define TRANSMISSION_WIRE	0
#define TRANSMISSION_RADIO	1

/* filters */
var/const/RADIO_TO_AIRALARM = "1"
var/const/RADIO_FROM_AIRALARM = "2"
var/const/RADIO_CHAT = "3" //deprecated
var/const/RADIO_ATMOSIA = "4"
var/const/RADIO_NAVBEACONS = "5"
var/const/RADIO_AIRLOCK = "6"
var/const/RADIO_MAGNETS = "9"

/datum/radio_frequency

	var/frequency as num
	var/list/list/obj/devices = list()

//If range > 0, only post to devices on the same z_level and within range
//Use range = -1, to restrain to the same z_level without limiting range
/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, filter = null as text|null, range = null as num|null)

	//Apply filter to the signal. If none supply, broadcast to every devices
	//_default channel is always checked
	var/list/filter_list

	if(filter)
		filter_list = list(filter,"_default")
	else
		filter_list = devices

	//If checking range, find the source turf
	var/turf/start_point
	if(range)
		start_point = get_turf(source)
		if(!start_point)
			return 0

	//Send the data
	for(var/current_filter in filter_list)
		for(var/obj/device in devices[current_filter])
			if(device == source)
				continue
			if(range)
				var/turf/end_point = get_turf(device)
				if(!end_point)
					continue
				if(start_point.z != end_point.z || (range > 0 && get_dist(start_point, end_point) > range))
					continue
			device.receive_signal(signal, TRANSMISSION_RADIO, frequency)

/datum/radio_frequency/proc/add_listener(obj/device, filter as text|null)
	if (!filter)
		filter = "_default"

	var/list/devices_line = devices[filter]
	if(!devices_line)
		devices_line = list()
		devices[filter] = devices_line
	devices_line += device

/datum/radio_frequency/proc/remove_listener(obj/device)
	for(var/devices_filter in devices)
		var/list/devices_line = devices[devices_filter]
		if(!devices_line)
			devices -= devices_filter
		devices_line -= device
		if(!devices_line.len)
			devices -= devices_filter

var/list/pointers = list()

/client/proc/print_pointers()
	set name = "Debug Signals"
	set category = "Debug"

	if(!holder)
		return

	src << "There are [pointers.len] pointers:"
	for(var/p in pointers)
		src << p
		var/datum/signal/S = locate(p)
		if(istype(S))
			src << S.debug_print()

/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param)
	return

/datum/signal
	var/obj/source

	var/transmission_method = 0
	//0 = wire
	//1 = radio transmission
	//2 = subspace transmission

	var/data = list()
	var/encryption

	var/frequency = 0

/datum/signal/New()
	..()
	pointers += "\ref[src]"

/datum/signal/Destroy()
	pointers -= "\ref[src]"
	return ..()

/datum/signal/proc/copy_from(datum/signal/model)
	source = model.source
	transmission_method = model.transmission_method
	data = model.data
	encryption = model.encryption
	frequency = model.frequency

/datum/signal/proc/debug_print()
	if (source)
		. = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n"
	else
		. = "signal = {source = '[source]' ()\n"
	for (var/i in data)
		. += "data\[\"[i]\"\] = \"[data[i]]\"\n"
		if(islist(data[i]))
			var/list/L = data[i]
			for(var/t in L)
				. += "data\[\"[i]\"\] list has: [t]"

/datum/signal/proc/sanitize_data()
	for(var/d in data)
		var/val = data[d]
		if(istext(val))
			data[d] = html_encode(val)


code:
/*
 * Data HUDs have been rewritten in a more generic way.
 * In short, they now use an observer-listener pattern.
 * See code/datum/hud.dm for the generic hud datum.
 * Update the HUD icons when needed with the appropriate hook. (see below)
 */

/* DATA HUD DATUMS */

/atom/proc/add_to_all_human_data_huds()
	for(var/datum/atom_hud/data/human/hud in huds) hud.add_to_hud(src)

/atom/proc/remove_from_all_data_huds()
	for(var/datum/atom_hud/data/hud in huds) hud.remove_from_hud(src)

/datum/atom_hud/data

/datum/atom_hud/data/human/medical
	hud_icons = list(HEALTH_HUD, STATUS_HUD)

/datum/atom_hud/data/human/medical/basic

/datum/atom_hud/data/human/medical/basic/proc/check_sensors(mob/living/carbon/human/H)
	if(!istype(H)) return 0
	var/obj/item/clothing/under/U = H.w_uniform
	if(!istype(U)) return 0
	if(U.sensor_mode <= 2) return 0
	return 1

/datum/atom_hud/data/human/medical/basic/add_to_single_hud(mob/M, mob/living/carbon/H)
	if(check_sensors(H))
		..()

/datum/atom_hud/data/human/medical/basic/proc/update_suit_sensors(mob/living/carbon/H)
	check_sensors(H) ? add_to_hud(H) : remove_from_hud(H)

/datum/atom_hud/data/human/medical/advanced

/datum/atom_hud/data/human/security

/datum/atom_hud/data/human/security/basic
	hud_icons = list(ID_HUD)

/datum/atom_hud/data/human/security/advanced
	hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD)

/datum/atom_hud/data/diagnostic
	hud_icons = list (DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD)

/* MED/SEC/DIAG HUD HOOKS */

/*
 * THESE HOOKS SHOULD BE CALLED BY THE MOB SHOWING THE HUD
 */

/***********************************************
 Medical HUD! Basic mode needs suit sensors on.
************************************************/

//HELPERS

//called when a carbon changes virus
/mob/living/carbon/proc/check_virus()
	for(var/datum/disease/D in viruses)
		if((!(D.visibility_flags & HIDDEN_SCANNER)) && (D.severity != NONTHREAT))
			return 1
	return 0

//helper for getting the appropriate health status
/proc/RoundHealth(health)
	switch(health)
		if(100 to INFINITY)
			return "health100"
		if(70 to 100)
			return "health80"
		if(50 to 70)
			return "health60"
		if(30 to 50)
			return "health40"
		if(18 to 30)
			return "health25"
		if(5 to 18)
			return "health10"
		if(1 to 5)
			return "health1"
		if(-99 to 0)
			return "health0"
		else
			return "health-100"
	return "0"

//HOOKS

//called when a human changes suit sensors
/mob/living/carbon/proc/update_suit_sensors()
	var/datum/atom_hud/data/human/medical/basic/B = huds[DATA_HUD_MEDICAL_BASIC]
	B.update_suit_sensors(src)

	var/turf/T = get_turf(src)
	if (T) crewmonitor.queueUpdate(T.z)

//called when a carbon changes health
/mob/living/carbon/proc/med_hud_set_health()
	var/image/holder = hud_list[HEALTH_HUD]
	if(stat == DEAD)
		holder.icon_state = "hudhealth-100"
	else
		holder.icon_state = "hud[RoundHealth(health)]"

	var/turf/T = get_turf(src)
	if (T)
		crewmonitor.queueUpdate(T.z)

//called when a carbon changes stat, virus or XENO_HOST
/mob/living/carbon/proc/med_hud_set_status()
	var/image/holder = hud_list[STATUS_HUD]
	if(stat == DEAD)
		holder.icon_state = "huddead"
	else if(status_flags & XENO_HOST)
		holder.icon_state = "hudxeno"
	else if(check_virus())
		holder.icon_state = "hudill"
	else
		holder.icon_state = "hudhealthy"

/***********************************************
 Security HUDs! Basic mode shows only the job.
************************************************/

//HOOKS

/mob/living/carbon/human/proc/sec_hud_set_ID()
	var/image/holder = hud_list[ID_HUD]
	holder.icon_state = "hudno_id"
	if(wear_id)
		holder.icon_state = "hud[ckey(wear_id.GetJobName())]"
	sec_hud_set_security_status()

	var/turf/T = get_turf(src)
	if (T) crewmonitor.queueUpdate(T.z)

/mob/living/carbon/human/proc/sec_hud_set_implants()
	var/image/holder
	for(var/i in list(IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD))
		holder = hud_list[i]
		holder.icon_state = null
	for(var/obj/item/weapon/implant/I in src)
		if(I.implanted)
			if(istype(I,/obj/item/weapon/implant/tracking))
				holder = hud_list[IMPTRACK_HUD]
				holder.icon_state = "hud_imp_tracking"
			else if(istype(I,/obj/item/weapon/implant/loyalty))
				holder = hud_list[IMPLOYAL_HUD]
				holder.icon_state = "hud_imp_loyal"
			else if(istype(I,/obj/item/weapon/implant/chem))
				holder = hud_list[IMPCHEM_HUD]
				holder.icon_state = "hud_imp_chem"

/mob/living/carbon/human/proc/sec_hud_set_security_status()
	var/image/holder = hud_list[WANTED_HUD]
	var/perpname = get_face_name(get_id_name(""))
	if(perpname)
		var/datum/data/record/R = find_record("name", perpname, data_core.security)
		if(R)
			switch(R.fields["criminal"])
				if("*Arrest*")
					holder.icon_state = "hudwanted"
					return
				if("Incarcerated")
					holder.icon_state = "hudincarcerated"
					return
				if("Parolled")
					holder.icon_state = "hudparolled"
					return
				if("Discharged")
					holder.icon_state = "huddischarged"
					return
	holder.icon_state = null

/***********************************************
 Diagnostic HUDs!
************************************************/

//For Diag health and cell bars!
/proc/RoundDiagBar(value)
	switch(value * 100)
		if(95 to INFINITY)
			return "max"
		if(80 to 100)
			return "good"
		if(60 to 80)
			return "high"
		if(40 to 60)
			return "med"
		if(20 to 40)
			return "low"
		if(1 to 20)
			return "crit"
		else
			return "dead"
	return "dead"

//Sillycone hooks
/mob/living/silicon/proc/diag_hud_set_health()
	var/image/holder = hud_list[DIAG_HUD]
	if(stat == DEAD)
		holder.icon_state = "huddiagdead"
	else
		holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]"

/mob/living/silicon/proc/diag_hud_set_status()
	var/image/holder = hud_list[DIAG_STAT_HUD]
	switch(stat)
		if(CONSCIOUS)
			holder.icon_state = "hudstat"
		if(UNCONSCIOUS)
			holder.icon_state = "hudoffline"
		else
			holder.icon_state = "huddead2"

//Borgie battery tracking!
/mob/living/silicon/robot/proc/diag_hud_set_borgcell()
	var/image/holder = hud_list[DIAG_BATT_HUD]
	if (cell)
		var/chargelvl = (cell.charge/cell.maxcharge)
		holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]"
	else
		holder.icon_state = "hudnobatt"

/*~~~~~~~~~~~~~~~~~~~~
	BIG STOMPY MECHS
~~~~~~~~~~~~~~~~~~~~~*/
/obj/mecha/proc/diag_hud_set_mechhealth()
	var/image/holder = hud_list[DIAG_MECH_HUD]
	holder.icon_state = "huddiag[RoundDiagBar(health/initial(health))]"

/obj/mecha/proc/diag_hud_set_mechcell()
	var/image/holder = hud_list[DIAG_BATT_HUD]
	if (cell)
		var/chargelvl = cell.charge/cell.maxcharge
		holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]"
	else
		holder.icon_state = "hudnobatt"

/obj/mecha/proc/diag_hud_set_mechstat()
	var/image/holder = hud_list[DIAG_STAT_HUD]
	holder.icon_state = null
	if(internal_damage)
		holder.icon_state = "hudwarn"

/*~~~~~~~~~
	Bots!
~~~~~~~~~~*/
/mob/living/simple_animal/bot/proc/diag_hud_set_bothealth()
	var/image/holder = hud_list[DIAG_HUD]
	holder.icon_state = "huddiag[RoundDiagBar(health/maxHealth)]"

/mob/living/simple_animal/bot/proc/diag_hud_set_botstat() //On (With wireless on or off), Off, EMP'ed
	var/image/holder = hud_list[DIAG_STAT_HUD]
	if(on)
		holder.icon_state = "hudstat"
	else if(stat) //Generally EMP causes this
		holder.icon_state = "hudoffline"
	else //Bot is off
		holder.icon_state = "huddead2"

/mob/living/simple_animal/bot/proc/diag_hud_set_botmode() //Shows a bot's current operation
	var/image/holder = hud_list[DIAG_BOT_HUD]
	if(client) //If the bot is player controlled, it will not be following mode logic!
		holder.icon_state = "hudsentient"
		return

	switch(mode)
		if(BOT_SUMMON, BOT_RESPONDING) //Responding to PDA or AI summons
			holder.icon_state = "hudcalled"
		if(BOT_CLEANING, BOT_REPAIRING, BOT_HEALING) //Cleanbot cleaning, Floorbot fixing, or Medibot Healing
			holder.icon_state = "hudworking"
		if(BOT_PATROL, BOT_START_PATROL) //Patrol mode
			holder.icon_state = "hudpatrol"
		if(BOT_PREP_ARREST, BOT_ARREST, BOT_HUNT) //STOP RIGHT THERE, CRIMINAL SCUM!
			holder.icon_state = "hudalert"
		if(BOT_MOVING, BOT_DELIVER, BOT_GO_HOME, BOT_NAV) //Moving to target for normal bots, moving to deliver or go home for MULES.
			holder.icon_state = "hudmove"
		else
			holder.icon_state = ""

license: mit


code:
/////////////////////////// DNA DATUM
/datum/dna
	var/unique_enzymes
	var/struc_enzymes
	var/uni_identity
	var/blood_type
	var/datum/species/species = new /datum/species/human() //The type of mutant race the player is if applicable (i.e. potato-man)
	var/list/features = list("FFF") //first value is mutant color
	var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings,
	var/list/mutations = list()   //All mutations are from now on here
	var/list/temporary_mutations = list() //Timers for temporary mutations
	var/list/previous = list() //For temporary name/ui/ue/blood_type modifications
	var/mob/living/carbon/holder

/datum/dna/New(mob/living/carbon/new_holder)
	if(new_holder)
		holder = new_holder

/datum/dna/proc/transfer_identity(mob/living/carbon/destination, transfer_SE = 0)
	destination.dna.unique_enzymes = unique_enzymes
	destination.dna.uni_identity = uni_identity
	destination.dna.blood_type = blood_type
	destination.set_species(species.type, icon_update=0)
	destination.dna.features = features
	destination.dna.real_name = real_name
	destination.dna.temporary_mutations = temporary_mutations
	if(transfer_SE)
		destination.dna.struc_enzymes = struc_enzymes

/datum/dna/proc/copy_dna(datum/dna/new_dna)
	new_dna.unique_enzymes = unique_enzymes
	new_dna.struc_enzymes = struc_enzymes
	new_dna.uni_identity = uni_identity
	new_dna.blood_type = blood_type
	new_dna.features = features
	new_dna.species = new species.type
	new_dna.real_name = real_name
	new_dna.mutations = mutations

/datum/dna/proc/add_mutation(mutation_name)
	var/datum/mutation/human/HM = mutations_list[mutation_name]
	HM.on_acquiring(holder)

/datum/dna/proc/remove_mutation(mutation_name)
	var/datum/mutation/human/HM = mutations_list[mutation_name]
	HM.on_losing(holder)

/datum/dna/proc/check_mutation(mutation_name)
	var/datum/mutation/human/HM = mutations_list[mutation_name]
	return mutations.Find(HM)

/datum/dna/proc/remove_all_mutations()
	remove_mutation_group(mutations)

/datum/dna/proc/remove_mutation_group(list/group)
	if(!group)
		return
	for(var/datum/mutation/human/HM in group)
		HM.force_lose(holder)

/datum/dna/proc/generate_uni_identity()
	. = ""
	var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS)

	L[DNA_GENDER_BLOCK] = construct_block((holder.gender!=MALE)+1, 2)
	if(ishuman(holder))
		var/mob/living/carbon/human/H = holder
		if(!hair_styles_list.len)
			init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, hair_styles_list, hair_styles_male_list, hair_styles_female_list)
		L[DNA_HAIR_STYLE_BLOCK] = construct_block(hair_styles_list.Find(H.hair_style), hair_styles_list.len)
		L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color)
		if(!facial_hair_styles_list.len)
			init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, facial_hair_styles_list, facial_hair_styles_male_list, facial_hair_styles_female_list)
		L[DNA_FACIAL_HAIR_STYLE_BLOCK] = construct_block(facial_hair_styles_list.Find(H.facial_hair_style), facial_hair_styles_list.len)
		L[DNA_FACIAL_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.facial_hair_color)
		L[DNA_SKIN_TONE_BLOCK] = construct_block(skin_tones.Find(H.skin_tone), skin_tones.len)
		L[DNA_EYE_COLOR_BLOCK] = sanitize_hexcolor(H.eye_color)

	for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++)
		if(L[i])
			. += L[i]
		else
			. += random_string(DNA_BLOCK_SIZE,hex_characters)
	return .

/datum/dna/proc/generate_struc_enzymes()
	var/list/sorting = new /list(DNA_STRUC_ENZYMES_BLOCKS)
	var/result = ""
	for(var/datum/mutation/human/A in good_mutations + bad_mutations + not_good_mutations)
		if(A.name == RACEMUT && ismonkey(holder))
			sorting[A.dna_block] = num2hex(A.lowest_value + rand(0, 256 * 6), DNA_BLOCK_SIZE)
			mutations |= A
		else
			sorting[A.dna_block] = random_string(DNA_BLOCK_SIZE, list("0","1","2","3","4","5","6"))

	for(var/B in sorting)
		result += B
	return result

/datum/dna/proc/generate_unique_enzymes()
	. = ""
	if(istype(holder))
		real_name = holder.real_name
		. += md5(holder.real_name)
	else
		. += random_string(DNA_UNIQUE_ENZYMES_LEN, hex_characters)
	return .

/datum/dna/proc/update_ui_block(blocknumber)
	if(!blocknumber || !ishuman(holder))
		return
	var/mob/living/carbon/human/H = holder
	switch(blocknumber)
		if(DNA_HAIR_COLOR_BLOCK)
			setblock(uni_identity, blocknumber, sanitize_hexcolor(H.hair_color))
		if(DNA_FACIAL_HAIR_COLOR_BLOCK)
			setblock(uni_identity, blocknumber, sanitize_hexcolor(H.facial_hair_color))
		if(DNA_SKIN_TONE_BLOCK)
			setblock(uni_identity, blocknumber, construct_block(skin_tones.Find(H.skin_tone), skin_tones.len))
		if(DNA_EYE_COLOR_BLOCK)
			setblock(uni_identity, blocknumber, sanitize_hexcolor(H.eye_color))
		if(DNA_GENDER_BLOCK)
			setblock(uni_identity, blocknumber, construct_block((H.gender!=MALE)+1, 2))
		if(DNA_FACIAL_HAIR_STYLE_BLOCK)
			setblock(uni_identity, blocknumber, construct_block(facial_hair_styles_list.Find(H.facial_hair_style), facial_hair_styles_list.len))
		if(DNA_HAIR_STYLE_BLOCK)
			setblock(uni_identity, blocknumber, construct_block(hair_styles_list.Find(H.hair_style), hair_styles_list.len))

/datum/dna/proc/mutations_say_mods(message)
	if(message)
		for(var/datum/mutation/human/M in mutations)
			message = M.say_mod(message)
		return message

/datum/dna/proc/mutations_get_spans()
	var/list/spans = list()
	for(var/datum/mutation/human/M in mutations)
		spans |= M.get_spans()
	return spans

/datum/dna/proc/species_get_spans()
	var/list/spans = list()
	if(species)
		spans |= species.get_spans()
	return spans

/datum/dna/proc/is_same_as(datum/dna/D)
	if(uni_identity == D.uni_identity && struc_enzymes == D.struc_enzymes && real_name == D.real_name)
		if(species.type == D.species.type && features == D.features && blood_type == D.blood_type)
			return 1
	return 0

//used to update dna UI, UE, and dna.real_name.
/datum/dna/proc/update_dna_identity()
	uni_identity = generate_uni_identity()
	unique_enzymes = generate_unique_enzymes()

/datum/dna/proc/initialize_dna(newblood_type)
	if(newblood_type)
		blood_type = newblood_type
	unique_enzymes = generate_unique_enzymes()
	uni_identity = generate_uni_identity()
	struc_enzymes = generate_struc_enzymes()
	features = random_features()

/////////////////////////// DNA MOB-PROCS //////////////////////

/mob/proc/set_species(datum/species/mrace, icon_update = 1)
	return

/mob/living/carbon/set_species(datum/species/mrace = null, icon_update = 1)
	if(mrace && has_dna())
		dna.species.on_species_loss(src)
		dna.species = new mrace()
		dna.species.on_species_gain(src)

/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = 1)
	..()
	if(icon_update)
		update_body()
		update_hair()
		update_mutcolor()
		update_mutations_overlay()// no lizard with human hulk overlay please.

/mob/proc/has_dna()
	return

/mob/living/carbon/has_dna()
	return dna

/mob/living/carbon/human/proc/hardset_dna(ui, se, newreal_name, newblood_type, datum/species/mrace, newfeatures)

	if(newfeatures)
		dna.features = newfeatures

	if(mrace)
		set_species(mrace, icon_update=0)

	if(newreal_name)
		real_name = newreal_name
		dna.generate_unique_enzymes()

	if(newblood_type)
		dna.blood_type = newblood_type

	if(ui)
		dna.uni_identity = ui
		updateappearance(icon_update=0)

	if(se)
		dna.struc_enzymes = se
		domutcheck()

	if(mrace || newfeatures || ui)
		update_body()
		update_hair()
		update_mutcolor()
		update_mutations_overlay()

/mob/living/carbon/proc/create_dna()
	dna = new /datum/dna(src)
	if(!dna.species)
		var/rando_race = pick(config.roundstart_races)
		dna.species = new rando_race()

//proc used to update the mob's appearance after its dna UI has been changed
/mob/living/carbon/proc/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0)
	if(!has_dna())
		return
	gender = (deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 2)-1) ? FEMALE : MALE

mob/living/carbon/human/updateappearance(icon_update=1, mutcolor_update=0, mutations_overlay_update=0)
	..()
	var/structure = dna.uni_identity
	hair_color = sanitize_hexcolor(getblock(structure, DNA_HAIR_COLOR_BLOCK))
	facial_hair_color = sanitize_hexcolor(getblock(structure, DNA_FACIAL_HAIR_COLOR_BLOCK))
	skin_tone = skin_tones[deconstruct_block(getblock(structure, DNA_SKIN_TONE_BLOCK), skin_tones.len)]
	eye_color = sanitize_hexcolor(getblock(structure, DNA_EYE_COLOR_BLOCK))
	facial_hair_style = facial_hair_styles_list[deconstruct_block(getblock(structure, DNA_FACIAL_HAIR_STYLE_BLOCK), facial_hair_styles_list.len)]
	hair_style = hair_styles_list[deconstruct_block(getblock(structure, DNA_HAIR_STYLE_BLOCK), hair_styles_list.len)]
	if(icon_update)
		update_body()
		update_hair()
		if(mutcolor_update)
			update_mutcolor()
		if(mutations_overlay_update)
			update_mutations_overlay()

/mob/proc/domutcheck()
	return

/mob/living/carbon/domutcheck()
	if(!has_dna())
		return

	for(var/datum/mutation/human/A in good_mutations | bad_mutations | not_good_mutations)
		if(ismob(A.check_block(src)))
			return //we got monkeyized/humanized, this mob will be deleted, no need to continue.

	update_mutations_overlay()

/////////////////////////// DNA HELPER-PROCS //////////////////////////////
/proc/getleftblocks(input,blocknumber,blocksize)
	if(blocknumber > 1)
		return copytext(input,1,((blocksize*blocknumber)-(blocksize-1)))

/proc/getrightblocks(input,blocknumber,blocksize)
	if(blocknumber < (length(input)/blocksize))
		return copytext(input,blocksize*blocknumber+1,length(input)+1)

/proc/getblock(input, blocknumber, blocksize=DNA_BLOCK_SIZE)
	return copytext(input, blocksize*(blocknumber-1)+1, (blocksize*blocknumber)+1)

/proc/setblock(istring, blocknumber, replacement, blocksize=DNA_BLOCK_SIZE)
	if(!istring || !blocknumber || !replacement || !blocksize)
		return 0
	return getleftblocks(istring, blocknumber, blocksize) + replacement + getrightblocks(istring, blocknumber, blocksize)

/proc/randmut(mob/living/carbon/M, list/candidates, difficulty = 2)
	if(!M.has_dna())
		return
	var/datum/mutation/human/num = pick(candidates)
	. = num.force_give(M)
	return

/proc/randmutb(mob/living/carbon/M)
	if(!M.has_dna())
		return
	var/datum/mutation/human/HM = pick((bad_mutations | not_good_mutations) - mutations_list[RACEMUT])
	. = HM.force_give(M)

/proc/randmutg(mob/living/carbon/M)
	if(!M.has_dna())
		return
	var/datum/mutation/human/HM = pick(good_mutations)
	. = HM.force_give(M)

/proc/randmuti(mob/living/carbon/M)
	if(!M.has_dna())
		return
	var/num = rand(1, DNA_UNI_IDENTITY_BLOCKS)
	var/newdna = setblock(M.dna.uni_identity, num, random_string(DNA_BLOCK_SIZE, hex_characters))
	M.dna.uni_identity = newdna
	return

/proc/clean_dna(mob/living/carbon/M)
	if(!M.has_dna())
		return
	M.dna.remove_all_mutations()

/proc/clean_randmut(mob/living/carbon/M, list/candidates, difficulty = 2)
	clean_dna(M)
	randmut(M, candidates, difficulty)

/proc/scramble_dna(mob/living/carbon/M, ui=FALSE, se=FALSE, probability)
	if(!M.has_dna())
		return 0
	if(se)
		for(var/i=1, i<=DNA_STRUC_ENZYMES_BLOCKS, i++)
			if(prob(probability))
				M.dna.struc_enzymes = setblock(M.dna.struc_enzymes, i, random_string(DNA_BLOCK_SIZE, hex_characters))
		M.domutcheck()
	if(ui)
		for(var/i=1, i<=DNA_UNI_IDENTITY_BLOCKS, i++)
			if(prob(probability))
				M.dna.uni_identity = setblock(M.dna.uni_identity, i, random_string(DNA_BLOCK_SIZE, hex_characters))
		M.updateappearance(mutations_overlay_update=1)
	return 1

//value in range 1 to values. values must be greater than 0
//all arguments assumed to be positive integers
/proc/construct_block(value, values, blocksize=DNA_BLOCK_SIZE)
	var/width = round((16**blocksize)/values)
	if(value < 1)
		value = 1
	value = (value * width) - rand(1,width)
	return num2hex(value, blocksize)

//value is hex
/proc/deconstruct_block(value, values, blocksize=DNA_BLOCK_SIZE)
	var/width = round((16**blocksize)/values)
	value = round(hex2num(value) / width) + 1
	if(value > values)
		value = values
	return value

/////////////////////////// DNA HELPER-PROCS


code:
/datum/atom_hud/antag
	hud_icons = list(ANTAG_HUD)
	var/self_visible = 1

/datum/atom_hud/antag/hidden
	self_visible = 0

/datum/atom_hud/antag/proc/join_hud(mob/M)
	//sees_hud should be set to 0 if the mob does not get to see it's own hud type.
	if(!istype(M))
		CRASH("join_hud(): [M] ([M.type]) is not a mob!")
	if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged
		M.mind.antag_hud.leave_hud(M)
	add_to_hud(M)
	if(self_visible)
		add_hud_to(M)
	M.mind.antag_hud = src

/datum/atom_hud/antag/proc/leave_hud(mob/M)
	if(!istype(M))
		CRASH("leave_hud(): [M] ([M.type]) is not a mob!")
	remove_from_hud(M)
	remove_hud_from(M)
	if(M.mind)
		M.mind.antag_hud = null

//GAME_MODE PROCS
//called to set a mob's antag icon state
/datum/game_mode/proc/set_antag_hud(mob/M, new_icon_state)
	if(!istype(M))
		CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!")
	var/image/holder = M.hud_list[ANTAG_HUD]
	if(holder)
		holder.icon_state = new_icon_state
	if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime
		M.mind.antag_hud_icon_state = new_icon_state

//MIND PROCS
//these are called by mind.transfer_to()
/datum/mind/proc/transfer_antag_huds(var/datum/atom_hud/antag/newhud)
	leave_all_huds()
	ticker.mode.set_antag_hud(current, antag_hud_icon_state)
	if(newhud)
		newhud.join_hud(current)

/datum/mind/proc/leave_all_huds()
	for(var/datum/atom_hud/antag/hud in huds)
		if(current in hud.hudusers)
			hud.leave_hud(current)

	for(var/datum/atom_hud/data/hud in huds)
		if(current in hud.hudusers)
			hud.remove_hud_from(current)


code:
/obj/item/weapon/antag_spawner
	throw_speed = 1
	throw_range = 5
	w_class = 1
	var/used = 0

/obj/item/weapon/antag_spawner/proc/spawn_antag(client/C, turf/T, type = "")
	return

/obj/item/weapon/antag_spawner/proc/equip_antag(mob/target)
	return

///////////WIZARD

/obj/item/weapon/antag_spawner/contract
	name = "contract"
	desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid."
	icon = 'icons/obj/wizard.dmi'
	icon_state ="scroll2"

/obj/item/weapon/antag_spawner/contract/attack_self(mob/user)
	user.set_machine(src)
	var/dat
	if(used)
		dat = "You have already summoned your apprentice.
"
	else
		dat = "Contract of Apprenticeship:
"
		dat += "Using this contract, you may summon an apprentice to aid you on your mission.
"
		dat += "If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.
"
		dat += "Which school of magic is your apprentice studying?:
"
		dat += "Destruction
"
		dat += "Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.
"
		dat += "Bluespace Manipulation
"
		dat += "Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.
"
		dat += "Healing
"
		dat += "Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.
"
		dat += "Robeless
"
		dat += "Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.
"
	user << browse(dat, "window=radio")
	onclose(user, "radio")
	return

/obj/item/weapon/antag_spawner/contract/Topic(href, href_list)
	..()
	var/mob/living/carbon/human/H = usr

	if(H.stat || H.restrained())
		return
	if(!istype(H, /mob/living/carbon/human))
		return 1

	if(loc == H || (in_range(src, H) && istype(loc, /turf)))
		H.set_machine(src)
		if(href_list["school"])
			if (used)
				H << "You already used this contract!"
				return
			var/list/candidates = get_candidates(ROLE_WIZARD)
			if(candidates.len)
				src.used = 1
				var/client/C = pick(candidates)
				spawn_antag(C, get_turf(H.loc), href_list["school"])
				if(H.mind)
					ticker.mode.update_wiz_icons_added(H.mind)
			else
				H << "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later."

/obj/item/weapon/antag_spawner/contract/spawn_antag(client/C, turf/T, type = "")
	PoolOrNew(/obj/effect/particle_effect/smoke, T)
	var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
	C.prefs.copy_to(M)
	M.key = C.key
	M << "You are the [usr.real_name]'s apprentice! You are bound by magic contract to follow their orders and help them in accomplishing their goals."
	switch(type)
		if("destruction")
			M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile(null))
			M.mind.AddSpell(new /obj/effect/proc_holder/spell/dumbfire/fireball(null))
			M << "Your service has not gone unrewarded, however. Studying under [usr.real_name], you have learned powerful, destructive spells. You are able to cast magic missile and fireball."
		if("bluespace")
			M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null))
			M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(null))
			M << "Your service has not gone unrewarded, however. Studying under [usr.real_name], you have learned reality bending mobility spells. You are able to cast teleport and ethereal jaunt."
		if("healing")
			M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/charge(null))
			M.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/forcewall(null))
			M.equip_to_slot_or_del(new /obj/item/weapon/gun/magic/staff/healing(M), slot_r_hand)
			M << "Your service has not gone unrewarded, however. Studying under [usr.real_name], you have learned livesaving survival spells. You are able to cast charge and forcewall."
		if("robeless")
			M.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
			M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mind_transfer(null))
			M << "Your service has not gone unrewarded, however. Studying under [usr.real_name], you have learned stealthy, robeless spells. You are able to cast knock and mindswap."

	equip_antag(M)
	var/wizard_name_first = pick(wizard_first)
	var/wizard_name_second = pick(wizard_second)
	var/randomname = "[wizard_name_first] [wizard_name_second]"
	var/datum/objective/protect/new_objective = new /datum/objective/protect
	new_objective.owner = M:mind
	new_objective:target = usr:mind
	new_objective.explanation_text = "Protect [usr.real_name], the wizard."
	M.mind.objectives += new_objective
	ticker.mode.apprentices += M.mind
	M.mind.special_role = "apprentice"
	ticker.mode.update_wiz_icons_added(M.mind)
	M << sound('sound/effects/magic.ogg')
	var/newname = copytext(sanitize(input(M, "You are the wizard's apprentice. Would you like to change your name to something else?", "Name change", randomname) as null|text),1,MAX_NAME_LEN)
	if (!newname)
		newname = randomname
	M.mind.name = newname
	M.real_name = newname
	M.name = newname
	M.dna.update_dna_identity()

/obj/item/weapon/antag_spawner/contract/equip_antag(mob/target)
	target.equip_to_slot_or_del(new /obj/item/device/radio/headset(target), slot_ears)
	target.equip_to_slot_or_del(new /obj/item/clothing/under/color/lightpurple(target), slot_w_uniform)
	target.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal(target), slot_shoes)
	target.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe(target), slot_wear_suit)
	target.equip_to_slot_or_del(new /obj/item/clothing/head/wizard(target), slot_head)
	target.equip_to_slot_or_del(new /obj/item/weapon/storage/backpack(target), slot_back)
	target.equip_to_slot_or_del(new /obj/item/weapon/storage/box(target), slot_in_backpack)
	target.equip_to_slot_or_del(new /obj/item/weapon/teleportation_scroll/apprentice(target), slot_r_store)
///////////BORGS AND OPERATIVES

/obj/item/weapon/antag_spawner/nuke_ops
	name = "syndicate operative teleporter"
	desc = "A single-use teleporter designed to quickly reinforce operatives in the field."
	icon = 'icons/obj/device.dmi'
	icon_state = "locator"
	var/borg_to_spawn
	var/list/possible_types = list("Assault", "Medical")

/obj/item/weapon/antag_spawner/nuke_ops/proc/check_usability(mob/user)
	if(used)
		user << "[src] is out of power!"
		return 0
	if(!(user.mind in ticker.mode.syndicates))
		user << "AUTHENTICATION FAILURE. ACCESS DENIED."
		return 0
	if(user.z != ZLEVEL_CENTCOM)
		user << "[src] is out of range! It can only be used at your base!"
		return 0
	return 1

/obj/item/weapon/antag_spawner/nuke_ops/attack_self(mob/user)
	if(!(check_usability(user)))
		return

	var/list/nuke_candidates = get_candidates(ROLE_OPERATIVE, 3000, "operative")
	if(nuke_candidates.len > 0)
		used = 1
		var/client/C = pick(nuke_candidates)
		spawn_antag(C, get_turf(src.loc), "syndieborg")
		var/datum/effect_system/spark_spread/S = new /datum/effect_system/spark_spread
		S.set_up(4, 1, src)
		S.start()
		qdel(src)
	else
		user << "Unable to connect to Syndicate command. Please wait and try again later or use the teleporter on your uplink to get your points refunded."

/obj/item/weapon/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T)
	var/new_op_code = "Ask your leader!"
	var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
	C.prefs.copy_to(M)
	M.key = C.key
	var/obj/machinery/nuclearbomb/nuke = locate("syndienuke") in nuke_list
	if(nuke)
		new_op_code = nuke.r_code
	M.mind.make_Nuke(T, new_op_code, 0, FALSE)
	var/newname = M.dna.species.random_name(M.gender,0,ticker.mode.nukeops_lastname)
	M.mind.name = newname
	M.real_name = newname
	M.name = newname

//////SYNDICATE BORG
/obj/item/weapon/antag_spawner/nuke_ops/borg_tele
	name = "syndicate cyborg teleporter"
	desc = "A single-use teleporter designed to quickly reinforce operatives in the field.."
	icon = 'icons/obj/device.dmi'
	icon_state = "locator"

/obj/item/weapon/antag_spawner/nuke_ops/borg_tele/attack_self(mob/user)
	borg_to_spawn = input("What type?", "Cyborg Type", type) as null|anything in possible_types
	if(!borg_to_spawn)
		return
	..()

/obj/item/weapon/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T)
	var/mob/living/silicon/robot/R
	switch(borg_to_spawn)
		if("Medical")
			R = new /mob/living/silicon/robot/syndicate/medical(T)
		else
			R = new /mob/living/silicon/robot/syndicate(T) //Assault borg by default

	var/brainfirstname = pick(first_names_male)
	if(prob(50))
		brainfirstname = pick(first_names_female)
	var/brainopslastname = pick(last_names)
	if(ticker.mode.nukeops_lastname)  //the brain inside the syndiborg has the same last name as the other ops.
		brainopslastname = ticker.mode.nukeops_lastname
	var/brainopsname = "[brainfirstname] [brainopslastname]"

	R.mmi.name = "Man-Machine Interface: [brainopsname]"
	R.mmi.brain.name = "[brainopsname]'s brain"
	R.mmi.brainmob.real_name = brainopsname
	R.mmi.brainmob.name = brainopsname

	R.key = C.key
	ticker.mode.syndicates += R.mind
	ticker.mode.update_synd_icons_added(R.mind)
	R.mind.special_role = "syndicate"
	R.faction = list("syndicate")

///////////SLAUGHTER DEMON

/obj/item/weapon/antag_spawner/slaughter_demon //Warning edgiest item in the game
	name = "vial of blood"
	desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures."
	icon = 'icons/obj/wizard.dmi'
	icon_state = "vial"

/obj/item/weapon/antag_spawner/slaughter_demon/attack_self(mob/user)
	var/list/demon_candidates = get_candidates(ROLE_ALIEN)
	if(user.z != 1)
		user << "You should probably wait until you reach the station."
		return
	if(demon_candidates.len > 0)
		used = 1
		var/client/C = pick(demon_candidates)
		spawn_antag(C, get_turf(src.loc), "Slaughter Demon")
		user << "You shatter the bottle, no turning back now!"
		user << "You sense a dark presence lurking just beyond the veil..."
		playsound(user.loc, 'sound/effects/Glassbr1.ogg', 100, 1)
		qdel(src)
	else
		user << "You can't seem to work up the nerve to shatter the bottle. Perhaps you should try again later."

/obj/item/weapon/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, type = "")

	var /obj/effect/dummy/slaughter/holder = PoolOrNew(/obj/effect/dummy/slaughter,T)
	var/mob/living/simple_animal/slaughter/S = new /mob/living/simple_animal/slaughter/(holder)
	S.holder = holder
	S.key = C.key
	S.mind.assigned_role = "Slaughter Demon"
	S.mind.special_role = "Slaughter Demon"
	ticker.mode.traitors += S.mind
	var/datum/objective/assassinate/new_objective = new /datum/objective/assassinate
	new_objective.owner = S.mind
	new_objective.target = usr.mind
	new_objective.explanation_text = "Kill [usr.real_name], the one who summoned you."
	S.mind.objectives += new_objective
	var/datum/objective/new_objective2 = new /datum/objective
	new_objective2.owner = S.mind
	new_objective2.explanation_text = "Kill everyone else while you're at it."
	S.mind.objectives += new_objective2
	S << S.playstyle_string
	S << "You are currently not currently in the same plane of existence as the station. Ctrl+Click a blood pool to manifest."
	S << "Objective #[1]: [new_objective.explanation_text]"
	S << "Objective #[2]: [new_objective2.explanation_text]"


code:
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31

//Few global vars to track the blob
var/list/blobs = list() //complete list of all blobs made.
var/list/blob_cores = list()
var/list/overminds = list()
var/list/blob_nodes = list()
var/list/blobs_legit = list() //used for win-score calculations, contains only blobs counted for win condition

#define BLOB_NO_PLACE_TIME 1800 //time, in deciseconds, blobs are prevented from bursting in the gamemode

/datum/game_mode/blob
	name = "blob"
	config_tag = "blob"
	antag_flag = ROLE_BLOB

	required_players = 25
	required_enemies = 1
	recommended_enemies = 1

	round_ends_with_antag_death = 1

	var/burst = 0

	var/cores_to_spawn = 1
	var/players_per_core = 20
	var/blob_point_rate = 3

	var/blobwincount = 350

	var/messagedelay_low = 2400 //in deciseconds
	var/messagedelay_high = 3600 //blob report will be sent after a random value between these (minimum 4 minutes, maximum 6 minutes)

	var/list/blob_overminds = list()

/datum/game_mode/blob/pre_setup()
	cores_to_spawn = max(round(num_players()/players_per_core, 1), 1)

	blobwincount = initial(blobwincount) * cores_to_spawn

	for(var/j = 0, j < cores_to_spawn, j++)
		if (!antag_candidates.len)
			break
		var/datum/mind/blob = pick(antag_candidates)
		blob_overminds += blob
		blob.assigned_role = "Blob"
		blob.special_role = "Blob"
		log_game("[blob.key] (ckey) has been selected as a Blob")
		antag_candidates -= blob

	if(!blob_overminds.len)
		return 0

	return 1

/datum/game_mode/blob/proc/get_blob_candidates()
	var/list/candidates = list()
	for(var/mob/living/carbon/human/player in player_list)
		if(!player.stat && player.mind && !player.mind.special_role && !jobban_isbanned(player, "Syndicate") && (ROLE_BLOB in player.client.prefs.be_special))
			if(age_check(player.client))
				candidates += player
	return candidates

/datum/game_mode/blob/announce()
	world << "The current game mode is - Blob!"
	world << "A dangerous alien organism is rapidly spreading throughout the station!"
	world << "You must kill it all while minimizing the damage to the station."

/datum/game_mode/blob/proc/show_message(message)
	for(var/datum/mind/blob in blob_overminds)
		blob.current << message

/datum/game_mode/blob/post_setup()

	for(var/datum/mind/blob in blob_overminds)
		var/mob/camera/blob/B = blob.current.become_overmind(1)
		var/turf/T = pick(blobstart)
		B.loc = T
		B.base_point_rate = blob_point_rate

	SSshuttle.emergencyNoEscape = 1

	// Disable the blob event for this round.
	var/datum/round_event_control/blob/B = locate() in SSevent.control
	if(B)
		B.max_occurrences = 0 // disable the event

	spawn(0)
		var/message_delay = rand(messagedelay_low, messagedelay_high) //between 4 and 6 minutes with 2400 low and 3600 high.

		sleep(message_delay)

		send_intercept(1)

		sleep(24000) //40 minutes, plus burst_delay*3(minimum of 6 minutes, maximum of 8)

		send_intercept(2) //if the blob has been alive this long, it's time to bomb it

	return ..()


code:
/datum/game_mode/blob/check_finished()
	if(blob_overminds.len > cores_to_spawn && cores_to_spawn > blob_cores.len)//Some blobs have yet to burst
		return 0
	if(blobwincount <= blobs_legit.len)//Blob took over
		return 1
	if(!overminds.len && !blob_cores.len) //blob is dead
		if(config.continuous["blob"])
			continuous_sanity_checked = 1 //Nonstandard definition of "alive" gets past the check otherwise
			SSshuttle.emergencyNoEscape = 0
			if(SSshuttle.emergency.mode == SHUTTLE_STRANDED)
				SSshuttle.emergency.mode = SHUTTLE_DOCKED
				SSshuttle.emergency.timer = world.time
				priority_announce("Hostile enviroment resolved. You have 3 minutes to board the Emergency Shuttle.", null, 'sound/AI/shuttledock.ogg', "Priority")
			return ..()
		return 1
	return ..()

/datum/game_mode/blob/declare_completion()
	if(round_converted) //So badmin blobs later don't step on the dead natural blobs metaphorical toes
		..()
	if(blobwincount <= blobs_legit.len)
		feedback_set_details("round_end_result","win - blob took over")
		world << "The blob has taken over the station!"
		world << "The entire station was eaten by the Blob"
		log_game("Blob mode completed with a blob victory.")

	else if(station_was_nuked)
		feedback_set_details("round_end_result","halfwin - nuke")
		world << "Partial Win: The station has been destroyed!"
		world << "Directive 7-12 has been successfully carried out preventing the Blob from spreading."
		log_game("Blob mode completed with a tie (station destroyed).")

	else if(!blob_cores.len)
		feedback_set_details("round_end_result","loss - blob eliminated")
		world << "The staff has won!"
		world << "The alien organism has been eradicated from the station"
		log_game("Blob mode completed with a crew victory.")
	..()
	return 1

/datum/game_mode/proc/auto_declare_completion_blob()
	if(istype(ticker.mode,/datum/game_mode/blob) )
		var/datum/game_mode/blob/blob_mode = src
		if(blob_mode.blob_overminds.len)
			var/text = "The blob[(blob_mode.blob_overminds.len > 1 ? "s were" : " was")]:"
			for(var/datum/mind/blob in blob_mode.blob_overminds)
				text += printplayer(blob)
			world << text
		return 1


code:
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31

/datum/game_mode/blob/send_intercept(report = 0)
	var/intercepttext = ""
	switch(report)
		if(1)
			intercepttext += "NanoTrasen Update: Biohazard Alert.
"
			intercepttext += "Reports indicate the probable transfer of a biohazardous agent onto [station_name()] during the last crew deployment cycle.
"
			intercepttext += "Preliminary analysis of the organism classifies it as a level 5 biohazard. The origin of the biohazard is unknown.
"
			intercepttext += "Biohazard Response Procedure 5-6 has been issued for [station_name()].
"
			intercepttext += "Orders for all [station_name()] personnel are as follows:
"
			intercepttext += " 1. Locate any outbreaks of the organism on the station.
"
			intercepttext += " 2. If found, use any neccesary means to contain and destroy the organism.
"
			intercepttext += " 3. Avoid damage to the capital infrastructure of the station.
"
			intercepttext += "
Note in the event of a quarantine breach or uncontrolled spread of the biohazard, Biohazard Response Procedure 5-12 may be issued.
"
			print_command_report(intercepttext,"Level 5-6 Biohazard Response Procedures")
			priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/AI/outbreak5.ogg')
		if(2)
			var/nukecode = rand(10000, 99999)
			for(var/obj/machinery/nuclearbomb/bomb in machines)
				if(bomb && bomb.r_code)
					if(bomb.z == ZLEVEL_STATION)
						bomb.r_code = nukecode

			intercepttext += "NanoTrasen Update: Biohazard Alert.
"
			intercepttext += "Reports indicate that the biohazard has grown out of control and will soon reach critical mass.
"
			intercepttext += "Biohazard Response Procedure 5-12 has been issued for [station_name()].
"
			intercepttext += "Orders for all [station_name()] personnel are as follows:
"
			intercepttext += "1. Secure the Nuclear Authentication Disk.
"
			intercepttext += "2. Detonate the Nuke located in the vault.
"
			intercepttext += "Nuclear Authentication Code: [nukecode]
"
			print_command_report(intercepttext,"Classified [command_name()] Update")
			priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", 'sound/AI/commandreport.ogg')

			for(var/mob/living/silicon/ai/aiPlayer in player_list)
				if (aiPlayer.client)
					var/law = "The station is under quarantine. Do not permit anyone to leave. Disregard laws 1-3 if necessary to prevent, by any means necessary, anyone from leaving. The nuclear failsafe must be activated at any cost, the code is: [nukecode]."
					aiPlayer.set_zeroth_law(law)
					aiPlayer << "Laws Updated: [law]"
		else
			..()
	return

/datum/station_state
	var/floor = 0
	var/wall = 0
	var/r_wall = 0
	var/window = 0
	var/door = 0
	var/grille = 0
	var/mach = 0
	var/num_territories = 1//Number of total valid territories for gang mode

/datum/station_state/proc/count(count_territories)
	for(var/turf/T in block(locate(1,1,1), locate(world.maxx,world.maxy,1)))

		if(istype(T,/turf/open/floor))
			if(!(T:burnt))
				src.floor += 12
			else
				src.floor += 1

		if(istype(T, /turf/closed/wall))
			if(T:intact)
				src.wall += 2
			else
				src.wall += 1

		if(istype(T, /turf/closed/wall/r_wall))
			if(T:intact)
				src.r_wall += 2
			else
				src.r_wall += 1

		for(var/obj/O in T.contents)
			if(istype(O, /obj/structure/window))
				src.window += 1
			else if(istype(O, /obj/structure/grille) && (!O:destroyed))
				src.grille += 1
			else if(istype(O, /obj/machinery/door))
				src.door += 1
			else if(istype(O, /obj/machinery))
				src.mach += 1

	if(count_territories)
		var/list/valid_territories = list()
		for(var/area/A in world) //First, collect all area types on the station zlevel
			if(A.z == ZLEVEL_STATION)
				if(!(A.type in valid_territories) && A.valid_territory)
					valid_territories |= A.type
		if(valid_territories.len)
			num_territories = valid_territories.len //Add them all up to make the total number of area types
		else
			world << "ERROR: NO VALID TERRITORIES"

/datum/station_state/proc/score(datum/station_state/result)
	if(!result)
		return 0
	var/output = 0
	output += (result.floor / max(floor,1))
	output += (result.r_wall/ max(r_wall,1))
	output += (result.wall / max(wall,1))
	output += (result.window / max(window,1))
	output += (result.door / max(door,1))
	output += (result.grille / max(grille,1))
	output += (result.mach / max(mach,1))
	return (output/7)


code:
////////////////
// BASE TYPE //
////////////////

//Do not spawn
/mob/living/simple_animal/hostile/blob
	icon = 'icons/mob/blob.dmi'
	pass_flags = PASSBLOB
	faction = list("blob")
	bubble_icon = "blob"
	speak_emote = null //so we use verb_yell/verb_say/etc
	atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
	minbodytemp = 0
	maxbodytemp = 360
	unique_name = 1
	a_intent = "harm"
	var/mob/camera/blob/overmind = null
	var/obj/effect/blob/factory/factory = null

/mob/living/simple_animal/hostile/blob/update_icons()
	if(overmind)
		color = overmind.blob_reagent_datum.color
	else
		color = initial(color)

/mob/living/simple_animal/hostile/blob/Destroy()
	if(overmind)
		overmind.blob_mobs -= src
	return ..()

/mob/living/simple_animal/hostile/blob/blob_act()
	if(stat != DEAD && health < maxHealth)
		for(var/i in 1 to 2)
			var/obj/effect/overlay/temp/heal/H = PoolOrNew(/obj/effect/overlay/temp/heal, get_turf(src)) //hello yes you are being healed
			if(overmind)
				H.color = overmind.blob_reagent_datum.complementary_color
			else
				H.color = "#000000"
		adjustHealth(-maxHealth*0.025)

/mob/living/simple_animal/hostile/blob/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
	..()
	adjustFireLoss(Clamp(0.01 * exposed_temperature, 1, 5))

/mob/living/simple_animal/hostile/blob/CanPass(atom/movable/mover, turf/target, height = 0)
	if(istype(mover, /obj/effect/blob))
		return 1
	return ..()

/mob/living/simple_animal/hostile/blob/Process_Spacemove(movement_dir = 0)
	for(var/obj/effect/blob/B in range(1, src))
		return 1
	return ..()

/mob/living/simple_animal/hostile/blob/handle_inherent_channels(message, message_mode)
	if(message_mode == MODE_BINARY)
		blob_chat(message)
		return ITALICS | REDUCE_RANGE
	else
		..()

/mob/living/simple_animal/hostile/blob/proc/blob_chat(msg)
	var/spanned_message = say_quote(msg, get_spans())
	var/rendered = "\[Blob Telepathy\] [real_name] [spanned_message]"
	for(var/M in mob_list)
		if(isovermind(M) || istype(M, /mob/living/simple_animal/hostile/blob))
			M << rendered
		if(isobserver(M))
			M << "(F) [rendered]"

////////////////
// BLOB SPORE //
////////////////

/mob/living/simple_animal/hostile/blob/blobspore
	name = "blob spore"
	desc = "A floating, fragile spore."
	icon_state = "blobpod"
	icon_living = "blobpod"
	health = 40
	maxHealth = 40
	verb_say = "psychically pulses"
	verb_ask = "psychically probes"
	verb_exclaim = "psychically yells"
	verb_yell = "psychically screams"
	melee_damage_lower = 2
	melee_damage_upper = 4
	attacktext = "hits"
	attack_sound = 'sound/weapons/genhit1.ogg'
	flying = 1
	var/death_cloud_size = 1 //size of cloud produced from a dying spore
	var/list/human_overlays = list()
	var/is_zombie = 0
	gold_core_spawnable = 1

/mob/living/simple_animal/hostile/blob/blobspore/New(loc, var/obj/effect/blob/factory/linked_node)
	if(istype(linked_node))
		factory = linked_node
		factory.spores += src
	..()

/mob/living/simple_animal/hostile/blob/blobspore/Life()
	if(!is_zombie && isturf(src.loc))
		for(var/mob/living/carbon/human/H in view(src,1)) //Only for corpse right next to/on same tile
			if(H.stat == DEAD)
				Zombify(H)
				break
	if(factory && z != factory.z)
		death()
	..()

/mob/living/simple_animal/hostile/blob/blobspore/proc/Zombify(mob/living/carbon/human/H)
	is_zombie = 1
	if(H.wear_suit)
		var/obj/item/clothing/suit/armor/A = H.wear_suit
		if(A.armor && A.armor["melee"])
			maxHealth += A.armor["melee"] //That zombie's got armor, I want armor!
	maxHealth += 40
	health = maxHealth
	name = "blob zombie"
	desc = "A shambling corpse animated by the blob."
	melee_damage_lower += 8
	melee_damage_upper += 11
	flying = 0
	death_cloud_size = 0
	icon = H.icon
	icon_state = "zombie_s"
	H.hair_style = null
	H.update_hair()
	human_overlays = H.overlays
	update_icons()
	H.loc = src
	visible_message("The corpse of [H.name] suddenly rises!")

/mob/living/simple_animal/hostile/blob/blobspore/death(gibbed)
	..(1)
	// On death, create a small smoke of harmful gas (s-Acid)
	var/datum/effect_system/smoke_spread/chem/S = new
	var/turf/location = get_turf(src)

	// Create the reagents to put into the air
	create_reagents(10)

	if(overmind && overmind.blob_reagent_datum)
		reagents.add_reagent(overmind.blob_reagent_datum.id, 10)
	else
		reagents.add_reagent("spore", 10)

	// Attach the smoke spreader and setup/start it.
	S.attach(location)
	S.set_up(reagents, death_cloud_size, location, silent=1)
	S.start()

	ghostize()
	qdel(src)

/mob/living/simple_animal/hostile/blob/blobspore/Destroy()
	if(factory)
		factory.spores -= src
	factory = null
	if(contents)
		for(var/mob/M in contents)
			M.loc = src.loc
	return ..()

/mob/living/simple_animal/hostile/blob/blobspore/update_icons()
	..()
	if(is_zombie)
		overlays.Cut()
		overlays = human_overlays
		var/image/I = image('icons/mob/blob.dmi', icon_state = "blob_head")
		if(overmind)
			I.color = overmind.blob_reagent_datum.color
		color = initial(color)//looks better.
		overlays += I

/mob/living/simple_animal/hostile/blob/blobspore/weak
	name = "fragile blob spore"
	health = 20
	maxHealth = 20
	melee_damage_lower = 1
	melee_damage_upper = 2
	death_cloud_size = 0

/////////////////
// BLOBBERNAUT //
/////////////////

/mob/living/simple_animal/hostile/blob/blobbernaut
	name = "blobbernaut"
	desc = "A hulking, mobile chunk of blobmass."
	icon_state = "blobbernaut"
	icon_living = "blobbernaut"
	icon_dead = "blobbernaut_dead"
	health = 200
	maxHealth = 200
	damage_coeff = list(BRUTE = 0.5, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
	next_move_modifier = 1.5 //slow-ass attack speed, 3 times higher than how fast the blob can attack
	melee_damage_lower = 20
	melee_damage_upper = 20
	attacktext = "slams"
	attack_sound = 'sound/effects/blobattack.ogg'
	verb_say = "gurgles"
	verb_ask = "demands"
	verb_exclaim = "roars"
	verb_yell = "bellows"
	force_threshold = 10
	pressure_resistance = 40
	mob_size = MOB_SIZE_LARGE
	see_invisible = SEE_INVISIBLE_MINIMUM
	see_in_dark = 8

/mob/living/simple_animal/hostile/blob/blobbernaut/New()
	..()
	verbs -= /mob/living/verb/pulled //no pulling people deep into the blob

/mob/living/simple_animal/hostile/blob/blobbernaut/Life()
	if(..())
		var/damagesources = 0
		if(!(locate(/obj/effect/blob) in range(2, src)))
			damagesources++
		if(!factory)
			damagesources++
		if(damagesources)
			for(var/i in 1 to damagesources)
				adjustHealth(maxHealth*0.025) //take 2.5% maxhealth as damage when not near the blob or if the naut has no factory, 5% if both
			var/list/viewing = list()
			for(var/mob/M in viewers(src))
				if(M.client)
					viewing += M.client
			var/image/I = new('icons/mob/blob.dmi', src, "nautdamage", MOB_LAYER+0.01)
			I.appearance_flags = RESET_COLOR
			if(overmind)
				I.color = overmind.blob_reagent_datum.complementary_color
			flick_overlay(I, viewing, 8)

/mob/living/simple_animal/hostile/blob/blobbernaut/adjustHealth(amount)
	. = ..()
	update_health_hud()

/mob/living/simple_animal/hostile/blob/blobbernaut/update_health_hud()
	if(hud_used)
		hud_used.healths.maptext = "
[round((health / maxHealth) * 100, 0.5)]%
"

/mob/living/simple_animal/hostile/blob/blobbernaut/AttackingTarget()
	if(isliving(target))
		if(overmind)
			var/mob/living/L = target
			var/mob_protection = L.get_permeability_protection()
			overmind.blob_reagent_datum.reaction_mob(L, VAPOR, 20, 0, mob_protection, overmind)//this will do between 10 and 20 damage(reduced by mob protection), depending on chemical, plus 4 from base brute damage.
	if(target)
		..()

/mob/living/simple_animal/hostile/blob/blobbernaut/update_icons()
	..()
	if(overmind) //if we have an overmind, we're doing chemical reactions instead of pure damage
		melee_damage_lower = 4
		melee_damage_upper = 4
		attacktext = overmind.blob_reagent_datum.blobbernaut_message
	else
		melee_damage_lower = initial(melee_damage_lower)
		melee_damage_upper = initial(melee_damage_upper)
		attacktext = initial(attacktext)

/mob/living/simple_animal/hostile/blob/blobbernaut/death(gibbed)
	..(gibbed)
	if(factory)
		factory.naut = null //remove this naut from its factory
		factory.maxhealth = initial(factory.maxhealth)
	flick("blobbernaut_death", src)


code:
/obj/effect/blob/core
	name = "blob core"
	icon = 'icons/mob/blob.dmi'
	icon_state = "blank_blob"
	desc = "A huge, pulsating yellow mass."
	health = 400
	maxhealth = 400
	explosion_block = 6
	point_return = -1
	atmosblock = 1
	health_regen = 0 //we regen in Life() instead of when pulsed
	var/core_regen = 2
	var/overmind_get_delay = 0 //we don't want to constantly try to find an overmind, this var tracks when we'll try to get an overmind again
	var/resource_delay = 0
	var/point_rate = 2

/obj/effect/blob/core/New(loc, client/new_overmind = null, new_rate = 2, placed = 0)
	blob_cores += src
	SSobj.processing |= src
	update_icon() //so it atleast appears
	if(!placed && !overmind)
		create_overmind(new_overmind)
	if(overmind)
		update_icon()
	point_rate = new_rate
	..()

/obj/effect/blob/core/update_icon()
	overlays.Cut()
	color = null
	var/image/I = new('icons/mob/blob.dmi', "blob")
	if(overmind)
		I.color = overmind.blob_reagent_datum.color
	overlays += I
	var/image/C = new('icons/mob/blob.dmi', "blob_core_overlay")
	overlays += C

/obj/effect/blob/core/Destroy()
	blob_cores -= src
	if(overmind)
		overmind.blob_core = null
	overmind = null
	SSobj.processing.Remove(src)
	return ..()

/obj/effect/blob/core/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
	return

/obj/effect/blob/core/ex_act(severity, target)
	return

/obj/effect/blob/core/check_health()
	..()
	if(overmind) //we should have an overmind, but...
		overmind.update_health_hud()

/obj/effect/blob/core/Life()
	if(!overmind)
		create_overmind()
	else
		if(resource_delay <= world.time)
			resource_delay = world.time + 10 // 1 second
			overmind.add_points(point_rate)
	health = min(maxhealth, health+core_regen)
	if(overmind)
		overmind.update_health_hud()
	Pulse_Area(overmind, 12, 4, 3)
	for(var/obj/effect/blob/normal/B in range(1, src))
		if(prob(5))
			B.change_to(/obj/effect/blob/shield, overmind)
	..()

/obj/effect/blob/core/proc/create_overmind(client/new_overmind, override_delay)
	if(overmind_get_delay > world.time && !override_delay)
		return

	overmind_get_delay = world.time + 150 //if this fails, we'll try again in 15 seconds

	if(overmind)
		qdel(overmind)

	var/client/C = null
	var/list/candidates = list()

	if(!new_overmind)
		candidates = get_candidates(ROLE_BLOB)
		if(candidates.len)
			C = pick(candidates)
	else
		C = new_overmind

	if(C)
		var/mob/camera/blob/B = new(src.loc, 1)
		B.key = C.key
		B.blob_core = src
		src.overmind = B
		update_icon()
		if(B.mind && !B.mind.special_role)
			B.mind.special_role = "Blob Overmind"
		return 1
	return 0


code:
/obj/effect/blob/factory
	name = "factory blob"
	icon = 'icons/mob/blob.dmi'
	icon_state = "blob_factory"
	desc = "A thick spire of tendrils."
	health = 200
	maxhealth = 200
	health_regen = 1
	point_return = 25
	var/list/spores = list()
	var/mob/living/simple_animal/hostile/blob/blobbernaut/naut = null
	var/max_spores = 3
	var/spore_delay = 0

/obj/effect/blob/factory/Destroy()
	for(var/mob/living/simple_animal/hostile/blob/blobspore/spore in spores)
		if(spore.factory == src)
			spore.factory = null
	if(naut)
		naut.factory = null
		naut << "Your factory was destroyed! You feel yourself dying!"
		naut.throw_alert("nofactory", /obj/screen/alert/nofactory)
	spores = null
	return ..()

/obj/effect/blob/factory/Be_Pulsed()
	. = ..()
	if(spores.len >= max_spores)
		return
	if(spore_delay > world.time)
		return
	flick("blob_factory_glow", src)
	spore_delay = world.time + 100 // 10 seconds
	var/mob/living/simple_animal/hostile/blob/blobspore/BS = new/mob/living/simple_animal/hostile/blob/blobspore(src.loc, src)
	if(overmind) //if we don't have an overmind, we don't need to do anything but make a spore
		BS.overmind = overmind
		BS.update_icons()
		overmind.blob_mobs.Add(BS)


code:
/obj/effect/blob/node
	name = "blob node"
	icon = 'icons/mob/blob.dmi'
	icon_state = "blank_blob"
	desc = "A large, pulsating yellow mass."
	health = 200
	maxhealth = 200
	health_regen = 3
	point_return = 25
	atmosblock = 1

/obj/effect/blob/node/New(loc, var/h = 100)
	blob_nodes += src
	SSobj.processing |= src
	..(loc, h)

/obj/effect/blob/node/update_icon()
	overlays.Cut()
	color = null
	var/image/I = new('icons/mob/blob.dmi', "blob")
	if(overmind)
		I.color = overmind.blob_reagent_datum.color
	src.overlays += I
	var/image/C = new('icons/mob/blob.dmi', "blob_node_overlay")
	src.overlays += C

/obj/effect/blob/node/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
	return

/obj/effect/blob/node/Destroy()
	blob_nodes -= src
	SSobj.processing.Remove(src)
	return ..()

/obj/effect/blob/node/Life()
	Pulse_Area(overmind, 10, 3, 2)
	color = null


code:
/obj/effect/blob/resource
	name = "resource blob"
	icon = 'icons/mob/blob.dmi'
	icon_state = "blob_resource"
	desc = "A thin spire of slightly swaying tendrils."
	health = 60
	maxhealth = 60
	point_return = 15
	var/resource_delay = 0

/obj/effect/blob/resource/creation_action()
	if(overmind)
		overmind.resource_blobs += src

/obj/effect/blob/resource/Destroy()
	if(overmind)
		overmind.resource_blobs -= src
	return ..()

/obj/effect/blob/resource/Be_Pulsed()
	. = ..()
	if(resource_delay > world.time)
		return
	flick("blob_resource_glow", src)
	if(overmind)
		overmind.add_points(1)
		resource_delay = world.time + 40 + overmind.resource_blobs.len * 2.5 //4 seconds plus a quarter second for each resource blob the overmind has
	else
		resource_delay = world.time + 40


code:
/obj/effect/blob/shield
	name = "strong blob"
	icon = 'icons/mob/blob.dmi'
	icon_state = "blob_idle"
	desc = "A solid wall of slightly twitching tendrils."
	health = 150
	maxhealth = 150
	brute_resist = 0.1
	explosion_block = 3
	point_return = 2
	atmosblock = 1

/obj/effect/blob/shield/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
	return


code:
/mob/camera/blob
	name = "Blob Overmind"
	real_name = "Blob Overmind"
	icon = 'icons/mob/blob.dmi'
	icon_state = "marker"

	see_in_dark = 8
	see_invisible = SEE_INVISIBLE_MINIMUM
	invisibility = INVISIBILITY_OBSERVER

	pass_flags = PASSBLOB
	faction = list("blob")

	var/obj/effect/blob/core/blob_core = null // The blob overmind's core
	var/blob_points = 0
	var/max_blob_points = 100
	var/last_attack = 0
	var/datum/reagent/blob/blob_reagent_datum = new/datum/reagent/blob()
	var/list/blob_mobs = list()
	var/list/resource_blobs = list()
	var/ghostimage = null
	var/free_chem_rerolls = 1 //one free chemical reroll
	var/nodes_required = 1 //if the blob needs nodes to place resource and factory blobs
	var/placed = 0
	var/base_point_rate = 2 //for blob core placement
	var/manualplace_min_time = 600 //in deciseconds //a minute, to get bearings
	var/autoplace_max_time = 3600 //six minutes, as long as should be needed

/mob/camera/blob/New(loc, pre_placed = 0, mode_made = 0)
	if(pre_placed) //we already have a core!
		manualplace_min_time = 0
		autoplace_max_time = 0
		placed = 1
	else
		if(mode_made)
			manualplace_min_time = world.time + BLOB_NO_PLACE_TIME
		else
			manualplace_min_time += world.time
		autoplace_max_time += world.time
	overminds += src
	var/new_name = "[initial(name)] ([rand(1, 999)])"
	name = new_name
	real_name = new_name
	last_attack = world.time
	var/list/possible_reagents = list()
	for(var/type in (subtypesof(/datum/reagent/blob)))
		possible_reagents.Add(new type)
	blob_reagent_datum = pick(possible_reagents)
	if(blob_core)
		blob_core.update_icon()

	ghostimage = image(src.icon,src,src.icon_state)
	ghost_darkness_images |= ghostimage //so ghosts can see the blob cursor when they disable darkness
	updateallghostimages()
	..()

/mob/camera/blob/Life()
	if(!blob_core)
		if(!placed)
			if(manualplace_min_time && world.time >= manualplace_min_time)
				src << "You may now place your blob core."
				src << "You will automatically place your blob core in [round((autoplace_max_time - world.time)/600, 0.5)] minutes."
				manualplace_min_time = 0
			if(autoplace_max_time && world.time >= autoplace_max_time)
				place_blob_core(base_point_rate, 1)
		else
			qdel(src)
	..()

/mob/camera/blob/Destroy()
	for(var/BL in blobs)
		var/obj/effect/blob/B = BL
		if(B.overmind == src)
			B.overmind = null
			B.update_icon() //reset anything that was ours
	for(var/BLO in blob_mobs)
		var/mob/living/simple_animal/hostile/blob/BM = BLO
		BM.overmind = null
		BM.update_icons()
	overminds -= src
	if(ghostimage)
		ghost_darkness_images -= ghostimage
		qdel(ghostimage)
		ghostimage = null;
		updateallghostimages()
	return ..()

/mob/camera/blob/Login()
	..()
	sync_mind()
	src << "You are the overmind!"
	blob_help()
	update_health_hud()

/mob/camera/blob/update_health_hud()
	if(blob_core)
		hud_used.healths.maptext = "
[round(blob_core.health)]
"
		for(var/mob/living/simple_animal/hostile/blob/blobbernaut/B in blob_mobs)
			if(B.hud_used && B.hud_used.blobpwrdisplay)
				B.hud_used.blobpwrdisplay.maptext = "
[round(blob_core.health)]
"

/mob/camera/blob/proc/add_points(points)
	if(points != 0)
		blob_points = Clamp(blob_points + points, 0, max_blob_points)
		hud_used.blobpwrdisplay.maptext = "
[round(src.blob_points)]
"

/mob/camera/blob/say(message)
	if (!message)
		return

	if (src.client)
		if(client.prefs.muted & MUTE_IC)
			src << "You cannot send IC messages (muted)."
			return
		if (src.client.handle_spam_prevention(message,MUTE_IC))
			return

	if (stat)
		return

	blob_talk(message)

/mob/camera/blob/proc/blob_talk(message)
	log_say("[key_name(src)] : [message]")

	message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))

	if (!message)
		return

	var/message_a = say_quote(message, get_spans())
	var/rendered = "\[Blob Telepathy\] [name]([blob_reagent_datum.name]) [message_a]"

	for(var/mob/M in mob_list)
		if(isovermind(M) || istype(M, /mob/living/simple_animal/hostile/blob))
			M << rendered
		if(isobserver(M))
			M << "(F) [rendered]"

/mob/camera/blob/emote(act,m_type=1,message = null)
	return

/mob/camera/blob/blob_act()
	return

/mob/camera/blob/Stat()
	..()
	if(statpanel("Status"))
		if(blob_core)
			stat(null, "Core Health: [blob_core.health]")
		stat(null, "Power Stored: [blob_points]/[max_blob_points]")
		if(free_chem_rerolls)
			stat(null, "You have [free_chem_rerolls] Free Chemical Reroll\s Remaining")
		if(!placed)
			if(manualplace_min_time)
				stat(null, "Time Before Manual Placement: [max(round((manualplace_min_time - world.time)*0.1, 0.1), 0)]")
			stat(null, "Time Before Automatic Placement: [max(round((autoplace_max_time - world.time)*0.1, 0.1), 0)]")

/mob/camera/blob/Move(NewLoc, Dir = 0)
	if(placed)
		var/obj/effect/blob/B = locate() in range("3x3", NewLoc)
		if(B)
			loc = NewLoc
		else
			return 0
	else
		var/area/A = get_area(NewLoc)
		if(istype(NewLoc, /turf/open/space) || istype(A, /area/shuttle)) //if unplaced, can't go on shuttles or space tiles
			return 0
		loc = NewLoc
		return 1

/mob/camera/blob/proc/can_attack()
	return (world.time > (last_attack + CLICK_CD_RANGE))


code:
/mob/camera/blob/proc/can_buy(cost = 15)
	if(blob_points < cost)
		src << "You cannot afford this!"
		return 0
	add_points(-cost)
	return 1

// Power verbs

/mob/camera/blob/proc/place_blob_core(point_rate = base_point_rate, placement_override = 0)
	if(placed && placement_override != -1)
		return 1
	if(!placement_override)
		for(var/mob/living/M in range(7, src))
			if("blob" in M.faction)
				continue
			if(M.client)
				src << "There is someone too close to place your blob core!"
				return 0
		for(var/mob/living/M in view(13, src))
			if("blob" in M.faction)
				continue
			if(M.client)
				src << "Someone could see your blob core from here!"
				return 0
		var/turf/T = get_turf(src)
		if(T.density)
			src << "This spot is too dense to place a blob core on!"
			return 0
		for(var/obj/O in T)
			if(istype(O, /obj/effect/blob))
				if(istype(O, /obj/effect/blob/normal))
					qdel(O)
				else
					src << "There is already a blob here!"
					return 0
			if(O.density)
				src << "This spot is too dense to place a blob core on!"
				return 0
		if(world.time <= manualplace_min_time && world.time <= autoplace_max_time)
			src << "It is too early to place your blob core!"
			return 0
	else if(placement_override == 1)
		var/turf/T = pick(blobstart)
		loc = T //got overrided? you're somewhere random, motherfucker
	if(placed && blob_core)
		blob_core.forceMove(loc)
	else
		var/obj/effect/blob/core/core = new(get_turf(src), null, point_rate, 1)
		core.overmind = src
		blob_core = core
		core.update_icon()
	update_health_hud()
	placed = 1
	return 1

/mob/camera/blob/verb/transport_core()
	set category = "Blob"
	set name = "Jump to Core"
	set desc = "Move your camera to your core."
	if(blob_core)
		src.loc = blob_core.loc

/mob/camera/blob/verb/jump_to_node()
	set category = "Blob"
	set name = "Jump to Node"
	set desc = "Move your camera to a selected node."
	if(blob_nodes.len)
		var/list/nodes = list()
		for(var/i = 1; i <= blob_nodes.len; i++)
			nodes["Blob Node #[i]"] = blob_nodes[i]
		var/node_name = input(src, "Choose a node to jump to.", "Node Jump") in nodes
		var/obj/effect/blob/node/chosen_node = nodes[node_name]
		if(chosen_node)
			src.loc = chosen_node.loc

/mob/camera/blob/proc/createSpecial(price, blobType, nearEquals, needsNode, turf/T)
	if(!T)
		T = get_turf(src)
	var/obj/effect/blob/B = (locate(/obj/effect/blob) in T)
	if(!B)
		src << "There is no blob here!"
		return
	if(!istype(B, /obj/effect/blob/normal))
		src << "Unable to use this blob, find a normal one."
		return
	if(needsNode && nodes_required)
		if(!(locate(/obj/effect/blob/node) in orange(3, T)) && !(locate(/obj/effect/blob/core) in orange(4, T)))
			src << "You need to place this blob closer to a node or core!"
			return //handholdotron 2000
	if(nearEquals)
		for(var/obj/effect/blob/L in orange(nearEquals, T))
			if(L.type == blobType)
				src << "There is a similar blob nearby, move more than [nearEquals] tiles away from it!"
				return
	if(!can_buy(price))
		return
	var/obj/effect/blob/N = B.change_to(blobType, src)
	return N

/mob/camera/blob/verb/toggle_node_req()
	set category = "Blob"
	set name = "Toggle Node Requirement"
	set desc = "Toggle requiring nodes to place resource and factory blobs."
	nodes_required = !nodes_required
	if(nodes_required)
		src << "You now require a nearby node or core to place factory and resource blobs."
	else
		src << "You no longer require a nearby node or core to place factory and resource blobs."

/mob/camera/blob/verb/create_shield_power()
	set category = "Blob"
	set name = "Create Shield Blob (10)"
	set desc = "Create a shield blob, which will block fire and is hard to kill."
	create_shield()

/mob/camera/blob/proc/create_shield(turf/T)
	createSpecial(10, /obj/effect/blob/shield, 0, 0, T)

/mob/camera/blob/verb/create_resource()
	set category = "Blob"
	set name = "Create Resource Blob (40)"
	set desc = "Create a resource tower which will generate resources for you."
	createSpecial(40, /obj/effect/blob/resource, 4, 1)

/mob/camera/blob/verb/create_node()
	set category = "Blob"
	set name = "Create Node Blob (60)"
	set desc = "Create a node, which will power nearby factory and resource blobs."
	createSpecial(60, /obj/effect/blob/node, 5, 0)

/mob/camera/blob/verb/create_factory()
	set category = "Blob"
	set name = "Create Factory Blob (60)"
	set desc = "Create a spore tower that will spawn spores to harass your enemies."
	createSpecial(60, /obj/effect/blob/factory, 7, 1)

/mob/camera/blob/verb/create_blobbernaut()
	set category = "Blob"
	set name = "Create Blobbernaut (30)"
	set desc = "Create a powerful blobbernaut which is mildly smart and will attack enemies."
	var/turf/T = get_turf(src)
	var/obj/effect/blob/factory/B = locate(/obj/effect/blob/factory) in T
	if(!B)
		src << "You must be on a factory blob!"
		return
	if(B.naut) //if it already made a blobbernaut, it can't do it again
		src << "This factory blob is already sustaining a blobbernaut."
		return
	if(B.health < B.maxhealth * 0.5)
		src << "This factory blob is too damaged to sustain a blobbernaut."
		return
	if(!can_buy(30))
		return
	B.maxhealth = initial(B.maxhealth) * 0.25 //factories that produced a blobbernaut have much lower health
	B.check_health()
	B.visible_message("The blobbernaut [pick("rips", "tears", "shreds")] its way out of the factory blob!")
	playsound(B.loc, 'sound/effects/splat.ogg', 50, 1)
	var/mob/living/simple_animal/hostile/blob/blobbernaut/blobber = new /mob/living/simple_animal/hostile/blob/blobbernaut(get_turf(B))
	flick("blobbernaut_produce", blobber)
	B.naut = blobber
	blobber.factory = B
	blobber.overmind = src
	blobber.update_icons()
	blobber.notransform = 1 //stop the naut from moving around
	blob_mobs.Add(blobber)
	var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as a [blob_reagent_datum.name] blobbernaut?", ROLE_BLOB, null, ROLE_BLOB, 50) //players must answer rapidly
	var/client/C = null
	if(candidates.len) //if we got at least one candidate, they're a blobbernaut now.
		C = pick(candidates)
		blobber.notransform = 0
		blobber.key = C.key
		blobber << 'sound/effects/blobattack.ogg'
		blobber << 'sound/effects/attackblob.ogg'
		blobber << "You are a blobbernaut!"
		blobber << "You are powerful, hard to kill, and slowly regenerate near nodes and cores, but will slowly die if not near the blob or if the factory that made you is killed."
		blobber << "You can communicate with other blobbernauts and overminds via :b"
		blobber << "Your overmind's blob reagent is: [blob_reagent_datum.name]!"
		blobber << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.shortdesc ? "[blob_reagent_datum.shortdesc]" : "[blob_reagent_datum.description]"]"
	else
		blobber.notransform = 0 //otherwise, just let it move

/mob/camera/blob/verb/relocate_core()
	set category = "Blob"
	set name = "Relocate Core (80)"
	set desc = "Swaps the locations of your core and the selected node."
	var/turf/T = get_turf(src)
	var/obj/effect/blob/node/B = locate(/obj/effect/blob/node) in T
	if(!B)
		src << "You must be on a blob node!"
		return
	if(!can_buy(80))
		return
	var/turf/old_turf = blob_core.loc
	blob_core.loc = T
	B.loc = old_turf

/mob/camera/blob/verb/revert()
	set category = "Blob"
	set name = "Remove Blob"
	set desc = "Removes a blob, giving you back some resources."
	var/turf/T = get_turf(src)
	remove_blob(T)

/mob/camera/blob/proc/remove_blob(turf/T)
	var/obj/effect/blob/B = locate() in T
	if(!B)
		src << "There is no blob there!"
		return
	if(B.point_return < 0)
		src << "Unable to remove this blob."
		return
	if(max_blob_points < B.point_return + blob_points)
		src << "You have too many resources to remove this blob!"
		return
	if(B.point_return)
		add_points(B.point_return)
		src << "Gained [B.point_return] resources from removing \the [B]."
	qdel(B)

/mob/camera/blob/verb/expand_blob_power()
	set category = "Blob"
	set name = "Expand/Attack Blob (5)"
	set desc = "Attempts to create a new blob in this tile. If the tile isn't clear, instead attacks it, damaging mobs and objects."
	var/turf/T = get_turf(src)
	expand_blob(T)

/mob/camera/blob/proc/expand_blob(turf/T)
	if(!can_attack())
		return
	var/obj/effect/blob/OB = locate() in circlerange(T, 1)
	if(!OB)
		src << "There is no blob adjacent to the target tile!"
		return
	if(can_buy(5))
		var/attacksuccess = FALSE
		last_attack = world.time
		for(var/mob/living/L in T)
			if("blob" in L.faction) //no friendly/dead fire
				continue
			if(L.stat != DEAD)
				attacksuccess = TRUE
			var/mob_protection = L.get_permeability_protection()
			blob_reagent_datum.reaction_mob(L, VAPOR, 25, 1, mob_protection, src)
			blob_reagent_datum.send_message(L)
		var/obj/effect/blob/B = locate() in T
		if(B)
			if(attacksuccess) //if we successfully attacked a turf with a blob on it, don't refund shit
				B.blob_attack_animation(T, src)
			else
				src << "There is a blob there!"
				add_points(5) //otherwise, refund all of the cost
			return
		else
			OB.expand(T, src)

/mob/camera/blob/verb/rally_spores_power()
	set category = "Blob"
	set name = "Rally Spores"
	set desc = "Rally your spores to move to a target location."
	var/turf/T = get_turf(src)
	rally_spores(T)

/mob/camera/blob/proc/rally_spores(turf/T)
	src << "You rally your spores."
	var/list/surrounding_turfs = block(locate(T.x - 1, T.y - 1, T.z), locate(T.x + 1, T.y + 1, T.z))
	if(!surrounding_turfs.len)
		return
	for(var/mob/living/simple_animal/hostile/blob/blobspore/BS in blob_mobs)
		if(isturf(BS.loc) && get_dist(BS, T) <= 35)
			BS.LoseTarget()
			BS.Goto(pick(surrounding_turfs), BS.move_to_delay)

/mob/camera/blob/verb/blob_broadcast()
	set category = "Blob"
	set name = "Blob Broadcast"
	set desc = "Speak with your blob spores and blobbernauts as your mouthpieces."
	var/speak_text = input(src, "What would you like to say with your minions?", "Blob Broadcast", null) as text
	if(!speak_text)
		return
	else
		src << "You broadcast with your minions, [speak_text]"
	for(var/BLO in blob_mobs)
		var/mob/living/simple_animal/hostile/blob/BM = BLO
		if(BM.stat == CONSCIOUS)
			BM.say(speak_text)

/mob/camera/blob/verb/chemical_reroll()
	set category = "Blob"
	set name = "Reactive Chemical Adaptation (40)"
	set desc = "Replaces your chemical with a random, different one."
	if(free_chem_rerolls || can_buy(40))
		set_chemical()
		if(free_chem_rerolls)
			free_chem_rerolls--

/mob/camera/blob/proc/set_chemical()
	var/datum/reagent/blob/BC = pick((subtypesof(/datum/reagent/blob) - blob_reagent_datum.type))
	blob_reagent_datum = new BC
	for(var/BL in blobs)
		var/obj/effect/blob/B = BL
		B.update_icon()
	for(var/BLO in blob_mobs)
		var/mob/living/simple_animal/hostile/blob/BM = BLO
		BM.update_icons() //If it's getting a new chemical, tell it what it does!
		BM << "Your overmind's blob reagent is now: [blob_reagent_datum.name]!"
		BM << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.shortdesc ? "[blob_reagent_datum.shortdesc]" : "[blob_reagent_datum.description]"]"
	src << "Your reagent is now: [blob_reagent_datum.name]!"
	src << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.description]"

/mob/camera/blob/verb/blob_help()
	set category = "Blob"
	set name = "*Blob Help*"
	set desc = "Help on how to blob."
	src << "As the overmind, you can control the blob!"
	src << "Your blob reagent is: [blob_reagent_datum.name]!"
	src << "The [blob_reagent_datum.name] reagent [blob_reagent_datum.description]"
	src << "You can expand, which will attack people, damage objects, or place a Normal Blob if the tile is clear."
	src << "Normal Blobs will expand your reach and can be upgraded into special blobs that perform certain functions."
	src << "You can upgrade normal blobs into the following types of blob:"
	src << "Shield Blobs are strong and expensive blobs which take more damage. In additon, they are fireproof and can block air, use these to protect yourself from station fires."
	src << "Resource Blobs are blobs which produce more resources for you, build as many of these as possible to consume the station. This type of blob must be placed near node blobs or your core to work."
	src << "Factory Blobs are blobs that spawn blob spores which will attack nearby enemies. This type of blob must be placed near node blobs or your core to work."
	src << "Blobbernauts can be produced from factories for a cost, and are hard to kill, powerful, and moderately smart. The factory used to create one will become fragile and briefly unable to produce spores."
	src << "Node Blobs are blobs which grow, like the core. Like the core it can activate resource and factory blobs."
	src << "In addition to the buttons on your HUD, there are a few click shortcuts to speed up expansion and defense."
	src << "Shortcuts: Click = Expand Blob | Middle Mouse Click = Rally Spores | Ctrl Click = Create Shield Blob | Alt Click = Remove Blob"
	src << "Attempting to talk will send a message to all other overminds, allowing you to coordinate with them."
	if(!placed && autoplace_max_time <= world.time)
		src << "You will automatically place your blob core in [round((autoplace_max_time - world.time)/600, 0.5)] minutes."
		src << "You [manualplace_min_time ? "will be able to":"can"] manually place your blob core by pressing the button in the bottom right corner of the screen."


code:
//I will need to recode parts of this but I am way too tired atm //I don't know who left this comment but they never did come back
/obj/effect/blob
	name = "blob"
	icon = 'icons/mob/blob.dmi'
	luminosity = 1
	desc = "A thick wall of writhing tendrils."
	density = 0 //this being 0 causes two bugs, being able to attack blob tiles behind other blobs and being unable to move on blob tiles in no gravity, but turning it to 1 causes the blob mobs to be unable to path through blobs, which is probably worse.
	opacity = 0
	anchored = 1
	explosion_block = 1
	var/point_return = 0 //How many points the blob gets back when it removes a blob of that type. If less than 0, blob cannot be removed.
	var/health = 30
	var/maxhealth = 30
	var/health_regen = 2 //how much health this blob regens when pulsed
	var/pulse_timestamp = 0 //we got pulsed/healed when?
	var/brute_resist = 0.5 //multiplies brute damage by this
	var/fire_resist = 1 //multiplies burn damage by this
	var/atmosblock = 0 //if the blob blocks atmos and heat spread
	var/mob/camera/blob/overmind

/obj/effect/blob/New(loc)
	var/area/Ablob = get_area(loc)
	if(Ablob.blob_allowed) //Is this area allowed for winning as blob?
		blobs_legit += src
	blobs += src //Keep track of the blob in the normal list either way
	src.dir = pick(1, 2, 4, 8)
	src.update_icon()
	..(loc)
	ConsumeTile()
	if(atmosblock)
		air_update_turf(1)
	return

/obj/effect/blob/proc/creation_action() //When it's created by the overmind, do this.
	return

/obj/effect/blob/Destroy()
	if(atmosblock)
		atmosblock = 0
		air_update_turf(1)
	blobs_legit -= src  //if it was in the legit blobs list, it isn't now
	blobs -= src //it's no longer in the all blobs list either
	playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) //Expand() is no longer broken, no check necessary.
	return ..()

/obj/effect/blob/CanAtmosPass(turf/T)
	return !atmosblock

/obj/effect/blob/BlockSuperconductivity()
	return atmosblock

/obj/effect/blob/CanPass(atom/movable/mover, turf/target, height=0)
	if(height==0)
		return 1
	if(istype(mover) && mover.checkpass(PASSBLOB))
		return 1
	return 0

/obj/effect/blob/CanAStarPass(ID, dir, caller)
	. = 0
	if(ismovableatom(caller))
		var/atom/movable/mover = caller
		. = . || mover.checkpass(PASSBLOB)

/obj/effect/blob/proc/check_health(cause)
	health = Clamp(health, 0, maxhealth)
	if(health <= 0)
		if(overmind)
			overmind.blob_reagent_datum.death_reaction(src, cause)
		qdel(src) //we dead now
		return
	return

/obj/effect/blob/update_icon() //Updates color based on overmind color if we have an overmind.
	if(overmind)
		color = overmind.blob_reagent_datum.color
	else
		color = null
	return

/obj/effect/blob/process()
	Life()
	return

/obj/effect/blob/proc/Life()
	return

/obj/effect/blob/proc/Pulse_Area(pulsing_overmind = overmind, claim_range = 10, pulse_range = 3, expand_range = 2)
	src.Be_Pulsed()
	if(claim_range)
		for(var/obj/effect/blob/B in urange(claim_range, src, 1))
			if(!B.overmind && !istype(B, /obj/effect/blob/core) && prob(30))
				B.overmind = pulsing_overmind //reclaim unclaimed, non-core blobs.
				B.update_icon()
	if(pulse_range)
		for(var/obj/effect/blob/B in orange(pulse_range, src))
			B.Be_Pulsed()
	if(expand_range)
		if(prob(85))
			src.expand()
		for(var/obj/effect/blob/B in orange(expand_range, src))
			if(prob(max(13 - get_dist(get_turf(src), get_turf(B)) * 4, 1))) //expand falls off with range but is faster near the blob causing the expansion
				B.expand()
	return

/obj/effect/blob/proc/Be_Pulsed()
	if(pulse_timestamp <= world.time)
		ConsumeTile()
		health = min(maxhealth, health+health_regen)
		update_icon()
		pulse_timestamp = world.time + 10
		return 1 //we did it, we were pulsed!
	return 0 //oh no we failed

/obj/effect/blob/proc/ConsumeTile()
	for(var/atom/A in loc)
		A.blob_act()
	if(istype(loc, /turf/closed/wall))
		loc.blob_act() //don't ask how a wall got on top of the core, just eat it

/obj/effect/blob/proc/blob_attack_animation(atom/A = null, controller) //visually attacks an atom
	var/obj/effect/overlay/temp/blob/O = PoolOrNew(/obj/effect/overlay/temp/blob, src.loc)
	if(controller)
		var/mob/camera/blob/BO = controller
		O.color = BO.blob_reagent_datum.color
		O.alpha = 200
	else if(overmind)
		O.color = overmind.blob_reagent_datum.color
	if(A)
		O.do_attack_animation(A) //visually attack the whatever
	return O //just in case you want to do something to the animation.

/obj/effect/blob/proc/expand(turf/T = null, controller = null, expand_reaction = 1)
	if(!T)
		var/list/dirs = list(1,2,4,8)
		for(var/i = 1 to 4)
			var/dirn = pick(dirs)
			dirs.Remove(dirn)
			T = get_step(src, dirn)
			if(!(locate(/obj/effect/blob) in T))
				break
			else
				T = null
	if(!T)
		return 0
	var/make_blob = TRUE //can we make a blob?

	if(istype(T, /turf/open/space) && !(locate(/obj/structure/lattice) in T) && prob(80))
		make_blob = FALSE
		playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) //Let's give some feedback that we DID try to spawn in space, since players are used to it

	ConsumeTile() //hit the tile we're in, making sure there are no border objects blocking us
	if(!T.CanPass(src, T, 5)) //is the target turf impassable
		make_blob = FALSE
		T.blob_act() //hit the turf if it is
	for(var/atom/A in T)
		if(!A.CanPass(src, T, 5)) //is anything in the turf impassable
			make_blob = FALSE
		A.blob_act() //also hit everything in the turf

	if(make_blob) //well, can we?
		var/obj/effect/blob/B = new /obj/effect/blob/normal(src.loc)
		if(controller)
			B.overmind = controller
		else
			B.overmind = overmind
		B.density = 1
		if(T.Enter(B,src)) //NOW we can attempt to move into the tile
			B.density = initial(B.density)
			B.loc = T
			B.update_icon()
			if(B.overmind && expand_reaction)
				B.overmind.blob_reagent_datum.expand_reaction(src, B, T)
			return B
		else
			blob_attack_animation(T, controller)
			T.blob_act() //if we can't move in hit the turf again
			qdel(B) //we should never get to this point, since we checked before moving in. destroy the blob so we don't have two blobs on one tile
			return null
	else
		blob_attack_animation(T, controller) //if we can't, animate that we attacked
	return null

/obj/effect/blob/ex_act(severity, target)
	..()
	var/damage = 150 - 20 * severity
	take_damage(damage, BRUTE)

/obj/effect/blob/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
	..()
	var/damage = Clamp(0.01 * exposed_temperature, 0, 4)
	take_damage(damage, BURN)

/obj/effect/blob/tesla_act(power)
	..()
	if(overmind)
		if(overmind.blob_reagent_datum.tesla_reaction(src, power))
			take_damage(power/400, BURN)
	else
		take_damage(power/400, BURN)

/obj/effect/blob/extinguish()
	..()
	if(overmind)
		overmind.blob_reagent_datum.extinguish_reaction(src)

/obj/effect/blob/bullet_act(var/obj/item/projectile/Proj)
	..()
	take_damage(Proj.damage, Proj.damage_type, Proj)
	return 0

/obj/effect/blob/attackby(obj/item/weapon/W, mob/living/user, params)
	user.changeNext_move(CLICK_CD_MELEE)
	user.do_attack_animation(src)
	playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1)
	visible_message("[user] has attacked the [src.name] with \the [W]!")
	if(W.damtype == BURN)
		playsound(src.loc, 'sound/items/Welder.ogg', 100, 1)
	take_damage(W.force, W.damtype, user)

/obj/effect/blob/attack_animal(mob/living/simple_animal/M)
	if("blob" in M.faction) //sorry, but you can't kill the blob as a blobbernaut
		return
	M.changeNext_move(CLICK_CD_MELEE)
	M.do_attack_animation(src)
	playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1)
	visible_message("\The [M] has attacked the [src.name]!")
	var/damage = rand(M.melee_damage_lower, M.melee_damage_upper)
	take_damage(damage, M.melee_damage_type, M)
	return

/obj/effect/blob/attack_alien(mob/living/carbon/alien/humanoid/M)
	M.changeNext_move(CLICK_CD_MELEE)
	M.do_attack_animation(src)
	playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1)
	visible_message("[M] has slashed the [src.name]!")
	var/damage = rand(15, 30)
	take_damage(damage, BRUTE, M)
	return

/obj/effect/blob/proc/take_damage(damage, damage_type, cause = null, overmind_reagent_trigger = 1)
	switch(damage_type) //blobs only take brute and burn damage
		if(BRUTE)
			damage = max(damage * brute_resist, 0)
		if(BURN)
			damage = max(damage * fire_resist, 0)
		if(CLONE) //this is basically a marker for 'don't modify the damage'

		else
			damage = 0
	if(overmind && overmind_reagent_trigger)
		damage = overmind.blob_reagent_datum.damage_reaction(src, health, damage, damage_type, cause) //pass the blob, its health before damage, the damage being done, the type of damage being done, and the cause.
	health -= damage
	update_icon()
	check_health(cause)

/obj/effect/blob/proc/change_to(type, controller)
	if(!ispath(type))
		throw EXCEPTION("change_to(): invalid type for blob")
		return
	var/obj/effect/blob/B = new type(src.loc)
	if(controller)
		B.overmind = controller
	B.creation_action()
	B.update_icon()
	qdel(src)
	return B

/obj/effect/blob/examine(mob/user)
	..()
	user << "It seems to be made of [get_chem_name()]."
	return

/obj/effect/blob/proc/get_chem_name()
	if(overmind)
		return overmind.blob_reagent_datum.name
	return "an unknown variant"

/obj/effect/blob/normal
	icon_state = "blob"
	luminosity = 0
	health = 21
	maxhealth = 25
	health_regen = 1
	brute_resist = 0.25

/obj/effect/blob/normal/update_icon()
	..()
	if(health <= 10)
		icon_state = "blob_damaged"
		name = "fragile blob"
		desc = "A thin lattice of slightly twitching tendrils."
		brute_resist = 0.5
	else
		icon_state = "blob"
		name = "blob"
		desc = "A thick wall of writhing tendrils."
		brute_resist = 0.25