code:
GLOBAL_LIST_EMPTY(sacrificed) //a mixed list of minds and mobs
GLOBAL_LIST(rune_types) //Every rune that can be drawn by ritual daggers
GLOBAL_LIST_EMPTY(teleport_runes)
GLOBAL_LIST_EMPTY(wall_runes)
/*

This file contains runes.
Runes are used by the cult to cause many different effects and are paramount to their success.
They are drawn with a ritual dagger in blood, and are distinguishable to cultists and normal crew by examining.
Fake runes can be drawn in crayon to fool people.
Runes can either be invoked by one's self or with many different cultists. Each rune has a specific incantation that the cultists will say when invoking it.

*/

/obj/effect/rune
	name = "rune"
	var/cultist_name = "basic rune"
	desc = "An odd collection of symbols drawn in what seems to be blood."
	var/cultist_desc = "a basic rune with no function." //This is shown to cultists who examine the rune in order to determine its true purpose.
	anchored = TRUE
	icon = 'icons/obj/rune.dmi'
	icon_state = "1"
	resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
	layer = SIGIL_LAYER
	color = RUNE_COLOR_RED

	var/invocation = "Aiy ele-mayo!" //This is said by cultists when the rune is invoked.
	var/req_cultists = 1 //The amount of cultists required around the rune to invoke it. If only 1, any cultist can invoke it.
	var/req_cultists_text //if we have a description override for required cultists to invoke
	var/rune_in_use = FALSE // Used for some runes, this is for when you want a rune to not be usable when in use.
	var/log_when_erased = FALSE //Used when you want to keep track of who erased the rune
	var/erase_time = 1.5 SECONDS //How the rune takes to erase

	var/scribe_delay = 40 //how long the rune takes to create
	var/scribe_damage = 0.1 //how much damage you take doing it
	var/invoke_damage = 0 //how much damage invokers take when invoking it
	var/construct_invoke = TRUE //if constructs can invoke it

	var/req_keyword = 0 //If the rune requires a keyword - go figure amirite
	var/keyword //The actual keyword for the rune

/obj/effect/rune/Initialize(mapload, set_keyword)
	. = ..()
	if(set_keyword)
		keyword = set_keyword
	var/image/I = image(icon = 'icons/effects/blood.dmi', icon_state = null, loc = src)
	I.override = TRUE
	add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "cult_runes", I)

/obj/effect/rune/examine(mob/user)
	. = ..()
	if(IS_CULTIST(user) || user.stat == DEAD) //If they're a cultist or a ghost, tell them the effects
		. += "Name: [cultist_name]\n"+\
		"Effects: [capitalize(cultist_desc)]\n"+\
		"Required Acolytes: [req_cultists_text ? "[req_cultists_text]":"[req_cultists]"]"
		if(req_keyword && keyword)
			. += "Keyword: [keyword]"

/obj/effect/rune/attackby(obj/I, mob/user, params)
	if(istype(I, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user))
		if(log_when_erased)
			var/confirm = tgui_alert(user, "Erasing this [cultist_name] rune might be against your goal to summon Nar'Sie.", "Begin to erase the [cultist_name] rune?", list("Proceed", "Abort"))
			if(confirm != "Proceed")
				return
		if(!user.is_holding_item_of_type(/obj/item/melee/cultblade/dagger) || !Adjacent(user) || user.incapacitated() || user.stat == DEAD) //Gee, good thing we made sure cultists can't input stall to grief their team and get banned anyway
			return
		SEND_SOUND(user,'sound/items/sheath.ogg')
		if(do_after(user, erase_time, target = src))
			if(log_when_erased)
				log_game("[cultist_name] rune erased by [key_name(user)] with [I.name]")
				message_admins("[ADMIN_LOOKUPFLW(user)] erased a [cultist_name] rune with [I.name]")
			to_chat(user, span_notice("You carefully erase the [lowertext(cultist_name)] rune."))
			qdel(src)
	else if(istype(I, /obj/item/nullrod))
		if(log_when_erased)
			log_game("[cultist_name] rune erased by [key_name(user)] using a null rod")
			message_admins("[ADMIN_LOOKUPFLW(user)] erased a [cultist_name] rune with a null rod")
		user.say("BEGONE FOUL MAGIKS!!", forced = "nullrod")
		to_chat(user, span_danger("You disrupt the magic of [src] with [I]."))
		SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_NARNAR] = TRUE
		qdel(src)

/obj/effect/rune/attack_hand(mob/living/user, list/modifiers)
	. = ..()
	if(.)
		return
	if(!IS_CULTIST(user))
		to_chat(user, span_warning("You aren't able to understand the words of [src]."))
		return
	var/list/invokers = can_invoke(user)
	if(invokers.len >= req_cultists)
		invoke(invokers)
	else
		to_chat(user, span_danger("You need [req_cultists - invokers.len] more adjacent cultists to use this rune in such a manner."))
		fail_invoke()

/obj/effect/rune/attack_animal(mob/living/simple_animal/user, list/modifiers)
	if(istype(user, /mob/living/simple_animal/shade) || istype(user, /mob/living/simple_animal/hostile/construct))
		if(istype(user, /mob/living/simple_animal/hostile/construct/wraith/angelic) || istype(user, /mob/living/simple_animal/hostile/construct/juggernaut/angelic) || istype(user, /mob/living/simple_animal/hostile/construct/artificer/angelic))
			to_chat(user, span_warning("You purge the rune!"))
			qdel(src)
		else if(construct_invoke || !IS_CULTIST(user)) //if you're not a cult construct we want the normal fail message
			attack_hand(user)
		else
			to_chat(user, span_warning("You are unable to invoke the rune!"))

/obj/effect/rune/proc/conceal() //for talisman of revealing/hiding
	visible_message(span_danger("[src] fades away."))
	invisibility = INVISIBILITY_OBSERVER
	alpha = 100 //To help ghosts distinguish hidden runes

/obj/effect/rune/proc/reveal() //for talisman of revealing/hiding
	invisibility = 0
	visible_message(span_danger("[src] suddenly appears!"))
	alpha = initial(alpha)

/*

There are a few different procs each rune runs through when a cultist activates it.
can_invoke() is called when a cultist activates the rune with an empty hand. If there are multiple cultists, this rune determines if the required amount is nearby.
invoke() is the rune's actual effects.
fail_invoke() is called when the rune fails, via not enough people around or otherwise. Typically this just has a generic 'fizzle' effect.
structure_check() searches for nearby cultist structures required for the invocation. Proper structures are pylons, forges, archives, and altars.

*/

/obj/effect/rune/proc/can_invoke(mob/living/user=null)
	//This proc determines if the rune can be invoked at the time. If there are multiple required cultists, it will find all nearby cultists.
	var/list/invokers = list() //people eligible to invoke the rune
	if(user)
		invokers += user
	if(req_cultists > 1 || istype(src, /obj/effect/rune/convert))
		var/list/things_in_range = range(1, src)
		for(var/mob/living/L in things_in_range)
			if(IS_CULTIST(L))
				if(L == user)
					continue
				if(ishuman(L))
					var/mob/living/carbon/human/H = L
					if((HAS_TRAIT(H, TRAIT_MUTE)) || H.silent)
						continue
				if(L.stat)
					continue
				invokers += L
	return invokers

/obj/effect/rune/proc/invoke(list/invokers)
	//This proc contains the effects of the rune as well as things that happen afterwards. If you want it to spawn an object and then delete itself, have both here.
	for(var/M in invokers)
		if(isliving(M))
			var/mob/living/L = M
			if(invocation)
				L.say(invocation, language = /datum/language/common, ignore_spam = TRUE, forced = "cult invocation")
			if(invoke_damage)
				L.apply_damage(invoke_damage, BRUTE)
				to_chat(L, "[src] saps your strength!")
		else if(istype(M, /obj/item/toy/plush/narplush))
			var/obj/item/toy/plush/narplush/P = M
			P.visible_message("[P] squeaks loudly!")
	do_invoke_glow()

/obj/effect/rune/proc/do_invoke_glow()
	set waitfor = FALSE
	animate(src, transform = matrix()*2, alpha = 0, time = 5, flags = ANIMATION_END_NOW) //fade out
	sleep(5)
	animate(src, transform = matrix(), alpha = 255, time = 0, flags = ANIMATION_END_NOW)

/obj/effect/rune/proc/fail_invoke()
	//This proc contains the effects of a rune if it is not invoked correctly, through either invalid wording or not enough cultists. By default, it's just a basic fizzle.
	visible_message(span_warning("The markings pulse with a small flash of red light, then fall dark."))
	var/oldcolor = color
	color = rgb(255, 0, 0)
	animate(src, color = oldcolor, time = 5)
	addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 5)

//Malformed Rune: This forms if a rune is not drawn correctly. Invoking it does nothing but hurt the user.
/obj/effect/rune/malformed
	cultist_name = "malformed rune"
	cultist_desc = "a senseless rune written in gibberish. No good can come from invoking this."
	invocation = "Ra'sha yoka!"
	invoke_damage = 30

/obj/effect/rune/malformed/Initialize(mapload, set_keyword)
	. = ..()
	icon_state = "[rand(1,7)]"
	color = rgb(rand(0,255), rand(0,255), rand(0,255))

/obj/effect/rune/malformed/invoke(list/invokers)
	..()
	qdel(src)

//Rite of Offering: Converts or sacrifices a target.
/obj/effect/rune/convert
	cultist_name = "Offer"
	cultist_desc = "offers a noncultist above it to Nar'Sie, either converting them or sacrificing them."
	req_cultists_text = "2 for conversion, 3 for living sacrifices and sacrifice targets."
	invocation = "Mah'weyh pleggh at e'ntrath!"
	icon_state = "3"
	color = RUNE_COLOR_OFFER
	req_cultists = 1
	rune_in_use = FALSE

/obj/effect/rune/convert/do_invoke_glow()
	return

/obj/effect/rune/convert/invoke(list/invokers)
	if(rune_in_use)
		return
	var/list/myriad_targets = list()
	var/turf/T = get_turf(src)
	for(var/mob/living/M in T)
		if(!IS_CULTIST(M))
			myriad_targets |= M
	if(!myriad_targets.len)
		fail_invoke()
		log_game("Offer rune failed - no eligible targets")
		return
	rune_in_use = TRUE
	visible_message(span_warning("[src] pulses blood red!"))
	var/oldcolor = color
	color = RUNE_COLOR_DARKRED
	var/mob/living/L = pick(myriad_targets)

	var/mob/living/F = invokers[1]
	var/datum/antagonist/cult/C = F.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
	var/datum/team/cult/Cult_team = C.cult_team
	var/is_convertable = is_convertable_to_cult(L,C.cult_team)
	if(L.stat != DEAD && is_convertable)
		invocation = "Mah'weyh pleggh at e'ntrath!"
		..()
		if(is_convertable)
			do_convert(L, invokers)
	else
		invocation = "Barhah hra zar'garis!"
		..()
		do_sacrifice(L, invokers)
	animate(src, color = oldcolor, time = 5)
	addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 5)
	Cult_team.check_size() // Triggers the eye glow or aura effects if the cult has grown large enough relative to the crew
	rune_in_use = FALSE

/obj/effect/rune/convert/proc/do_convert(mob/living/convertee, list/invokers)
	if(invokers.len < 2)
		for(var/M in invokers)
			to_chat(M, span_warning("You need at least two invokers to convert [convertee]!"))
		log_game("Offer rune failed - tried conversion with one invoker")
		return FALSE
	if(convertee.anti_magic_check(TRUE, TRUE, FALSE, 0)) //Not chargecost because it can be spammed
		for(var/M in invokers)
			to_chat(M, span_warning("Something is shielding [convertee]'s mind!"))
		log_game("Offer rune failed - convertee had anti-magic")
		return FALSE
	var/brutedamage = convertee.getBruteLoss()
	var/burndamage = convertee.getFireLoss()
	if(brutedamage || burndamage)
		convertee.adjustBruteLoss(-(brutedamage * 0.75))
		convertee.adjustFireLoss(-(burndamage * 0.75))
	convertee.visible_message("[convertee] writhes in pain \
	[brutedamage || burndamage ? "even as [convertee.p_their()] wounds heal and close" : "as the markings below [convertee.p_them()] glow a bloody red"]!", \
	span_cultlarge("AAAAAAAAAAAAAA-"))
	convertee.mind?.add_antag_datum(/datum/antagonist/cult)
	convertee.Unconscious(100)
	new /obj/item/melee/cultblade/dagger(get_turf(src))
	convertee.mind.special_role = ROLE_CULTIST
	to_chat(convertee, "Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible, truth. The veil of reality has been ripped away \
	and something evil takes root.")
	to_chat(convertee, "Assist your new compatriots in their dark dealings. Your goal is theirs, and theirs is yours. You serve the Geometer above all else. Bring it back.\
	")
	if(ishuman(convertee))
		var/mob/living/carbon/human/H = convertee
		H.uncuff()
		H.stuttering = 0
		H.cultslurring = 0
		if(prob(1) || SSevents.holidays && SSevents.holidays[APRIL_FOOLS])
			H.say("You son of a bitch! I'm in.", forced = "That son of a bitch! They're in.")
	return TRUE

