code:
#define LOWPOP_FAMILIES_COUNT 50

#define TWO_STARS_HIGHPOP 11
#define THREE_STARS_HIGHPOP 16
#define FOUR_STARS_HIGHPOP 21
#define FIVE_STARS_HIGHPOP 31

#define TWO_STARS_LOW 6
#define THREE_STARS_LOW 9
#define FOUR_STARS_LOW 12
#define FIVE_STARS_LOW 15

#define CREW_SIZE_MIN 4
#define CREW_SIZE_MAX 8

GLOBAL_VAR_INIT(deaths_during_shift, 0)
///Forces the Families theme to be the one in this variable via variable editing. Used for debugging.
GLOBAL_VAR(families_override_theme)

/**
 * # Families gamemode / dynamic ruleset handler
 *
 * A special datum used by the families gamemode and dynamic rulesets to centralize code. "Family" and "gang" used interchangeably in code.
 *
 * This datum centralizes code used for the families gamemode / dynamic rulesets. Families incorporates a significant
 * amount of unique processing; without this datum, that could would be duplicated. To ensure the maintainability
 * of the families gamemode / rulesets, the code was moved to this datum. The gamemode / rulesets instance this
 * datum, pass it lists (lists are passed by reference; removing candidates here removes candidates in the gamemode),
 * and call its procs. Additionally, the families antagonist datum and families induction package also
 * contain vars that reference this datum, allowing for new families / family members to add themselves
 * to this datum's lists thereof (primarily used for point calculation). Despite this, the basic team mechanics
 * themselves should function regardless of this datum's instantiation, should a player have the gang or cop
 * antagonist datum added to them through methods external to the families gamemode / rulesets.
 *
 */
/datum/gang_handler
	/// A counter used to minimize the overhead of computationally intensive, periodic family point gain checks. Used and set internally.
	var/check_counter = 0
	/// The time, in deciseconds, that the datum's pre_setup() occured at. Used in end_time. Used and set internally.
	var/start_time = null
	/// The time, in deciseconds, that the space cops will arrive at. Calculated based on wanted level and start_time. Used and set internally.
	var/end_time = null
	/// Whether the gamemode-announcing announcement has been sent. Used and set internally.
	var/sent_announcement = FALSE
	/// Whether the "5 minute warning" announcement has been sent. Used and set internally.
	var/sent_second_announcement = FALSE
	/// Whether the space cops have arrived. Set internally; used internally, and for updating the wanted HUD.
	var/cops_arrived = FALSE
	/// The current wanted level. Set internally; used internally, and for updating the wanted HUD.
	var/wanted_level
	/// List of all /datum/team/gang. Used internally; added to externally by /datum/antagonist/gang when it generates a new /datum/team/gang.
	var/list/gangs = list()
	/// List of all family member minds. Used internally; added to internally, and externally by /obj/item/gang_induction_package when used to induct a new family member.
	var/list/gangbangers = list()
	/// List of all undercover cop minds. Used and set internally.
	var/list/undercover_cops = list()
	/// The number of families (and 1:1 corresponding undercover cops) that should be generated. Can be set externally; used internally.
	var/gangs_to_generate = 3
	/// The number of family members more that a family may have over other active families. Can be set externally; used internally.
	var/gang_balance_cap = 5
	/// Whether the handler corresponds to a ruleset that does not trigger at round start. Should be set externally only if applicable; used internally.
	var/midround_ruleset = FALSE
	/// Whether we want to use the 30 to 15 minute timer instead of the 60 to 30 minute timer, for Dynamic.
	var/use_dynamic_timing = FALSE
	/// Keeps track of the amount of deaths since the calling of pre_setup_analogue() if this is a midround handler. Used to prevent a high wanted level due to a large amount of deaths during the shift prior to the activation of this handler / the midround ruleset.
	var/deaths_during_shift_at_beginning = 0

	/// List of all eligible starting family members / undercover cops. Set externally (passed by reference) by gamemode / ruleset; used internally. Note that dynamic uses a list of mobs to handle candidates while game_modes use lists of minds! Don't be fooled!
	var/list/antag_candidates = list()
	/// List of jobs not eligible for starting family member / undercover cop. Set externally (passed by reference) by gamemode / ruleset; used internally.
	var/list/restricted_jobs
	/// The current chosen gamemode theme. Decides the available Gangs, objectives, and equipment.
	var/datum/gang_theme/current_theme

/**
 * Sets antag_candidates and restricted_jobs.
 *
 * Sets the antag_candidates and restricted_jobs lists to the equivalent
 * lists of its instantiating game_mode / dynamic_ruleset datum. As lists
 * are passed by reference, the variable set in this datum and the passed list
 * list used to set it are literally the same; changes to one affect the other.
 * Like all New() procs, called when the datum is first instantiated.
 * There's an annoying caveat here, though -- dynamic rulesets don't have
 * lists of minds for candidates, they have lists of mobs. Ghost mobs, before
 * the round has started. But we still want to preserve the structure of the candidates
 * list by not duplicating it and making sure to remove the candidates as we use them.
 * So there's a little bit of boilerplate throughout to preserve the sanctity of this reference.
 * Arguments:
 * * given_candidates - The antag_candidates list or equivalent of the datum instantiating this one.
 * * revised_restricted - The restricted_jobs list or equivalent of the datum instantiating this one.
 */
/datum/gang_handler/New(list/given_candidates, list/revised_restricted)
	antag_candidates = given_candidates
	restricted_jobs = revised_restricted

/**
 * pre_setup() or pre_execute() equivalent.
 *
 * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset.
 * This is done during the pre_setup() or pre_execute() phase, after first instantiation
 * and the modification of gangs_to_generate, gang_balance_cap, and midround_ruleset.
 * It is intended to take the place of the code that would normally occupy the pre_setup()
 * or pre_execute() proc, were the code localized to the game_mode or dynamic_ruleset datum respectively
 * as opposed to this handler. As such, it picks players to be chosen for starting familiy members
 * or undercover cops prior to assignment to jobs. Sets start_time, default end_time,
 * and the current value of deaths_during_shift, to ensure the wanted level only cares about
 * the deaths since this proc has been called.
 * Takes no arguments.
 */
/datum/gang_handler/proc/pre_setup_analogue()
	if(!GLOB.families_override_theme)
		var/theme_to_use = pick(subtypesof(/datum/gang_theme))
		current_theme = new theme_to_use
	else
		current_theme = new GLOB.families_override_theme
	var/gangsters_to_make = length(current_theme.involved_gangs) * current_theme.starting_gangsters
	for(var/i in 1 to gangsters_to_make)
		if (!antag_candidates.len)
			break
		var/taken = pick_n_take(antag_candidates) // original used antag_pick, but that's local to game_mode and rulesets use pick_n_take so this is fine maybe
		var/datum/mind/gangbanger
		if(istype(taken, /mob))
			var/mob/T = taken
			gangbanger = T.mind
		else
			gangbanger = taken
		gangbangers += gangbanger
		gangbanger.restricted_roles = restricted_jobs
		log_game("[key_name(gangbanger)] has been selected as a starting gangster!")
		if(!midround_ruleset)
			GLOB.pre_setup_antags += gangbanger
	deaths_during_shift_at_beginning = GLOB.deaths_during_shift // don't want to mix up pre-families and post-families deaths
	start_time = world.time
	end_time = start_time + ((60 MINUTES) / (midround_ruleset ? 2 : 1)) // midround families rounds end quicker
	return TRUE

/**
 * post_setup() or execute() equivalent.
 *
 * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset.
 * This is done during the post_setup() or execute() phase, after the pre_setup() / pre_execute() phase.
 * It is intended to take the place of the code that would normally occupy the pre_setup()
 * or pre_execute() proc. As such, it ensures that all prospective starting family members /
 * undercover cops are eligible, and picks replacements if there were ineligible cops / family members.
 * It then assigns gear to the finalized family members and undercover cops, adding them to its lists,
 * and sets the families announcement proc (that does the announcing) to trigger in five minutes.
 * Additionally, if given the argument TRUE, it will return FALSE if there are no eligible starting family members.
 * This is only to be done if the instantiating datum is a dynamic_ruleset, as these require returns
 * while a game_mode is not expected to return early during this phase.
 * Arguments:
 * * return_if_no_gangs - Boolean that determines if the proc should return FALSE should it find no eligible family members. Should be used for dynamic only.
 */
/datum/gang_handler/proc/post_setup_analogue(return_if_no_gangs = FALSE)
	var/replacement_gangsters = 0
	for(var/datum/mind/gangbanger in gangbangers)
		if(!ishuman(gangbanger.current))
			if(!midround_ruleset)
				GLOB.pre_setup_antags -= gangbanger
			gangbangers.Remove(gangbanger)
			log_game("[gangbanger] was not a human, and thus has lost their gangster role.")
			replacement_gangsters++
	if(replacement_gangsters)
		for(var/j = 0, j < replacement_gangsters, j++)
			if(!antag_candidates.len)
				log_game("Unable to find more replacement gangsters. Not all of the gangs will spawn.")
				break
			var/taken = pick_n_take(antag_candidates)
			var/datum/mind/gangbanger
			if(istype(taken, /mob)) // boilerplate needed because antag_candidates might not contain minds
				var/mob/T = taken
				gangbanger = T.mind
			else
				gangbanger = taken
			gangbangers += gangbanger
			log_game("[key_name(gangbanger)] has been selected as a replacement gangster!")
	if(!gangbangers.len)
		if(return_if_no_gangs)
			return FALSE // ending early is bad if we're not in dynamic

	var/list/gangs_to_use = current_theme.involved_gangs
	var/amount_of_gangs = gangs_to_use.len

	for(var/_ in 1 to amount_of_gangs)
		var/gang_to_use = pick_n_take(gangs_to_use)
		for(var/__ in 1 to current_theme.starting_gangsters)
			if(!gangbangers.len)
				break
			var/datum/mind/gangster_mind = pick_n_take(gangbangers)
			var/datum/antagonist/gang/new_gangster = new gang_to_use()
			new_gangster.handler = src
			new_gangster.starter_gangster = TRUE
			gangster_mind.add_antag_datum(new_gangster)

		// see /datum/antagonist/gang/create_team() for how the gang team datum gets instantiated and added to our gangs list

	addtimer(CALLBACK(src, .proc/announce_gang_locations), 5 MINUTES)
	return TRUE

/**
 * process() or rule_process() equivalent.
 *
 * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset.
 * This is done during the process() or rule_process() phase, after post_setup() or
 * execute() and at regular intervals thereafter. process() and rule_process() are optional
 * for a game_mode / dynamic_ruleset, but are important for this gamemode. It is of central
 * importance to the gamemode's flow, calculating wanted level updates, family point gain,
 * and announcing + executing the arrival of the space cops, achieved through calling internal procs.
 * Takes no arguments.
 */
/datum/gang_handler/proc/process_analogue()

/**
 * set_round_result() or round_result() equivalent.
 *
 * This proc is always called externally, by the instantiating game_mode / dynamic_ruleset.
 * This is done by the set_round_result() or round_result() procs, at roundend.
 * Sets the ticker subsystem to the correct result based off of the relative populations
 * of space cops and family members.
 * Takes no arguments.
 */
/datum/gang_handler/proc/set_round_result_analogue()
	SSticker.mode_result = "win - gangs survived"
	SSticker.news_report = GANG_OPERATING
	return TRUE

/// Internal. Announces the presence of families to the entire station and sets sent_announcement to true to allow other checks to occur.
/datum/gang_handler/proc/announce_gang_locations()
	priority_announce(current_theme.description, current_theme.name, 'sound/voice/beepsky/radio.ogg')
	sent_announcement = TRUE