/obj/effect/rune/convert/proc/do_sacrifice(mob/living/sacrificial, list/invokers)
	var/mob/living/first_invoker = invokers[1]
	if(!first_invoker)
		return FALSE
	var/datum/antagonist/cult/C = first_invoker.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
	if(!C)
		return

	if(ispAI(sacrificial))
		for(var/M in invokers)
			to_chat(M, span_cultitalic("You don't think this is what Nar'Sie had in mind when She asked for blood sacrifices..."))
		log_game("Offer rune failed - tried sacrificing pAI")
		return FALSE

	var/big_sac = FALSE
	if((((ishuman(sacrificial) || iscyborg(sacrificial)) && sacrificial.stat != DEAD) || C.cult_team.is_sacrifice_target(sacrificial.mind)) && invokers.len < 3)
		for(var/M in invokers)
			to_chat(M, span_cultitalic("[sacrificial] is too greatly linked to the world! You need three acolytes!"))
		log_game("Offer rune failed - not enough acolytes and target is living or sac target")
		return FALSE
	if(sacrificial.mind)
		GLOB.sacrificed += sacrificial.mind
		for(var/datum/objective/sacrifice/sac_objective in C.cult_team.objectives)
			if(sac_objective.target == sacrificial.mind)
				sac_objective.sacced = TRUE
				sac_objective.update_explanation_text()
				big_sac = TRUE
	else
		GLOB.sacrificed += sacrificial

	new /obj/effect/temp_visual/cult/sac(get_turf(src))
	for(var/M in invokers)
		if(big_sac)
			to_chat(M, span_cultlarge("\"Yes! This is the one I desire! You have done well.\""))
		else
			if(ishuman(sacrificial) || iscyborg(sacrificial))
				to_chat(M, span_cultlarge("\"I accept this sacrifice.\""))
			else
				to_chat(M, span_cultlarge("\"I accept this meager sacrifice.\""))

	if(iscyborg(sacrificial))
		var/construct_class = show_radial_menu(first_invoker, sacrificial, GLOB.construct_radial_images, require_near = TRUE, tooltips = TRUE)
		if(QDELETED(sacrificial) || !construct_class)
			return FALSE
		sacrificial.grab_ghost()
		make_new_construct_from_class(construct_class, THEME_CULT, sacrificial, first_invoker, TRUE, get_turf(src))
		var/mob/living/silicon/robot/sacriborg = sacrificial
		sacriborg.mmi = null
		qdel(sacrificial)
		return TRUE
	var/obj/item/soulstone/stone = new /obj/item/soulstone(get_turf(src))
	if(sacrificial.mind && !sacrificial.suiciding)
		stone.invisibility = INVISIBILITY_MAXIMUM //so it's not picked up during transfer_soul()
		stone.capture_soul(sacrificial, first_invoker, TRUE)
		stone.invisibility = 0

	if(sacrificial)
		playsound(sacrificial, 'sound/magic/disintegrate.ogg', 100, TRUE)
		sacrificial.gib()
	return TRUE

/obj/effect/rune/empower
	cultist_name = "Empower"
	cultist_desc = "allows cultists to prepare greater amounts of blood magic at far less of a cost."
	invocation = "H'drak v'loso, mir'kanas verbot!"
	icon_state = "3"
	color = RUNE_COLOR_TALISMAN
	construct_invoke = FALSE

/obj/effect/rune/empower/invoke(list/invokers)
	. = ..()
	var/mob/living/user = invokers[1] //the first invoker is always the user
	for(var/datum/action/innate/cult/blood_magic/BM in user.actions)
		BM.Activate()

/obj/effect/rune/teleport
	cultist_name = "Teleport"
	cultist_desc = "warps everything above it to another chosen teleport rune."
	invocation = "Sas'so c'arta forbici!"
	icon_state = "2"
	color = RUNE_COLOR_TELEPORT
	req_keyword = TRUE
	light_power = 4
	var/obj/effect/temp_visual/cult/portal/inner_portal //The portal "hint" for off-station teleportations
	var/obj/effect/temp_visual/cult/rune_spawn/rune2/outer_portal
	var/listkey

/obj/effect/rune/teleport/Initialize(mapload, set_keyword)
	. = ..()
	var/area/A = get_area(src)
	var/locname = initial(A.name)
	listkey = set_keyword ? "[set_keyword] [locname]":"[locname]"
	GLOB.teleport_runes += src

/obj/effect/rune/teleport/Destroy()
	GLOB.teleport_runes -= src
	return ..()

/obj/effect/rune/teleport/invoke(list/invokers)
	var/mob/living/user = invokers[1] //the first invoker is always the user
	var/list/potential_runes = list()
	var/list/teleportnames = list()
	for(var/R in GLOB.teleport_runes)
		var/obj/effect/rune/teleport/T = R
		if(T != src && !is_away_level(T.z))
			potential_runes[avoid_assoc_duplicate_keys(T.listkey, teleportnames)] = T

	if(!potential_runes.len)
		to_chat(user, span_warning("There are no valid runes to teleport to!"))
		log_game("Teleport rune failed - no other teleport runes")
		fail_invoke()
		return

	var/turf/T = get_turf(src)
	if(is_away_level(T.z))
		to_chat(user, "You are not in the right dimension!")
		log_game("Teleport rune failed - user in away mission")
		fail_invoke()
		return

	var/input_rune_key = input(user, "Choose a rune to teleport to.", "Rune to Teleport to") as null|anything in potential_runes //we know what key they picked
	var/obj/effect/rune/teleport/actual_selected_rune = potential_runes[input_rune_key] //what rune does that key correspond to?
	if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated() || !actual_selected_rune)
		fail_invoke()
		return

	var/turf/target = get_turf(actual_selected_rune)
	if(target.is_blocked_turf(TRUE))
		to_chat(user, span_warning("The target rune is blocked. Attempting to teleport to it would be massively unwise."))
		fail_invoke()
		return
	var/movedsomething = FALSE
	var/moveuserlater = FALSE
	var/movesuccess = FALSE
	for(var/atom/movable/A in T)
		if(istype(A, /obj/effect/dummy/phased_mob))
			continue
		if(ismob(A))
			if(!isliving(A)) //Let's not teleport ghosts and AI eyes.
				continue
			if(ishuman(A))
				new /obj/effect/temp_visual/dir_setting/cult/phase/out(T, A.dir)
				new /obj/effect/temp_visual/dir_setting/cult/phase(target, A.dir)
		if(A == user)
			moveuserlater = TRUE
			movedsomething = TRUE
			continue
		if(!A.anchored)
			movedsomething = TRUE
			if(do_teleport(A, target, channel = TELEPORT_CHANNEL_CULT))
				movesuccess = TRUE
	if(movedsomething)
		..()
		if(moveuserlater)
			if(do_teleport(user, target, channel = TELEPORT_CHANNEL_CULT))
				movesuccess = TRUE
		if(movesuccess)
			visible_message(span_warning("There is a sharp crack of inrushing air, and everything above the rune disappears!"), null, "You hear a sharp crack.")
			to_chat(user, span_cult("You[moveuserlater ? "r vision blurs, and you suddenly appear somewhere else":" send everything above the rune away"]."))
		else
			to_chat(user, span_cult("You[moveuserlater ? "r vision blurs briefly, but nothing happens":" try send everything above the rune away, but the teleportation fails"]."))
		if(is_mining_level(z) && !is_mining_level(target.z)) //No effect if you stay on lavaland
			actual_selected_rune.handle_portal("lava")
		else
			var/area/A = get_area(T)
			if(initial(A.name) == "Space")
				actual_selected_rune.handle_portal("space", T)
		if(movesuccess)
			target.visible_message(span_warning("There is a boom of outrushing air as something appears above the rune!"), null, "You hear a boom.")
	else
		fail_invoke()

/obj/effect/rune/teleport/proc/handle_portal(portal_type, turf/origin)
	var/turf/T = get_turf(src)
	close_portal() // To avoid stacking descriptions/animations
	playsound(T, pick('sound/effects/sparks1.ogg', 'sound/effects/sparks2.ogg', 'sound/effects/sparks3.ogg', 'sound/effects/sparks4.ogg'), 100, TRUE, 14)
	inner_portal = new /obj/effect/temp_visual/cult/portal(T)
	if(portal_type == "space")
		set_light_color(color)
		desc += "
A tear in reality reveals a black void interspersed with dots of light... something recently teleported here from space.
The void feels like it's trying to pull you to the [dir2text(get_dir(T, origin))]!"
	else
		inner_portal.icon_state = "lava"
		set_light_color(LIGHT_COLOR_FIRE)
		desc += "
A tear in reality reveals a coursing river of lava... something recently teleported here from the Lavaland Mines!"
	outer_portal = new(T, 600, color)
	set_light_range(4)
	update_light()
	addtimer(CALLBACK(src, .proc/close_portal), 600, TIMER_UNIQUE)

/obj/effect/rune/teleport/proc/close_portal()
	qdel(inner_portal)
	qdel(outer_portal)
	desc = initial(desc)
	set_light_range(0)
	update_light()

//Ritual of Dimensional Rending: Calls forth the avatar of Nar'Sie upon the station.
/obj/effect/rune/narsie
	cultist_name = "Nar'Sie"
	cultist_desc = "tears apart dimensional barriers, calling forth the Geometer. Requires 9 invokers."
	invocation = "TOK-LYR RQA-NAP G'OLT-ULOFT!!"
	req_cultists = 9
	icon = 'icons/effects/96x96.dmi'
	color = RUNE_COLOR_DARKRED
	icon_state = "rune_large"
	pixel_x = -32 //So the big ol' 96x96 sprite shows up right
	pixel_y = -32
	scribe_delay = 500 //how long the rune takes to create
	scribe_damage = 40.1 //how much damage you take doing it
	log_when_erased = TRUE
	erase_time = 5 SECONDS
	var/used = FALSE

/obj/effect/rune/narsie/Initialize(mapload, set_keyword)
	. = ..()
	SSpoints_of_interest.make_point_of_interest(src)

/obj/effect/rune/narsie/conceal() //can't hide this, and you wouldn't want to
	return

/obj/effect/rune/narsie/invoke(list/invokers)
	if(used)
		return
	if(!is_station_level(z))
		return
	var/mob/living/user = invokers[1]
	var/datum/antagonist/cult/user_antag = user.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
	var/datum/objective/eldergod/summon_objective = locate() in user_antag.cult_team.objectives
	var/area/place = get_area(src)
	if(!(place in summon_objective.summon_spots))
		to_chat(user, span_cultlarge("The Geometer can only be summoned where the veil is weak - in [english_list(summon_objective.summon_spots)]!"))
		return
	if(locate(/obj/narsie) in SSpoints_of_interest.narsies)
		for(var/M in invokers)
			to_chat(M, span_warning("Nar'Sie is already on this plane!"))
		log_game("Nar'Sie rune failed - already summoned")
		return
	//BEGIN THE SUMMONING
	used = TRUE
	..()
	sound_to_playing_players('sound/effects/dimensional_rend.ogg')
	var/turf/T = get_turf(src)
	sleep(40)
	if(src)
		color = RUNE_COLOR_RED
	new /obj/narsie(T) //Causes Nar'Sie to spawn even if the rune has been removed

//Rite of Resurrection: Requires a dead or inactive cultist. When reviving the dead, you can only perform one revival for every three sacrifices your cult has carried out.
/obj/effect/rune/raise_dead
	cultist_name = "Revive"
	cultist_desc = "requires a dead, mindless, or inactive cultist placed upon the rune. For each three bodies sacrificed to the dark patron, one body will be mended and their mind awoken"
	invocation = "Pasnar val'keriam usinar. Savrae ines amutan. Yam'toth remium il'tarat!" //Depends on the name of the user - see below
	icon_state = "1"
	color = RUNE_COLOR_MEDIUMRED
	var/static/sacrifices_used = -SOULS_TO_REVIVE // Cultists get one "free" revive

/obj/effect/rune/raise_dead/examine(mob/user)
	. = ..()
	if(IS_CULTIST(user) || user.stat == DEAD)
		. += "Sacrifices unrewarded: [LAZYLEN(GLOB.sacrificed) - sacrifices_used]"