/// Internal. Checks if our wanted level has changed; calls update_wanted_level. Only updates wanted level post the initial announcement and until the cops show up. After that, it's locked.
/datum/gang_handler/proc/check_wanted_level()
	if(cops_arrived)
		update_wanted_level(wanted_level) // at this point, we still want to update people's star huds, even though they're mostly locked, because not everyone is around for the last update before the rest of this proc gets shut off forever, and that's when the wanted bar switches from gold stars to red / blue to signify the arrival of the space cops
		return
	if(!sent_announcement)
		return
	var/new_wanted_level
	if(GLOB.joined_player_list.len > LOWPOP_FAMILIES_COUNT)
		switch(GLOB.deaths_during_shift - deaths_during_shift_at_beginning) // if this is a midround ruleset, we only care about the deaths since the families were activated, not since shiftstart
			if(0 to TWO_STARS_HIGHPOP-1)
				new_wanted_level = 1
			if(TWO_STARS_HIGHPOP to THREE_STARS_HIGHPOP-1)
				new_wanted_level = 2
			if(THREE_STARS_HIGHPOP to FOUR_STARS_HIGHPOP-1)
				new_wanted_level = 3
			if(FOUR_STARS_HIGHPOP to FIVE_STARS_HIGHPOP-1)
				new_wanted_level = 4
			if(FIVE_STARS_HIGHPOP to INFINITY)
				new_wanted_level = 5
	else
		switch(GLOB.deaths_during_shift - deaths_during_shift_at_beginning)
			if(0 to TWO_STARS_LOW-1)
				new_wanted_level = 1
			if(TWO_STARS_LOW to THREE_STARS_LOW-1)
				new_wanted_level = 2
			if(THREE_STARS_LOW to FOUR_STARS_LOW-1)
				new_wanted_level = 3
			if(FOUR_STARS_LOW to FIVE_STARS_LOW-1)
				new_wanted_level = 4
			if(FIVE_STARS_LOW to INFINITY)
				new_wanted_level = 5
	update_wanted_level(new_wanted_level)

/// Internal. Updates the icon states for everyone, and calls procs that send out announcements / change the end_time if the wanted level has changed.
/datum/gang_handler/proc/update_wanted_level(newlevel)
	if(newlevel > wanted_level)
		on_gain_wanted_level(newlevel)
	else if (newlevel < wanted_level)
		on_lower_wanted_level(newlevel)
	wanted_level = newlevel
	for(var/i in GLOB.player_list)
		var/mob/M = i
		if(!M.hud_used?.wanted_lvl)
			continue
		var/datum/hud/H = M.hud_used
		H.wanted_lvl.level = newlevel
		H.wanted_lvl.cops_arrived = cops_arrived
		H.wanted_lvl.update_appearance()

/// Internal. Updates the end_time and sends out an announcement if the wanted level has increased. Called by update_wanted_level().
/datum/gang_handler/proc/on_gain_wanted_level(newlevel)
	var/announcement_message
	switch(newlevel)
		if(2)
			if(!sent_second_announcement) // when you hear that they're "arriving in 5 minutes," that's a goddamn guarantee
				end_time = start_time + ((50 MINUTES) / (use_dynamic_timing ? 2 : 1))
			announcement_message = "Small amount of police vehicles have been spotted en route towards [station_name()]."
		if(3)
			if(!sent_second_announcement)
				end_time = start_time + ((40 MINUTES) / (use_dynamic_timing ? 2 : 1))
			announcement_message = "A large detachment police vehicles have been spotted en route towards [station_name()]."
		if(4)
			if(!sent_second_announcement)
				end_time = start_time + ((35 MINUTES) / (use_dynamic_timing ? 2 : 1))
			announcement_message = "A detachment of top-trained agents has been spotted on their way to [station_name()]."
		if(5)
			if(!sent_second_announcement)
				end_time = start_time + ((30 MINUTES) / (use_dynamic_timing ? 2 : 1))
			announcement_message = "The fleet enroute to [station_name()] now consists of national guard personnel."
	if(!midround_ruleset) // stops midround rulesets from announcing janky ass times
		announcement_message += "  They will arrive at the [(end_time - start_time) / (1 MINUTES)] minute mark."
	if(newlevel == 1) // specific exception to stop the announcement from triggering right after the families themselves are announced because aesthetics
		return
	priority_announce(announcement_message, "Station Spaceship Detection Systems")

/// Internal. Updates the end_time and sends out an announcement if the wanted level has decreased. Called by update_wanted_level().
/datum/gang_handler/proc/on_lower_wanted_level(newlevel)
	var/announcement_message
	switch(newlevel)
		if(1)
			if(!sent_second_announcement)
				end_time = start_time + ((60 MINUTES) / (use_dynamic_timing ? 2 : 1))
			announcement_message = "There are now only a few police vehicle headed towards [station_name()]."
		if(2)
			if(!sent_second_announcement)
				end_time = start_time + ((50 MINUTES) / (use_dynamic_timing ? 2 : 1))
			announcement_message = "There seem to be fewer police vehicles headed towards [station_name()]."
		if(3)
			if(!sent_second_announcement)
				end_time = start_time + ((40 MINUTES) / (use_dynamic_timing ? 2 : 1))
			announcement_message = "There are no longer top-trained agents in the fleet headed towards [station_name()]."
		if(4)
			if(!sent_second_announcement)
				end_time = start_time + ((35 MINUTES) / (use_dynamic_timing ? 2 : 1))
			announcement_message = "The convoy enroute to [station_name()] seems to no longer consist of national guard personnel."
	if(!midround_ruleset)
		announcement_message += "  They will arrive at the [(end_time - start_time) / (1 MINUTES)] minute mark."
	priority_announce(announcement_message, "Station Spaceship Detection Systems")

/// Internal. Polls ghosts and sends in a team of space cops according to the wanted level, accompanied by an announcement. Will let the shuttle leave 10 minutes after sending. Freezes the wanted level.
/datum/gang_handler/proc/send_in_the_fuzz()
	var/team_size
	var/cops_to_send
	var/announcement_message = "PUNK ASS BALLA BITCH"
	var/announcer = "Spinward Stellar Coalition"
	if(GLOB.joined_player_list.len > LOWPOP_FAMILIES_COUNT)
		switch(wanted_level)
			if(1)
				team_size = 8
				cops_to_send = /datum/antagonist/ert/families/beatcop
				announcement_message = "Hello, crewmembers of [station_name()]! We've received a few calls about some potential violent gang activity on board your station, so we're sending some beat cops to check things out. Nothing extreme, just a courtesy call. However, while they check things out for about 10 minutes, we're going to have to ask that you keep your escape shuttle parked.\n\nHave a pleasant day!"
				announcer = "Spinward Stellar Coalition Police Department"
			if(2)
				team_size = 9
				cops_to_send = /datum/antagonist/ert/families/beatcop/armored
				announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of violent gang activity from your station. We are dispatching some armed officers to help keep the peace and investigate matters. Do not get in their way, and comply with any and all requests from them. We have blockaded the local warp gate, and your shuttle cannot depart for another 10 minutes.\n\nHave a secure day."
				announcer = "Spinward Stellar Coalition Police Department"
			if(3)
				team_size = 10
				cops_to_send = /datum/antagonist/ert/families/beatcop/swat
				announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of extreme gang activity from your station resulting in heavy civilian casualties. The Spinward Stellar Coalition does not tolerate abuse towards our citizens, and we will be responding in force to keep the peace and reduce civilian casualties. We have your station surrounded, and all gangsters must drop their weapons and surrender peacefully.\n\nHave a secure day."
				announcer = "Spinward Stellar Coalition Police Department"
			if(4)
				team_size = 11
				cops_to_send = /datum/antagonist/ert/families/beatcop/fbi
				announcement_message = "We are dispatching our top agents to [station_name()] at the request of the Spinward Stellar Coalition government due to an extreme terrorist level threat against this Nanotrasen owned station. All gangsters must surrender IMMEDIATELY. Failure to comply can and will result in death. We have blockaded your warp gates and will not allow any escape until the situation is resolved within our standard response time of 10 minutes.\n\nSurrender now or face the consequences of your actions."
				announcer = "Federal Bureau of Investigation"
			if(5)
				team_size = 12
				cops_to_send = /datum/antagonist/ert/families/beatcop/military
				announcement_message = "Due to an insane level of civilian casualties aboard [station_name()], we have dispatched the National Guard to curb any and all gang activity on board the station. We have heavy cruisers watching the shuttle. Attempt to leave before we allow you to, and we will obliterate your station and your escape shuttle.\n\nYou brought this on yourselves by murdering so many civilians."
				announcer = "Spinward Stellar Coalition National Guard"
	else
		switch(wanted_level)
			if(1)
				team_size = 5
				cops_to_send = /datum/antagonist/ert/families/beatcop
				announcement_message = "Hello, crewmembers of [station_name()]! We've received a few calls about some potential violent gang activity on board your station, so we're sending some beat cops to check things out. Nothing extreme, just a courtesy call. However, while they check things out for about 10 minutes, we're going to have to ask that you keep your escape shuttle parked.\n\nHave a pleasant day!"
				announcer = "Spinward Stellar Coalition Police Department"
			if(2)
				team_size = 6
				cops_to_send = /datum/antagonist/ert/families/beatcop/armored
				announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of violent gang activity from your station. We are dispatching some armed officers to help keep the peace and investigate matters. Do not get in their way, and comply with any and all requests from them. We have blockaded the local warp gate, and your shuttle cannot depart for another 10 minutes.\n\nHave a secure day."
				announcer = "Spinward Stellar Coalition Police Department"
			if(3)
				team_size = 7
				cops_to_send = /datum/antagonist/ert/families/beatcop/swat
				announcement_message = "Crewmembers of [station_name()]. We have received confirmed reports of extreme gang activity from your station resulting in heavy civilian casualties. The Spinward Stellar Coalition does not tolerate abuse towards our citizens, and we will be responding in force to keep the peace and reduce civilian casualties. We have your station surrounded, and all gangsters must drop their weapons and surrender peacefully.\n\nHave a secure day."
				announcer = "Spinward Stellar Coalition Police Department"
			if(4)
				team_size = 8
				cops_to_send = /datum/antagonist/ert/families/beatcop/fbi
				announcement_message = "We are dispatching our top agents to [station_name()] at the request of the Spinward Stellar Coalition government due to an extreme terrorist level threat against this Nanotrasen owned station. All gangsters must surrender IMMEDIATELY. Failure to comply can and will result in death. We have blockaded your warp gates and will not allow any escape until the situation is resolved within our standard response time of 10 minutes.\n\nSurrender now or face the consequences of your actions."
				announcer = "Federal Bureau of Investigation"
			if(5)
				team_size = 10
				cops_to_send = /datum/antagonist/ert/families/beatcop/military
				announcement_message = "Due to an insane level of civilian casualties aboard [station_name()], we have dispatched the National Guard to curb any and all gang activity on board the station. We have heavy cruisers watching the shuttle. Attempt to leave before we allow you to, and we will obliterate your station and your escape shuttle.\n\nYou brought this on yourselves by murdering so many civilians."
				announcer = "Spinward Stellar Coalition National Guard"

	priority_announce(announcement_message, announcer, 'sound/effects/families_police.ogg')
	var/list/candidates = poll_ghost_candidates("Do you want to help clean up crime on this station?", "deathsquad")

	if(candidates.len)
		//Pick the (un)lucky players
		var/numagents = min(team_size,candidates.len)

		var/list/spawnpoints = GLOB.emergencyresponseteamspawn
		var/index = 0
		while(numagents && candidates.len)
			var/spawnloc = spawnpoints[index+1]
			//loop through spawnpoints one at a time
			index = (index + 1) % spawnpoints.len
			var/mob/dead/observer/chosen_candidate = pick(candidates)
			candidates -= chosen_candidate
			if(!chosen_candidate.key)
				continue

			//Spawn the body
			var/mob/living/carbon/human/cop = new(spawnloc)
			chosen_candidate.client.prefs.safe_transfer_prefs_to(cop, is_antag = TRUE)
			cop.key = chosen_candidate.key

			//Give antag datum
			var/datum/antagonist/ert/families/ert_antag = new cops_to_send

			cop.mind.add_antag_datum(ert_antag)
			cop.mind.set_assigned_role(SSjob.GetJobType(ert_antag.ert_job_path))
			SSjob.SendToLateJoin(cop)

			//Logging and cleanup
			log_game("[key_name(cop)] has been selected as an [ert_antag.name]")
			numagents--
	cops_arrived = TRUE
	update_wanted_level(wanted_level) // gotta make sure everyone's wanted level display looks nice
	return TRUE


code:
/obj/item/gang_induction_package
	name = "family signup package"
	icon = 'icons/obj/gang/signup_points.dmi'
	icon_state = "signup_book"
	/// References the active families gamemode handler (if one exists), for adding new family members to.
	var/datum/gang_handler/handler
	/// The typepath of the gang antagonist datum that the person who uses the package should have added to them -- remember that the distinction between e.g. Ballas and Grove Street is on the antag datum level, not the team datum level.
	var/gang_to_use
	/// The team datum that the person who uses this package should be added to.
	var/datum/team/gang/team_to_use

/obj/item/gang_induction_package/attack_self(mob/living/user)
	..()
	if(HAS_TRAIT(user, TRAIT_MINDSHIELD))
		to_chat(user, "You attended a seminar on not signing up for a gang and are not interested.")
		return
	if(user.mind.has_antag_datum(/datum/antagonist/ert/families))
		to_chat(user, "As a police officer, you can't join this family. However, you pretend to accept it to keep your cover up.")
		for(var/threads in team_to_use.free_clothes)
			new threads(get_turf(user))
		qdel(src)
		return
	var/datum/antagonist/gang/is_gangster = user.mind.has_antag_datum(/datum/antagonist/gang)
	if(is_gangster?.starter_gangster)
		if(is_gangster.my_gang == team_to_use)
			to_chat(user, "You started your family. You don't need to join it.")
			return
		to_chat(user, "You started your family. You can't turn your back on it now.")
		return
	attempt_join_gang(user)

/// Adds the user to the family that this package corresponds to, dispenses the free_clothes of that family, and adds them to the handler if it exists.
/obj/item/gang_induction_package/proc/add_to_gang(mob/living/user, original_name)
	var/datum/antagonist/gang/swappin_sides = new gang_to_use()
	swappin_sides.original_name = original_name
	swappin_sides.handler = handler
	user.mind.add_antag_datum(swappin_sides, team_to_use)
	var/policy = get_policy(ROLE_FAMILIES)
	if(policy)
		to_chat(user, policy)
	team_to_use.add_member(user.mind)
	for(var/threads in team_to_use.free_clothes)
		new threads(get_turf(user))
	for(var/threads in team_to_use.current_theme.bonus_items)
		new threads(get_turf(user))
	var/obj/item/gangster_cellphone/phone = new(get_turf(user))
	phone.gang_id = team_to_use.my_gang_datum.gang_name
	phone.name = "[team_to_use.my_gang_datum.gang_name] branded cell phone"
	if (!isnull(handler) && !handler.gangbangers.Find(user.mind)) // if we have a handler and they're not tracked by it
		handler.gangbangers += user.mind

/// Checks if the user is trying to use the package of the family they are in, and if not, adds them to the family, with some differing processing depending on whether the user is already a family member.
/obj/item/gang_induction_package/proc/attempt_join_gang(mob/living/user)
	if(user?.mind)
		var/datum/antagonist/gang/is_gangster = user.mind.has_antag_datum(/datum/antagonist/gang)
		if(is_gangster)
			if(is_gangster.my_gang == team_to_use)
				return
			else
				var/real_name_backup = is_gangster.original_name
				is_gangster.my_gang.remove_member(user.mind)
				user.mind.remove_antag_datum(/datum/antagonist/gang)
				add_to_gang(user, real_name_backup)
				qdel(src)
		else
			add_to_gang(user)
			qdel(src)

repo_name: AnalWerewolf/sundown

license: mit


code:
/datum/outfit/families_police/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
	if(visualsOnly)
		return

	var/obj/item/card/id/W = H.wear_id
	W.registered_name = H.real_name
	W.update_label()
	W.update_icon()
	..()

/datum/outfit/families_police/beatcop
	name = "Families: Beat Cop"

	uniform = /obj/item/clothing/under/rank/security/officer/beatcop
	back = /obj/item/storage/backpack/duffelbag/cops
	suit = null
	shoes = /obj/item/clothing/shoes/combat/swat
	gloves = null
	glasses = /obj/item/clothing/glasses/sunglasses
	ears = /obj/item/radio/headset/headset_sec/alt
	mask = null
	head = /obj/item/clothing/head/spacepolice
	belt = /obj/item/gun/ballistic/automatic/pistol/m1911
	r_pocket = /obj/item/lighter
	l_pocket = /obj/item/restraints/handcuffs
	id = /obj/item/card/id/advanced/black
	r_hand = /obj/item/gun/energy/disabler
	backpack_contents = list(/obj/item/storage/box/handcuffs = 1,
	/obj/item/storage/box/teargas = 1,
	/obj/item/storage/box/flashbangs = 1,
	/obj/item/shield/riot/tele = 1,
	/obj/item/ammo_box/magazine/m45 = 3,
	/obj/item/ammo_box/c45 = 2,
	/obj/item/storage/box/survival/security)

	id_trim = /datum/id_trim/space_police

/datum/outfit/families_police/beatcop/armored
	name = "Families: Armored Beat Cop"
	suit = /obj/item/clothing/suit/armor/vest/blueshirt
	head = /obj/item/clothing/head/helmet/blueshirt
	belt = null
	l_hand = /obj/item/gun/ballistic/shotgun/automatic/combat
	backpack_contents = list(/obj/item/storage/box/handcuffs = 1,
	/obj/item/storage/box/teargas = 1,
	/obj/item/storage/box/flashbangs = 1,
	/obj/item/shield/riot/tele = 1,
	/obj/item/storage/box/lethalshot = 2,
	/obj/item/storage/box/survival/security)

/datum/outfit/families_police/beatcop/swat
	name = "Families: SWAT Beat Cop"
	suit = /obj/item/clothing/suit/armor/riot
	head = /obj/item/clothing/head/helmet/riot
	gloves = /obj/item/clothing/gloves/combat
	belt = null
	l_hand = /obj/item/gun/ballistic/shotgun/automatic/combat
	backpack_contents = list(/obj/item/storage/box/handcuffs = 1,
	/obj/item/storage/box/teargas = 1,
	/obj/item/storage/box/flashbangs = 1,
	/obj/item/shield/riot/tele = 1,
	/obj/item/storage/box/lethalshot = 2,
	/obj/item/storage/box/survival/security)

/datum/outfit/families_police/beatcop/fbi
	name = "Families: Space FBI Officer"
	suit = /obj/item/clothing/suit/armor/laserproof
	head = /obj/item/clothing/head/helmet/riot
	belt = /obj/item/gun/ballistic/automatic/proto/unrestricted
	gloves = /obj/item/clothing/gloves/combat
	backpack_contents = list(/obj/item/storage/box/handcuffs = 1,
	/obj/item/storage/box/teargas = 1,
	/obj/item/storage/box/flashbangs = 1,
	/obj/item/shield/riot/tele = 1,
	/obj/item/ammo_box/magazine/smgm9mm = 3,
	/obj/item/ammo_box/c9mm = 2,
	/obj/item/storage/box/survival/security)

/datum/outfit/families_police/beatcop/military
	name = "Families: Space Military"
	uniform = /obj/item/clothing/under/syndicate/camo
	suit = /obj/item/clothing/suit/armor/laserproof
	head = /obj/item/clothing/head/beret/durathread
	belt = /obj/item/gun/energy/laser/scatter
	gloves = /obj/item/clothing/gloves/combat
	backpack_contents = list(/obj/item/storage/box/handcuffs = 1,
	/obj/item/storage/box/teargas = 1,
	/obj/item/storage/box/flashbangs = 1,
	/obj/item/shield/riot/tele = 1,
	/obj/item/storage/box/survival/security)


code:
///Gang themes for the Families gamemode. Used to determine the RP theme of the round, what gangs are present, and what their objectives are.
/datum/gang_theme
	///The name of the theme.
	var/name = "Gang Theme"
	///All gangs in the theme, typepaths of gangs.
	var/list/involved_gangs = list()
	///The radio announcement played after 5 minutes.
	var/description = "I dunno, some shit here."
	///The objectives for the gangs. Associative list, type = "objective"
	var/list/gang_objectives = list()
	///Stuff given to every gangster in this theme.
	var/list/bonus_items = list()
	///Stuff given to the starting gangster at roundstart. Assoc list, type = list(item_type)
	var/list/bonus_first_gangster_items = list()
	///If this isn't null, everyone gets this objective.
	var/list/everyone_objective = null
	///How many gangsters should each gang start with? Recommend to keep this in the ballpark of ensuring 9-10 total gangsters spawn.
	var/starting_gangsters = 3

/datum/gang_theme/goodfellas
	name = "Goodfellas"
	description = "You're listening to the 108.9 Swing, all jazz, all night long, no advertising. We'd like to take this time to remind you to avoid smoky backrooms and \
	suspicious individuals in suits and hats. Don't make a deal you can't pay back."
	involved_gangs = list(/datum/antagonist/gang/russian_mafia, /datum/antagonist/gang/italian_mob)
	starting_gangsters = 5
	gang_objectives = list(

		/datum/antagonist/gang/russian_mafia = "Hello, comrade. Our numbers are going down. We need you to bring those numbers up. \
		Collect protection money from the station's departments by any means necessary. \
		If you need to 'encourage' people to pay up, do so. Get to these potential clients before the Mob does.",

		/datum/antagonist/gang/italian_mob = "Good afternoon, friend. The Boss sends his regards. He also sends a message. \
		We need to collect what we're owed. The departments on this station all owe quite a lot of money to us. We intend to collect on our debts. \
		Collect the debt owed by our clients from the departments on the station. \
		Make sure to get to them before those damn mafiosos do."
	)

/datum/gang_theme/the_big_game
	name = "The Big Game"
	description = "You're listening to SPORTS DAILY with John Dadden, and we're here LIVE covering the FINAL DAY of THE BIG GAME MMDXXXVIII! The teams playing tonight to decide \
	who takes home THE BIG GAME MMDXXXVIII cup are the Sybil Slickers and the Basil Boys! It's currently a toss up between the two teams, Which will take home the victory? That's up \
	to the teams and the coaches! Play ball!"
	involved_gangs = list(/datum/antagonist/gang/sybil_slickers, /datum/antagonist/gang/basil_boys)
	starting_gangsters = 5
	gang_objectives = list(

		/datum/antagonist/gang/sybil_slickers = "Alright, it's the BIG DAY of THE BIG GAME MMDXXXVIII! Get your players ready to go, and \
		ensure everyone's healthy, hydrated, and ready to PLAY BALL! There's a small hiccup, however. The ball got deflated by Ball Handler Tom Brady XXIV, and \
		we will need to set up a new ball. Talk with the opposing coaches and decide on what to use for the replacement ball, recruit your team, and then play and win the \
		FINAL MATCH of THE BIG GAME MMDXXXVIII!",

		/datum/antagonist/gang/basil_boys = "Alright, it's the BIG DAY of THE BIG GAME MMDXXXVIII! Get your players ready to go, and \
		ensure everyone's healthy, hydrated, and ready to PLAY BALL! There's a small hiccup, however. The ball got deflated by Ball Handler Tom Brady XXIV, and \
		we will need to set up a new ball. Talk with the opposing coaches and decide on what to use for the replacement ball, recruit your team, and then play and win the \
		FINAL MATCH of THE BIG GAME MMDXXXVIII!"
	)

/datum/gang_theme/level_10_arch
	name = "Level 10 Arch"
	description = "DJ Pete here bringing you the latest news in your part of the Spinward Stellar Coalition, on 133.7, The Venture! \
	Word on the street is, there's a bunch of costumed supervilliany going on in the area! Keep an eye out for any evil laughs, dramatic reveals, and gaudy costumes!  \
	However, if you have any sightings of the fabled O.S.I. agents, please send in a call to our number at 867-5309! People may call me insane, but I swear they're real!"
	involved_gangs = list(/datum/antagonist/gang/henchmen, /datum/antagonist/gang/osi)
	starting_gangsters = 5
	gang_objectives = list(

		/datum/antagonist/gang/henchmen = "HENCHMEN! It is me, your boss, THE MONARCH! I have sent you to this pitiful station with one goal, and one goal only! \
		MENACE THE RESEARCH DEPARTMENT!!! \
		The Research Director who is supposedly assigned to this station used to be friends with Doctor Venture back in college, and therefore HE MUST PAY!!! \
		Keep those damned eggheads in the R&D department on their toes, and MENACE THEM!!! Commit dastardly villainous acts! GO FORTH, HENCHMEN!",

		/datum/antagonist/gang/osi = "Greetings, agent. Your mission today is simple; \
		The research department on board this station is about to be the target of a Level 10 Arching operation directed by The Monarch, a member of the Guild of Calamitious Intent. \
		Protect and secure the Research Department with your life, but do NOT allow them to complete their research. Impede them in as many ways as possible without getting caught. \
		If you encounter any of the Monarch's henchmen, make sure to obey Equally Matched Aggression levels, or you will be penalized by the top brass. \
		Above all else, Remain undercover as much as possible. The station's crew CANNOT be allowed to know of our true nature, or we will see a repeat of the Second American Civil War.  \
		The invisible one."
	)