/obj/effect/rune/raise_dead/invoke(list/invokers)
	var/turf/T = get_turf(src)
	var/mob/living/mob_to_revive
	var/list/potential_revive_mobs = list()
	var/mob/living/user = invokers[1]
	if(rune_in_use)
		return
	rune_in_use = TRUE
	for(var/mob/living/M in T.contents)
		if(IS_CULTIST(M) && (M.stat == DEAD || !M.client || M.client.is_afk()))
			potential_revive_mobs |= M
	if(!potential_revive_mobs.len)
		to_chat(user, "There are no dead cultists on the rune!")
		log_game("Raise Dead rune failed - no cultists to revive")
		fail_invoke()
		return
	if(potential_revive_mobs.len > 1)
		mob_to_revive = input(user, "Choose a cultist to revive.", "Cultist to Revive") as null|anything in potential_revive_mobs
	else
		mob_to_revive = potential_revive_mobs[1]
	if(QDELETED(src) || !validness_checks(mob_to_revive, user))
		fail_invoke()
		return
	if(user.name == "Herbert West")
		invocation = "To life, to life, I bring them!"
	else
		invocation = initial(invocation)
	..()
	if(mob_to_revive.stat == DEAD)
		var/diff = LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - sacrifices_used
		if(diff < 0)
			to_chat(user, span_warning("Your cult must carry out [abs(diff)] more sacrifice\s before it can revive another cultist!"))
			fail_invoke()
			return
		sacrifices_used += SOULS_TO_REVIVE
		mob_to_revive.revive(full_heal = TRUE, admin_revive = TRUE) //This does remove traits and such, but the rune might actually see some use because of it!
		mob_to_revive.grab_ghost()
	if(!mob_to_revive.client || mob_to_revive.client.is_afk())
		set waitfor = FALSE
		var/list/mob/dead/observer/candidates = poll_candidates_for_mob("Do you want to play as a [mob_to_revive.real_name], an inactive blood cultist?", ROLE_CULTIST, ROLE_CULTIST, 5 SECONDS, mob_to_revive)
		if(LAZYLEN(candidates))
			var/mob/dead/observer/C = pick(candidates)
			to_chat(mob_to_revive.mind, "Your physical form has been taken over by another soul due to your inactivity! Ahelp if you wish to regain your form.")
			message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(mob_to_revive)]) to replace an AFK player.")
			mob_to_revive.ghostize(0)
			mob_to_revive.key = C.key
		else
			fail_invoke()
			return
	SEND_SOUND(mob_to_revive, 'sound/ambience/antag/bloodcult.ogg')
	to_chat(mob_to_revive, span_cultlarge("\"PASNAR SAVRAE YAM'TOTH. Arise.\""))
	mob_to_revive.visible_message(span_warning("[mob_to_revive] draws in a huge breath, red light shining from [mob_to_revive.p_their()] eyes."), \
								  span_cultlarge("You awaken suddenly from the void. You're alive!"))
	rune_in_use = FALSE

/obj/effect/rune/raise_dead/proc/validness_checks(mob/living/target_mob, mob/living/user)
	var/turf/T = get_turf(src)
	if(QDELETED(user))
		return FALSE
	if(!Adjacent(user) || user.incapacitated())
		return FALSE
	if(QDELETED(target_mob))
		return FALSE
	if(!(target_mob in T.contents))
		to_chat(user, "The cultist to revive has been moved!")
		log_game("Raise Dead rune failed - revival target moved")
		return FALSE
	return TRUE

/obj/effect/rune/raise_dead/fail_invoke()
	..()
	rune_in_use = FALSE
	for(var/mob/living/M in range(1,src))
		if(IS_CULTIST(M) && M.stat == DEAD)
			M.visible_message(span_warning("[M] twitches."))

//Rite of the Corporeal Shield: When invoked, becomes solid and cannot be passed. Invoke again to undo.
/obj/effect/rune/wall
	cultist_name = "Barrier"
	cultist_desc = "when invoked, makes a temporary invisible wall to block passage. Can be invoked again to reverse this."
	invocation = "Khari'd! Eske'te tannin!"
	icon_state = "4"
	color = RUNE_COLOR_DARKRED
	///The barrier summoned by the rune when invoked. Tracked as a variable to prevent refreshing the barrier's integrity.
	var/obj/structure/emergency_shield/cult/barrier/barrier //barrier is the path and variable name.... i am not a clever man

/obj/effect/rune/wall/Initialize(mapload, set_keyword)
	. = ..()
	GLOB.wall_runes += src

/obj/effect/rune/wall/Destroy()
	GLOB.wall_runes -= src
	if(barrier)
		QDEL_NULL(barrier)
	return ..()

/obj/effect/rune/wall/invoke(list/invokers)
	var/mob/living/user = invokers[1]
	..()
	if(!barrier)
		barrier = new /obj/structure/emergency_shield/cult/barrier(src.loc)
		barrier.parent_rune = src
	barrier.Toggle()
	if(iscarbon(user))
		var/mob/living/carbon/C = user
		C.apply_damage(2, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))

//Rite of Joined Souls: Summons a single cultist.
/obj/effect/rune/summon
	cultist_name = "Summon Cultist"
	cultist_desc = "summons a single cultist to the rune. Requires 2 invokers."
	invocation = "N'ath reth sh'yro eth d'rekkathnor!"
	req_cultists = 2
	invoke_damage = 10
	icon_state = "3"
	color = RUNE_COLOR_SUMMON

/obj/effect/rune/summon/invoke(list/invokers)
	var/mob/living/user = invokers[1]
	var/list/cultists = list()
	for(var/datum/mind/M as anything in get_antag_minds(/datum/antagonist/cult))
		if(!(M.current in invokers) && M.current && M.current.stat != DEAD)
			cultists |= M.current
	var/mob/living/cultist_to_summon = input(user, "Who do you wish to call to [src]?", "Followers of the Geometer") as null|anything in cultists
	if(!Adjacent(user) || !src || QDELETED(src) || user.incapacitated())
		return
	if(!cultist_to_summon)
		to_chat(user, "You require a summoning target!")
		fail_invoke()
		log_game("Summon Cultist rune failed - no target")
		return
	if(cultist_to_summon.stat == DEAD)
		to_chat(user, "[cultist_to_summon] has died!")
		fail_invoke()
		log_game("Summon Cultist rune failed - target died")
		return
	if(cultist_to_summon.pulledby || cultist_to_summon.buckled)
		to_chat(user, "[cultist_to_summon] is being held in place!")
		fail_invoke()
		log_game("Summon Cultist rune failed - target restrained")
		return
	if(!IS_CULTIST(cultist_to_summon))
		to_chat(user, "[cultist_to_summon] is not a follower of the Geometer!")
		fail_invoke()
		log_game("Summon Cultist rune failed - target was deconverted")
		return
	if(is_away_level(cultist_to_summon.z))
		to_chat(user, "[cultist_to_summon] is not in our dimension!")
		fail_invoke()
		log_game("Summon Cultist rune failed - target in away mission")
		return
	cultist_to_summon.visible_message(span_warning("[cultist_to_summon] suddenly disappears in a flash of red light!"), \
									  "Overwhelming vertigo consumes you as you are hurled through the air!")
	..()
	visible_message(span_warning("A foggy shape materializes atop [src] and solidifies into [cultist_to_summon]!"))
	cultist_to_summon.forceMove(get_turf(src))
	qdel(src)

//Rite of Boiling Blood: Deals extremely high amounts of damage to non-cultists nearby
/obj/effect/rune/blood_boil
	cultist_name = "Boil Blood"
	cultist_desc = "boils the blood of non-believers who can see the rune, rapidly dealing extreme amounts of damage. Requires 3 invokers."
	invocation = "Dedo ol'btoh!"
	icon_state = "4"
	color = RUNE_COLOR_BURNTORANGE
	light_color = LIGHT_COLOR_LAVA
	req_cultists = 3
	invoke_damage = 10
	construct_invoke = FALSE
	var/tick_damage = 25
	rune_in_use = FALSE

/obj/effect/rune/blood_boil/do_invoke_glow()
	return

/obj/effect/rune/blood_boil/invoke(list/invokers)
	if(rune_in_use)
		return
	..()
	rune_in_use = TRUE
	var/turf/T = get_turf(src)
	visible_message(span_warning("[src] turns a bright, glowing orange!"))
	color = "#FC9B54"
	set_light(6, 1, color)
	//for(var/mob/living/L in viewers(T)) //SEPTIC EDIT REMOVAL
	//SEPTIC EDIT BEGIN
	for(var/mob/living/L in fov_viewers(world.view, T))
	//SEPTIC EDIT END
		if(!IS_CULTIST(L) && L.blood_volume)
			var/atom/I = L.anti_magic_check(chargecost = 0)
			if(I)
				if(isitem(I))
					to_chat(L, span_userdanger("[I] suddenly burns hotly before returning to normal!"))
				continue
			to_chat(L, span_cultlarge("Your blood boils in your veins!"))
	animate(src, color = "#FCB56D", time = 4)
	sleep(4)
	if(QDELETED(src))
		return
	do_area_burn(T, 0.5)
	animate(src, color = "#FFDF80", time = 5)
	sleep(5)
	if(QDELETED(src))
		return
	do_area_burn(T, 1)
	animate(src, color = "#FFFDF4", time = 6)
	sleep(6)
	if(QDELETED(src))
		return
	do_area_burn(T, 1.5)
	new /obj/effect/hotspot(T)
	qdel(src)

/obj/effect/rune/blood_boil/proc/do_area_burn(turf/T, multiplier)
	set_light(6, 1, color)
	//for(var/mob/living/L in viewers(T)) //SEPTIC EDIT REMOVAL
	//SEPTIC EDIT BEGIN
	for(var/mob/living/L in fov_viewers(world.view, T))
	//SEPTIC EDIT END
		if(!IS_CULTIST(L) && L.blood_volume)
			if(L.anti_magic_check(chargecost = 0))
				continue
			L.take_overall_damage(tick_damage*multiplier, tick_damage*multiplier)

//Rite of Spectral Manifestation: Summons a ghost on top of the rune as a cultist human with no items. User must stand on the rune at all times, and takes damage for each summoned ghost.
/obj/effect/rune/manifest
	cultist_name = "Spirit Realm"
	cultist_desc = "manifests a spirit servant of the Geometer and allows you to ascend as a spirit yourself. The invoker must not move from atop the rune, and will take damage for each summoned spirit."
	invocation = "Gal'h'rfikk harfrandid mud'gib!" //how the fuck do you pronounce this
	icon_state = "7"
	invoke_damage = 10
	construct_invoke = FALSE
	color = RUNE_COLOR_DARKRED
	var/mob/living/affecting = null
	var/ghost_limit = 3
	var/ghosts = 0

/obj/effect/rune/manifest/Initialize(mapload)
	. = ..()

/obj/effect/rune/manifest/can_invoke(mob/living/user)
	if(!(user in get_turf(src)))
		to_chat(user, "You must be standing on [src]!")
		fail_invoke()
		log_game("Manifest rune failed - user not standing on rune")
		return list()
	if(user.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST))
		to_chat(user, "Ghosts can't summon more ghosts!")
		fail_invoke()
		log_game("Manifest rune failed - user is a ghost")
		return list()
	return ..()

/obj/effect/rune/manifest/invoke(list/invokers)
	. = ..()
	var/mob/living/user = invokers[1]
	var/turf/T = get_turf(src)
	var/choice = tgui_alert(user,"You tear open a connection to the spirit realm...",,list("Summon a Cult Ghost","Ascend as a Dark Spirit","Cancel"))
	if(choice == "Summon a Cult Ghost")
		if(!is_station_level(T.z))
			to_chat(user, span_cultitalic("The veil is not weak enough here to manifest spirits, you must be on station!"))
			return
		if(ghosts >= ghost_limit)
			to_chat(user, span_cultitalic("You are sustaining too many ghosts to summon more!"))
			fail_invoke()
			log_game("Manifest rune failed - too many summoned ghosts")
			return list()
		notify_ghosts("Manifest rune invoked in [get_area(src)].", 'sound/effects/ghost2.ogg', source = src, header = "Manifest rune")
		var/list/ghosts_on_rune = list()
		for(var/mob/dead/observer/O in T)
			if(O.client && !is_banned_from(O.ckey, ROLE_CULTIST) && !QDELETED(src) && !(isAdminObserver(O) && (O.client.prefs.toggles & ADMIN_IGNORE_CULT_GHOST)) && !QDELETED(O))
				ghosts_on_rune += O
		if(!ghosts_on_rune.len)
			to_chat(user, span_cultitalic("There are no spirits near [src]!"))
			fail_invoke()
			log_game("Manifest rune failed - no nearby ghosts")
			return list()
		var/mob/dead/observer/ghost_to_spawn = pick(ghosts_on_rune)
		var/mob/living/carbon/human/cult_ghost/new_human = new(T)
		new_human.real_name = ghost_to_spawn.real_name
		new_human.alpha = 150 //Makes them translucent
		new_human.equipOutfit(/datum/outfit/ghost_cultist) //give them armor
		new_human.apply_status_effect(STATUS_EFFECT_SUMMONEDGHOST) //ghosts can't summon more ghosts
		new_human.see_invisible = SEE_INVISIBLE_OBSERVER
		ghosts++
		playsound(src, 'sound/magic/exit_blood.ogg', 50, TRUE)
		visible_message(span_warning("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo":""]man."))
		to_chat(user, span_cultitalic("Your blood begins flowing into [src]. You must remain in place and conscious to maintain the forms of those summoned. This will hurt you slowly but surely..."))
		var/obj/structure/emergency_shield/cult/weak/N = new(T)
		new_human.key = ghost_to_spawn.key
		new_human.mind?.add_antag_datum(/datum/antagonist/cult)
		to_chat(new_human, span_cultitalic("You are a servant of the Geometer. You have been made semi-corporeal by the cult of Nar'Sie, and you are to serve them at all costs."))

		while(!QDELETED(src) && !QDELETED(user) && !QDELETED(new_human) && (user in T))
			if(user.stat != CONSCIOUS || HAS_TRAIT(new_human, TRAIT_CRITICAL_CONDITION))
				break
			user.apply_damage(0.1, BRUTE)
			sleep(1)

		qdel(N)
		ghosts--
		if(new_human)
			new_human.visible_message(span_warning("[new_human] suddenly dissolves into bones and ashes."), \
					span_cultlarge("Your link to the world fades. Your form breaks apart."))
			for(var/obj/I in new_human)
				new_human.dropItemToGround(I, TRUE)
			new_human.dust()
	else if(choice == "Ascend as a Dark Spirit")
		affecting = user
		affecting.add_atom_colour(RUNE_COLOR_DARKRED, ADMIN_COLOUR_PRIORITY)
		affecting.visible_message(span_warning("[affecting] freezes statue-still, glowing an unearthly red."), \
						span_cult("You see what lies beyond. All is revealed. In this form you find that your voice booms louder and you can mark targets for the entire cult"))
		var/mob/dead/observer/G = affecting.ghostize(1)
		var/datum/action/innate/cult/comm/spirit/CM = new
		var/datum/action/innate/cult/ghostmark/GM = new
		G.name = "Dark Spirit of [G.name]"
		G.color = "red"
		CM.Grant(G)
		GM.Grant(G)
		while(!QDELETED(affecting))
			if(!(affecting in T))
				user.visible_message(span_warning("A spectral tendril wraps around [affecting] and pulls [affecting.p_them()] back to the rune!"))
				Beam(affecting, icon_state="drainbeam", time = 2)
				affecting.forceMove(get_turf(src)) //NO ESCAPE :^)
			if(affecting.key)
				affecting.visible_message(span_warning("[affecting] slowly relaxes, the glow around [affecting.p_them()] dimming."), \
					span_danger("You are re-united with your physical form. [src] releases its hold over you."))
				affecting.Paralyze(40)
				break
			if(affecting.health <= 10)
				to_chat(G, span_cultitalic("Your body can no longer sustain the connection!"))
				break
			sleep(5)
		CM.Remove(G)
		GM.Remove(G)
		affecting.remove_atom_colour(ADMIN_COLOUR_PRIORITY, RUNE_COLOR_DARKRED)
		affecting.grab_ghost()
		affecting = null
		rune_in_use = FALSE

/mob/living/carbon/human/cult_ghost/spill_organs(no_brain, no_organs, no_bodyparts) //cult ghosts never drop a brain
	no_brain = TRUE
	. = ..()

/mob/living/carbon/human/cult_ghost/getorganszone(zone, subzones = 0)
	. = ..()
	for(var/obj/item/organ/brain/B in .) //they're not that smart, really
		. -= B

/obj/effect/rune/apocalypse
	cultist_name = "Apocalypse"
	cultist_desc = "a harbinger of the end times. Grows in strength with the cult's desperation - but at the risk of... side effects."
	invocation = "Ta'gh fara'qha fel d'amar det!"
	icon = 'icons/effects/96x96.dmi'
	icon_state = "apoc"
	pixel_x = -32
	pixel_y = -32
	color = RUNE_COLOR_DARKRED
	req_cultists = 3
	scribe_delay = 100

/obj/effect/rune/apocalypse/invoke(list/invokers)
	if(rune_in_use)
		return
	. = ..()
	var/area/place = get_area(src)
	var/mob/living/user = invokers[1]
	var/datum/antagonist/cult/user_antag = user.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
	var/datum/objective/eldergod/summon_objective = locate() in user_antag.cult_team.objectives
	if(summon_objective.summon_spots.len <= 1)
		to_chat(user, span_cultlarge("Only one ritual site remains - it must be reserved for the final summoning!"))
		return
	if(!(place in summon_objective.summon_spots))
		to_chat(user, span_cultlarge("The Apocalypse rune will remove a ritual site, where Nar'Sie can be summoned, it can only be scribed in [english_list(summon_objective.summon_spots)]!"))
		return
	summon_objective.summon_spots -= place
	rune_in_use = TRUE
	var/turf/T = get_turf(src)
	new /obj/effect/temp_visual/dir_setting/curse/grasp_portal/fading(T)
	var/intensity = 0
	for(var/mob/living/M in GLOB.player_list)
		if(IS_CULTIST(M))
			intensity++
	intensity = max(60, 360 - (360*(intensity/GLOB.player_list.len + 0.3)**2)) //significantly lower intensity for "winning" cults
	var/duration = intensity*10
	playsound(T, 'sound/magic/enter_blood.ogg', 100, TRUE)
	visible_message(span_warning("A colossal shockwave of energy bursts from the rune, disintegrating it in the process!"))
	for(var/mob/living/L in range(src, 3))
		L.Paralyze(30)
	empulse(T, 0.42*(intensity), 1)
	var/list/images = list()
	var/zmatch = T.z
	var/datum/atom_hud/AH = GLOB.huds[DATA_HUD_SECURITY_ADVANCED]
	for(var/mob/living/M in GLOB.alive_mob_list)
		if(M.z != zmatch)
			continue
		if(ishuman(M))
			if(!IS_CULTIST(M))
				AH.remove_hud_from(M)
				addtimer(CALLBACK(GLOBAL_PROC, .proc/hudFix, M), duration)
			var/image/A = image('icons/mob/cult.dmi',M,"cultist", ABOVE_MOB_LAYER)
			A.override = 1
			add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/noncult, "human_apoc", A, NONE)
			addtimer(CALLBACK(M,/atom/.proc/remove_alt_appearance,"human_apoc",TRUE), duration)
			images += A
			SEND_SOUND(M, pick(sound('sound/ambience/antag/bloodcult.ogg'),sound('sound/spookoween/ghost_whisper.ogg'),sound('sound/spookoween/ghosty_wind.ogg')))
		else
			var/construct = pick("floater","artificer","behemoth")
			var/image/B = image('icons/mob/mob.dmi',M,construct, ABOVE_MOB_LAYER)
			B.override = 1
			add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/noncult, "mob_apoc", B, NONE)
			addtimer(CALLBACK(M,/atom/.proc/remove_alt_appearance,"mob_apoc",TRUE), duration)
			images += B
		if(!IS_CULTIST(M))
			if(M.client)
				var/image/C = image('icons/effects/cult_effects.dmi',M,"bloodsparkles", ABOVE_MOB_LAYER)
				add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/cult, "cult_apoc", C, NONE)
				addtimer(CALLBACK(M,/atom/.proc/remove_alt_appearance,"cult_apoc",TRUE), duration)
				images += C
		else
			to_chat(M, span_cultlarge("An Apocalypse Rune was invoked in the [place.name], it is no longer available as a summoning site!"))
			SEND_SOUND(M, 'sound/effects/pope_entry.ogg')
	image_handler(images, duration)
	if(intensity>=285) // Based on the prior formula, this means the cult makes up <15% of current players
		var/outcome = rand(1,100)
		switch(outcome)
			if(1 to 10)
				var/datum/round_event_control/disease_outbreak/D = new()
				var/datum/round_event_control/mice_migration/M = new()
				D.runEvent()
				M.runEvent()
			if(11 to 20)
				var/datum/round_event_control/radiation_storm/RS = new()
				RS.runEvent()
			if(21 to 30)
				var/datum/round_event_control/brand_intelligence/BI = new()
				BI.runEvent()
			if(31 to 40)
				var/datum/round_event_control/immovable_rod/R = new()
				R.runEvent()
				R.runEvent()
				R.runEvent()
			if(41 to 50)
				var/datum/round_event_control/meteor_wave/MW = new()
				MW.runEvent()
			if(51 to 60)
				var/datum/round_event_control/spider_infestation/SI = new()
				SI.runEvent()
			if(61 to 70)
				var/datum/round_event_control/anomaly/anomaly_flux/AF
				var/datum/round_event_control/anomaly/anomaly_grav/AG
				var/datum/round_event_control/anomaly/anomaly_pyro/AP
				var/datum/round_event_control/anomaly/anomaly_vortex/AV
				AF.runEvent()
				AG.runEvent()
				AP.runEvent()
				AV.runEvent()
			if(71 to 80)
				var/datum/round_event_control/spacevine/SV = new()
				var/datum/round_event_control/grey_tide/GT = new()
				SV.runEvent()
				GT.runEvent()
			if(81 to 100)
				var/datum/round_event_control/portal_storm_narsie/N = new()
				N.runEvent()
	qdel(src)

/obj/effect/rune/apocalypse/proc/image_handler(list/images, duration)
	var/end = world.time + duration
	set waitfor = 0
	while(end>world.time)
		for(var/image/I in images)
			I.override = FALSE
			animate(I, alpha = 0, time = 25, flags = ANIMATION_PARALLEL)
		sleep(35)
		for(var/image/I in images)
			animate(I, alpha = 255, time = 25, flags = ANIMATION_PARALLEL)
		sleep(25)
		for(var/image/I in images)
			if(I.icon_state != "bloodsparkles")
				I.override = TRUE
		sleep(190)

/proc/hudFix(mob/living/carbon/human/target)
	if(!target || !target.client)
		return
	var/obj/O = target.get_item_by_slot(ITEM_SLOT_EYES)
	if(istype(O, /obj/item/clothing/glasses/hud/security))
		var/datum/atom_hud/AH = GLOB.huds[DATA_HUD_SECURITY_ADVANCED]
		AH.add_hud_to(target)


code:
/*
Abilities that can be purchased by disease mobs. Most are just passive symptoms that will be
added to their disease, but some are active abilites that affect only the target the overmind
is currently following.
*/

GLOBAL_LIST_INIT(disease_ability_singletons, list(
new /datum/disease_ability/action/cough,
new /datum/disease_ability/action/sneeze,
new /datum/disease_ability/action/infect,
new /datum/disease_ability/symptom/mild/cough,
new /datum/disease_ability/symptom/mild/sneeze,
new /datum/disease_ability/symptom/medium/shedding,
new /datum/disease_ability/symptom/medium/beard,
new /datum/disease_ability/symptom/medium/hallucigen,
new /datum/disease_ability/symptom/medium/choking,
new /datum/disease_ability/symptom/medium/confusion,
new /datum/disease_ability/symptom/medium/vomit,
new /datum/disease_ability/symptom/medium/voice_change,
new /datum/disease_ability/symptom/medium/visionloss,
new /datum/disease_ability/symptom/medium/deafness,
new /datum/disease_ability/symptom/powerful/narcolepsy,
new /datum/disease_ability/symptom/medium/fever,
new /datum/disease_ability/symptom/medium/chills,
new /datum/disease_ability/symptom/medium/headache,
new /datum/disease_ability/symptom/medium/viraladaptation,
new /datum/disease_ability/symptom/medium/viralevolution,
new /datum/disease_ability/symptom/medium/disfiguration,
new /datum/disease_ability/symptom/medium/polyvitiligo,
new /datum/disease_ability/symptom/medium/itching,
new /datum/disease_ability/symptom/medium/heal/weight_loss,
new /datum/disease_ability/symptom/medium/heal/sensory_restoration,
new /datum/disease_ability/symptom/medium/heal/mind_restoration,
new /datum/disease_ability/symptom/powerful/fire,
new /datum/disease_ability/symptom/powerful/flesh_eating,
new /datum/disease_ability/symptom/powerful/genetic_mutation,
new /datum/disease_ability/symptom/powerful/inorganic_adaptation,
new /datum/disease_ability/symptom/powerful/heal/starlight,
new /datum/disease_ability/symptom/powerful/heal/oxygen,
new /datum/disease_ability/symptom/powerful/heal/chem,
new /datum/disease_ability/symptom/powerful/heal/metabolism,
new /datum/disease_ability/symptom/powerful/heal/dark,
new /datum/disease_ability/symptom/powerful/heal/water,
new /datum/disease_ability/symptom/powerful/heal/plasma,
new /datum/disease_ability/symptom/powerful/heal/radiation,
new /datum/disease_ability/symptom/powerful/heal/coma,
new /datum/disease_ability/symptom/powerful/youth
))

/datum/disease_ability
	var/name
	var/cost = 0
	var/required_total_points = 0
	var/start_with = FALSE
	var/short_desc = ""
	var/long_desc = ""
	var/stat_block = ""
	var/threshold_block = list()
	var/category = ""

	var/list/symptoms
	var/list/actions

/datum/disease_ability/New()
	..()
	if(symptoms)
		var/stealth = 0
		var/resistance = 0
		var/stage_speed = 0
		var/transmittable = 0
		for(var/T in symptoms)
			var/datum/symptom/S = T
			stealth += initial(S.stealth)
			resistance += initial(S.resistance)
			stage_speed += initial(S.stage_speed)
			transmittable += initial(S.transmittable)
			threshold_block += initial(S.threshold_descs)
			stat_block = "Resistance: [resistance]
Stealth: [stealth]
Stage Speed: [stage_speed]
Transmissibility: [transmittable]

"
			if(symptoms.len == 1) //lazy boy's dream
				name = initial(S.name)
				if(short_desc == "")
					short_desc = initial(S.desc)
				if(long_desc == "")
					long_desc = initial(S.desc)

/datum/disease_ability/proc/CanBuy(mob/camera/disease/D)
	if(world.time < D.next_adaptation_time)
		return FALSE
	if(!D.unpurchased_abilities[src])
		return FALSE
	return (D.points >= cost) && (D.total_points >= required_total_points)