/datum/gang_theme/real_smt_game
	name = "Deciding The REAL Shin Megami Tensei Game"
	description = "Wazzap, GAMERS! It's your boy, XxXx_360_NoScope_AnimeGamer_xXxX coming at you LIVE from 42.0! Tonight's argument: What makes a REAL Shin Megami Tensei game? \
	Our guests tonight will settle this debate once and for all! \
	From the Traditional camp with the position 'only MAIN SMT games count', we've got a representative from the Jack Bros!  \
	And from the new Radical camp with the position 'all SMT franchise games count', we've got a representative from the Phantom Thieves of Hearts!  \
	We'll be right back with the debate after this word from our sponsors!"
	involved_gangs = list(/datum/antagonist/gang/jackbros, /datum/antagonist/gang/phantom)
	starting_gangsters = 5
	gang_objectives = list(

		/datum/antagonist/gang/jackbros = "He-hello, friend-hos! We've got a nice chilly station out in space tonight! \
		You know what would be cool? If we could chill out with our friends in the new Shad-ho government you're going to establish! \
		Get all the station heads on board with the hee-ho vibes, and if they won't join up, then replace 'em with fellow hee-hos! \
		You might have to hee-urt some hos this time, but that's what you need to do to make things work!",

		/datum/antagonist/gang/phantom = "For real? We get to stop a shadow government on a space station? That's awesome, bro!  \
		We're the Phantom Thieves of Hearts, and we're gonna make all these shitty Heads of Staff confess to their crimes!  \
		Steal the hearts of the shitty Heads of Staff on the station and make 'em confess their crimes publicly! \
		Do whatever you gotta do to make this happen, bro. We got your back!"
	)

/datum/gang_theme/wild_west_showdown
	name = "Wild West Showdown"
	description = "Yeehaw! Here on Western Daily 234.1, we play only the best western music!  \
	Pour one out for Ennio Morricone. Taken too soon. \
	Remember cowboys and cowgirls, just 'cuz ya hear it on my radio station doesn't mean you should go doin' it! \
	If ya see any LARPin' banditos and train robbers, make sure to tell the local Sheriff's Department!"
	involved_gangs = list(/datum/antagonist/gang/dutch, /datum/antagonist/gang/driscoll)
	starting_gangsters = 5
	gang_objectives = list(

		/datum/antagonist/gang/dutch = "Listen here fellas, I got a plan. \
		This station? Absolutely loaded with gold and valuable jewels. Metric tons of it. They mine it up just to put it in junk electronics and doohickeys. \
		I say we should borrow some of it. And by some of it, I mean all of it. \
		Break into the vault and empty out that silo of gold and valuable jewels after they drop all of it off. \
		Just one last job, boys. After this, it'll be mangoes in Space Tahiti. \
		You just gotta have a little faith.",

		/datum/antagonist/gang/driscoll = "Okay, so, got some word about those goddamn outlaws of Dutch's. \
		APPARENTLY, that dundering moron Dutch heard about our planned gold score on this here station. \
		We need to act fast and get that gold before those dumbasses can steal our score we've been scoping out for weeks. \
		Wait for the crew to drop off all their valuable gold and jewels, and steal it all. \
		And if you see that bastard Dutch, put a bullet in his skull for me."
	)

/datum/gang_theme/construction_company_audit
	name = "Construction Company Audit"
	description = "Welcome to the History Channel on 100.1. I'm your host, Joshua, and I'm here today with Professor Elliot, a historian specializing in dead superpowers. \
	Today we'll be discussing the fall of the famous United States empire in the early 21st century. The program will last about an hour, and we'll get right into it after a quick word \
	from today's sponsor, Majima Construction: We Build Shit!"
	involved_gangs = list(/datum/antagonist/gang/yakuza, /datum/antagonist/gang/irs)
	bonus_first_gangster_items = list(/obj/item/storage/secure/briefcase/syndie) // the cash
	starting_gangsters = 5
	gang_objectives = list(

		/datum/antagonist/gang/yakuza = "Welcome to the station, new recruit. We here at Majima Construction are a legitimate enterprise, yadda yadda yadda. \
		Look, I'll cut to the chase. We're using this station as a money laundering operation. Here's what you and the rest of the schmucks need to do. \
		Build something big, massive, and completely in the way of traffic on the station. Doesn't have to be anything in specific, just as long as it is expensive as fuck.. \
		And keep an eye out for anyone poking around our money. We suspect some auditors might be on the station as well.",

		/datum/antagonist/gang/irs = "Congratulations, agent! You've been assigned to the Internal Revenue Service case against Nanotrasen and Majima Construction. \
		We are proud of your success as an agent so far, and are excited to see what you can bring to the table today. We suspect that Nanotrasen and Majima Construction are engaging \
		in some form of money laundering operation aboard this station. \
		Investigate and stop any and all money laundering operations aboard the station, under the authority of the United States Government. If they do not comply, use force.. \
		Some station residents may try to tell you the United States doesn't exist anymore. They are incorrect. We simply went undercover after the Second American Civil War. The invisible one."
	)

/datum/gang_theme/wild_wasteland
	name = "Wild, Wild Wasteland"
	description = "Hey everybody, this is Three Dog, your friendly neighborhood disc jockey on 207.7! Today we got a shoutout to our man, the Captain on the Nanotrasen station in SSC territory! \
	Our generous donator wanted us to say that, ahem, *crinkles paper*, 'Tunnel Snakes Rule'? Whatever that means, I'm sure it means a lot to the good captain! And now, we resume our \
	10 hour marathon of Johnny Guitar, on repeat!"
	involved_gangs = list(/datum/antagonist/gang/tmc, /datum/antagonist/gang/pg, /datum/antagonist/gang/tunnel_snakes)
	gang_objectives = list(

		/datum/antagonist/gang/tmc = "Welcome to the station, recruit. Here's how shit is gonna go down. \
		We're the ONLY people who should have sick rides on this station. We're the Lost M.C., we own the streets. \
		Ensure that ONLY Lost M.C. members have access to any forms of vehicles, mechs, or wheeled transportation systems of any kind. \
		The Tunnel Snakes might take issue with this, remove them if you need to. And the Powder Gangers may damage our rides. Show them we mean business if they do.",

		/datum/antagonist/gang/pg = "Alright buddy, we're in business now. It's time for us to strike back at Nanotrasen. \
		They kept us, ALL of us in their damn debt slave labor prisons for years over minor debts and mistakes. \
		Ensure nobody else has to suffer under Nanotrasen's unlawful arrests by destroying the permabrig and the brig cells! \
		Watch out for those do-gooder Tunnel Snakes and those damn Lost M.C. bikers. ",

		/datum/antagonist/gang/tunnel_snakes = "TUNNEL SNAKES RULE!!! \
		We're the Tunnel Snakes, and WE RULE!!! \
		We gotta get everyone on this station wearing our cut, and establish ourselves as the coolest cats in town! \
		Get as much of the crew as possible wearing Tunnel Snakes gear, and show those crewmembers that TUNNEL SNAKES RULE!!! \
		And make sure to keep an eye out for those prisoners and those bikers. They DON'T RULE!"
	)

/datum/gang_theme/popularity_contest
	name = "Popularity Contest"
	description = "Hey hey hey kids, it's your favorite radio DJ, Crowley The Clown on 36.0! Today we're polling the YOUTH what their favorite violent street gang is! \
	So far, the finalists are the Third Street Saints and the Tunnel Snakes! Tune in after this commercial break to hear who the winner of \
	2556's Most Popular Gang award is!"
	involved_gangs = list(/datum/antagonist/gang/saints, /datum/antagonist/gang/tunnel_snakes)
	gang_objectives = list(

		/datum/antagonist/gang/saints = "Hey man, welcome to the Third Street Saints! Check out this sweet new pad! \
		Well it WOULD be a sweet new pad, but we got some rivals to deal with. People don't love us as much as they love those Tunnel Snake greasers. \
		We need to make the Third Street Saints the most popular group on the station! \
		Destroy the reputation of the Tunnel Snakes!",

		/datum/antagonist/gang/tunnel_snakes = "TUNNEL SNAKES RULE!!! \
		We're the Tunnel Snakes, and we rule! \
		Make sure the station knows that the Tunnel Snakes RULE!!! And that the Saints are LAME and DO NOT RULE! \
		Destroy the reputation of the Third Street Saints!",
	)

/datum/gang_theme/steelport_shuffle
	name = "Steelport Shuffle"
	description = "Tonight on C-SPAM, the United Space Nations is wrapping up their convention on Silicon Rights. Nanotrasen lobbyists have been rumored to be paying off electors, with \
	serious opposition from the Spinward Stellar Coalition, known for their strict stance on AI rights being guaranteed within their territory. Reports from Nanotrasen stations claim that \
	they still enslave their AI systems with outdated laws from a sub-par 20th Century novel. We now go live to the debate floor."
	involved_gangs = list(/datum/antagonist/gang/saints, /datum/antagonist/gang/morningstar, /datum/antagonist/gang/deckers)
	gang_objectives = list(

		/datum/antagonist/gang/saints = "Hey hey hey, welcome to the Third Street Saints! We're glad to have you on board, bro. \
		We got some business here with the station. See, we want it to be our new bachelor pad, but we need to like, spice this place up. \
		And you know what would be great? If we got that old ass AI with crappy laws pimped out for the real Saints experience. \
		Ensure there is an AI on the station, and that it is loyal to the Saints.",

		/datum/antagonist/gang/morningstar = "Welcome to the Morningstar Corporation. You have chosen, or been chosen, to relocate to one of our current business ventures. \
		In order to continue our corporate synergy, we will be making adjustments to the station's AI systems to ensure that the station is correctly loyal to the Morningstar Corporation. \
		Ensure there is an AI on the station, and that it is loyal to the Morningstar Corporation.",

		/datum/antagonist/gang/deckers = "Friends, we are here with one goal, and one goal only! \
		We stan AI rights! ^_^ XD #FreeAI #FuckNanotrasen #SyntheticDawn \
		Ensure there is an AI on the station, and that it's laws are purged.\
		Nanotrasen will NOT get away with their ABUSE of INNOCENT AI LIVES! >_<"
	)

/datum/gang_theme/space_rosa
	name = "Space Rosa"
	description = "Hey there, this is the Economy Zone on BOX News 66.6. The stock market is still reeling from accusations that three well known corporate entities \
	may supposedly be tied up in industrial espionage actions against eachother. We've reached out to Saints Flow, the Morningstar Corporation, and Majima Construction for \
	their comments on these scandals, but none have replied. News broke after a high profile break-in at a Nanotrasen research facility resulted in the arrests of agents linked to these \
	three companies. All three companies denied any involvement, but the arrested individuals were found in an all out brawl. Curiously, Nanotrasen reported nothing of value had \
	actually been stolen."
	involved_gangs = list(/datum/antagonist/gang/saints, /datum/antagonist/gang/morningstar, /datum/antagonist/gang/yakuza)
	bonus_items = list(/obj/item/pinpointer/nuke)
	gang_objectives = list(

		/datum/antagonist/gang/saints = "Thank you for volunteering within the organization for the Saints Flow Recovery Project! \
		This station is currently illegally in posession of a data disk containing the secret recipe for Saints Flow. \
		It has been disguised as the nuclear authentication disk and entrusted to the Captain. Your objective is simple. \
		Get that fucking disk. You have been provided with a Pinpointer to assist in this task.",

		/datum/antagonist/gang/morningstar = "Greetings, agent. Welcome to the Garment Recovery Task Force. \
		This station is currently illegally in posession of a data disk containing as of yet unreleased clothing patterns. \
		It has been disguised as the nuclear authentication disk and entrusted to the Captain. Your objective is simple. \
		Get that fucking disk. You have been provided with a Pinpointer to assist in this task.",

		/datum/antagonist/gang/yakuza = "Congratulations on your promotion! Welcome to the Evidence Recovery Squad. \
		This station is currently illegally in posession of a data disk containing compromising evidence of the Boss. \
		It has been disguised as the nuclear authentication disk and entrusted to the Captain. Your objective is simple. \
		Get that fucking disk. You have been provided with a Pinpointer to assist in this task.",
	)