/datum/disease_ability/proc/Buy(mob/camera/disease/D, silent = FALSE, trigger_cooldown = TRUE)
	if(!silent)
		to_chat(D, span_notice("Purchased [name]."))
	D.points -= cost
	D.unpurchased_abilities -= src
	if(trigger_cooldown)
		D.adapt_cooldown()
	D.purchased_abilities[src] = TRUE
	for(var/V in (D.disease_instances+D.disease_template))
		var/datum/disease/advance/sentient_disease/SD = V
		if(symptoms)
			for(var/T in symptoms)
				var/datum/symptom/S = new T()
				SD.symptoms += S
				S.OnAdd(SD)
				if(SD.processing)
					if(S.Start(SD))
						S.next_activation = world.time + rand(S.symptom_delay_min * 10, S.symptom_delay_max * 10)
			SD.Refresh()
	for(var/T in actions)
		var/datum/action/A = new T()
		A.Grant(D)

/datum/disease_ability/proc/CanRefund(mob/camera/disease/D)
	if(world.time < D.next_adaptation_time)
		return FALSE
	return D.purchased_abilities[src]

/datum/disease_ability/proc/Refund(mob/camera/disease/D, silent = FALSE, trigger_cooldown = TRUE)
	if(!silent)
		to_chat(D, span_notice("Refunded [name]."))
	D.points += cost
	D.unpurchased_abilities[src] = TRUE
	if(trigger_cooldown)
		D.adapt_cooldown()
	D.purchased_abilities -= src
	for(var/V in (D.disease_instances+D.disease_template))
		var/datum/disease/advance/sentient_disease/SD = V
		if(symptoms)
			for(var/T in symptoms)
				var/datum/symptom/S = locate(T) in SD.symptoms
				if(S)
					SD.symptoms -= S
					S.OnRemove(SD)
					if(SD.processing)
						S.End(SD)
					qdel(S)
			SD.Refresh()
	for(var/T in actions)
		var/datum/action/A = locate(T) in D.actions
		qdel(A)

//these sybtypes are for conveniently separating the different categories, they have no unique code.

/datum/disease_ability/action
	category = "Active"

/datum/disease_ability/symptom
	category = "Symptom"

//active abilities and their associated actions

/datum/disease_ability/action/cough
	name = "Voluntary Coughing"
	actions = list(/datum/action/cooldown/disease_cough)
	cost = 0
	required_total_points = 0
	start_with = TRUE
	short_desc = "Force the host you are following to cough, spreading your infection to those nearby."
	long_desc = "Force the host you are following to cough with extra force, spreading your infection to those within two meters of your host even if your transmissibility is low.
Cooldown: 10 seconds"

/datum/action/cooldown/disease_cough
	name = "Cough"
	icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
	button_icon_state = "cough"
	desc = "Force the host you are following to cough with extra force, spreading your infection to those within two meters of your host even if your transmissibility is low.
Cooldown: 10 seconds"
	cooldown_time = 100

/datum/action/cooldown/disease_cough/Trigger()
	if(!..())
		return FALSE
	var/mob/camera/disease/D = owner
	var/mob/living/L = D.following_host
	if(!L)
		return FALSE
	if(L.stat != CONSCIOUS)
		to_chat(D, span_warning("Your host must be conscious to cough."))
		return FALSE
	to_chat(D, span_notice("You force [L.real_name] to cough."))
	L.emote("cough")
	if(L.CanSpreadAirborneDisease()) //don't spread germs if they covered their mouth
		var/datum/disease/advance/sentient_disease/SD = D.hosts[L]
		SD.spread(2)
	StartCooldown()
	return TRUE

/datum/disease_ability/action/sneeze
	name = "Voluntary Sneezing"
	actions = list(/datum/action/cooldown/disease_sneeze)
	cost = 2
	required_total_points = 3
	short_desc = "Force the host you are following to sneeze, spreading your infection to those in front of them."
	long_desc = "Force the host you are following to sneeze with extra force, spreading your infection to any victims in a 4 meter cone in front of your host.
Cooldown: 20 seconds"

/datum/action/cooldown/disease_sneeze
	name = "Sneeze"
	icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
	button_icon_state = "sneeze"
	desc = "Force the host you are following to sneeze with extra force, spreading your infection to any victims in a 4 meter cone in front of your host even if your transmissibility is low.
Cooldown: 20 seconds"
	cooldown_time = 200

/datum/action/cooldown/disease_sneeze/Trigger()
	if(!..())
		return FALSE
	var/mob/camera/disease/D = owner
	var/mob/living/L = D.following_host
	if(!L)
		return FALSE
	if(L.stat != CONSCIOUS)
		to_chat(D, span_warning("Your host must be conscious to sneeze."))
		return FALSE
	to_chat(D, span_notice("You force [L.real_name] to sneeze."))
	L.emote("sneeze")
	if(L.CanSpreadAirborneDisease()) //don't spread germs if they covered their mouth
		var/datum/disease/advance/sentient_disease/SD = D.hosts[L]

		for(var/mob/living/M in oview(4, SD.affected_mob))
			if(is_source_facing_target(SD.affected_mob, M) && disease_air_spread_walk(get_turf(SD.affected_mob), get_turf(M)))
				M.AirborneContractDisease(SD, TRUE)

	StartCooldown()
	return TRUE

/datum/disease_ability/action/infect
	name = "Secrete Infection"
	actions = list(/datum/action/cooldown/disease_infect)
	cost = 2
	required_total_points = 3
	short_desc = "Cause all objects your host is touching to become infectious for a limited time, spreading your infection to anyone who touches them."
	long_desc = "Cause the host you are following to excrete an infective substance from their pores, causing all objects touching their skin to transmit your infection to anyone who touches them for the next 30 seconds. This includes the floor, if they are not wearing shoes, and any items they are holding, if they are not wearing gloves.
Cooldown: 40 seconds"

/datum/action/cooldown/disease_infect
	name = "Secrete Infection"
	icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
	button_icon_state = "infect"
	desc = "Cause the host you are following to excrete an infective substance from their pores, causing all objects touching their skin to transmit your infection to anyone who touches them for the next 30 seconds.
Cooldown: 40 seconds"
	cooldown_time = 400

/datum/action/cooldown/disease_infect/Trigger()
	if(!..())
		return FALSE
	var/mob/camera/disease/D = owner
	var/mob/living/carbon/human/H = D.following_host
	if(!H)
		return FALSE
	for(var/V in H.get_equipped_items(FALSE))
		var/obj/O = V
		O.AddComponent(/datum/component/infective, D.disease_template, 300)
	//no shoes? infect the floor.
	if(!H.shoes)
		var/turf/T = get_turf(H)
		if(T && !isspaceturf(T))
			T.AddComponent(/datum/component/infective, D.disease_template, 300)
	//no gloves? infect whatever we are holding.
	if(!H.gloves)
		for(var/V in H.held_items)
			if(!V)
				continue
			var/obj/O = V
			O.AddComponent(/datum/component/infective, D.disease_template, 300)
	StartCooldown()
	return TRUE

/*******************BASE SYMPTOM TYPES*******************/
// cost is for convenience and can be changed. If you're changing req_tot_points then don't use the subtype...
//healing costs more so you have to techswitch from naughty disease otherwise we'd have friendly disease for easy greentext (no fun!)

/datum/disease_ability/symptom/mild
	cost = 2
	required_total_points = 4
	category = "Symptom (Weak)"

/datum/disease_ability/symptom/medium
	cost = 4
	required_total_points = 8
	category = "Symptom"

/datum/disease_ability/symptom/medium/heal
	cost = 5
	category = "Symptom (+)"

/datum/disease_ability/symptom/powerful
	cost = 4
	required_total_points = 16
	category = "Symptom (Strong)"

/datum/disease_ability/symptom/powerful/heal
	cost = 8
	category = "Symptom (Strong+)"

/******MILD******/

/datum/disease_ability/symptom/mild/cough
	name = "Involuntary Coughing"
	symptoms = list(/datum/symptom/cough)
	short_desc = "Cause victims to cough intermittently."
	long_desc = "Cause victims to cough intermittently, spreading your infection."

/datum/disease_ability/symptom/mild/sneeze
	name = "Involuntary Sneezing"
	symptoms = list(/datum/symptom/sneeze)
	short_desc = "Cause victims to sneeze intermittently."
	long_desc = "Cause victims to sneeze intermittently, spreading your infection and also increasing transmissibility and resistance, at the cost of stealth."

/******MEDIUM******/

/datum/disease_ability/symptom/medium/shedding
	symptoms = list(/datum/symptom/shedding)

/datum/disease_ability/symptom/medium/beard
	symptoms = list(/datum/symptom/beard)
	short_desc = "Cause all victims to grow a luscious beard."
	long_desc = "Cause all victims to grow a luscious beard. Ineffective against Santa Claus."

/datum/disease_ability/symptom/medium/hallucigen
	symptoms = list(/datum/symptom/hallucigen)
	short_desc = "Cause victims to hallucinate."
	long_desc = "Cause victims to hallucinate. Decreases stats, especially resistance."

/datum/disease_ability/symptom/medium/choking
	symptoms = list(/datum/symptom/choking)
	short_desc = "Cause victims to choke."
	long_desc = "Cause victims to choke, threatening asphyxiation. Decreases stats, especially transmissibility."

/datum/disease_ability/symptom/medium/confusion
	symptoms = list(/datum/symptom/confusion)
	short_desc = "Cause victims to become confused."
	long_desc = "Cause victims to become confused intermittently."

/datum/disease_ability/symptom/medium/vomit
	symptoms = list(/datum/symptom/vomit)
	short_desc = "Cause victims to vomit."
	long_desc = "Cause victims to vomit. Slightly increases transmissibility. Vomiting also also causes the victims to lose nutrition and removes some toxin damage."

/datum/disease_ability/symptom/medium/voice_change
	symptoms = list(/datum/symptom/voice_change)
	short_desc = "Change the voice of victims."
	long_desc = "Change the voice of victims, causing confusion in communications."

/datum/disease_ability/symptom/medium/visionloss
	symptoms = list(/datum/symptom/visionloss)
	short_desc = "Damage the eyes of victims, eventually causing blindness."
	long_desc = "Damage the eyes of victims, eventually causing blindness. Decreases all stats."

/datum/disease_ability/symptom/medium/deafness
	symptoms = list(/datum/symptom/deafness)

/datum/disease_ability/symptom/medium/fever
	symptoms = list(/datum/symptom/fever)

/datum/disease_ability/symptom/medium/chills
	symptoms = list(/datum/symptom/chills)

/datum/disease_ability/symptom/medium/headache
	symptoms = list(/datum/symptom/headache)

/datum/disease_ability/symptom/medium/viraladaptation
	symptoms = list(/datum/symptom/viraladaptation)
	short_desc = "Cause your infection to become more resistant to detection and eradication."
	long_desc = "Cause your infection to mimic the function of normal body cells, becoming much harder to spot and to eradicate, but reducing its speed."

/datum/disease_ability/symptom/medium/viralevolution
	symptoms = list(/datum/symptom/viralevolution)

/datum/disease_ability/symptom/medium/polyvitiligo
	symptoms = list(/datum/symptom/polyvitiligo)

/datum/disease_ability/symptom/medium/disfiguration
	symptoms = list(/datum/symptom/disfiguration)

/datum/disease_ability/symptom/medium/itching
	symptoms = list(/datum/symptom/itching)
	short_desc = "Cause victims to itch."
	long_desc = "Cause victims to itch, increasing all stats except stealth."

/datum/disease_ability/symptom/medium/heal/weight_loss
	symptoms = list(/datum/symptom/weight_loss)
	short_desc = "Cause victims to lose weight."
	long_desc = "Cause victims to lose weight, and make it almost impossible for them to gain nutrition from food. Reduced nutrition allows your infection to spread more easily from hosts, especially by sneezing."

/datum/disease_ability/symptom/medium/heal/sensory_restoration
	symptoms = list(/datum/symptom/sensory_restoration)
	short_desc = "Regenerate eye and ear damage of victims."
	long_desc = "Regenerate eye and ear damage of victims."

/datum/disease_ability/symptom/medium/heal/mind_restoration
	symptoms = list(/datum/symptom/mind_restoration)

/******POWERFUL******/

/datum/disease_ability/symptom/powerful/fire
	symptoms = list(/datum/symptom/fire)

/datum/disease_ability/symptom/powerful/flesh_eating
	symptoms = list(/datum/symptom/flesh_eating)

/datum/disease_ability/symptom/powerful/genetic_mutation
	symptoms = list(/datum/symptom/genetic_mutation)
	cost = 8

/datum/disease_ability/symptom/powerful/inorganic_adaptation
	symptoms = list(/datum/symptom/inorganic_adaptation)

/datum/disease_ability/symptom/powerful/narcolepsy
	symptoms = list(/datum/symptom/narcolepsy)

/datum/disease_ability/symptom/powerful/youth
	symptoms = list(/datum/symptom/youth)
	short_desc = "Cause victims to become eternally young."
	long_desc = "Cause victims to become eternally young. Provides boosts to all stats except transmissibility."

/****HEALING SUBTYPE****/

/datum/disease_ability/symptom/powerful/heal/starlight
	symptoms = list(/datum/symptom/heal/starlight)

/datum/disease_ability/symptom/powerful/heal/oxygen
	symptoms = list(/datum/symptom/oxygen)

/datum/disease_ability/symptom/powerful/heal/chem
	symptoms = list(/datum/symptom/heal/chem)

/datum/disease_ability/symptom/powerful/heal/metabolism
	symptoms = list(/datum/symptom/heal/metabolism)
	short_desc = "Increase the metabolism of victims, causing them to process chemicals and grow hungry faster."
	long_desc = "Increase the metabolism of victims, causing them to process chemicals twice as fast and grow hungry more quickly."

/datum/disease_ability/symptom/powerful/heal/dark
	symptoms = list(/datum/symptom/heal/darkness)

/datum/disease_ability/symptom/powerful/heal/water
	symptoms = list(/datum/symptom/heal/water)

/datum/disease_ability/symptom/powerful/heal/plasma
	symptoms = list(/datum/symptom/heal/plasma)

/datum/disease_ability/symptom/powerful/heal/radiation
	symptoms = list(/datum/symptom/heal/radiation)

/datum/disease_ability/symptom/powerful/heal/coma
	symptoms = list(/datum/symptom/heal/coma)
	short_desc = "Cause victims to fall into a healing coma when hurt."
	long_desc = "Cause victims to fall into a healing coma when hurt."

repo_name: AnalWerewolf/sundown

license: mit


code:
/datum/antagonist/disease
	name = "Sentient Disease"
	roundend_category = "diseases"
	antagpanel_category = "Biohazards"
	show_to_ghosts = TRUE
	var/disease_name = ""

/datum/antagonist/disease/on_gain()
	owner.set_assigned_role(SSjob.GetJobType(/datum/job/sentient_disease))
	owner.special_role = ROLE_SENTIENT_DISEASE
	var/datum/objective/O = new /datum/objective/disease_infect()
	O.owner = owner
	objectives += O

	O = new /datum/objective/disease_infect_centcom()
	O.owner = owner
	objectives += O

	. = ..()

/datum/antagonist/disease/greet()
	to_chat(owner.current, span_notice("You are the [owner.special_role]!"))
	to_chat(owner.current, span_notice("Infect members of the crew to gain adaptation points, and spread your infection further."))
	owner.announce_objectives()

/datum/antagonist/disease/apply_innate_effects(mob/living/mob_override)
	if(!istype(owner.current, /mob/camera/disease))
		var/turf/T = get_turf(owner.current)
		T = T ? T : SSmapping.get_station_center()
		var/mob/camera/disease/D = new /mob/camera/disease(T)
		owner.transfer_to(D)

/datum/antagonist/disease/admin_add(datum/mind/new_owner,mob/admin)
	..()
	var/mob/camera/disease/D = new_owner.current
	D.pick_name()

/datum/antagonist/disease/roundend_report()
	var/list/result = list()

	result += "Disease name: [disease_name]"
	result += printplayer(owner)

	var/win = TRUE
	var/objectives_text = ""
	var/count = 1
	for(var/datum/objective/objective in objectives)
		if(objective.check_completion())
			objectives_text += "
Objective #[count]: [objective.explanation_text] [span_greentext("Success!")]"
		else
			objectives_text += "
Objective #[count]: [objective.explanation_text] [span_redtext("Fail.")]"
			win = FALSE
		count++

	result += objectives_text

	var/special_role_text = lowertext(name)

	if(win)
		result += span_greentext("The [special_role_text] was successful!")
	else
		result += span_redtext("The [special_role_text] has failed!")

	if(istype(owner.current, /mob/camera/disease))
		var/mob/camera/disease/D = owner.current
		result += "[disease_name] completed the round with [D.hosts.len] infected hosts, and reached a maximum of [D.total_points] concurrent infections."
		result += "[disease_name] completed the round with the following adaptations:"
		var/list/adaptations = list()
		for(var/V in D.purchased_abilities)
			var/datum/disease_ability/A = V
			adaptations += A.name
		result += adaptations.Join(", ")

	return result.Join("
")

/datum/antagonist/disease/get_preview_icon()
	var/icon/icon = icon('icons/mob/hud.dmi', "virus_infected")
	icon.Blend(COLOR_GREEN_GRAY, ICON_MULTIPLY)
	icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
	return icon

/datum/objective/disease_infect
	explanation_text = "Survive and infect as many people as possible."

/datum/objective/disease_infect/check_completion()
	var/mob/camera/disease/D = owner.current
	if(istype(D) && D.hosts.len) //theoretically it should not exist if it has no hosts, but better safe than sorry.
		return TRUE
	return FALSE

/datum/objective/disease_infect_centcom
	explanation_text = "Ensure that at least one infected host escapes on the shuttle or an escape pod."

/datum/objective/disease_infect_centcom/check_completion()
	var/mob/camera/disease/D = owner.current
	if(!istype(D))
		return FALSE
	for(var/V in D.hosts)
		var/mob/living/L = V
		if(L.onCentCom() || L.onSyndieBase())
			return TRUE
	return FALSE


code:
/datum/disease/advance/sentient_disease
	form = "Virus"
	name = "Sentient Virus"
	desc = "An apparently sentient virus, extremely adaptable and resistant to outside sources of mutation."
	viable_mobtypes = list(/mob/living/carbon/human)
	mutable = FALSE
	var/mob/camera/disease/overmind
	var/disease_id

/datum/disease/advance/sentient_disease/New()
	..()
	GLOB.sentient_disease_instances += src

/datum/disease/advance/sentient_disease/Destroy()
	. = ..()
	overmind = null
	GLOB.sentient_disease_instances -= src

/datum/disease/advance/sentient_disease/remove_disease()
	if(overmind)
		overmind.remove_infection(src)
	..()

/datum/disease/advance/sentient_disease/infect(mob/living/infectee, make_copy = TRUE)
	if(make_copy && overmind && (overmind.disease_template != src))
		overmind.disease_template.infect(infectee, TRUE) //get an updated version of the virus
	else
		..()

/datum/disease/advance/sentient_disease/IsSame(datum/disease/D)
	if(istype(D, /datum/disease/advance/sentient_disease))
		var/datum/disease/advance/sentient_disease/V = D
		if(V.overmind == overmind)
			return TRUE
	return FALSE

/datum/disease/advance/sentient_disease/Copy()
	var/datum/disease/advance/sentient_disease/D = ..()
	D.overmind = overmind
	D.disease_id = disease_id
	return D

/datum/disease/advance/sentient_disease/after_add()
	if(overmind)
		overmind.add_infection(src)

/datum/disease/advance/sentient_disease/GetDiseaseID()
	if (!disease_id) //if we don't set this here it can reinfect people after the disease dies, since overmind.tag won't be null when the disease is alive, but will be null afterwards, thus the disease ID changes
		disease_id = "[type]|[overmind?.tag]"
	return disease_id

/datum/disease/advance/sentient_disease/GenerateCure()
	if(cures.len)
		return
	var/list/not_used = advance_cures.Copy()
	not_used.Cut(1, 6) // Removes the first five tiers of cures.
	cures = list(pick(pick_n_take(not_used)), pick(pick_n_take(not_used)))

	// Get the cure name from the cure_id
	var/datum/reagent/D1 = GLOB.chemical_reagents_list[cures[1]]
	var/datum/reagent/D2 = GLOB.chemical_reagents_list[cures[2]]
	cure_text = "[D1.name] and [D2.name]"


code:
/datum/round_event_control/sentient_disease
	name = "Spawn Sentient Disease"
	typepath = /datum/round_event/ghost_role/sentient_disease
	weight = 7
	max_occurrences = 1
	min_players = 5

/datum/round_event/ghost_role/sentient_disease
	role_name = "sentient disease"

/datum/round_event/ghost_role/sentient_disease/spawn_role()
	var/list/candidates = get_candidates(ROLE_ALIEN, ROLE_ALIEN)
	if(!candidates.len)
		return NOT_ENOUGH_PLAYERS

	var/mob/dead/observer/selected = pick_n_take(candidates)

	var/mob/camera/disease/virus = new /mob/camera/disease(SSmapping.get_station_center())
	virus.key = selected.key
	INVOKE_ASYNC(virus, /mob/camera/disease/proc/pick_name)
	message_admins("[ADMIN_LOOKUPFLW(virus)] has been made into a sentient disease by an event.")
	log_game("[key_name(virus)] was spawned as a sentient disease by an event.")
	spawned_mobs += virus
	return SUCCESSFUL_SPAWN


code:
#define FREEMOVE_TIME 2 MINUTES

/*
A mob of type /mob/camera/disease is an overmind coordinating at least one instance of /datum/disease/advance/sentient_disease
that has infected a host. All instances in a host will be synchronized with the stats of the overmind's disease_template. Any
samples outside of a host will retain the stats they had when they left the host, but infecting a new host will cause
the new instance inside the host to be updated to the template's stats.
*/

/mob/camera/disease
	name = "Sentient Disease"
	real_name = "Sentient Disease"
	desc = ""
	icon = 'icons/mob/cameramob.dmi'
	icon_state = "marker"
	mouse_opacity = MOUSE_OPACITY_ICON
	move_on_shuttle = FALSE
	see_in_dark = 8
	invisibility = INVISIBILITY_OBSERVER
	see_invisible = SEE_INVISIBLE_LIVING
	layer = BELOW_MOB_LAYER
	lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
	sight = SEE_SELF|SEE_THRU
	initial_language_holder = /datum/language_holder/universal

	var/freemove = TRUE
	var/freemove_end = 0
	var/freemove_end_timerid

	var/datum/action/innate/disease_adapt/adaptation_menu_action
	var/datum/disease_ability/examining_ability
	var/datum/browser/browser
	var/browser_open = FALSE

	var/mob/living/following_host
	var/list/disease_instances
	var/list/hosts //this list is associative, affected_mob -> disease_instance
	var/datum/disease/advance/sentient_disease/disease_template

	var/total_points = 0
	var/points = 0

	var/last_move_tick = 0
	var/move_delay = 1

	var/next_adaptation_time = 0
	var/adaptation_cooldown = 600

	var/list/purchased_abilities
	var/list/unpurchased_abilities

/mob/camera/disease/Initialize(mapload)
	.= ..()

	ADD_TRAIT(src, TRAIT_SIXTHSENSE, INNATE_TRAIT) //at least they'll have SOMEONE to talk to

	disease_instances = list()
	hosts = list()

	purchased_abilities = list()
	unpurchased_abilities = list()

	disease_template = new /datum/disease/advance/sentient_disease()
	disease_template.overmind = src
	qdel(SSdisease.archive_diseases[disease_template.GetDiseaseID()])
	SSdisease.archive_diseases[disease_template.GetDiseaseID()] = disease_template //important for stuff that uses disease IDs

	var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE]
	my_hud.add_hud_to(src)

	browser = new /datum/browser(src, "disease_menu", "Adaptation Menu", 1000, 770, src)

	freemove_end = world.time + FREEMOVE_TIME
	freemove_end_timerid = addtimer(CALLBACK(src, .proc/infect_random_patient_zero), FREEMOVE_TIME, TIMER_STOPPABLE)

/mob/camera/disease/Destroy()
	. = ..()
	QDEL_NULL(adaptation_menu_action)
	disease_template = null
	for(var/V in GLOB.sentient_disease_instances)
		var/datum/disease/advance/sentient_disease/S = V
		if(S.overmind == src)
			S.overmind = null
	browser = null

/mob/camera/disease/Login()
	. = ..()
	if(!. || !client)
		return FALSE
	if(freemove)
		to_chat(src, span_warning("You have [DisplayTimeText(freemove_end - world.time)] to select your first host. Click on a human to select your host."))

/mob/camera/disease/get_status_tab_items()
	. = ..()
	if(freemove)
		. += "Host Selection Time: [round((freemove_end - world.time)/10)]s"
	else
		. += "Adaptation Points: [points]/[total_points]"
		. += "Hosts: [disease_instances.len]"
		var/adapt_ready = next_adaptation_time - world.time
		if(adapt_ready > 0)
			. += "Adaptation Ready: [round(adapt_ready/10, 0.1)]s"

/mob/camera/disease/examine(mob/user)
	. = ..()
	if(isobserver(user))
		. += {"[span_notice("[src] has [points]/[total_points] adaptation points.")]
		[span_notice("[src] has the following unlocked:")]"}
		for(var/datum/disease_ability/ability in purchased_abilities)
			. += span_notice("[ability.name]")

/mob/camera/disease/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
	if(!message)
		return
	if(sanitize)
		message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN))
	log_talk(message, LOG_SAY)
	var/rendered = "[src] says, \"[message]\""
	for(var/mob/listener in GLOB.mob_list)
		if(issentientdisease(listener))
			to_chat(listener, rendered)
		else if(isobserver(listener))
			var/link = FOLLOW_LINK(listener, src)
			to_chat(listener, "[link] [rendered]")
	return

/mob/camera/disease/Move(NewLoc, Dir = 0)
	if(freemove)
		forceMove(NewLoc)
	else
		if(world.time > (last_move_tick + move_delay))
			follow_next(Dir & NORTHWEST)
			last_move_tick = world.time