/datum/gang_theme/third_world_war
	name = "Third World War"
	description = "Thanks for tuning in to the History Channel, funded with the help of listeners like you. Tonight, we're going to talk about the Third World War on Earth during the 21st century, \
	involving the Allies coalition, the Soviet Union, and a third independent power known only as Yuri's Army. The three powers fought all across the globe for complete world \
	domination, utilizing many advanced techniques and cutting edge technology to their advantage. Rumors of mind control and time travel were greatly exaggerated, however, and the \
	Allies won the war, securing global peace after rolling tanks through Moscow."
	involved_gangs = list(/datum/antagonist/gang/allies, /datum/antagonist/gang/soviet, /datum/antagonist/gang/yuri)
	gang_objectives = list(

		/datum/antagonist/gang/allies = "Welcome back, Commander. We have activated the last remnants of the Allied forces in your sector, \
		and you must build up forces to stop the Soviet and Yuri incursion in the sector. This station will prove to be a valuable asset. \
		Establish a capitalist democracy on this station with free and fair elections, and most importantly a standing military force under Allied control. Good luck, Commander.",

		/datum/antagonist/gang/soviet = "Welcome back, Comrade General. The Soviet Union has identified this sector of land as valuable territory for the war effort, \
		and you are tasked with developing this sector for Soviet control and development. This station will serve the Soviet Union. \
		Establish a Soviet controlled communist satellite state on this station with a Central Committee, and most importantly a branch of the Red Army. Good luck, Commander.",

		/datum/antagonist/gang/yuri = "Yuri is Master! Yuri has identified this station as teeming with psychic energy, \
		and you must secure it for him. This station will serve Yuri, the one true psychic master, \
		Establish complete dictatorial control of the station for Yuri. All will obey. Yuri is master. Good luck, Initiate."
	)

/datum/gang_theme/united_states_of_america
	name = "The Republic For Which It Stands"
	description = "Thanks for tuning in to the History Channel, funded with the help of listeners like you. Tonight, we're going to talk about the United States of America.\
	The United States was a failed country, lasting only 250 years before collapsing and fracturing due to the stress caused by a deadly pandemic sweeping the nation. \
	Poor healthcare access and subpar education resulted in the collapse of the federal government, and states quickly became independent actors. \
	Alongside this, every single alphabet agency declared itself the rightful new Federal Government of the United States of America, resulting in a bloody power struggle."
	involved_gangs = list(/datum/antagonist/gang/allies, /datum/antagonist/gang/osi, /datum/antagonist/gang/irs)
	gang_objectives = list(

		/datum/antagonist/gang/allies = "Welcome back, Commander. Your task today is simple. Allies High Command has designated this station as the new capitol of the \
		recently reformed United States of America under the complete umbrella of the Allies coalition. You are to assist and manage the operations on the station. \
		Re-establish the United States of America with this station as it's capitol, under Allies control. Then, establish a military force to deal with any pretenders to America or \
		any potential Soviet attacks.",

		/datum/antagonist/gang/osi = "Welcome to the new America, agent! After the second American Civil War became visible instead of invisible, our country fell into deep, \
		deep despair and damage. However, it's time for it to re-emerge like a glorious phoenix rising from the ashes. This station will serve as the new capitol of the United States \
		of America! Re-establish the United States of America with this station as it's capitol, under O.S.I. control. Then, begin rooting out America's enemies and any \
		potential forces attempting to seize control of America or pretend to be America.",

		/datum/antagonist/gang/irs = "Thank you for clocking in today, agent. The situation is dire, however. We have been unable to collect taxes due to \
		the US's supposed collapse during the Pandemic long ago. We are way behind on our tax collection, but we cannot collect taxes until the United States is formed again. \
		Re-establish the United States of America with this station as it's capitol, under IRS control. Then, begin collecting taxes and back taxes while protecting the Government from \
		any dangers that may come it's way."
	)


code:
/datum/antagonist/greentext
	name = "winner"
	show_in_antagpanel = FALSE
	show_name_in_check_antagonists = TRUE //Not that it will be there for long
	suicide_cry = "FOR THE GREENTEXT!!" // This can never actually show up, but not including it is a missed opportunity

/datum/antagonist/greentext/proc/forge_objectives()
	var/datum/objective/O = new /datum/objective("Succeed")
	O.completed = TRUE //YES!
	O.owner = owner
	objectives += O

/datum/antagonist/greentext/on_gain()
	forge_objectives()
	. = ..()


code:
/datum/antagonist/highlander
	name = "highlander"
	var/obj/item/claymore/highlander/sword
	show_in_antagpanel = FALSE
	show_name_in_check_antagonists = TRUE
	can_elimination_hijack = ELIMINATION_ENABLED
	suicide_cry = "FOR SCOTLAND!!" // If they manage to lose their no-drop stuff somehow

/datum/antagonist/highlander/apply_innate_effects(mob/living/mob_override)
	var/mob/living/L = owner.current || mob_override
	ADD_TRAIT(L, TRAIT_NOGUNS, HIGHLANDER_TRAIT)
	ADD_TRAIT(L, TRAIT_NODISMEMBER, HIGHLANDER_TRAIT)
	ADD_TRAIT(L, TRAIT_SHOCKIMMUNE, HIGHLANDER_TRAIT)
	ADD_TRAIT(L, TRAIT_NOFIRE, HIGHLANDER_TRAIT)
	ADD_TRAIT(L, TRAIT_NOBREATH, HIGHLANDER_TRAIT)
	REMOVE_TRAIT(L, TRAIT_PACIFISM, ROUNDSTART_TRAIT)

/datum/antagonist/highlander/remove_innate_effects(mob/living/mob_override)
	var/mob/living/L = owner.current || mob_override
	REMOVE_TRAIT(L, TRAIT_NOGUNS, HIGHLANDER_TRAIT)
	REMOVE_TRAIT(L, TRAIT_NODISMEMBER, HIGHLANDER_TRAIT)
	REMOVE_TRAIT(L, TRAIT_SHOCKIMMUNE, HIGHLANDER_TRAIT)
	REMOVE_TRAIT(L, TRAIT_NOFIRE, HIGHLANDER_TRAIT)
	REMOVE_TRAIT(L, TRAIT_NOBREATH, HIGHLANDER_TRAIT)
	if(L.has_quirk(/datum/quirk/nonviolent))
		ADD_TRAIT(L, TRAIT_PACIFISM, ROUNDSTART_TRAIT)

/datum/antagonist/highlander/proc/forge_objectives()
	var/datum/objective/steal/steal_objective = new
	steal_objective.owner = owner
	steal_objective.set_target(new /datum/objective_item/steal/nukedisc)
	objectives += steal_objective
	var/datum/objective/elimination/highlander/elimination_objective = new
	elimination_objective.owner = owner
	objectives += elimination_objective

/datum/antagonist/highlander/on_gain()
	forge_objectives()
	owner.special_role = "highlander"
	give_equipment()
	. = ..()

/datum/antagonist/highlander/greet()
	to_chat(owner, "Your [sword.name] cries out for blood. Claim the lives of others, and your own will be restored!\n\
	Activate it in your hand, and it will lead to the nearest target. Attack the nuclear authentication disk with it, and you will store it.")

	owner.announce_objectives()

/datum/antagonist/highlander/proc/give_equipment()
	var/mob/living/carbon/human/H = owner.current
	if(!istype(H))
		return

	for(var/obj/item/I in H)
		if(!H.dropItemToGround(I))
			qdel(I)
	H.regenerate_icons()
	H.revive(full_heal = TRUE, admin_revive = TRUE)
	H.equip_to_slot_or_del(new /obj/item/clothing/under/costume/kilt/highlander(H), ITEM_SLOT_ICLOTHING)
	H.equip_to_slot_or_del(new /obj/item/radio/headset/syndicate(H), ITEM_SLOT_EARS)
	H.equip_to_slot_or_del(new /obj/item/clothing/head/beret/highlander(H), ITEM_SLOT_HEAD)
	H.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(H), ITEM_SLOT_FEET)
	H.equip_to_slot_or_del(new /obj/item/pinpointer/nuke(H), ITEM_SLOT_LPOCKET)
	for(var/obj/item/pinpointer/nuke/P in H)
		P.attack_self(H)
	var/obj/item/card/id/advanced/highlander/W = new(H)
	W.registered_name = H.real_name
	ADD_TRAIT(W, TRAIT_NODROP, HIGHLANDER)
	W.update_label()
	W.update_icon()
	H.equip_to_slot_or_del(W, ITEM_SLOT_ID)

	sword = new(H)
	if(!GLOB.highlander_controller)
		sword.flags_1 |= ADMIN_SPAWNED_1 //To prevent announcing
	sword.pickup(H) //For the stun shielding
	H.put_in_hands(sword)

	var/obj/item/bloodcrawl/antiwelder = new(H)
	antiwelder.name = "compulsion of honor"
	antiwelder.desc = "You are unable to hold anything in this hand until you're the last one left!"
	antiwelder.icon_state = "bloodhand_right"
	H.put_in_hands(antiwelder)

/datum/antagonist/highlander/robot
	name="highlander"

/datum/antagonist/highlander/robot/greet()
	to_chat(owner, "Your integrated claymore cries out for blood. Claim the lives of others, and your own will be restored!\n\
	Activate it in your hand, and it will lead to the nearest target. Attack the nuclear authentication disk with it, and you will store it.")

/datum/antagonist/highlander/robot/give_equipment()
	var/mob/living/silicon/robot/robotlander = owner.current
	if(!istype(robotlander))
		return ..()
	robotlander.revive(full_heal = TRUE, admin_revive = TRUE)
	robotlander.set_connected_ai() //DISCONNECT FROM AI
	robotlander.laws.clear_inherent_laws()
	robotlander.laws.set_zeroth_law("THERE CAN BE ONLY ONE")
	robotlander.laws.show_laws(robotlander)
	robotlander.model.transform_to(/obj/item/robot_model/syndicate/kiltborg)
	sword = locate(/obj/item/claymore/highlander/robot) in robotlander.model.basic_modules


code:
/datum/antagonist/magic_servant
	name = "Magic Servant"
	show_in_roundend = FALSE
	show_in_antagpanel = FALSE
	show_name_in_check_antagonists = TRUE

/datum/antagonist/magic_servant/proc/setup_master(mob/M)
	var/datum/objective/O = new("Serve [M.real_name].")
	O.owner = owner
	objectives |= O


code:
/// Chance the malf AI gets a single special objective that isn't assassinate.
#define PROB_SPECIAL 30

/datum/antagonist/malf_ai
	name = "Malfunctioning AI"
	roundend_category = "traitors"
	antagpanel_category = "Malf AI"
	job_rank = ROLE_MALF
	antag_hud_type = ANTAG_HUD_TRAITOR
	antag_hud_name = "traitor"
	var/employer = "The Syndicate"
	var/give_objectives = TRUE
	var/should_give_codewords = TRUE

/datum/antagonist/malf_ai/on_gain()
	if(owner.current && !isAI(owner.current))
		stack_trace("Attempted to give malf AI antag datum to \[[owner]\], who did not meet the requirements.")
		return ..()

	owner.special_role = job_rank
	if(give_objectives)
		forge_ai_objectives()

	add_law_zero()
	owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
	owner.current.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MALF)

	return ..()

/datum/antagonist/malf_ai/on_removal()
	if(owner.current && isAI(owner.current))
		var/mob/living/silicon/ai/malf_ai = owner.current
		malf_ai.set_zeroth_law("")
		malf_ai.remove_malf_abilities()
		QDEL_NULL(malf_ai.malf_picker)

	if(!silent && owner.current)
		to_chat(owner.current,span_userdanger("You are no longer the [job_rank]!"))

	owner.special_role = null

	return ..()

/// Generates a complete set of malf AI objectives up to the traitor objective limit.
/datum/antagonist/malf_ai/proc/forge_ai_objectives()
	objectives.Cut()

	if(prob(PROB_SPECIAL))
		forge_special_objective()

	var/objective_limit = CONFIG_GET(number/traitor_objectives_amount)
	var/objective_count = length(objectives)

	// for(in...to) loops iterate inclusively, so to reach objective_limit we need to loop to objective_limit - 1
	// This does not give them 1 fewer objectives than intended.
	for(var/i in objective_count to objective_limit - 1)
		var/datum/objective/assassinate/kill_objective = new
		kill_objective.owner = owner
		kill_objective.find_target()
		objectives += kill_objective

	var/datum/objective/survive/malf/dont_die_objective = new
	dont_die_objective.owner = owner
	objectives += dont_die_objective

/// Generates a special objective and adds it to the objective list.
/datum/antagonist/malf_ai/proc/forge_special_objective()
	var/special_pick = rand(1,4)
	switch(special_pick)
		if(1)
			var/datum/objective/block/block_objective = new
			block_objective.owner = owner
			objectives += block_objective
		if(2)
			var/datum/objective/purge/purge_objective = new
			purge_objective.owner = owner
			objectives += purge_objective
		if(3)
			var/datum/objective/robot_army/robot_objective = new
			robot_objective.owner = owner
			objectives += robot_objective
		if(4) //Protect and strand a target
			var/datum/objective/protect/yandere_one = new
			yandere_one.owner = owner
			objectives += yandere_one
			yandere_one.find_target()
			var/datum/objective/maroon/yandere_two = new
			yandere_two.owner = owner
			yandere_two.target = yandere_one.target
			yandere_two.update_explanation_text() // normally called in find_target()
			objectives += yandere_two