/mob/camera/disease/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
	. = ..()
	var/atom/movable/to_follow = speaker
	if(radio_freq)
		var/atom/movable/virtualspeaker/V = speaker
		to_follow = V.source
	var/link
	if(to_follow in hosts)
		link = FOLLOW_LINK(src, to_follow)
	else
		link = ""
	// Create map text prior to modifying message for goonchat
	if (client?.prefs.read_preference(/datum/preference/toggle/enable_runechat) && (client.prefs.read_preference(/datum/preference/toggle/enable_runechat_non_mobs) || ismob(speaker)))
		create_chat_message(speaker, message_language, raw_message, spans)
	// Recompose the message, because it's scrambled by default
	message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mods)
	to_chat(src, "[link] [message]")

/mob/camera/disease/mind_initialize()
	. = ..()
	if(!mind.has_antag_datum(/datum/antagonist/disease))
		mind.add_antag_datum(/datum/antagonist/disease)
	var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
	medsensor.add_hud_to(src)

/mob/camera/disease/proc/pick_name()
	var/static/list/taken_names
	if(!taken_names)
		taken_names = list("Unknown" = TRUE)
		for(var/T in (subtypesof(/datum/disease) - /datum/disease/advance))
			var/datum/disease/D = T
			taken_names[initial(D.name)] = TRUE
	var/set_name
	while(!set_name)
		var/input = sanitize_name(stripped_input(src, "Select a name for your disease", "Select Name", "", MAX_NAME_LEN))
		if(!input)
			set_name = "Sentient Virus"
			break
		if(taken_names[input])
			to_chat(src, span_warning("You cannot use the name of such a well-known disease!"))
		else
			set_name = input
	real_name = "[set_name] (Sentient Disease)"
	name = "[set_name] (Sentient Disease)"
	disease_template.AssignName(set_name)
	var/datum/antagonist/disease/A = mind.has_antag_datum(/datum/antagonist/disease)
	if(A)
		A.disease_name = set_name

/mob/camera/disease/proc/infect_random_patient_zero(del_on_fail = TRUE)
	if(!freemove)
		return FALSE
	var/list/possible_hosts = list()
	var/list/afk_possible_hosts = list()
	for(var/i in GLOB.human_list)
		var/mob/living/carbon/human/H = i
		var/turf/T = get_turf(H)
		if((H.stat != DEAD) && T && is_station_level(T.z) && H.CanContractDisease(disease_template))
			if(H.client && !H.client.is_afk())
				possible_hosts += H
			else
				afk_possible_hosts += H

	shuffle_inplace(possible_hosts)
	shuffle_inplace(afk_possible_hosts)
	possible_hosts += afk_possible_hosts //ideally we want a not-afk person, but we will settle for an afk one if there are no others (mostly for testing)

	while(possible_hosts.len)
		var/mob/living/carbon/human/target = possible_hosts[1]
		if(force_infect(target))
			return TRUE
		possible_hosts.Cut(1, 2)

	if(del_on_fail)
		to_chat(src, "No hosts were available for your disease to infect.")
		qdel(src)
	return FALSE

/mob/camera/disease/proc/force_infect(mob/living/L)
	var/datum/disease/advance/sentient_disease/V = disease_template.Copy()
	var/result = L.ForceContractDisease(V, FALSE, TRUE)
	if(result && freemove)
		end_freemove()
	return result

/mob/camera/disease/proc/end_freemove()
	if(!freemove)
		return
	freemove = FALSE
	move_on_shuttle = TRUE
	adaptation_menu_action = new /datum/action/innate/disease_adapt()
	adaptation_menu_action.Grant(src)
	for(var/V in GLOB.disease_ability_singletons)
		unpurchased_abilities[V] = TRUE
		var/datum/disease_ability/A = V
		if(A.start_with && A.CanBuy(src))
			A.Buy(src, TRUE, FALSE)
	if(freemove_end_timerid)
		deltimer(freemove_end_timerid)
	sight = SEE_SELF

/mob/camera/disease/proc/add_infection(datum/disease/advance/sentient_disease/V)
	disease_instances += V
	hosts[V.affected_mob] = V
	total_points = max(total_points, disease_instances.len)
	points += 1

	var/image/holder = V.affected_mob.hud_list[SENTIENT_DISEASE_HUD]
	var/mutable_appearance/MA = new /mutable_appearance(holder)
	MA.icon_state = "virus_infected"
	MA.layer = BELOW_MOB_LAYER
	MA.color = COLOR_GREEN_GRAY
	MA.alpha = 200
	holder.appearance = MA
	var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE]
	my_hud.add_to_hud(V.affected_mob)

	to_chat(src, span_notice("A new host, [V.affected_mob.real_name], has been infected."))

	if(!following_host)
		set_following(V.affected_mob)
	refresh_adaptation_menu()

/mob/camera/disease/proc/remove_infection(datum/disease/advance/sentient_disease/V)
	if(QDELETED(src))
		disease_instances -= V
		hosts -= V.affected_mob
	else
		to_chat(src, span_notice("One of your hosts, [V.affected_mob.real_name], has been purged of your infection."))

		var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE]
		my_hud.remove_from_hud(V.affected_mob)

		if(following_host == V.affected_mob)
			follow_next()

		disease_instances -= V
		hosts -= V.affected_mob

		if(!disease_instances.len)
			to_chat(src, span_userdanger("The last of your infection has disappeared."))
			set_following(null)
			qdel(src)
		refresh_adaptation_menu()

/mob/camera/disease/proc/set_following(mob/living/L)
	if(following_host)
		UnregisterSignal(following_host, COMSIG_MOVABLE_MOVED)
	RegisterSignal(L, COMSIG_MOVABLE_MOVED, .proc/follow_mob)
	following_host = L
	follow_mob()

/mob/camera/disease/proc/follow_next(reverse = FALSE)
	var/index = hosts.Find(following_host)
	if(index)
		if(reverse)
			index = index == 1 ? hosts.len : index - 1
		else
			index = index == hosts.len ? 1 : index + 1
		set_following(hosts[index])

/mob/camera/disease/proc/follow_mob(datum/source, newloc, dir)
	SIGNAL_HANDLER

	var/turf/T = get_turf(following_host)
	if(T)
		forceMove(T)

/mob/camera/disease/DblClickOn(atom/A, params)
	if(hosts[A])
		set_following(A)
	else
		..()

/mob/camera/disease/ClickOn(atom/A, params)
	if(freemove && ishuman(A))
		var/mob/living/carbon/human/H = A
		if(tgui_alert(usr, "Select [H.name] as your initial host?", "Select Host", list("Yes", "No")) != "Yes")
			return
		if(!freemove)
			return
		if(QDELETED(H) || !force_infect(H))
			to_chat(src, span_warning("[H ? H.name : "Host"] cannot be infected."))
	else
		..()

/mob/camera/disease/proc/adapt_cooldown()
	to_chat(src, span_notice("You have altered your genetic structure. You will be unable to adapt again for [DisplayTimeText(adaptation_cooldown)]."))
	next_adaptation_time = world.time + adaptation_cooldown
	addtimer(CALLBACK(src, .proc/notify_adapt_ready), adaptation_cooldown)

/mob/camera/disease/proc/notify_adapt_ready()
	to_chat(src, span_notice("You are now ready to adapt again."))
	refresh_adaptation_menu()

/mob/camera/disease/proc/refresh_adaptation_menu()
	if(browser_open)
		adaptation_menu()

/mob/camera/disease/proc/adaptation_menu()
	var/datum/disease/advance/sentient_disease/DT = disease_template
	if(!DT)
		return
	var/list/dat = list()

	if(examining_ability)
		dat += "Back
"
		dat += "
[examining_ability.name]
"
		dat += "[examining_ability.stat_block][examining_ability.long_desc][examining_ability.threshold_block]"
		for(var/entry in examining_ability.threshold_block)
			dat += "[entry]: [examining_ability.threshold_block[entry]]
"
	else
		dat += "
Disease Statistics

\
			Resistance: [DT.totalResistance()]
\
			Stealth: [DT.totalStealth()]
\
			Stage Speed: [DT.totalStageSpeed()]
\
			Transmissibility: [DT.totalTransmittable()]
\
			Cure: [DT.cure_text]"
		dat += "

Adaptations
\
			Points: [points] / [total_points]\

\

Cost		Unlock	Name	Type	Description
"
		for(var/V in GLOB.disease_ability_singletons)
			var/datum/disease_ability/A = V
			var/purchase_text
			if(unpurchased_abilities[A])
				if(A.CanBuy(src))
					purchase_text = "Purchase"
				else
					purchase_text = "Purchase"
			else
				if(A.CanRefund(src))
					purchase_text = "Refund"
				else
					purchase_text = "Refund"
			dat += "
[A.cost]	[purchase_text]	[A.required_total_points]	[A.name]	[A.category]	[A.short_desc]
"

		dat += "

Infect many hosts at once to gain adaptation points.

Infected Hosts
"
		for(var/V in hosts)
			var/mob/living/L = V
			dat += "
[L.real_name]"

	browser.set_content(dat.Join())
	browser.open()
	browser_open = TRUE

/mob/camera/disease/Topic(href, list/href_list)
	..()
	if(href_list["close"])
		browser_open = FALSE
	if(usr != src)
		return
	if(href_list["follow_instance"])
		var/mob/living/L = locate(href_list["follow_instance"]) in hosts
		set_following(L)

	if(href_list["buy_ability"])
		var/datum/disease_ability/A = locate(href_list["buy_ability"]) in unpurchased_abilities
		if(!istype(A))
			return
		if(A.CanBuy(src))
			A.Buy(src)
		adaptation_menu()

	if(href_list["refund_ability"])
		var/datum/disease_ability/A = locate(href_list["refund_ability"]) in purchased_abilities
		if(!istype(A))
			return
		if(A.CanRefund(src))
			A.Refund(src)
		adaptation_menu()

	if(href_list["examine_ability"])
		var/datum/disease_ability/A = locate(href_list["examine_ability"]) in GLOB.disease_ability_singletons
		if(!istype(A))
			return
		examining_ability = A
		adaptation_menu()

	if(href_list["main_menu"])
		examining_ability = null
		adaptation_menu()

/datum/action/innate/disease_adapt
	name = "Adaptation Menu"
	icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
	button_icon_state = "disease_menu"

/datum/action/innate/disease_adapt/Activate()
	var/mob/camera/disease/D = owner
	D.adaptation_menu()

#undef FREEMOVE_TIME


code:
/datum/antagonist/heretic
	name = "Heretic"
	roundend_category = "Heretics"
	antagpanel_category = "Heretic"
	ui_name = "AntagInfoHeretic"
	antag_moodlet = /datum/mood_event/heretics
	job_rank = ROLE_HERETIC
	antag_hud_type = ANTAG_HUD_HERETIC
	antag_hud_name = "heretic"
	hijack_speed = 0.5
	suicide_cry = "THE MANSUS SMILES UPON ME!!"
	preview_outfit = /datum/outfit/heretic
	var/give_equipment = TRUE
	var/list/researched_knowledge = list()
	var/total_sacrifices = 0
	var/ascended = FALSE

/datum/antagonist/heretic/ui_static_data(mob/user)
	var/list/data = list()
	data["total_sacrifices"] = total_sacrifices
	data["ascended"] = ascended
	data["objectives"] = get_objectives()
	return data

/datum/antagonist/heretic/admin_add(datum/mind/new_owner,mob/admin)
	give_equipment = FALSE
	new_owner.add_antag_datum(src)
	message_admins("[key_name_admin(admin)] has heresized [key_name_admin(new_owner)].")
	log_admin("[key_name(admin)] has heresized [key_name(new_owner)].")

/datum/antagonist/heretic/greet()
	owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)//subject to change
	to_chat(owner, "You are the Heretic!
The old ones gave you these tasks to fulfill:")
	owner.announce_objectives()
	to_chat(owner, span_cult("The book whispers softly, its forbidden knowledge walks this plane once again!"))
	var/policy = get_policy(ROLE_HERETIC)
	if(policy)
		to_chat(owner, policy)

/datum/antagonist/heretic/farewell()
	to_chat(owner.current, span_userdanger("Your mind begins to flare as the otherwordly knowledge escapes your grasp!"))
	owner.announce_objectives()

/datum/antagonist/heretic/get_preview_icon()
	var/icon/icon = render_preview_outfit(preview_outfit)

	// MOTHBLOCKS TOOD: Copied and pasted from cult, make this its own proc

	// The sickly blade is 64x64, but getFlatIcon crunches to 32x32.
	// So I'm just going to add it in post, screw it.

	// Center the dude, because item icon states start from the center.
	// This makes the image 64x64.
	icon.Crop(-15, -15, 48, 48)

	var/obj/item/melee/sickly_blade/blade = new
	icon.Blend(icon(blade.lefthand_file, blade.inhand_icon_state), ICON_OVERLAY)
	qdel(blade)

	// Move the guy back to the bottom left, 32x32.
	icon.Crop(17, 17, 48, 48)

	return finish_preview_icon(icon)

/datum/antagonist/heretic/on_gain()
	var/mob/living/current = owner.current
	if(ishuman(current))
		forge_primary_objectives()
		for(var/eldritch_knowledge in GLOB.heretic_start_knowledge)
			gain_knowledge(eldritch_knowledge)
	current.log_message("has been made into a heretic!", LOG_ATTACK, color="#960000")
	GLOB.reality_smash_track.AddMind(owner)
	START_PROCESSING(SSprocessing, src)
	RegisterSignal(owner.current, COMSIG_LIVING_DEATH, .proc/on_death)
	if(give_equipment)
		equip_cultist()
	return ..()

/datum/antagonist/heretic/on_removal()

	for(var/knowledge_index in researched_knowledge)
		var/datum/eldritch_knowledge/knowledge = researched_knowledge[knowledge_index]
		knowledge.on_lose(owner.current)

	owner.current.log_message("is no longer a heretic!", LOG_ATTACK, color="#960000")

	GLOB.reality_smash_track.RemoveMind(owner)
	STOP_PROCESSING(SSprocessing, src)

	on_death()

	return ..()

/datum/antagonist/heretic/proc/equip_cultist()
	var/mob/living/carbon/heretic = owner.current
	if(!istype(heretic))
		return
	. += ecult_give_item(/obj/item/forbidden_book, heretic)
	. += ecult_give_item(/obj/item/living_heart, heretic)

/datum/antagonist/heretic/proc/ecult_give_item(obj/item/item_path, mob/living/carbon/human/heretic)
	var/list/slots = list(
		"backpack" = ITEM_SLOT_BACKPACK,
		"left pocket" = ITEM_SLOT_LPOCKET,
		"right pocket" = ITEM_SLOT_RPOCKET
	)

	var/T = new item_path(heretic)
	var/item_name = initial(item_path.name)
	var/where = heretic.equip_in_one_of_slots(T, slots)
	if(!where)
		to_chat(heretic, span_userdanger("Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1)."))
		return FALSE
	else
		to_chat(heretic, span_danger("You have a [item_name] in your [where]."))
		if(where == "backpack")
			SEND_SIGNAL(heretic.back, COMSIG_TRY_STORAGE_SHOW, heretic)
		return TRUE

/datum/antagonist/heretic/process()

	if(owner.current.stat == DEAD)
		return

	for(var/knowledge_index in researched_knowledge)
		var/datum/eldritch_knowledge/knowledge = researched_knowledge[knowledge_index]
		knowledge.on_life(owner.current)

///What happens to the heretic once he dies, used to remove any custom perks
/datum/antagonist/heretic/proc/on_death()
	SIGNAL_HANDLER

	for(var/knowledge_index in researched_knowledge)
		var/datum/eldritch_knowledge/knowledge = researched_knowledge[knowledge_index]
		knowledge.on_death(owner.current)

/datum/antagonist/heretic/proc/forge_primary_objectives()
	var/list/assasination = list()
	var/list/protection = list()

	var/choose_list_begin = list("assassinate","protect")
	var/choose_list_end = list("assassinate","hijack","protect","glory")

	var/pck1 = pick(choose_list_begin)
	var/pck2 = pick(choose_list_end)

	forge_objective(pck1,assasination,protection)
	forge_objective(pck2,assasination,protection)

	var/datum/objective/sacrifice_ecult/sac_objective = new
	sac_objective.owner = owner
	sac_objective.update_explanation_text()
	objectives += sac_objective

/datum/antagonist/heretic/proc/forge_objective(string,assasination,protection)
	switch(string)
		if("assassinate")
			var/datum/objective/assassinate/kill = new
			kill.owner = owner
			var/list/owners = kill.get_owners()
			kill.find_target(owners,protection)
			assasination += kill.target
			objectives += kill
		if("hijack")
			var/datum/objective/hijack/hijack = new
			hijack.owner = owner
			objectives += hijack
		if("glory")
			var/datum/objective/martyr/martyrdom = new
			martyrdom.owner = owner
			objectives += martyrdom
		if("protect")
			var/datum/objective/protect/protect = new
			protect.owner = owner
			var/list/owners = protect.get_owners()
			protect.find_target(owners,assasination)
			protection += protect.target
			objectives += protect

/datum/antagonist/heretic/apply_innate_effects(mob/living/mob_override)
	. = ..()
	var/mob/living/current = owner.current
	if(mob_override)
		current = mob_override
	add_antag_hud(antag_hud_type, antag_hud_name, current)
	handle_clown_mutation(current, mob_override ? null : "Ancient knowledge described in the book allows you to overcome your clownish nature, allowing you to use complex items effectively.")
	current.faction |= "heretics"

/datum/antagonist/heretic/remove_innate_effects(mob/living/mob_override)
	. = ..()
	var/mob/living/current = owner.current
	if(mob_override)
		current = mob_override
	remove_antag_hud(antag_hud_type, current)
	handle_clown_mutation(current, removing = FALSE)
	current.faction -= "heretics"

/datum/antagonist/heretic/get_admin_commands()
	. = ..()
	.["Equip"] = CALLBACK(src,.proc/equip_cultist)

/datum/antagonist/heretic/roundend_report()
	var/list/parts = list()

	var/cultiewin = TRUE

	parts += printplayer(owner)
	parts += "Sacrifices Made: [total_sacrifices]"

	if(length(objectives))
		var/count = 1
		for(var/o in objectives)
			var/datum/objective/objective = o
			if(objective.check_completion())
				parts += "Objective #[count]: [objective.explanation_text] [span_greentext("Success!")]"
			else
				parts += "Objective #[count]: [objective.explanation_text] [span_redtext("Fail.")]"
				cultiewin = FALSE
			count++
	if(ascended)
		parts += "THE HERETIC ASCENDED!"
	else
		if(cultiewin)
			parts += span_greentext("The heretic was successful!")
		else
			parts += span_redtext("The heretic has failed.")

	parts += "Knowledge Researched: "

	var/list/knowledge_message = list()
	var/list/researched_knowledge = get_all_knowledge()
	for(var/knowledge_index in researched_knowledge)
		var/datum/eldritch_knowledge/knowledge = researched_knowledge[knowledge_index]
		knowledge_message += "[knowledge.name]"
	parts += knowledge_message.Join(", ")

	return parts.Join("
")
////////////////
// Knowledge //
////////////////

/datum/antagonist/heretic/proc/gain_knowledge(datum/eldritch_knowledge/knowledge)
	if(get_knowledge(knowledge))
		return FALSE
	var/datum/eldritch_knowledge/initialized_knowledge = new knowledge
	researched_knowledge[initialized_knowledge.type] = initialized_knowledge
	initialized_knowledge.on_gain(owner.current)
	return TRUE

/datum/antagonist/heretic/proc/get_researchable_knowledge()
	var/list/researchable_knowledge = list()
	var/list/banned_knowledge = list()
	for(var/knowledge_index in researched_knowledge)
		var/datum/eldritch_knowledge/knowledge = researched_knowledge[knowledge_index]
		researchable_knowledge |= knowledge.next_knowledge
		banned_knowledge |= knowledge.banned_knowledge
		banned_knowledge |= knowledge.type
	researchable_knowledge -= banned_knowledge
	return researchable_knowledge

/datum/antagonist/heretic/proc/get_knowledge(wanted)
	return researched_knowledge[wanted]

/datum/antagonist/heretic/proc/get_all_knowledge()
	return researched_knowledge

////////////////
// Objectives //
////////////////

/datum/objective/sacrifice_ecult
	name = "sacrifice"

/datum/objective/sacrifice_ecult/update_explanation_text()
	. = ..()
	target_amount = rand(2,6)
	explanation_text = "Sacrifice at least [target_amount] people."

/datum/objective/sacrifice_ecult/check_completion()
	if(!owner)
		return FALSE
	var/datum/antagonist/heretic/cultie = owner.has_antag_datum(/datum/antagonist/heretic)
	if(!cultie)
		return FALSE
	return cultie.total_sacrifices >= target_amount

/datum/outfit/heretic
	name = "Heretic (Preview only)"

	suit = /obj/item/clothing/suit/hooded/cultrobes/eldritch
	r_hand = /obj/item/melee/touch_attack/mansus_fist

/datum/outfit/heretic/post_equip(mob/living/carbon/human/H, visualsOnly)
	var/obj/item/clothing/suit/hooded/hooded = locate() in H
	hooded.MakeHood() // This is usually created on Initialize, but we run before atoms
	hooded.ToggleHood()


code:
/obj/item/forbidden_book
	name = "Codex Cicatrix"
	desc = "This book describes the secrets of the veil between worlds."
	icon = 'icons/obj/eldritch.dmi'
	icon_state = "book"
	worn_icon_state = "book"
	w_class = WEIGHT_CLASS_SMALL
	///Last person that touched this
	var/mob/living/last_user
	///how many charges do we have?
	var/charge = 1
	///Where we cannot create the rune?
	var/static/list/blacklisted_turfs = typecacheof(list(/turf/closed,/turf/open/space,/turf/open/lava))

/obj/item/forbidden_book/Destroy()
	last_user = null
	. = ..()

/obj/item/forbidden_book/examine(mob/user)
	. = ..()
	if(!IS_HERETIC(user))
		return
	. += "The Tome holds [charge] charges."
	. += "Use it on the floor to create a transmutation rune, used to perform rituals."
	. += "Hit an influence in the black part with it to gain a charge."
	. += "Hit a transmutation rune to destroy it."

/obj/item/forbidden_book/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
	. = ..()
	if(!proximity_flag || !IS_HERETIC(user))
		return
	if(istype(target,/obj/effect/eldritch))
		remove_rune(target,user)
	if(istype(target,/obj/effect/reality_smash))
		get_power_from_influence(target,user)
	if(istype(target,/turf/open))
		draw_rune(target,user)

///Gives you a charge and destroys a corresponding influence
/obj/item/forbidden_book/proc/get_power_from_influence(atom/target, mob/user)
	var/obj/effect/reality_smash/RS = target
	to_chat(user, span_danger("You start drawing power from influence..."))
	if(do_after(user, 10 SECONDS, RS))
		qdel(RS)
		charge += 1

///Draws a rune on a selected turf
/obj/item/forbidden_book/proc/draw_rune(atom/target,mob/user)

	for(var/turf/T as anything in RANGE_TURFS(1,target))
		if(is_type_in_typecache(T, blacklisted_turfs))
			to_chat(user, span_warning("The terrain doesn't support runes!"))
			return
	var/A = get_turf(target)
	to_chat(user, span_danger("You start drawing a rune..."))

	if(do_after(user,30 SECONDS,A))

		new /obj/effect/eldritch/big(A)

///Removes runes from the selected turf
/obj/item/forbidden_book/proc/remove_rune(atom/target,mob/user)

	to_chat(user, span_danger("You start removing a rune..."))
	if(do_after(user,2 SECONDS,user))
		qdel(target)

/obj/item/forbidden_book/ui_interact(mob/user, datum/tgui/ui = null)
	if(!IS_HERETIC(user))
		return FALSE
	last_user = user
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		icon_state = "book_open"
		flick("book_opening", src)
		ui = new(user, src, "ForbiddenLore", name)
		ui.open()

/obj/item/forbidden_book/ui_data(mob/user)
	var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic)
	var/list/to_know = list()
	for(var/Y in cultie.get_researchable_knowledge())
		to_know += new Y
	var/list/known = cultie.get_all_knowledge()
	var/list/data = list()
	var/list/lore = list()

	data["charges"] = charge

	for(var/X in to_know)
		lore = list()
		var/datum/eldritch_knowledge/EK = X
		lore["type"] = EK.type
		lore["name"] = EK.name
		lore["cost"] = EK.cost
		lore["disabled"] = EK.cost <= charge ? FALSE : TRUE
		lore["path"] = EK.route
		lore["state"] = "Research"
		lore["flavour"] = EK.gain_text
		lore["desc"] = EK.desc
		data["to_know"] += list(lore)

	for(var/X in known)
		lore = list()
		var/datum/eldritch_knowledge/EK = known[X]
		lore["name"] = EK.name
		lore["cost"] = EK.cost
		lore["disabled"] = TRUE
		lore["path"] = EK.route
		lore["state"] = "Researched"
		lore["flavour"] = EK.gain_text
		lore["desc"] = EK.desc
		data["to_know"] += list(lore)

	if(!length(data["to_know"]))
		data["to_know"] = null

	return data

/obj/item/forbidden_book/ui_act(action, params)
	. = ..()
	if(.)
		return
	switch(action)
		if("research")
			var/datum/antagonist/heretic/cultie = last_user.mind.has_antag_datum(/datum/antagonist/heretic)
			var/ekname = params["name"]
			for(var/X in cultie.get_researchable_knowledge())
				var/datum/eldritch_knowledge/EK = X
				if(initial(EK.name) != ekname)
					continue
				if(cultie.gain_knowledge(EK))
					log_codex_ciatrix("[key_name(last_user)] gained knowledge of [EK]")
					charge -= initial(EK.cost)
					return TRUE

	update_appearance() // Not applicable to all objects.

/obj/item/forbidden_book/ui_close(mob/user)
	flick("book_closing",src)
	icon_state = initial(icon_state)
	return ..()

/obj/item/forbidden_book/debug
	charge = 100

/obj/item/forbidden_book/ritual
	charge = 0