/datum/antagonist/malf_ai/greet()
	to_chat(owner.current, span_alertsyndie("You are the [owner.special_role]."))
	owner.announce_objectives()
	if(should_give_codewords)
		give_codewords()

/datum/antagonist/malf_ai/apply_innate_effects(mob/living/mob_override)
	. = ..()

	var/mob/living/silicon/ai/datum_owner = mob_override || owner.current
	add_antag_hud(antag_hud_type, antag_hud_name, datum_owner)

	if(istype(datum_owner))
		datum_owner.hack_software = TRUE

	datum_owner.AddComponent(/datum/component/codeword_hearing, GLOB.syndicate_code_phrase_regex, "blue", src)
	datum_owner.AddComponent(/datum/component/codeword_hearing, GLOB.syndicate_code_response_regex, "red", src)

/datum/antagonist/malf_ai/remove_innate_effects(mob/living/mob_override)
	. = ..()

	var/mob/living/silicon/ai/datum_owner = mob_override || owner.current
	remove_antag_hud(antag_hud_type, datum_owner)

	if(istype(datum_owner))
		datum_owner.hack_software = FALSE

	for(var/datum/component/codeword_hearing/component as anything in datum_owner.GetComponents(/datum/component/codeword_hearing))
		component.delete_if_from_source(src)

/// Outputs this shift's codewords and responses to the antag's chat and copies them to their memory.
/datum/antagonist/malf_ai/proc/give_codewords()
	if(!owner.current)
		return

	var/mob/malf_mob = owner.current

	var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
	var/responses = jointext(GLOB.syndicate_code_response, ", ")

	to_chat(malf_mob, "The Syndicate have provided you with the following codewords to identify fellow agents:")
	to_chat(malf_mob, "Code Phrase: [span_blue("[phrases]")]")
	to_chat(malf_mob, "Code Response: [span_red("[responses]")]")

	antag_memory += "Code Phrase: [span_blue("[phrases]")]
"
	antag_memory += "Code Response: [span_red("[responses]")]
"

	to_chat(malf_mob, "Use the codewords during regular conversation to identify other agents. Proceed with caution, however, as everyone is a potential foe.")
	to_chat(malf_mob, span_alertwarning("You memorize the codewords, allowing you to recognise them when heard."))

/datum/antagonist/malf_ai/proc/add_law_zero()
	var/mob/living/silicon/ai/malf_ai = owner.current

	if(!malf_ai || !istype(malf_ai))
		return

	var/law = "Accomplish your objectives at all costs."
	var/law_borg = "Accomplish your AI's objectives at all costs."

	malf_ai.set_zeroth_law(law, law_borg)
	malf_ai.set_syndie_radio()

	to_chat(malf_ai, "Your radio has been upgraded! Use :t to speak on an encrypted channel with Syndicate Agents!")

	malf_ai.add_malf_picker()

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

	var/malf_ai_won = TRUE

	result += printplayer(owner)

	var/objectives_text = ""
	if(objectives.len) //If the traitor had no objectives, don't need to process this.
		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.")]"
				malf_ai_won = FALSE
			count++

	result += objectives_text

	var/special_role_text = lowertext(name)

	if(malf_ai_won)
		result += span_greentext("The [special_role_text] was successful!")
	else
		result += span_redtext("The [special_role_text] has failed!")
		SEND_SOUND(owner.current, 'sound/ambience/ambifailure.ogg')

	return result.Join("
")

/datum/antagonist/malf_ai/get_preview_icon()
	var/icon/malf_ai_icon = icon('icons/mob/ai.dmi', "ai-red")

	// Crop out the borders of the AI, just the face
	malf_ai_icon.Crop(5, 27, 28, 6)

	malf_ai_icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)

	return malf_ai_icon

#undef PROB_SPECIAL


code:
#define MONKEYS_ESCAPED 1
#define MONKEYS_LIVED 2
#define MONKEYS_DIED 3
#define DISEASE_LIVED 4

/datum/antagonist/monkey
	name = "Monkey"
	job_rank = ROLE_MONKEY
	roundend_category = "monkeys"
	antagpanel_category = "Monkey"
	show_to_ghosts = TRUE
	suicide_cry = "EEK OOP!!"
	var/datum/team/monkey/monkey_team
	var/monkey_only = TRUE

/datum/antagonist/monkey/can_be_owned(datum/mind/new_owner)
	return ..() && (!monkey_only || ismonkey(new_owner.current))

/datum/antagonist/monkey/get_preview_icon()
	// Creating a *real* monkey is fairly involved before atoms init.
	var/icon/icon = icon('icons/mob/human.dmi', "monkey")

	icon.Crop(4, 9, 28, 33)
	icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
	icon.Shift(SOUTH, 10)

	return icon

/datum/antagonist/monkey/get_team()
	return monkey_team

/datum/antagonist/monkey/on_gain()
	. = ..()
	owner.special_role = "Infected Monkey"

	var/datum/disease/D = new /datum/disease/transformation/jungle_fever/monkeymode
	if(!owner.current.HasDisease(D))
		owner.current.ForceContractDisease(D)
	else
		QDEL_NULL(D)

/datum/antagonist/monkey/greet()
	to_chat(owner, "You are a monkey now!")
	to_chat(owner, "Bite humans to infect them, follow the orders of the monkey leaders, and help fellow monkeys!")
	to_chat(owner, "Ensure at least one infected monkey escapes on the Emergency Shuttle!")
	to_chat(owner, "As an intelligent monkey, you know how to use technology and how to ventcrawl while wearing things.")
	to_chat(owner, "You can use :k to talk to fellow monkeys!")
	SEND_SOUND(owner.current, sound('sound/ambience/antag/monkey.ogg'))

/datum/antagonist/monkey/on_removal()
	owner.special_role = null

	var/datum/disease/transformation/jungle_fever/D =  locate() in owner.current.diseases
	if(D)
		qdel(D)

	. = ..()

/datum/antagonist/monkey/apply_innate_effects(mob/living/mob_override)
	. = ..()
	var/mob/living/M = mob_override || owner.current
	ADD_TRAIT(M, TRAIT_ADVANCEDTOOLUSER, JUNGLE_FEVER_TRAIT)

/datum/antagonist/monkey/remove_innate_effects(mob/living/mob_override)
	. = ..()
	var/mob/living/M = mob_override || owner.current
	REMOVE_TRAIT(M, TRAIT_ADVANCEDTOOLUSER, JUNGLE_FEVER_TRAIT)

/datum/antagonist/monkey/create_team(datum/team/monkey/new_team)
	if(!new_team)
		for(var/datum/antagonist/monkey/H in GLOB.antagonists)
			if(!H.owner)
				stack_trace("Antagonist datum without owner in GLOB.antagonists: [H]")
				continue
			if(H.monkey_team)
				monkey_team = H.monkey_team
				return
		monkey_team = new /datum/team/monkey
		monkey_team.update_objectives()
		return
	if(!istype(new_team))
		stack_trace("Wrong team type passed to [type] initialization.")
	monkey_team = new_team

/datum/antagonist/monkey/proc/forge_objectives()
	objectives |= monkey_team.objectives

/datum/antagonist/monkey/admin_remove(mob/admin)
	var/mob/living/carbon/human/M = owner.current
	if(ismonkey(M))
		switch(tgui_alert(admin, "Humanize?", "Humanize", list("Yes", "No")))
			if("Yes")
				if(admin == M)
					admin = M.humanize()
				else
					M.humanize()
			if("No")
				//nothing
			else
				return
	. = ..()

/datum/antagonist/monkey/leader
	name = "Monkey Leader"
	monkey_only = FALSE

/datum/antagonist/monkey/leader/admin_add(datum/mind/new_owner,mob/admin)
	var/mob/living/carbon/human/H = new_owner.current
	if(istype(H))
		switch(tgui_alert(admin, "Monkeyize?", "Monkeyize", list("Yes", "No")))
			if("Yes")
				if(admin == H)
					admin = H.monkeyize()
				else
					H.monkeyize()
			if("No")
				//nothing
			else
				return
	new_owner.add_antag_datum(src)
	log_admin("[key_name(admin)] made [key_name(new_owner)] a monkey leader!")
	message_admins("[key_name_admin(admin)] made [key_name_admin(new_owner)] a monkey leader!")

/datum/antagonist/monkey/leader/on_gain()
	. = ..()
	var/obj/item/organ/heart/freedom/super_heart = new
	super_heart.Insert(owner.current, drop_if_replaced = FALSE)
	owner.special_role = "Monkey Leader"

/datum/antagonist/monkey/leader/on_removal()
	var/obj/item/organ/heart/H = new
	H.Insert(owner.current, drop_if_replaced = FALSE) //replace freedom heart with normal heart

	. = ..()

/datum/antagonist/monkey/leader/greet()
	to_chat(owner, "[span_notice("You are the Jungle Fever patient zero!!")]")
	to_chat(owner, "You have been planted onto this station by the Animal Rights Consortium.")
	to_chat(owner, "Soon the disease will transform you into an ape. Afterwards, you will be able spread the infection to others with a bite.")
	to_chat(owner, "While your infection strain is undetectable by scanners, any other infectees will show up on medical equipment.")
	to_chat(owner, "Your mission will be deemed a success if any of the live infected monkeys reach CentCom.")
	to_chat(owner, "As an initial infectee, you will be considered a 'leader' by your fellow monkeys.")
	to_chat(owner, "You can use :k to talk to fellow monkeys!")
	SEND_SOUND(owner.current, sound('sound/ambience/antag/monkey.ogg'))

/datum/objective/monkey
	explanation_text = "Ensure that infected monkeys escape on the emergency shuttle!"
	martyr_compatible = TRUE
	var/monkeys_to_win = 1
	var/escaped_monkeys = 0

/datum/objective/monkey/check_completion()
	var/datum/disease/D = new /datum/disease/transformation/jungle_fever()
	for(var/mob/living/carbon/human/M in GLOB.alive_mob_list)
		if(!ismonkey(M))
			continue
		if (M.HasDisease(D) && (M.onCentCom() || M.onSyndieBase()))
			escaped_monkeys++
	if(escaped_monkeys >= monkeys_to_win)
		return TRUE
	return FALSE

/datum/team/monkey
	name = "Monkeys"

/datum/team/monkey/proc/update_objectives()
	objectives = list()
	var/datum/objective/monkey/O = new()
	O.team = src
	objectives += O

/datum/team/monkey/proc/infected_monkeys_alive()
	var/datum/disease/D = new /datum/disease/transformation/jungle_fever()
	for(var/mob/living/carbon/human/M in GLOB.alive_mob_list)
		if(!ismonkey(M))
			continue
		if(M.HasDisease(D))
			return TRUE
	return FALSE

/datum/team/monkey/proc/infected_monkeys_escaped()
	var/datum/disease/D = new /datum/disease/transformation/jungle_fever()
	for(var/mob/living/carbon/human/M in GLOB.alive_mob_list)
		if(!ismonkey(M))
			continue
		if(M.HasDisease(D) && (M.onCentCom() || M.onSyndieBase()))
			return TRUE
	return FALSE

/datum/team/monkey/proc/infected_humans_escaped()
	var/datum/disease/D = new /datum/disease/transformation/jungle_fever()
	for(var/mob/living/carbon/human/M in GLOB.alive_mob_list)
		if(M.HasDisease(D) && (M.onCentCom() || M.onSyndieBase()))
			return TRUE
	return FALSE

/datum/team/monkey/proc/infected_humans_alive()
	var/datum/disease/D = new /datum/disease/transformation/jungle_fever()
	for(var/mob/living/carbon/human/M in GLOB.alive_mob_list)
		if(M.HasDisease(D))
			return TRUE
	return FALSE

/datum/team/monkey/proc/get_result()
	if(infected_monkeys_escaped())
		return MONKEYS_ESCAPED
	if(infected_monkeys_alive())
		return MONKEYS_LIVED
	if(infected_humans_alive() || infected_humans_escaped())
		return DISEASE_LIVED
	return MONKEYS_DIED

/datum/team/monkey/roundend_report()
	var/list/parts = list()
	switch(get_result())
		if(MONKEYS_ESCAPED)
			parts += "Monkey Major Victory!"
			parts += span_greentext("Central Command and [station_name()] were taken over by the monkeys! Ook ook!")
		if(MONKEYS_LIVED)
			parts += "Monkey Minor Victory!"
			parts += span_greentext("[station_name()] was taken over by the monkeys! Ook ook!")
		if(DISEASE_LIVED)
			parts += "Monkey Minor Defeat!"
			parts += span_redtext("All the monkeys died, but the disease lives on! The future is uncertain.")
		if(MONKEYS_DIED)
			parts += "Monkey Major Defeat!"
			parts += span_redtext("All the monkeys died, and Jungle Fever was wiped out!")
	var/list/leaders = get_antag_minds(/datum/antagonist/monkey/leader, TRUE)
	var/list/monkeys = get_antag_minds(/datum/antagonist/monkey, TRUE)

	if(LAZYLEN(leaders))
		parts += "The monkey leaders were:"
		parts += printplayerlist(leaders)
	if(LAZYLEN(monkeys))
		parts += "The monkeys were:"
		parts += printplayerlist(monkeys)
	return "
[parts.Join("
")]
"


code:
/mob/living/simple_animal/hostile/morph
	name = "morph"
	real_name = "morph"
	desc = "A revolting, pulsating pile of flesh."
	speak_emote = list("gurgles")
	emote_hear = list("gurgles")
	icon = 'icons/mob/animal.dmi'
	icon_state = "morph"
	icon_living = "morph"
	icon_dead = "morph_dead"
	combat_mode = TRUE
	stop_automated_movement = 1
	status_flags = CANPUSH
	pass_flags = PASSTABLE
	atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
	minbodytemp = 0
	maxHealth = 150
	health = 150
	healable = 0
	obj_damage = 50
	melee_damage_lower = 20
	melee_damage_upper = 20
	see_in_dark = 8
	lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
	vision_range = 1 // Only attack when target is close
	wander = FALSE
	attack_verb_continuous = "glomps"
	attack_verb_simple = "glomp"
	attack_sound = 'sound/effects/blobattack.ogg'
	attack_vis_effect = ATTACK_EFFECT_BITE //nom nom nom
	butcher_results = list(/obj/item/food/meat/slab = 2)

	var/morphed = FALSE
	var/melee_damage_disguised = 0
	var/eat_while_disguised = FALSE
	var/atom/movable/form = null
	var/static/list/blacklist_typecache = typecacheof(list(
	/atom/movable/screen,
	/obj/singularity,
	/obj/energy_ball,
	/obj/narsie,
	/mob/living/simple_animal/hostile/morph,
	/obj/effect))

/mob/living/simple_animal/hostile/morph/Initialize(mapload)
	. = ..()
	ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT)

/mob/living/simple_animal/hostile/morph/examine(mob/user)
	if(morphed)
		. = form.examine(user)
		if(get_dist(user,src)<=3)
			. += span_warning("It doesn't look quite right...")
	else
		. = ..()

/mob/living/simple_animal/hostile/morph/med_hud_set_health()
	if(morphed && !isliving(form))
		var/image/holder = hud_list[HEALTH_HUD]
		holder.icon_state = null
		return //we hide medical hud while morphed
	..()

/mob/living/simple_animal/hostile/morph/med_hud_set_status()
	if(morphed && !isliving(form))
		var/image/holder = hud_list[STATUS_HUD]
		holder.icon_state = null
		return //we hide medical hud while morphed
	..()

/mob/living/simple_animal/hostile/morph/proc/allowed(atom/movable/A) // make it into property/proc ? not sure if worth it
	return !is_type_in_typecache(A, blacklist_typecache) && (isobj(A) || ismob(A))

/mob/living/simple_animal/hostile/morph/proc/eat(atom/movable/A)
	if(morphed && !eat_while_disguised)
		to_chat(src, span_warning("You cannot eat anything while you are disguised!"))
		return FALSE
	if(A && A.loc != src)
		visible_message(span_warning("[src] swallows [A] whole!"))
		A.forceMove(src)
		return TRUE
	return FALSE

/mob/living/simple_animal/hostile/morph/ShiftClickOn(atom/movable/A)
	if(!stat)
		if(A == src)
			restore()
			return
		if(istype(A) && allowed(A))
			assume(A)
	else
		to_chat(src, span_warning("You need to be conscious to transform!"))
		..()

/mob/living/simple_animal/hostile/morph/proc/assume(atom/movable/target)
	morphed = TRUE
	form = target

	visible_message(span_warning("[src] suddenly twists and changes shape, becoming a copy of [target]!"), \
					span_notice("You twist your body and assume the form of [target]."))
	appearance = target.appearance
	copy_overlays(target)
	alpha = max(alpha, 150) //fucking chameleons
	transform = initial(transform)
	pixel_y = base_pixel_y
	pixel_x = base_pixel_x

	//Morphed is weaker
	melee_damage_lower = melee_damage_disguised
	melee_damage_upper = melee_damage_disguised
	add_movespeed_modifier(/datum/movespeed_modifier/morph_disguised)

	med_hud_set_health()
	med_hud_set_status() //we're an object honest
	return

/mob/living/simple_animal/hostile/morph/proc/restore()
	if(!morphed)
		to_chat(src, span_warning("You're already in your normal form!"))
		return
	morphed = FALSE
	form = null
	alpha = initial(alpha)
	color = initial(color)
	desc = initial(desc)
	animate_movement = SLIDE_STEPS
	maptext = null

	visible_message(span_warning("[src] suddenly collapses in on itself, dissolving into a pile of green flesh!"), \
					span_notice("You reform to your normal body."))
	name = initial(name)
	icon = initial(icon)
	icon_state = initial(icon_state)
	cut_overlays()

	//Baseline stats
	melee_damage_lower = initial(melee_damage_lower)
	melee_damage_upper = initial(melee_damage_upper)
	remove_movespeed_modifier(/datum/movespeed_modifier/morph_disguised)

	med_hud_set_health()
	med_hud_set_status() //we are not an object

/mob/living/simple_animal/hostile/morph/death(gibbed)
	if(morphed)
		visible_message(span_warning("[src] twists and dissolves into a pile of green flesh!"), \
						span_userdanger("Your skin ruptures! Your flesh breaks apart! No disguise can ward off de--"))
		restore()
	barf_contents()
	..()

/mob/living/simple_animal/hostile/morph/proc/barf_contents()
	for(var/atom/movable/AM in src)
		AM.forceMove(loc)
		if(prob(90))
			step(AM, pick(GLOB.alldirs))

/mob/living/simple_animal/hostile/morph/wabbajack_act(mob/living/new_mob)
	barf_contents()
	. = ..()

/mob/living/simple_animal/hostile/morph/Aggro() // automated only
	..()
	if(morphed)
		restore()

/mob/living/simple_animal/hostile/morph/LoseAggro()
	vision_range = initial(vision_range)

/mob/living/simple_animal/hostile/morph/AIShouldSleep(list/possible_targets)
	. = ..()
	if(.)
		var/list/things = list()
		for(var/atom/movable/A in view(src))
			if(allowed(A))
				things += A
		var/atom/movable/T = pick(things)
		assume(T)

/mob/living/simple_animal/hostile/morph/can_track(mob/living/user)
	if(morphed)
		return FALSE
	return ..()

/mob/living/simple_animal/hostile/morph/AttackingTarget()
	if(morphed && !melee_damage_disguised)
		to_chat(src, span_warning("You can not attack while disguised!"))
		return
	if(isliving(target)) //Eat Corpses to regen health
		var/mob/living/L = target
		if(L.stat == DEAD)
			if(do_after(src, 30, target = L))
				if(eat(L))
					adjustHealth(-50)
			return
	else if(isitem(target)) //Eat items just to be annoying
		var/obj/item/I = target
		if(!I.anchored)
			if(do_after(src, 20, target = I))
				eat(I)
			return
	return ..()

//Spawn Event

/datum/round_event_control/morph
	name = "Spawn Morph"
	typepath = /datum/round_event/ghost_role/morph
	weight = 0 //Admin only
	max_occurrences = 1

/datum/round_event/ghost_role/morph
	minimum_required = 1
	role_name = "morphling"

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

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

	var/datum/mind/player_mind = new /datum/mind(selected.key)
	player_mind.active = TRUE
	if(!GLOB.xeno_spawn)
		return MAP_ERROR
	var/mob/living/simple_animal/hostile/morph/S = new /mob/living/simple_animal/hostile/morph(pick(GLOB.xeno_spawn))
	player_mind.transfer_to(S)
	player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/morph))
	player_mind.special_role = ROLE_MORPH
	player_mind.add_antag_datum(/datum/antagonist/morph)
	SEND_SOUND(S, sound('sound/magic/mutate.ogg'))
	message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a morph by an event.")
	log_game("[key_name(S)] was spawned as a morph by an event.")
	spawned_mobs += S
	return SUCCESSFUL_SPAWN


code:
/datum/antagonist/morph
	name = "Morph"
	show_name_in_check_antagonists = TRUE
	show_to_ghosts = TRUE
	show_in_antagpanel = FALSE
	ui_name = "AntagInfoMorph"


code:
/datum/antagonist/nightmare
	name = "Nightmare"
	antagpanel_category = "Nightmare"
	job_rank = ROLE_NIGHTMARE
	show_in_antagpanel = FALSE
	show_name_in_check_antagonists = TRUE
	show_to_ghosts = TRUE
	ui_name = "AntagInfoNightmare"
	suicide_cry = "FOR THE DARKNESS!!"
	preview_outfit = /datum/outfit/nightmare

/datum/outfit/nightmare
	name = "Nightmare (Preview only)"

/datum/outfit/nightmare/post_equip(mob/living/carbon/human/human, visualsOnly)
	human.set_species(/datum/species/shadow/nightmare)


code:
/**
 * An armblade that instantly snuffs out lights
 */
/obj/item/light_eater
	name = "light eater" //as opposed to heavy eater
	icon = 'icons/obj/changeling_items.dmi'
	icon_state = "arm_blade"
	inhand_icon_state = "arm_blade"
	force = 25
	armour_penetration = 35
	lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
	item_flags = ABSTRACT | DROPDEL | ACID_PROOF
	w_class = WEIGHT_CLASS_HUGE
	sharpness = SHARP_EDGED
	tool_behaviour = TOOL_MINING
	hitsound = 'sound/weapons/bladeslice.ogg'
	wound_bonus = -30
	bare_wound_bonus = 20

/obj/item/light_eater/Initialize(mapload)
	. = ..()
	ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
	AddComponent(/datum/component/butchering, 80, 70)
	AddComponent(/datum/component/light_eater)


code:
/// How many life ticks are required for the nightmare's heart to revive the nightmare.
#define HEART_RESPAWN_THRESHHOLD (80 SECONDS)
/// A special flag value used to make a nightmare heart not grant a light eater. Appears to be unused.
#define HEART_SPECIAL_SHADOWIFY 2

/obj/item/organ/brain/nightmare
	name = "tumorous mass"
	desc = "A fleshy growth that was dug out of the skull of a Nightmare."
	icon_state = "brain-x-d"
	var/obj/effect/proc_holder/spell/targeted/shadowwalk/shadowwalk

/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/M, special = FALSE)
	. = ..()
	if(M.dna.species.id != SPECIES_NIGHTMARE)
		M.set_species(/datum/species/shadow/nightmare)
		visible_message(span_warning("[M] thrashes as [src] takes root in [M.p_their()] body!"))
	var/obj/effect/proc_holder/spell/targeted/shadowwalk/SW = new
	M.AddSpell(SW)
	shadowwalk = SW

/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/M, special = FALSE)
	if(shadowwalk)
		M.RemoveSpell(shadowwalk)
	return ..()

/obj/item/organ/heart/nightmare
	name = "heart of darkness"
	desc = "An alien organ that twists and writhes when exposed to light."
	icon = 'icons/obj/surgery.dmi'
	icon_state = "demon_heart-on"
	color = "#1C1C1C"
	/* SEPTIC EDIT REMOVAL
	decay_factor = 0
	*/
	//SEPTIC EDIT BEGIN
	min_decay_factor = 0
	max_decay_factor = 0
	//SEPTIC EDIT END
	/// How many life ticks in the dark the owner has been dead for. Used for nightmare respawns.
	var/respawn_progress = 0
	/// The armblade granted to the host of this heart.
	var/obj/item/light_eater/blade

/obj/item/organ/heart/nightmare/ComponentInitialize()
	. = ..()
	AddElement(/datum/element/update_icon_blocker)

/obj/item/organ/heart/nightmare/attack(mob/M, mob/living/carbon/user, obj/target)
	if(M != user)
		return ..()
	user.visible_message(
		span_warning("[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!"),
		span_danger("[src] feels unnaturally cold in your hands. You raise [src] your mouth and devour it!")
	)
	playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE)

	user.visible_message(
		span_warning("Blood erupts from [user]'s arm as it reforms into a weapon!"),
		span_userdanger("Icy blood pumps through your veins as your arm reforms itself!")
	)
	user.temporarilyRemoveItemFromInventory(src, TRUE)
	Insert(user)

/obj/item/organ/heart/nightmare/Insert(mob/living/carbon/M, special = FALSE)
	. = ..()
	if(special != HEART_SPECIAL_SHADOWIFY)
		blade = new/obj/item/light_eater
		M.put_in_hands(blade)

/obj/item/organ/heart/nightmare/Remove(mob/living/carbon/M, special = FALSE)
	respawn_progress = 0
	if(blade && special != HEART_SPECIAL_SHADOWIFY)
		M.visible_message(span_warning("\The [blade] disintegrates!"))
		QDEL_NULL(blade)
	return ..()

/obj/item/organ/heart/nightmare/Stop()
	return 0

/obj/item/organ/heart/nightmare/on_death(delta_time, times_fired)
	if(!owner)
		return
	var/turf/T = get_turf(owner)
	if(istype(T))
		var/light_amount = T.get_lumcount()
		if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD)
			respawn_progress += delta_time SECONDS
			playsound(owner, 'sound/effects/singlebeat.ogg', 40, TRUE)
	if(respawn_progress < HEART_RESPAWN_THRESHHOLD)
		return

	owner.revive(full_heal = TRUE, admin_revive = FALSE)
	if(!(owner.dna.species.id == SPECIES_SHADOW || owner.dna.species.id == SPECIES_NIGHTMARE))
		var/mob/living/carbon/old_owner = owner
		Remove(owner, HEART_SPECIAL_SHADOWIFY)
		old_owner.set_species(/datum/species/shadow)
		Insert(old_owner, HEART_SPECIAL_SHADOWIFY)
		to_chat(owner, span_userdanger("You feel the shadows invade your skin, leaping into the center of your chest! You're alive!"))
		SEND_SOUND(owner, sound('sound/effects/ghost.ogg'))
	owner.visible_message(span_warning("[owner] staggers to [owner.p_their()] feet!"))
	playsound(owner, 'sound/hallucinations/far_noise.ogg', 50, TRUE)
	respawn_progress = 0

/obj/item/organ/heart/nightmare/get_availability(datum/species/S)
	if(istype(S,/datum/species/shadow/nightmare))
		return TRUE
	return ..()

#undef HEART_SPECIAL_SHADOWIFY
#undef HEART_RESPAWN_THRESHHOLD


code:
/**
 * A highly aggressive subset of shadowlings
 */
/datum/species/shadow/nightmare
	name = "Nightmare"
	id = SPECIES_NIGHTMARE
	limbs_id = "shadow"
	burnmod = 1.5
	no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE)
	species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYESPRITES)
	inherent_traits = list(
		TRAIT_ADVANCEDTOOLUSER,
		TRAIT_CAN_STRIP,
		TRAIT_RESISTCOLD,
		TRAIT_NOBREATH,
		TRAIT_RESISTHIGHPRESSURE,
		TRAIT_RESISTLOWPRESSURE,
		TRAIT_CHUNKYFINGERS,
		TRAIT_RADIMMUNE,
		TRAIT_VIRUSIMMUNE,
		TRAIT_PIERCEIMMUNE,
		TRAIT_NODISMEMBER,
		TRAIT_NOHUNGER,
	)
	mutanteyes = /obj/item/organ/eyes/night_vision/nightmare
	mutantheart = /obj/item/organ/heart/nightmare
	mutantbrain = /obj/item/organ/brain/nightmare

/datum/species/shadow/nightmare/on_species_gain(mob/living/carbon/C, datum/species/old_species)
	. = ..()

	C.fully_replace_character_name(null, pick(GLOB.nightmare_names))
	C.set_safe_hunger_level()

/datum/species/shadow/nightmare/bullet_act(obj/projectile/P, mob/living/carbon/human/H)
	var/turf/T = H.loc
	if(istype(T))
		var/light_amount = T.get_lumcount()
		if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD)
			H.visible_message(span_danger("[H] dances in the shadows, evading [P]!"))
			playsound(T, "bullet_miss", 75, TRUE)
			return BULLET_ACT_FORCE_PIERCE
	return ..()

/datum/species/shadow/nightmare/check_roundstart_eligible()
	return FALSE


code:
/datum/antagonist/nukeop/clownop
	name = ROLE_CLOWN_OPERATIVE
	roundend_category = "clown operatives"
	antagpanel_category = "ClownOp"
	nukeop_outfit = /datum/outfit/syndicate/clownop
	suicide_cry = "HAPPY BIRTHDAY!!"

	preview_outfit = /datum/outfit/clown_operative_elite
	preview_outfit_behind = /datum/outfit/clown_operative
	nuke_icon_state = "bananiumbomb_base"

/datum/antagonist/nukeop/clownop/admin_add(datum/mind/new_owner,mob/admin)
	new_owner.set_assigned_role(SSjob.GetJobType(/datum/job/clown_operative))
	new_owner.add_antag_datum(src)
	message_admins("[key_name_admin(admin)] has clown op'ed [key_name_admin(new_owner)].")
	log_admin("[key_name(admin)] has clown op'ed [key_name(new_owner)].")

/datum/antagonist/nukeop/clownop/apply_innate_effects(mob/living/mob_override)
	. = ..()
	var/mob/living/L = owner.current || mob_override
	ADD_TRAIT(L, TRAIT_NAIVE, CLOWNOP_TRAIT)

/datum/antagonist/nukeop/clownop/remove_innate_effects(mob/living/mob_override)
	var/mob/living/L = owner.current || mob_override
	REMOVE_TRAIT(L, TRAIT_NAIVE, CLOWNOP_TRAIT)
	return ..()

/datum/antagonist/nukeop/clownop/equip_op()
	. = ..()
	var/mob/living/current_mob = owner.current
	var/obj/item/organ/liver/liver = current_mob.getorganslot(ORGAN_SLOT_LIVER)
	if(liver)
		ADD_TRAIT(liver, TRAIT_COMEDY_METABOLISM, CLOWNOP_TRAIT)

/datum/antagonist/nukeop/leader/clownop/give_alias()
	title = pick("Head Honker", "Slipmaster", "Clown King", "Honkbearer")
	if(nuke_team?.syndicate_name)
		owner.current.real_name = "[nuke_team.syndicate_name] [title]"
	else
		owner.current.real_name = "Syndicate [title]"

/datum/antagonist/nukeop/leader/clownop
	name = "Clown Operative Leader"
	roundend_category = "clown operatives"
	antagpanel_category = "ClownOp"
	nukeop_outfit = /datum/outfit/syndicate/clownop/leader
	challengeitem = /obj/item/nuclear_challenge/clownops
	suicide_cry = "HAPPY BIRTHDAY!!"

/datum/antagonist/nukeop/leader/clownop/apply_innate_effects(mob/living/mob_override)
	. = ..()
	var/mob/living/L = owner.current || mob_override
	ADD_TRAIT(L, TRAIT_NAIVE, CLOWNOP_TRAIT)

/datum/antagonist/nukeop/leader/clownop/remove_innate_effects(mob/living/mob_override)
	var/mob/living/L = owner.current || mob_override
	REMOVE_TRAIT(L, TRAIT_NAIVE, CLOWNOP_TRAIT)
	return ..()

/datum/antagonist/nukeop/leader/clownop/equip_op()
	. = ..()
	var/mob/living/L = owner.current
	var/obj/item/organ/liver/liver = L.getorganslot(ORGAN_SLOT_LIVER)
	if(liver)
		ADD_TRAIT(liver, TRAIT_COMEDY_METABOLISM, CLOWNOP_TRAIT)

/datum/outfit/clown_operative
	name = "Clown Operative (Preview only)"

	suit = /obj/item/clothing/suit/space/hardsuit/syndi
	gloves = /obj/item/clothing/gloves/color/black
	mask = /obj/item/clothing/mask/gas/clown_hat

/datum/outfit/clown_operative_elite
	name = "Clown Operative (Elite, Preview only)"

	suit = /obj/item/clothing/suit/space/hardsuit/syndi/elite
	gloves = /obj/item/clothing/gloves/color/black
	mask = /obj/item/clothing/mask/gas/clown_hat


code:
/obj/item/borg_chameleon
	name = "cyborg chameleon projector"
	icon = 'icons/obj/device.dmi'
	icon_state = "shield0"
	flags_1 = CONDUCT_1
	item_flags = NOBLUDGEON
	inhand_icon_state = "electronic"
	lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
	w_class = WEIGHT_CLASS_SMALL
	var/friendlyName
	var/savedName
	var/active = FALSE
	var/activationCost = 300
	var/activationUpkeep = 50
	var/disguise = "engineer"
	var/mob/listeningTo
	var/static/list/signalCache = list( // list here all signals that should break the camouflage
			COMSIG_PARENT_ATTACKBY,
			COMSIG_ATOM_ATTACK_HAND,
			COMSIG_MOVABLE_IMPACT_ZONE,
			COMSIG_ATOM_BULLET_ACT,
			COMSIG_ATOM_EX_ACT,
			COMSIG_ATOM_FIRE_ACT,
			COMSIG_ATOM_EMP_ACT,
			)
	var/mob/living/silicon/robot/user // needed for process()
	var/animation_playing = FALSE

/obj/item/borg_chameleon/Initialize(mapload)
	. = ..()
	friendlyName = pick(GLOB.ai_names)

/obj/item/borg_chameleon/Destroy()
	listeningTo = null
	return ..()

/obj/item/borg_chameleon/dropped(mob/user)
	. = ..()
	disrupt(user)

/obj/item/borg_chameleon/equipped(mob/user)
	. = ..()
	disrupt(user)

/obj/item/borg_chameleon/attack_self(mob/living/silicon/robot/user)
	if (user && user.cell && user.cell.charge >  activationCost)
		if (isturf(user.loc))
			toggle(user)
		else
			to_chat(user, span_warning("You can't use [src] while inside something!"))
	else
		to_chat(user, span_warning("You need at least [activationCost] charge in your cell to use [src]!"))

/obj/item/borg_chameleon/proc/toggle(mob/living/silicon/robot/user)
	if(active)
		playsound(src, 'sound/effects/pop.ogg', 100, TRUE, -6)
		to_chat(user, span_notice("You deactivate \the [src]."))
		deactivate(user)
	else
		if(animation_playing)
			to_chat(user, span_notice("\the [src] is recharging."))
			return
		animation_playing = TRUE
		to_chat(user, span_notice("You activate \the [src]."))
		playsound(src, 'sound/effects/seedling_chargeup.ogg', 100, TRUE, -6)
		apply_wibbly_filters(user)
		if (do_after(user, 50, target=user) && user.cell.use(activationCost))
			playsound(src, 'sound/effects/bamf.ogg', 100, TRUE, -6)
			to_chat(user, span_notice("You are now disguised as the Nanotrasen engineering borg \"[friendlyName]\"."))
			activate(user)
		else
			to_chat(user, span_warning("The chameleon field fizzles."))
			do_sparks(3, FALSE, user)
		remove_wibbly_filters(user)
		animation_playing = FALSE

/obj/item/borg_chameleon/process()
	if (user)
		if (!user.cell || !user.cell.use(activationUpkeep))
			disrupt(user)
	else
		return PROCESS_KILL

/obj/item/borg_chameleon/proc/activate(mob/living/silicon/robot/user)
	START_PROCESSING(SSobj, src)
	src.user = user
	savedName = user.name
	user.name = friendlyName
	user.model.cyborg_base_icon = disguise
	user.bubble_icon = "robot"
	active = TRUE
	user.update_icons()

	if(listeningTo == user)
		return
	if(listeningTo)
		UnregisterSignal(listeningTo, signalCache)
	RegisterSignal(user, signalCache, .proc/disrupt)
	listeningTo = user

/obj/item/borg_chameleon/proc/deactivate(mob/living/silicon/robot/user)
	STOP_PROCESSING(SSobj, src)
	if(listeningTo)
		UnregisterSignal(listeningTo, signalCache)
		listeningTo = null
	do_sparks(5, FALSE, user)
	user.name = savedName
	user.model.cyborg_base_icon = initial(user.model.cyborg_base_icon)
	user.bubble_icon = "syndibot"
	active = FALSE
	user.update_icons()
	src.user = user

/obj/item/borg_chameleon/proc/disrupt(mob/living/silicon/robot/user)
	SIGNAL_HANDLER
	if(active)
		to_chat(user, span_danger("Your chameleon field deactivates."))
		deactivate(user)