diff --git a/.github/workflows/merge_upstream_master.yml b/.github/workflows/merge_upstream_master.yml
new file mode 100644
index 000000000000..4d010e90281c
--- /dev/null
+++ b/.github/workflows/merge_upstream_master.yml
@@ -0,0 +1,44 @@
+name: Merge Upstream Master
+on:
+  issue_comment:
+    types: [created]
+
+jobs:
+  merge-upstream:
+    if: ${{ github.event.issue.pull_request && github.event.comment.body == '!merge_upstream' }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: PR Data
+        run: |
+          curl -H "Authorization: token ${{ github.token }}" ${{ github.event.issue.pull_request.url }} > pr.json
+          echo "PR_REPO=`jq -r '.head.repo.full_name' < pr.json`" >> $GITHUB_ENV
+          echo "PR_BRANCH=`jq -r '.head.ref' < pr.json`" >> $GITHUB_ENV
+
+      - uses: actions/checkout@v4
+        with:
+          repository: ${{ env.PR_REPO }}
+          ref: ${{ env.PR_BRANCH }}
+          fetch-depth: 0
+
+      - uses: actions/setup-python@v5
+        with:
+          python-version: '3.11'
+
+      - name: Perform Merge
+        run: |
+          chmod +x tools/bootstrap/python
+          bash tools/hooks/install.sh
+          bash tgui/bin/tgui --install-git-hooks
+          chmod +x tools/hooks/*.merge tgui/bin/tgui
+          git config user.name github-actions
+          git config user.email github-actions@github.com
+          git remote add upstream "https://github.com/${{ github.repository }}.git"
+          git fetch upstream master
+          git merge upstream/master && git push origin
+
+      - name: Notify Failure
+        if: failure()
+        run: |
+          curl -s -H "Authorization: token ${{ github.token }}" \
+            -X POST -d '{"body": "Merging upstream failed:\nhttps://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' \
+            "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments"
diff --git a/code/__DEFINES/cult_defines.dm b/code/__DEFINES/cult_defines.dm
index 947a067c60f3..e927982e6d17 100644
--- a/code/__DEFINES/cult_defines.dm
+++ b/code/__DEFINES/cult_defines.dm
@@ -10,7 +10,7 @@
 #define RUNE_COLOR_EMP "#4D94FF"
 #define RUNE_COLOR_SUMMON "#00FF00"
 
-#define is_sacrifice_target(A) SSticker.mode?.cult_objs.is_sac_target(A)
+#define IS_SACRIFICE_TARGET(A) SSticker?.mode?.cult_team?.is_sac_target(A)
 
 // Blood magic
 /// Maximum number of spells with an empowering rune
@@ -42,9 +42,6 @@
 #define DEFAULT_TOOLTIP "6:-29,5:-2"
 
 // Text
-#define CULT_GREETING "<span class='cultlarge'>You catch a glimpse of the Realm of [SSticker.cultdat.entity_name], [SSticker.cultdat.entity_title3]. \
-						You now see how flimsy the world is, you see that it should be open to the knowledge of [SSticker.cultdat.entity_name].</span>"
-
 #define CULT_CURSES list("A fuel technician just slit his own throat and begged for death.",                                           \
 			"The shuttle's navigation programming was replaced by a file containing two words, IT COMES.",                             \
 			"The shuttle's custodian tore out his guts and began painting strange shapes on the floor.",                               \
@@ -66,3 +63,9 @@
 #define NARSIE_NEEDS_SUMMONING 2
 #define NARSIE_HAS_RISEN 3
 #define NARSIE_HAS_FALLEN -1
+
+/// Safely accesses SSticker.cult_data, returns the default if cult data is not set up yet. Allows for both variable and proc call access.
+#define GET_CULT_DATA(var_or_proc, default) (SSticker.cult_data ? SSticker.cult_data.var_or_proc : default)
+
+/// Checks that the given element is living an has a cult antag datum
+#define IS_CULTIST(mob) (isliving(mob) && mob?:mind?:has_antag_datum(/datum/antagonist/cultist)) // for someone TODO, move all antag checks over to TG's `IS_TRAITOR` defines. Also remove `isliving()` from this call someday
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 4628c23ba2c3..142b1be96b8e 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -952,7 +952,7 @@
 #define COMSIG_AIRLOCK_CLOSE "airlock_close"
 
 // /datum/objective signals
-///from datum/objective/proc/find_target()
+///from datum/objective/proc/find_target(list/target_blacklist)
 #define COMSIG_OBJECTIVE_TARGET_FOUND "objective_target_found"
 ///from datum/objective/is_invalid_target()
 #define COMSIG_OBJECTIVE_CHECK_VALID_TARGET "objective_check_valid_target"
diff --git a/code/__DEFINES/gamemode.dm b/code/__DEFINES/gamemode.dm
index f35c024881b1..f0ec9ff81e3d 100644
--- a/code/__DEFINES/gamemode.dm
+++ b/code/__DEFINES/gamemode.dm
@@ -1,15 +1,17 @@
 //objective defines
-#define TARGET_INVALID_IS_OWNER		1
-#define TARGET_INVALID_NOT_HUMAN	2
-#define TARGET_INVALID_DEAD			3
-#define TARGET_INVALID_NOCKEY		4
-#define TARGET_INVALID_UNREACHABLE	5
-#define TARGET_INVALID_GOLEM		6
-#define TARGET_INVALID_EVENT		7
-#define TARGET_INVALID_IS_TARGET	8
-#define TARGET_INVALID_BLACKLISTED	9
-#define TARGET_INVALID_CHANGELING	10
-#define TARGET_INVALID_NOTHEAD		11
+#define TARGET_INVALID_IS_OWNER			1
+#define TARGET_INVALID_NOT_HUMAN		2
+#define TARGET_INVALID_DEAD				3
+#define TARGET_INVALID_NOCKEY			4
+#define TARGET_INVALID_UNREACHABLE		5
+#define TARGET_INVALID_GOLEM			6
+#define TARGET_INVALID_EVENT			7
+#define TARGET_INVALID_IS_TARGET		8
+#define TARGET_INVALID_BLACKLISTED		9
+#define TARGET_INVALID_CHANGELING		10
+#define TARGET_INVALID_NOTHEAD			11
+#define TARGET_INVALID_CULTIST			12
+#define TARGET_INVALID_CULT_CONVERTABLE	13
 
 //gamemode istype helpers
 #define GAMEMODE_IS_CULT		(SSticker && istype(SSticker.mode, /datum/game_mode/cult))
diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm
index cf7079c33098..1cda0f2649e0 100644
--- a/code/__DEFINES/misc_defines.dm
+++ b/code/__DEFINES/misc_defines.dm
@@ -690,3 +690,7 @@ do { \
 
 #define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point_precise/vector(ATOM, null, null, null, null, ANGLE, SPEED))
 #define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point_precise/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT))
+
+#define TEAM_ADMIN_ADD_OBJ_SUCCESS				(1<<0)
+#define TEAM_ADMIN_ADD_OBJ_CANCEL_LOG 			(1<<1)
+#define TEAM_ADMIN_ADD_OBJ_PURPOSEFUL_CANCEL 	(1<<2)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 27b177003bf0..24fe4bab1ea3 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1386,6 +1386,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
 
 /mob/dview/New() //For whatever reason, if this isn't called, then BYOND will throw a type mismatch runtime when attempting to add this to the mobs list. -Fox
 	SHOULD_CALL_PARENT(FALSE)
+	return
 
 /mob/dview/Destroy()
 	SHOULD_CALL_PARENT(FALSE)
diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm
index bc8a6879ecc5..5e44bf77cab8 100644
--- a/code/controllers/controller.dm
+++ b/code/controllers/controller.dm
@@ -2,17 +2,22 @@
 	var/name
 
 /datum/controller/proc/Initialize()
+	return
 
 //cleanup actions
 /datum/controller/proc/Shutdown()
+	return
 
 //when we enter dmm_suite.load_map
 /datum/controller/proc/StartLoadingMap()
+	return
 
 //when we exit dmm_suite.load_map
 /datum/controller/proc/StopLoadingMap()
+	return
 
 /datum/controller/proc/Recover()
+	return
 
 /datum/controller/proc/stat_entry(msg)
 	SHOULD_CALL_PARENT(TRUE)
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 3c1ba0b0c5dc..e69962965c5e 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -323,6 +323,7 @@
 //usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
 //should attempt to salvage what it can from the old instance of subsystem
 /datum/controller/subsystem/Recover()
+	return
 
 /datum/controller/subsystem/vv_edit_var(var_name, var_value)
 	switch(var_name)
diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm
index 03513434cdb6..380109eef137 100644
--- a/code/controllers/subsystem/SSticker.dm
+++ b/code/controllers/subsystem/SSticker.dm
@@ -37,8 +37,8 @@ SUBSYSTEM_DEF(ticker)
 	var/Bible_name
 	/// Name of the bible deity
 	var/Bible_deity_name
-	/// Cult data. Here instead of cult for adminbus purposes
-	var/datum/cult_info/cultdat = null
+	/// Cult static info, used for things like sprites. Someone should refactor the sprites out of it someday and just use SEPERATE ICONS DEPNDING ON THE TYPE OF CULT... like a sane person
+	var/datum/cult_info/cult_data
 	/// If set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders
 	var/random_players = FALSE
 	/// Did we broadcast the tip of the round yet?
@@ -159,7 +159,8 @@ SUBSYSTEM_DEF(ticker)
 		reboot_helper("Round ended.", "proper completion")
 
 /datum/controller/subsystem/ticker/proc/setup()
-	cultdat = setupcult()
+	var/random_cult = pick(typesof(/datum/cult_info))
+	cult_data = new random_cult()
 	score = new()
 
 	// Create and announce mode
diff --git a/code/datums/ai_laws_datums.dm b/code/datums/ai_laws_datums.dm
index 3eb3a4fd5add..c844b72657dd 100644
--- a/code/datums/ai_laws_datums.dm
+++ b/code/datums/ai_laws_datums.dm
@@ -182,6 +182,7 @@
 		law.delete_law(src)
 
 /datum/ai_law/proc/delete_law(datum/ai_laws/laws)
+	return
 
 /datum/ai_law/zero/delete_law(datum/ai_laws/laws)
 	laws.clear_zeroth_laws()
@@ -245,6 +246,7 @@
 	return law.get_state_law(src)
 
 /datum/ai_law/proc/get_state_law(datum/ai_laws/laws)
+	return
 
 /datum/ai_law/zero/get_state_law(datum/ai_laws/laws)
 	if(src == laws.zeroth_law)
@@ -271,7 +273,8 @@
 /datum/ai_laws/proc/set_state_law(datum/ai_law/law, state)
 	law.set_state_law(src, state)
 
-/datum/ai_law/proc/set_state_law(datum/ai_law/law, state)
+/datum/ai_law/proc/set_state_law(datum/ai_laws/laws, state)
+	return
 
 /datum/ai_law/zero/set_state_law(datum/ai_laws/laws, state)
 	if(src == laws.zeroth_law)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index cff61ceed725..1a763a4e6690 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -56,6 +56,8 @@
 	var/miming = 0 // Mime's vow of silence
 	/// A list of all the antagonist datums that the player is (does not include undatumized antags)
 	var/list/antag_datums
+	/// A lazy list of all teams the player is part of but doesnt have an antag role for, (i.e. a custom admin team)
+	// var/list/teams // SS220 EDIT - Commented for #840
 
 	var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state
 	var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD
@@ -165,13 +167,6 @@
 		var/mob/living/carbon/human/H = new_character
 		if(H.mind in SSticker.mode.syndicates)
 			SSticker.mode.update_synd_icons_added()
-		if(H.mind in SSticker.mode.cult)
-			SSticker.mode.update_cult_icons_added(H.mind) // Adds the cult antag hud
-			SSticker.mode.add_cult_actions(H.mind) // And all the actions
-			if(SSticker.mode.cult_risen)
-				SSticker.mode.rise(H)
-				if(SSticker.mode.cult_ascendant)
-					SSticker.mode.ascend(H)
 
 /datum/mind/proc/store_memory(new_text)
 	memory += "[new_text]<br>"
@@ -235,6 +230,13 @@
 	for(var/datum/antagonist/A as anything in antag_datums)
 		if(A.has_antag_objectives(include_team)) // this checks teams also
 			return TRUE
+	// For custom non-antag role teams
+	// SS220 EDIT START - Commented for #840
+	// if(include_team && LAZYLEN(teams))
+		// for(var/datum/team/team as anything in teams)
+			// if(team.objective_holder.has_objectives())
+				// return TRUE
+	// SS220 EDIT END
 	return FALSE
 
 /**
@@ -252,6 +254,13 @@
 			if(team) // have to make asure a team exists here, team?. does not work below because it will add the null to the list
 				all_objectives += team.objective_holder.get_objectives() // Get all of their teams' objectives
 
+	// For custom non-antag role teams
+	// SS220 EDIT START - Commented for #840
+	/* if(include_team && LAZYLEN(teams))
+		for(var/datum/team/team as anything in teams)
+			all_objectives += team.objective_holder.get_objectives() */
+	// SS220 EDIT END
+
 	return all_objectives
 
 /**
@@ -327,7 +336,7 @@
 
 /datum/mind/proc/memory_edit_cult(mob/living/carbon/human/H)
 	. = _memory_edit_header("cult")
-	if(src in SSticker.mode.cult)
+	if(has_antag_datum(/datum/antagonist/cultist))
 		. += "<a href='?src=[UID()];cult=clear'>no</a>|<b><font color='red'>CULTIST</font></b>"
 		. += "<br>Give <a href='?src=[UID()];cult=dagger'>dagger</a>|<a href='?src=[UID()];cult=runedmetal'>runedmetal</a>."
 	else
@@ -809,8 +818,7 @@
 				to_chat(H, "<span class='userdanger'>You somehow have become the recipient of a mindshield transplant, and it just activated!</span>")
 				var/datum/antagonist/rev/has_rev = has_antag_datum(/datum/antagonist/rev)
 				if(has_rev)
-					has_rev.silent = TRUE // we have some custom text, lets make the removal silent
-					remove_antag_datum(/datum/antagonist/rev)
+					remove_antag_datum(/datum/antagonist/rev, silent_removal = TRUE) // we have some custom text, lets make the removal silent
 					to_chat(H, "<span class='userdanger'>The nanobots in the mindshield implant remove all thoughts about being a revolutionary. Get back to work!</span>")
 
 	else if(href_list["revolution"])
@@ -902,27 +910,25 @@
 	else if(href_list["cult"])
 		switch(href_list["cult"])
 			if("clear")
-				if(src in SSticker.mode.cult)
-					SSticker.mode.remove_cultist(src)
-					special_role = null
+				if(has_antag_datum(/datum/antagonist/cultist))
+					remove_antag_datum(/datum/antagonist/cultist)
 					log_admin("[key_name(usr)] has de-culted [key_name(current)]")
 					message_admins("[key_name_admin(usr)] has de-culted [key_name_admin(current)]")
 			if("cultist")
-				if(!(src in SSticker.mode.cult))
-					to_chat(current, CULT_GREETING)
-					SSticker.mode.add_cultist(src)
-					to_chat(current, "<span class='cultitalic'>Assist your new compatriots in their dark dealings. Their goal is yours, and yours is theirs. You serve [SSticker.cultdat.entity_title2] above all else. Bring It back.</span>")
-					log_and_message_admins("[key_name(usr)] has culted [key_name(current)]")
+				if(!has_antag_datum(/datum/antagonist/cultist))
+					add_antag_datum(/datum/antagonist/cultist)
+					to_chat(current, "<span class='cultitalic'>Assist your new compatriots in their dark dealings. Their goal is yours, and yours is theirs. You serve [GET_CULT_DATA(entity_title2, "your god")] above all else. Bring It back.</span>")
+					log_and_message_admins("has culted [key_name(current)]")
 			if("dagger")
-				var/mob/living/carbon/human/H = current
-				if(!SSticker.mode.cult_give_item(/obj/item/melee/cultblade/dagger, H))
+				var/datum/antagonist/cultist/cultist = has_antag_datum(/datum/antagonist/cultist)
+				if(!cultist.cult_give_item(/obj/item/melee/cultblade/dagger))
 					to_chat(usr, "<span class='warning'>Spawning dagger failed!</span>")
-				log_and_message_admins("[key_name(usr)] has equipped [key_name(current)] with a cult dagger")
+				log_and_message_admins("has equipped [key_name(current)] with a cult dagger")
 			if("runedmetal")
-				var/mob/living/carbon/human/H = current
-				if(!SSticker.mode.cult_give_item(/obj/item/stack/sheet/runed_metal/ten, H))
+				var/datum/antagonist/cultist/cultist = has_antag_datum(/datum/antagonist/cultist)
+				if(!cultist.cult_give_item(/obj/item/stack/sheet/runed_metal/ten))
 					to_chat(usr, "<span class='warning'>Spawning runed metal failed!</span>")
-				log_and_message_admins("[key_name(usr)] has equipped [key_name(current)] with 10 runed metal sheets")
+				log_and_message_admins("has equipped [key_name(current)] with 10 runed metal sheets")
 
 	else if(href_list["wizard"])
 
@@ -1527,9 +1533,11 @@
  * Arguments:
  * * datum_type - an antag datum typepath
  */
-/datum/mind/proc/remove_antag_datum(datum_type, check_subtypes = TRUE)
+/datum/mind/proc/remove_antag_datum(datum_type, check_subtypes = TRUE, silent_removal = FALSE)
 	var/datum/antagonist/A = has_antag_datum(datum_type, check_subtypes)
-	qdel(A)
+	if(A)
+		A.silent |= silent_removal
+		qdel(A)
 
 /**
  * Removes all antag datums from the src mind.
diff --git a/code/datums/spells/construct_spells.dm b/code/datums/spells/construct_spells.dm
index c3a7d5ceacdd..de300b929cfa 100644
--- a/code/datums/spells/construct_spells.dm
+++ b/code/datums/spells/construct_spells.dm
@@ -127,7 +127,7 @@
 		if(C.holy)
 			C.set_light(3, 5, LIGHT_COLOR_DARK_BLUE)
 		else
-			C.set_light(2, 3, l_color = SSticker.cultdat ? SSticker.cultdat.construct_glow : LIGHT_COLOR_BLOOD_MAGIC)
+			C.set_light(2, 3, l_color = GET_CULT_DATA(construct_glow, LIGHT_COLOR_BLOOD_MAGIC))
 
 /obj/effect/proc_holder/spell/ethereal_jaunt/shift/jaunt_steam(mobloc)
 	return
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index 65017ada48bb..92d462379644 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -60,9 +60,16 @@
 
 /datum/status_effect/proc/on_apply() //Called whenever the buff is applied; returning FALSE will cause it to autoremove itself.
 	return TRUE
+
 /datum/status_effect/proc/tick() //Called every tick.
+	return
+
 /datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null
+	return
+
 /datum/status_effect/proc/on_timeout()  // Called specifically whenever the status effect expires.
+	return
+
 /datum/status_effect/proc/be_replaced() //Called instead of on_remove when a status effect is replaced by itself or when a status effect with on_remove_on_mob_delete = FALSE has its mob deleted
 	owner.clear_alert(id)
 	LAZYREMOVE(owner.status_effects, src)
@@ -182,12 +189,16 @@
 	var/reset_ticks_on_stack = FALSE //resets the current tick timer if a stack is gained
 
 /datum/status_effect/stacking/proc/threshold_cross_effect() //what happens when threshold is crossed
+	return
 
 /datum/status_effect/stacking/proc/stacks_consumed_effect() //runs if status is deleted due to threshold being crossed
+	return
 
 /datum/status_effect/stacking/proc/fadeout_effect() //runs if status is deleted due to being under one stack
+	return
 
 /datum/status_effect/stacking/proc/stack_decay_effect() //runs every time tick() causes stacks to decay
+	return
 
 /datum/status_effect/stacking/proc/on_threshold_cross()
 	threshold_cross_effect()
@@ -196,6 +207,7 @@
 		qdel(src)
 
 /datum/status_effect/stacking/proc/on_threshold_drop()
+	return
 
 /datum/status_effect/stacking/proc/can_have_status()
 	return owner.stat != DEAD
diff --git a/code/game/gamemodes/cult/blood_magic.dm b/code/game/gamemodes/cult/blood_magic.dm
index 20f93ed3f949..3aeebff962d3 100644
--- a/code/game/gamemodes/cult/blood_magic.dm
+++ b/code/game/gamemodes/cult/blood_magic.dm
@@ -136,7 +136,7 @@
 	..()
 
 /datum/action/innate/cult/blood_spell/IsAvailable()
-	if(!iscultist(owner) || owner.incapacitated() || !charges)
+	if(!IS_CULTIST(owner) || owner.incapacitated() || !charges)
 		return FALSE
 	return ..()
 
@@ -229,8 +229,7 @@
 	button_icon_state = "cult_dagger"
 
 /datum/action/innate/cult/blood_spell/dagger/New()
-	if(SSticker.mode)
-		button_icon_state = SSticker.cultdat.dagger_icon
+	button_icon_state = GET_CULT_DATA(dagger_icon, "cult_dagger")
 	..()
 
 /datum/action/innate/cult/blood_spell/dagger/Activate()
@@ -298,7 +297,7 @@
 /obj/effect/proc_holder/horror/InterceptClickOn(mob/living/user, params, atom/target)
 	if(..())
 		return
-	if(ranged_ability_user.incapacitated() || !iscultist(user))
+	if(ranged_ability_user.incapacitated() || !IS_CULTIST(user))
 		user.ranged_ability.remove_ranged_ability(user)
 		return
 	if(user.holy_check())
@@ -307,7 +306,7 @@
 	if(!isturf(T))
 		return FALSE
 	if(target in view(7, ranged_ability_user))
-		if(!ishuman(target) || iscultist(target))
+		if(!ishuman(target) || IS_CULTIST(target))
 			return
 		var/mob/living/carbon/human/H = target
 		H.Hallucinate(120 SECONDS)
@@ -336,7 +335,7 @@
 		owner.visible_message("<span class='warning'>Thin grey dust falls from [owner]'s hand!</span>", \
 		"<span class='cultitalic'>You invoke the veiling spell, hiding nearby runes and cult structures.</span>")
 		charges--
-		if(!SSticker.mode.cult_risen || !SSticker.mode.cult_ascendant)
+		if(!SSticker.mode.cult_team.cult_risen || !SSticker.mode.cult_team.cult_ascendant)
 			playsound(owner, 'sound/magic/smoke.ogg', 25, TRUE, SOUND_RANGE_SET(4)) // If Cult is risen/ascendant.
 		else
 			playsound(owner, 'sound/magic/smoke.ogg', 25, TRUE, SOUND_RANGE_SET(1)) // If Cult is unpowered.
@@ -352,7 +351,7 @@
 		"<span class='cultitalic'>You invoke the counterspell, revealing nearby runes and cult structures.</span>")
 		charges--
 		owner.whisper(invocation)
-		if(!SSticker.mode.cult_risen || !SSticker.mode.cult_ascendant)
+		if(!SSticker.mode.cult_team.cult_risen || !SSticker.mode.cult_team.cult_ascendant)
 			playsound(owner, 'sound/misc/enter_blood.ogg', 25, TRUE, SOUND_RANGE_SET(7)) // If Cult is risen/ascendant.
 		else
 			playsound(owner, 'sound/magic/smoke.ogg', 25, TRUE, SOUND_RANGE_SET(1)) // If Cult is unpowered.
@@ -429,7 +428,7 @@
 	afterattack(user, user, TRUE)
 
 /obj/item/melee/blood_magic/attack(mob/living/M, mob/living/carbon/user)
-	if(!iscarbon(user) || !iscultist(user))
+	if(!iscarbon(user) || !IS_CULTIST(user))
 		uses = 0
 		qdel(src)
 		return
@@ -462,7 +461,7 @@
 	if(!isliving(target) || !proximity)
 		return
 	var/mob/living/L = target
-	if(iscultist(target))
+	if(IS_CULTIST(target))
 		return
 	if(user.holy_check())
 		return
@@ -511,7 +510,7 @@
 	var/list/teleportnames = list()
 	var/list/duplicaterunecount = list()
 	var/atom/movable/teleportee
-	if(!iscultist(target) || !proximity)
+	if(!IS_CULTIST(target) || !proximity)
 		to_chat(user, "<span class='warning'>You can only teleport adjacent cultists with this spell!</span>")
 		return
 	if(user != target) // So that the teleport effect shows on the correct mob
@@ -875,7 +874,7 @@
 	if(!proximity)
 		return ..()
 	if(ishuman(target))
-		if(iscultist(target))
+		if(IS_CULTIST(target))
 			heal_cultist(user, target)
 			target.clean_blood()
 		else
diff --git a/code/game/gamemodes/cult/cult_actions.dm b/code/game/gamemodes/cult/cult_actions.dm
index 4db5796e848a..1bafae2b6467 100644
--- a/code/game/gamemodes/cult/cult_actions.dm
+++ b/code/game/gamemodes/cult/cult_actions.dm
@@ -5,7 +5,7 @@
 	buttontooltipstyle = "cult"
 
 /datum/action/innate/cult/IsAvailable()
-	if(!iscultist(owner))
+	if(!IS_CULTIST(owner))
 		return FALSE
 	return ..()
 
@@ -55,7 +55,7 @@
 
 	living_message = "<span class='cult[(large ? "large" : "speech")]'>[title]: [message]</span>"
 	for(var/mob/M in GLOB.player_list)
-		if(iscultist(M))
+		if(IS_CULTIST(M))
 			to_chat(M, living_message)
 		else if((M in GLOB.dead_mob_list) && !isnewplayer(M))
 			to_chat(M, "<span class='cult[(large ? "large" : "speech")]'>[title] ([ghost_follow_link(user, ghost=M)]): [message]</span>")
@@ -78,7 +78,7 @@
 	living_message = "<span class='cultlarge'>[title]: [message]</span>"
 
 	for(var/mob/M in GLOB.player_list)
-		if(iscultist(M))
+		if(IS_CULTIST(M))
 			to_chat(M, living_message)
 		else if((M in GLOB.dead_mob_list) && !isnewplayer(M))
 			to_chat(M, "<span class='cultlarge'>[title] ([ghost_follow_link(user, ghost=M)]): [message]</span>")
@@ -92,20 +92,17 @@
 	check_flags = AB_CHECK_CONSCIOUS
 
 /datum/action/innate/cult/check_progress/New()
-	if(SSticker.mode)
-		button_icon_state = SSticker.cultdat.tome_icon
+	button_icon_state = GET_CULT_DATA(tome_icon, "tome")
 	..()
 
 /datum/action/innate/cult/check_progress/IsAvailable()
-	if(iscultist(owner) || isobserver(owner))
-		return TRUE
-	return FALSE
+	return IS_CULTIST(owner) || isobserver(owner)
 
 /datum/action/innate/cult/check_progress/Activate()
 	if(!IsAvailable())
 		return
-	if(SSticker && SSticker.mode)
-		SSticker.mode.cult_objs.study(usr, TRUE)
+	if(SSticker?.mode?.cult_team)
+		SSticker.mode.cult_team.study_objectives(usr, TRUE)
 	else
 		to_chat(usr, "<span class='cultitalic'>You fail to study the Veil. (This should never happen, adminhelp and/or yell at a coder)</span>")
 
@@ -117,8 +114,7 @@
 	button_icon_state = "blood_dagger"
 
 /datum/action/innate/cult/use_dagger/Grant()
-	if(SSticker.mode)
-		button_icon_state = SSticker.cultdat.dagger_icon
+	button_icon_state = GET_CULT_DATA(dagger_icon, "blood_dagger")
 	..()
 
 /datum/action/innate/cult/use_dagger/override_location()
diff --git a/code/game/gamemodes/cult/cult_datums.dm b/code/game/gamemodes/cult/cult_datums.dm
index 0161bc7c6357..7e0ec8697375 100644
--- a/code/game/gamemodes/cult/cult_datums.dm
+++ b/code/game/gamemodes/cult/cult_datums.dm
@@ -74,9 +74,6 @@
 	var/airlock_unruned_icon_file = 'icons/obj/doors/airlocks/cult/unruned/cult.dmi'
 	var/airlock_unruned_overlays_file = 'icons/obj/doors/airlocks/cult/unruned/cult-overlays.dmi'
 
-	/// Are cultist mirror shields active yet?
-	var/mirror_shields_active = FALSE
-
 
 /datum/cult_info/fire
 	name = "Cult of Kha'Rin"
diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm
index 23ec5b39dd0e..0126e8f88a1f 100644
--- a/code/game/gamemodes/cult/cult_items.dm
+++ b/code/game/gamemodes/cult/cult_items.dm
@@ -7,8 +7,7 @@
 	w_class = WEIGHT_CLASS_SMALL
 
 /obj/item/tome/New()
-	if(SSticker.mode)
-		icon_state = SSticker.cultdat.tome_icon
+	icon_state = GET_CULT_DATA(tome_icon, "tome")
 	..()
 
 /obj/item/melee/cultblade
@@ -26,9 +25,8 @@
 	sprite_sheets_inhand = list("Skrell" = 'icons/mob/clothing/species/skrell/held.dmi') // To stop skrell stabbing themselves in the head
 
 /obj/item/melee/cultblade/New()
-	if(SSticker.mode)
-		icon_state = SSticker.cultdat.sword_icon
-		item_state = SSticker.cultdat.sword_icon
+	icon_state = GET_CULT_DATA(sword_icon, "blood_blade")
+	item_state = GET_CULT_DATA(sword_icon, "blood_blade")
 	..()
 
 /obj/item/melee/cultblade/examine(mob/user)
@@ -36,7 +34,7 @@
 	. += "<span class='notice'>This blade is a powerful weapon, capable of severing limbs easily. Nonbelievers are unable to use this weapon. Striking a nonbeliever after downing them with your cult magic will stun them completely.</span>"
 
 /obj/item/melee/cultblade/attack(mob/living/target, mob/living/carbon/human/user)
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		user.Weaken(10 SECONDS)
 		user.unEquip(src, 1)
 		user.visible_message("<span class='warning'>A powerful force shoves [user] away from [target]!</span>",
@@ -47,7 +45,7 @@
 		else
 			user.adjustBruteLoss(rand(force/2, force))
 		return
-	if(!iscultist(target))
+	if(!IS_CULTIST(target))
 		var/datum/status_effect/cult_stun_mark/S = target.has_status_effect(STATUS_EFFECT_CULT_STUN)
 		if(S)
 			S.trigger()
@@ -55,7 +53,7 @@
 
 /obj/item/melee/cultblade/pickup(mob/living/user)
 	. = ..()
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		to_chat(user, "<span class='cultlarge'>\"I wouldn't advise that.\"</span>")
 		to_chat(user, "<span class='warning'>An overwhelming sense of nausea overpowers you!</span>")
 		user.Confused(20 SECONDS)
@@ -74,13 +72,13 @@
 	knockdown_duration = 2 SECONDS
 
 /obj/item/restraints/legcuffs/bola/cult/throw_at(atom/target, range, speed, mob/thrower, spin, diagonals_first, datum/callback/callback)
-	if(thrower && !iscultist(thrower)) // A couple of objs actually proc throw_at, so we need to make sure that yes, we got tossed by a person before trying to send a message
+	if(thrower && !IS_CULTIST(thrower)) // A couple of objs actually proc throw_at, so we need to make sure that yes, we got tossed by a person before trying to send a message
 		thrower.visible_message("<span class='danger'>The bola glows, and boomarangs back at [thrower]!</span>")
 		throw_impact(thrower)
 	. = ..()
 
 /obj/item/restraints/legcuffs/bola/cult/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
-	if(iscultist(hit_atom))
+	if(IS_CULTIST(hit_atom))
 		hit_atom.visible_message("<span class='warning'>[src] bounces off of [hit_atom], as if repelled by an unseen force!</span>")
 		return
 	. = ..()
@@ -164,7 +162,7 @@
 
 /obj/item/clothing/suit/hooded/cultrobes/cult_shield/equipped(mob/living/user, slot)
 	..()
-	if(!iscultist(user)) // Todo: Make this only happen when actually equipped to the correct slot. (For all cult items)
+	if(!IS_CULTIST(user)) // Todo: Make this only happen when actually equipped to the correct slot. (For all cult items)
 		to_chat(user, "<span class='cultlarge'>\"I wouldn't advise that.\"</span>")
 		to_chat(user, "<span class='warning'>An overwhelming sense of nausea overpowers you!</span>")
 		user.unEquip(src, 1)
@@ -201,7 +199,7 @@
 
 /obj/item/clothing/suit/hooded/cultrobes/flagellant_robe/equipped(mob/living/user, slot)
 	..()
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		to_chat(user, "<span class='cultlarge'>\"I wouldn't advise that.\"</span>")
 		to_chat(user, "<span class='warning'>An overwhelming sense of nausea overpowers you!</span>")
 		user.unEquip(src, 1)
@@ -268,7 +266,7 @@
 
 /obj/item/clothing/glasses/hud/health/night/cultblind/equipped(mob/living/user, slot)
 	..()
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		to_chat(user, "<span class='cultlarge'>\"You want to be blind, do you?\"</span>")
 		user.unEquip(src, 1)
 		user.Confused(60 SECONDS)
@@ -283,7 +281,7 @@
 	var/global/curselimit = 0
 
 /obj/item/shuttle_curse/attack_self(mob/living/user)
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		user.unEquip(src, 1)
 		user.Weaken(10 SECONDS)
 		to_chat(user, "<span class='warning'>A powerful force shoves you away from [src]!</span>")
@@ -334,7 +332,7 @@
 	if(!uses || !iscarbon(user))
 		to_chat(user, "<span class='warning'>[src] is dull and unmoving in your hands.</span>")
 		return
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		user.unEquip(src, TRUE)
 		step(src, pick(GLOB.alldirs))
 		to_chat(user, "<span class='warning'>[src] flickers out of your hands, too eager to move!</span>")
@@ -461,11 +459,11 @@
   */
 /obj/item/shield/mirror/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
 	// Incase they get one by some magic
-	if(!SSticker.cultdat.mirror_shields_active)
+	if(!SSticker.mode.cult_team.mirror_shields_active)
 		to_chat(owner, "<span class='warning'>This shield is powerless! You must perform the required sacrifice to empower it!</span>")
 		return
 
-	if(iscultist(owner) && !owner.holy_check()) // Cultist holding the shield
+	if(IS_CULTIST(owner) && !owner.holy_check()) // Cultist holding the shield
 
 		// Hit by a projectile
 		if(istype(hitby, /obj/item/projectile))
@@ -533,7 +531,7 @@
 		illusions++
 	else if(isliving(loc))
 		var/mob/living/holder = loc
-		if(iscultist(holder))
+		if(IS_CULTIST(holder))
 			to_chat(holder, "<span class='cultitalic'>The shield's illusions are back at full strength!</span>")
 		else
 			to_chat(holder, "<span class='warning'>[src] vibrates slightly, and starts glowing.")
@@ -584,7 +582,7 @@
 	var/turf/T = get_turf(hit_atom)
 	if(isliving(hit_atom))
 		var/mob/living/L = hit_atom
-		if(iscultist(L))
+		if(IS_CULTIST(L))
 			playsound(src, 'sound/weapons/throwtap.ogg', 50)
 			if(!L.restrained() && L.put_in_active_hand(src))
 				L.visible_message("<span class='warning'>[L] catches [src] out of the air!</span>")
@@ -688,7 +686,7 @@
 	hitsound = 'sound/effects/splat.ogg'
 
 /obj/item/projectile/magic/arcane_barrage/blood/prehit(atom/target)
-	if(iscultist(target))
+	if(IS_CULTIST(target))
 		damage = 0
 		nodamage = TRUE
 		if(ishuman(target))
@@ -720,7 +718,7 @@
 
 /obj/item/portal_amulet/afterattack(atom/O, mob/user, proximity)
 	. = ..()
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		if(!iscarbon(user))
 			return
 		var/mob/living/carbon/M = user
@@ -799,7 +797,7 @@
 		exit = new /obj/effect/cult_portal_exit(target)
 
 /obj/effect/portal/cult/attackby(obj/I, mob/user, params)
-	if(istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user) || istype(I, /obj/item/nullrod) && HAS_MIND_TRAIT(user, TRAIT_HOLY))
+	if(istype(I, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user) || istype(I, /obj/item/nullrod) && HAS_MIND_TRAIT(user, TRAIT_HOLY))
 		to_chat(user, "<span class='notice'>You close the portal with your [I].</span>")
 		playsound(src, 'sound/magic/magic_missile.ogg', 100, TRUE)
 		qdel(src)
diff --git a/code/game/gamemodes/cult/cult_mode.dm b/code/game/gamemodes/cult/cult_mode.dm
index 98a73d955844..fe6bb4705588 100644
--- a/code/game/gamemodes/cult/cult_mode.dm
+++ b/code/game/gamemodes/cult/cult_mode.dm
@@ -1,49 +1,10 @@
 /datum/game_mode
-	/// A list of all minds currently in the cult
-	var/list/datum/mind/cult = list()
-	var/datum/cult_objectives/cult_objs = new
-	/// Does the cult have glowing eyes
-	var/cult_risen = FALSE
-	/// Does the cult have halos
-	var/cult_ascendant = FALSE
-	/// How many crew need to be converted to rise
-	var/rise_number
-	/// How many crew need to be converted to ascend
-	var/ascend_number
-	/// Used for the CentComm announcement at ascension
-	var/ascend_percent
+	var/datum/team/cult/cult_team
 
-/proc/iscultist(mob/living/M)
-	return istype(M) && M.mind && SSticker && SSticker.mode && (M.mind in SSticker.mode.cult)
-
-/proc/is_convertable_to_cult(datum/mind/mind)
-	if(!mind)
-		return FALSE
-	if(!mind.current)
-		return FALSE
-	if(is_sacrifice_target(mind))
-		return FALSE
-	if(iscultist(mind.current))
-		return TRUE //If they're already in the cult, assume they are convertable
-	if(HAS_MIND_TRAIT(mind.current, TRAIT_HOLY))
-		return FALSE
-	if(ishuman(mind.current))
-		var/mob/living/carbon/human/H = mind.current
-		if(ismindshielded(H)) //mindshield protects against conversions unless removed
-			return FALSE
-	if(mind.offstation_role)
-		return FALSE
-	if(issilicon(mind.current))
-		return FALSE //can't convert machines, that's ratvar's thing
-	if(isguardian(mind.current))
-		var/mob/living/simple_animal/hostile/guardian/G = mind.current
-		if(iscultist(G.summoner))
-			return TRUE //can't convert it unless the owner is converted
-	if(isgolem(mind.current))
-		return FALSE
-	if(isanimal(mind.current))
-		return FALSE
-	return TRUE
+/datum/game_mode/proc/get_cult_team()
+	if(!cult_team)
+		new /datum/team/cult() // assignment happens in create_team()
+	return cult_team
 
 /datum/game_mode/cult
 	name = "cult"
@@ -54,6 +15,8 @@
 	required_enemies = 3
 	recommended_enemies = 4
 
+	var/list/pre_cult = list()
+
 	var/const/min_cultists_to_start = 3
 	var/const/max_cultists_to_start = 4
 
@@ -71,335 +34,24 @@
 			break
 		var/datum/mind/cultist = pick(cultists_possible)
 		cultists_possible -= cultist
-		cult += cultist
+		pre_cult += cultist
 		cultist.restricted_roles = restricted_jobs
 		cultist.special_role = SPECIAL_ROLE_CULTIST
-	return (length(cult) > 0)
+	return length(pre_cult)
 
 /datum/game_mode/cult/post_setup()
-	modePlayer += cult
-	cult_objs.setup()
-
-	for(var/datum/mind/cult_mind in cult)
-		SEND_SOUND(cult_mind.current, sound('sound/ambience/antag/bloodcult.ogg'))
-		to_chat(cult_mind.current, CULT_GREETING)
-		equip_cultist(cult_mind.current)
-		cult_mind.current.faction |= "cult"
-		cult_mind.add_mind_objective(/datum/objective/servecult)
-
-		if(cult_mind.assigned_role == "Clown")
-			to_chat(cult_mind.current, "<span class='cultitalic'>A dark power has allowed you to overcome your clownish nature, letting you wield weapons without harming yourself.</span>")
-			cult_mind.current.dna.SetSEState(GLOB.clumsyblock, FALSE)
-			singlemutcheck(cult_mind.current, GLOB.clumsyblock, MUTCHK_FORCED)
-			var/datum/action/innate/toggle_clumsy/A = new
-			A.Grant(cult_mind.current)
-
-		update_cult_icons_added(cult_mind)
-		cult_objs.study(cult_mind.current)
-		to_chat(cult_mind.current, "<span class='motd'>For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Cultist)</span>")
-	cult_threshold_check()
-	addtimer(CALLBACK(src, PROC_REF(cult_threshold_check)), 2 MINUTES) // Check again in 2 minutes for latejoiners
+	new /datum/team/cult(pre_cult)
 	..()
 
-/datum/game_mode/proc/equip_cultist(mob/living/carbon/human/H, metal = TRUE)
-	if(!istype(H))
-		return
-	. += cult_give_item(/obj/item/melee/cultblade/dagger, H)
-	if(metal)
-		. += cult_give_item(/obj/item/stack/sheet/runed_metal/ten, H)
-	to_chat(H, "<span class='cult'>These will help you start the cult on this station. Use them well, and remember - you are not the only one.</span>")
-
-/datum/game_mode/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/H)
-	var/list/slots = list(
-		"backpack" = SLOT_HUD_IN_BACKPACK,
-		"left pocket" = SLOT_HUD_LEFT_STORE,
-		"right pocket" = SLOT_HUD_RIGHT_STORE)
-	var/T = new item_path(H)
-	var/item_name = initial(item_path.name)
-	var/where = H.equip_in_one_of_slots(T, slots)
-	if(!where)
-		to_chat(H, "<span class='userdanger'>Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1).</span>")
-		return FALSE
-	else
-		to_chat(H, "<span class='danger'>You have a [item_name] in your [where].</span>")
-		return TRUE
-
-
-/datum/game_mode/proc/add_cultist(datum/mind/cult_mind)
-	if(!istype(cult_mind))
-		return FALSE
-
-	if(!ascend_percent) // If the rise/ascend thresholds haven't been set (non-cult rounds)
-		cult_objs.setup()
-		cult_threshold_check()
-
-	if(!(cult_mind in cult))
-		cult += cult_mind
-		cult_mind.current.faction |= "cult"
-		cult_mind.special_role = SPECIAL_ROLE_CULTIST
-
-		if(cult_mind.assigned_role == "Clown")
-			to_chat(cult_mind.current, "<span class='cultitalic'>A dark power has allowed you to overcome your clownish nature, letting you wield weapons without harming yourself.</span>")
-			cult_mind.current.dna.SetSEState(GLOB.clumsyblock, FALSE)
-			singlemutcheck(cult_mind.current, GLOB.clumsyblock, MUTCHK_FORCED)
-			var/datum/action/innate/toggle_clumsy/A = new
-			A.Grant(cult_mind.current)
-		SEND_SOUND(cult_mind.current, sound('sound/ambience/antag/bloodcult.ogg'))
-		cult_mind.current.create_attack_log("<span class='danger'>Has been converted to the cult!</span>")
-		cult_mind.current.create_log(CONVERSION_LOG, "Converted to the cult")
-
-		if(jobban_isbanned(cult_mind.current, ROLE_CULTIST) || jobban_isbanned(cult_mind.current, ROLE_SYNDICATE))
-			replace_jobbanned_player(cult_mind.current, ROLE_CULTIST)
-		if(!cult_objs.cult_status && ishuman(cult_mind.current))
-			cult_objs.setup()
-		update_cult_icons_added(cult_mind)
-		add_cult_actions(cult_mind)
-		cult_mind.add_mind_objective(/datum/objective/servecult)
-
-		if(cult_risen)
-			rise(cult_mind.current)
-			if(cult_ascendant)
-				ascend(cult_mind.current)
-		check_cult_size()
-		cult_objs.study(cult_mind.current)
-		to_chat(cult_mind.current, "<span class='motd'>For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Cultist)</span>")
-		RegisterSignal(cult_mind.current, COMSIG_MOB_STATCHANGE, PROC_REF(cultist_stat_change))
-		RegisterSignal(cult_mind.current, COMSIG_PARENT_QDELETING, PROC_REF(remove_cultist))
-		return TRUE
-
-/datum/game_mode/proc/remove_cultist(datum/mind/cult_mind, show_message = TRUE, remove_gear = FALSE, mob/target_mob)
-	if(!(cult_mind in cult)) // Not actually a cultist in the first place
-		return
-
-	var/mob/cultist = target_mob
-	if(!cultist)
-		cultist = cult_mind.current
-	cult -= cult_mind
-	cultist.faction -= "cult"
-	cult_mind.special_role = null
-	cult_mind.objective_holder.clear(/datum/objective/servecult)
-	for(var/datum/action/innate/cult/C in cultist.actions)
-		qdel(C)
-	update_cult_icons_removed(cult_mind)
-
-	if(ishuman(cultist))
-		var/mob/living/carbon/human/H = cultist
-		REMOVE_TRAIT(H, CULT_EYES, null)
-		H.change_eye_color(H.original_eye_color, FALSE)
-		H.update_eyes()
-		H.remove_overlay(HALO_LAYER)
-		H.update_body()
-		if(remove_gear) // No flagellants robe for non-cultists
-			for(var/I in H.contents)
-				if(is_type_in_list(I, CULT_CLOTHING))
-					H.unEquip(I)
-		if(cult_mind.assigned_role == "Clown")
-			to_chat(H, "<span class='sans'>You are free of the dark power suppressing your clownish nature. You are clumsy again! Honk!</span>")
-			H.dna.SetSEState(GLOB.clumsyblock, TRUE)
-			singlemutcheck(H, GLOB.clumsyblock, MUTCHK_FORCED)
-			for(var/datum/action/innate/toggle_clumsy/A in H.actions)
-				A.Remove(H)
-		cult_mind.current.create_log(CONVERSION_LOG, "Deconverted from the cult")
-	check_cult_size()
-	if(show_message)
-		cultist.visible_message("<span class='cult'>[cultist] looks like [cultist.p_they()] just reverted to [cultist.p_their()] old faith!</span>",
-		"<span class='userdanger'>An unfamiliar white light flashes through your mind, cleansing the taint of [SSticker.cultdat ? SSticker.cultdat.entity_title1 : "Nar'Sie"] and the memories of your time as their servant with it.</span>")
-	UnregisterSignal(cult_mind.current, COMSIG_MOB_STATCHANGE)
-	UnregisterSignal(cult_mind.current, COMSIG_PARENT_QDELETING)
-
-/datum/game_mode/proc/add_cult_immunity(mob/living/target)
-	ADD_TRAIT(target, TRAIT_CULT_IMMUNITY, CULT_TRAIT)
-	addtimer(CALLBACK(src, PROC_REF(remove_cult_immunity), target), 1 MINUTES)
-
-/datum/game_mode/proc/remove_cult_immunity(mob/living/target)
-	REMOVE_TRAIT(target, TRAIT_CULT_IMMUNITY, CULT_TRAIT)
-
-
-/**
-  * Decides at the start of the round how many conversions are needed to rise/ascend.
-  *
-  * The number is decided by (Percentage * (Players - Cultists)), so for example at 110 players it would be 11 conversions for rise. (0.1 * (110 - 4))
-  * These values change based on population because 20 cultists are MUCH more powerful if there's only 50 players, compared to 120.
-  *
-  * Below 100 players, [CULT_RISEN_LOW] and [CULT_ASCENDANT_LOW] are used.
-  * Above 100 players, [CULT_RISEN_HIGH] and [CULT_ASCENDANT_HIGH] are used.
-  */
-/datum/game_mode/proc/cult_threshold_check()
-	var/list/living_players = get_living_players(exclude_nonhuman = TRUE, exclude_offstation = TRUE)
-	var/players = length(living_players)
-	var/cultists = get_cultists() // Don't count the starting cultists towards the number of needed conversions
-	if(players >= CULT_POPULATION_THRESHOLD)
-		// Highpop
-		ascend_percent = CULT_ASCENDANT_HIGH
-		rise_number = round(CULT_RISEN_HIGH * (players - cultists))
-		ascend_number = round(CULT_ASCENDANT_HIGH * (players - cultists))
-	else
-		// Lowpop
-		ascend_percent = CULT_ASCENDANT_LOW
-		rise_number = round(CULT_RISEN_LOW * (players - cultists))
-		ascend_number = round(CULT_ASCENDANT_LOW * (players - cultists))
-
-/**
-  * Returns the current number of cultists and constructs.
-  *
-  * Returns the number of cultists and constructs in a list ([1] = Cultists, [2] = Constructs), or as one combined number.
-  *
-  * * separate - Should the number be returned as a list with two separate values (Humans and Constructs) or as one number.
-  */
-/datum/game_mode/proc/get_cultists(separate = FALSE)
-	var/cultists = 0
-	var/constructs = 0
-	for(var/datum/mind/M as anything in cult)
-		if(QDELETED(M) || M.current?.stat == DEAD)
-			continue
-		if(ishuman(M.current) && !M.current.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST))
-			cultists++
-		else if(isconstruct(M.current))
-			constructs++
-	if(separate)
-		return list(cultists, constructs)
-	return cultists + constructs
-
-/datum/game_mode/proc/cultist_stat_change(mob/target_cultist, new_stat, old_stat)
-	SIGNAL_HANDLER // COMSIG_MOB_STATCHANGE from cultists
-	if(new_stat == old_stat) // huh, how? whatever, we ignore it
-		return
-	if(new_stat != DEAD && old_stat != DEAD)
-		return // switching between alive and unconcious
-	// switching between dead and alive/unconcious
-	check_cult_size()
-
-/datum/game_mode/proc/check_cult_size()
-	var/cult_players = get_cultists()
-
-	if(cult_ascendant)
-		// The cult only falls if below 1/2 of the rising, usually pretty low. e.g. 5% on highpop, 10% on lowpop
-		if(cult_players < (rise_number / 2))
-			cult_fall()
-		return
-
-	if((cult_players >= rise_number) && !cult_risen)
-		cult_rise()
-		return
-
-	if(cult_players >= ascend_number)
-		cult_ascend()
-
-/datum/game_mode/proc/cult_rise()
-	cult_risen = TRUE
-	for(var/datum/mind/M in cult)
-		if(!ishuman(M.current))
-			continue
-		SEND_SOUND(M.current, sound('sound/hallucinations/i_see_you2.ogg'))
-		to_chat(M.current, "<span class='cultlarge'>The veil weakens as your cult grows, your eyes begin to glow...</span>")
-		addtimer(CALLBACK(src, PROC_REF(rise), M.current), 20 SECONDS)
-
-
-/datum/game_mode/proc/cult_ascend()
-	cult_ascendant = TRUE
-	for(var/datum/mind/M in cult)
-		if(!ishuman(M.current))
-			continue
-		SEND_SOUND(M.current, sound('sound/hallucinations/im_here1.ogg'))
-		to_chat(M.current, "<span class='cultlarge'>Your cult is ascendant and the red harvest approaches - you cannot hide your true nature for much longer!</span>")
-		addtimer(CALLBACK(src, PROC_REF(ascend), M.current), 20 SECONDS)
-	GLOB.major_announcement.Announce("Picking up extradimensional activity related to the Cult of [SSticker.cultdat ? SSticker.cultdat.entity_name : "Nar'Sie"] from your station. Data suggests that about [ascend_percent * 100]% of the station has been converted. Security staff are authorized to use lethal force freely against cultists. Non-security staff should be prepared to defend themselves and their work areas from hostile cultists. Self defense permits non-security staff to use lethal force as a last resort, but non-security staff should be defending their work areas, not hunting down cultists. Dead crewmembers must be revived and deconverted once the situation is under control.", "Central Command Higher Dimensional Affairs", 'sound/AI/commandreport.ogg')
-
-/datum/game_mode/proc/cult_fall()
-	cult_ascendant = FALSE
-	for(var/datum/mind/M in cult)
-		if(!ishuman(M.current))
-			continue
-		SEND_SOUND(M.current, sound('sound/hallucinations/wail.ogg'))
-		to_chat(M.current, "<span class='cultlarge'>The veil repairs itself, your power grows weaker...</span>")
-		addtimer(CALLBACK(src, PROC_REF(descend), M.current), 20 SECONDS)
-	GLOB.major_announcement.Announce("Paranormal activity has returned to minimal levels. \
-									Security staff should minimize lethal force against cultists, using non-lethals where possible. \
-									All dead cultists should be taken to medbay or robotics for immediate revival and deconversion. \
-									Non-security staff may defend themselves, but should prioritize leaving any areas with cultists and reporting the cultists to security. \
-									Self defense permits non-security staff to use lethal force as a last resort. Hunting down cultists may make you liable for a manslaughter charge. \
-									Any access granted in response to the paranormal threat should be reset. \
-									Any and all security gear that was handed out should be returned. Finally, all weapons (including improvised) should be removed from the crew.",
-									"Central Command Higher Dimensional Affairs", 'sound/AI/commandreport.ogg')
-
-/datum/game_mode/proc/rise(cultist)
-	if(!ishuman(cultist) || !iscultist(cultist))
-		return
-	var/mob/living/carbon/human/H = cultist
-	if(!H.original_eye_color)
-		H.original_eye_color = H.get_eye_color()
-	H.change_eye_color(BLOODCULT_EYE, FALSE)
-	H.update_eyes()
-	ADD_TRAIT(H, CULT_EYES, CULT_TRAIT)
-	H.update_body()
-
-/datum/game_mode/proc/ascend(cultist)
-	if(!ishuman(cultist) || !iscultist(cultist))
-		return
-	var/mob/living/carbon/human/H = cultist
-	new /obj/effect/temp_visual/cult/sparks(get_turf(H), H.dir)
-	H.update_halo_layer()
-
-/datum/game_mode/proc/descend(cultist)
-	if(!ishuman(cultist) || !iscultist(cultist))
-		return
-	var/mob/living/carbon/human/H = cultist
-	new /obj/effect/temp_visual/cult/sparks(get_turf(H), H.dir)
-	H.update_halo_layer()
-	to_chat(cultist, "<span class='userdanger'>The halo above your head shatters!</span>")
-	playsound(cultist, "shatter", 50, TRUE)
-
-/datum/game_mode/proc/update_cult_icons_added(datum/mind/cult_mind)
-	var/datum/atom_hud/antag/culthud = GLOB.huds[ANTAG_HUD_CULT]
-	if(cult_mind.current)
-		culthud.join_hud(cult_mind.current)
-		set_antag_hud(cult_mind.current, "hudcultist")
-
-/datum/game_mode/proc/update_cult_icons_removed(datum/mind/cult_mind)
-	var/datum/atom_hud/antag/culthud = GLOB.huds[ANTAG_HUD_CULT]
-	if(cult_mind.current)
-		culthud.leave_hud(cult_mind.current)
-		set_antag_hud(cult_mind.current, null)
-
-/datum/game_mode/proc/add_cult_actions(datum/mind/cult_mind)
-	if(cult_mind.current)
-		var/datum/action/innate/cult/comm/C = new
-		var/datum/action/innate/cult/check_progress/D = new
-		C.Grant(cult_mind.current)
-		D.Grant(cult_mind.current)
-		if(ishuman(cult_mind.current))
-			var/datum/action/innate/cult/blood_magic/magic = new
-			magic.Grant(cult_mind.current)
-			var/datum/action/innate/cult/use_dagger/dagger = new
-			dagger.Grant(cult_mind.current)
-		cult_mind.current.update_action_buttons(TRUE)
-
-
 /datum/game_mode/cult/declare_completion()
-	if(cult_objs.cult_status == NARSIE_HAS_RISEN)
+	if(cult_team.sacrifices_required == NARSIE_HAS_RISEN)
 		SSticker.mode_result = "cult win - cult win"
-		to_chat(world, "<span class='danger'> <FONT size = 3>The cult wins! It has succeeded in summoning [SSticker.cultdat.entity_name]!</FONT></span>")
-	else if(cult_objs.cult_status == NARSIE_HAS_FALLEN)
+		to_chat(world, "<span class='danger'> <FONT size = 3>The cult wins! It has succeeded in summoning [GET_CULT_DATA(entity_name, "their god")]!</FONT></span>")
+	else if(cult_team.sacrifices_required == NARSIE_HAS_FALLEN)
 		SSticker.mode_result = "cult draw - narsie died, nobody wins"
-		to_chat(world, "<span class='danger'> <FONT size = 3>Nobody wins! [SSticker.cultdat.entity_name] was summoned, but banished!</FONT></span>")
+		to_chat(world, "<span class='danger'> <FONT size = 3>Nobody wins! [GET_CULT_DATA(entity_name, "the cult god")] was summoned, but banished!</FONT></span>")
 	else
 		SSticker.mode_result = "cult loss - staff stopped the cult"
 		to_chat(world, "<span class='warning'> <FONT size = 3>The staff managed to stop the cult!</FONT></span>")
 
-	var/list/endtext = list()
-	endtext += "<br><b>The cultists' objectives were:</b>"
-	for(var/datum/objective/obj in cult_objs.presummon_objs)
-		endtext += "<br>[obj.explanation_text] - "
-		if(!obj.check_completion())
-			endtext += "<font color='red'>Fail.</font>"
-		else
-			endtext += "<font color='green'><B>Success!</B></font>"
-	if(cult_objs.cult_status >= NARSIE_NEEDS_SUMMONING)
-		endtext += "<br>[cult_objs.obj_summon.explanation_text] - "
-		if(!cult_objs.obj_summon.check_completion())
-			endtext+= "<font color='red'>Fail.</font>"
-		else
-			endtext += "<font color='green'><B>Success!</B></font>"
-
-	to_chat(world, endtext.Join(""))
 	..()
diff --git a/code/game/gamemodes/cult/cult_objectives.dm b/code/game/gamemodes/cult/cult_objectives.dm
index 6afc676e3eeb..99c53c66e3b8 100644
--- a/code/game/gamemodes/cult/cult_objectives.dm
+++ b/code/game/gamemodes/cult/cult_objectives.dm
@@ -1,123 +1,4 @@
-/// Replace with team antag datum objectives from tg once ported
-/datum/cult_objectives
-	var/cult_status = NARSIE_IS_ASLEEP
-	var/list/presummon_objs = list()
-	var/datum/objective/eldergod/obj_summon = new
-	var/sacrifices_done = 0
-	var/sacrifices_required = 2
-
-/datum/cult_objectives/proc/setup()
-	if(cult_status != NARSIE_IS_ASLEEP)
-		return FALSE
-	cult_status = NARSIE_DEMANDS_SACRIFICE
-	var/datum/objective/sacrifice/obj_sac = new
-	if(obj_sac.find_target())
-		presummon_objs.Add(obj_sac)
-	else
-		ready_to_summon()
-
-/datum/cult_objectives/proc/study(mob/living/M, display_members = FALSE) //Called by cultists/cult constructs checking their objectives
-	if(!M)
-		return FALSE
-
-	switch(cult_status)
-		if(NARSIE_IS_ASLEEP)
-			to_chat(M, "<span class='cult'>[SSticker.cultdat ? SSticker.cultdat.entity_name : "The Dark One"] is asleep.</span>")
-		if(NARSIE_DEMANDS_SACRIFICE)
-			if(!length(presummon_objs))
-				to_chat(M, "<span class='danger'>Error: No objectives in sacrifice list. Something went wrong. Oof.</span>")
-			else
-				var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)] //get the last obj in the list, ie the current one
-				to_chat(M, "<span class='cult'>The Veil needs to be weakened before we are able to summon [SSticker.cultdat ? SSticker.cultdat.entity_title1 : "The Dark One"].</span>")
-				to_chat(M, "<span class='cult'>Current goal: [current_obj.explanation_text]</span>")
-		if(NARSIE_NEEDS_SUMMONING)
-			to_chat(M, "<span class='cult'>The Veil is weak! We can summon [SSticker.cultdat ? SSticker.cultdat.entity_title3 : "The Dark One"]!</span>")
-			to_chat(M, "<span class='cult'>Current goal: [obj_summon.explanation_text]</span>")
-		if(NARSIE_HAS_RISEN)
-			to_chat(M, "<span class='cultlarge'>\"I am here.\"</span>")
-			to_chat(M, "<span class='cult'>Current goal:</span> <span class='cultlarge'>\"Feed me.\"</span>")
-		if(NARSIE_HAS_FALLEN)
-			to_chat(M, "<span class='cultlarge'>[SSticker.cultdat ? SSticker.cultdat.entity_name : "The Dark One"] has been banished!</span>")
-			to_chat(M, "<span class='cult'>Current goal: Slaughter the unbelievers!</span>")
-		else
-			to_chat(M, "<span class='danger'>Error: Cult objective status currently unknown. Something went wrong. Oof.</span>")
-
-	if(display_members)
-		var/list/cult = SSticker.mode.get_cultists(TRUE)
-		var/total_cult = cult[1] + cult[2]
-		var/rise = SSticker.mode.rise_number - total_cult
-		var/ascend = SSticker.mode.ascend_number - total_cult
-
-		var/overview = "<span class='cultitalic'><br><b>Current cult members: [total_cult]"
-		if(!SSticker.mode.cult_ascendant)
-			if(rise > 0)
-				overview += " | Conversions until Rise: [rise]"
-			else if(ascend > 0)
-				overview += " | Conversions until Ascension: [ascend]"
-		to_chat(M, "[overview]</b></span>")
-
-		if(cult[2]) // If there are any constructs, separate them out
-			to_chat(M, "<span class='cultitalic'><b>Cultists:</b> [cult[1]]")
-			to_chat(M, "<span class='cultitalic'><b>Constructs:</b> [cult[2]]")
-
-
-/datum/cult_objectives/proc/current_sac_objective() //Return the current sacrifice objective datum, if any
-	if(cult_status == NARSIE_DEMANDS_SACRIFICE && length(presummon_objs))
-		var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)]
-		return current_obj
-	return FALSE
-
-/datum/cult_objectives/proc/is_sac_target(datum/mind/mind)
-	if(cult_status != NARSIE_DEMANDS_SACRIFICE || !length(presummon_objs))
-		return FALSE
-	var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)]
-	if(current_obj.target == mind)
-		return TRUE
-	return FALSE
-
-/datum/cult_objectives/proc/find_new_sacrifice_target(datum/mind/mind)
-	var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)]
-	if(current_obj.find_target())
-		for(var/datum/mind/cult_mind in SSticker.mode.cult)
-			if(cult_mind && cult_mind.current)
-				to_chat(cult_mind.current, "<span class='danger'>[SSticker.cultdat.entity_name]</span> murmurs, <span class='cultlarge'>Our goal is beyond your reach. Sacrifice [current_obj.target] instead...</span>")
-		return TRUE
-	return FALSE
-
-/datum/cult_objectives/proc/succesful_sacrifice()
-	var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)]
-	current_obj.sacced = TRUE
-	sacrifices_done++
-	if(sacrifices_done >= sacrifices_required)
-		ready_to_summon()
-	else
-		var/datum/objective/sacrifice/obj_sac = new
-		if(obj_sac.find_target())
-			presummon_objs += obj_sac
-			for(var/datum/mind/cult_mind in SSticker.mode.cult)
-				if(cult_mind && cult_mind.current)
-					to_chat(cult_mind.current, "<span class='cult'>You and your acolytes have made progress, but there is more to do still before [SSticker.cultdat ? SSticker.cultdat.entity_title1 : "The Dark One"] can be summoned!</span>")
-					to_chat(cult_mind.current, "<span class='cult'>Current goal: [obj_sac.explanation_text]</span>")
-		else
-			ready_to_summon()
-
-/datum/cult_objectives/proc/ready_to_summon()
-	cult_status = NARSIE_NEEDS_SUMMONING
-	for(var/datum/mind/cult_mind in SSticker.mode.cult)
-		if(cult_mind && cult_mind.current)
-			to_chat(cult_mind.current, "<span class='cult'>You and your acolytes have succeeded in preparing the station for the ultimate ritual!</span>")
-			to_chat(cult_mind.current, "<span class='cult'>Current goal: [obj_summon.explanation_text]</span>")
-
-/datum/cult_objectives/proc/succesful_summon()
-	cult_status = NARSIE_HAS_RISEN
-	obj_summon.summoned = TRUE
-
-/datum/cult_objectives/proc/narsie_death()
-	cult_status = NARSIE_HAS_FALLEN
-	obj_summon.killed = TRUE
-
-//Objectives
-
+// Objectives
 /// Given to cultists on conversion/roundstart
 /datum/objective/servecult
 	explanation_text = "Assist your fellow cultists and Tear the Veil! (Use the Study Veil action to check your progress.)"
@@ -131,26 +12,49 @@
 /datum/objective/sacrifice/check_completion()
 	return sacced || completed
 
-/datum/objective/sacrifice/find_target()
-	var/list/target_candidates = list()
-	for(var/mob/living/carbon/human/H in GLOB.player_list)
-		if(is_admin_level(H.z)) //We can't sacrifice people that are on the centcom z-level
+/datum/objective/sacrifice/find_target(list/target_blacklist)
+	. = ..()
+	if(target && !(target in target_blacklist)) // check the blacklist, it wont update otherwise
+		return
+
+	//There are no living unconvertables on the station. Looking for a Sacrifice Target among the convertable minds
+	var/list/possible_targets = list()
+	for(var/datum/mind/possible_target in SSticker.minds)
+		if(possible_target in target_blacklist)
 			continue
-		if(H.mind && !iscultist(H) && !is_convertable_to_cult(H.mind) && (H.stat != DEAD) && !H.mind.offstation_role)
-			target_candidates += H.mind
-	if(!length(target_candidates))	//There are no living unconvertables on the station. Looking for a Sacrifice Target among the ordinary crewmembers
-		for(var/mob/living/carbon/human/H in GLOB.player_list)
-			if(is_admin_level(H.z)) //We can't sacrifice people that are on the centcom z-level
-				continue
-			if(H.mind && !iscultist(H) && (H.stat != DEAD) && !H.mind.offstation_role) // Same checks, but add them even if they could be converted
-				target_candidates += H.mind
-	if(length(target_candidates))
-		target = pick(target_candidates)
-		explanation_text = "Sacrifice [target], the [target.assigned_role] via invoking an Offer rune with [target.p_their()] body or brain on it and three acolytes around it."
+		var/mind_result = is_invalid_target(possible_target)
+		if(mind_result && mind_result != TARGET_INVALID_CULT_CONVERTABLE)
+			continue
+
+		possible_targets += possible_target
+
+	if(length(possible_targets))
+		target = pick(possible_targets)
+		update_explanation_text()
 		return TRUE
+
 	message_admins("Cult Sacrifice: Could not find unconvertible or convertible target. Nar'Sie summoning unlocked!")
 	return FALSE
 
+/datum/objective/sacrifice/is_invalid_target(datum/mind/possible_target)
+	. = ..()
+	if(.)
+		return
+	if(possible_target.has_antag_datum(/datum/antagonist/cultist))
+		return TARGET_INVALID_CULTIST
+	if(!SSticker.mode.cult_team)
+		stack_trace("/datum/objective/sacrifice/is_invalid_target was called without there being an assigned cult team")
+		return
+	if(SSticker.mode.cult_team.is_convertable_to_cult(possible_target))
+		return TARGET_INVALID_CULT_CONVERTABLE
+
+/datum/objective/sacrifice/update_explanation_text()
+	if(target?.current)
+		explanation_text = "Sacrifice [target], the [target.assigned_role] via invoking an Offer rune with [target.p_their()] body or brain on it and three acolytes around it."
+	else
+		// Code will reach here as part of find_target, but people should never be able to READ it.
+		explanation_text = "If you're reading this, something went very wrong. Contact an admin."
+
 
 /datum/objective/eldergod
 	needs_target = FALSE
@@ -183,7 +87,7 @@
 		if(valid_spot)
 			summon_spots += summon
 		sanity++
-	explanation_text = "Summon [SSticker.cultdat ? SSticker.cultdat.entity_name : "your god"] by invoking the rune 'Tear Veil' with 9 cultists, constructs, or summoned ghosts on it.\
+	explanation_text = "Summon [GET_CULT_DATA(entity_name, "your god")] by invoking the rune 'Tear Veil' with 9 cultists, constructs, or summoned ghosts on it.\
 	\nThe summoning can only be accomplished in [english_list(summon_spots)] - where the veil is weak enough for the ritual to begin."
 
 
diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm
index efd2e7a02b51..481d2dbfc02c 100644
--- a/code/game/gamemodes/cult/cult_structures.dm
+++ b/code/game/gamemodes/cult/cult_structures.dm
@@ -48,25 +48,25 @@
 
 /obj/structure/cult/functional/examine(mob/user)
 	. = ..()
-	if(iscultist(user) && cooldowntime > world.time)
+	if(IS_CULTIST(user) && cooldowntime > world.time)
 		. += "<span class='cultitalic'>The magic in [src] is weak, it will be ready to use again in [get_ETA()].</span>"
 	. += "<span class='notice'>[src] is [anchored ? "":"not "]secured to the floor.</span>"
 
 /obj/structure/cult/functional/attackby(obj/item/I, mob/user, params)
-	if(istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user))
+	if(istype(I, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user))
 		if(user.holy_check())
 			return
 		anchored = !anchored
 		to_chat(user, "<span class='notice'>You [anchored ? "":"un"]secure [src] [anchored ? "to":"from"] the floor.</span>")
 		if(!anchored)
-			icon_state = SSticker.cultdat?.get_icon("[initial(icon_state)]_off")
+			icon_state = GET_CULT_DATA(get_icon("[initial(icon_state)]_off"), "[initial(icon_state)]_off")
 		else
-			icon_state = SSticker.cultdat?.get_icon("[initial(icon_state)]")
+			icon_state = GET_CULT_DATA(get_icon(initial(icon_state)), initial(icon_state))
 		return
 	return ..()
 
 /obj/structure/cult/functional/attack_hand(mob/living/user)
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		to_chat(user, "[heathen_message]")
 		return
 	if(invisibility)
@@ -150,7 +150,7 @@
 
 /obj/structure/cult/functional/altar/Initialize(mapload)
 	. = ..()
-	icon_state = SSticker.cultdat?.altar_icon_state
+	icon_state = GET_CULT_DATA(altar_icon_state, "altar")
 	cooldowntime = world.time + CULT_STRUCTURE_COOLDOWN
 
 /obj/structure/cult/functional/forge
@@ -170,7 +170,7 @@
 
 /obj/structure/cult/functional/forge/get_choosable_items()
 	. = ..()
-	if(SSticker.cultdat.mirror_shields_active)
+	if(SSticker.mode.cult_team.mirror_shields_active)
 		// Both lines here are needed. If you do it without, youll get issues.
 		. += "Mirror Shield"
 		.["Mirror Shield"] = /obj/item/shield/mirror
@@ -178,7 +178,7 @@
 
 /obj/structure/cult/functional/forge/Initialize(mapload)
 	. = ..()
-	icon_state = SSticker.cultdat?.forge_icon_state
+	icon_state = GET_CULT_DATA(forge_icon_state, "forge")
 
 /obj/structure/cult/functional/forge/attackby(obj/item/I, mob/user, params)
 	if(istype(I, /obj/item/grab))
@@ -236,7 +236,7 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list(
 /obj/structure/cult/functional/pylon/Initialize(mapload)
 	. = ..()
 	START_PROCESSING(SSobj, src)
-	icon_state = SSticker.cultdat?.pylon_icon_state
+	icon_state = GET_CULT_DATA(pylon_icon_state, "pylon")
 
 /obj/structure/cult/functional/pylon/attack_hand(mob/living/user)//override as it should not create anything
 	return
@@ -260,7 +260,7 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list(
 	if(last_heal <= world.time)
 		last_heal = world.time + heal_delay
 		for(var/mob/living/L in range(5, src))
-			if(iscultist(L) || iswizard(L) || isshade(L) || isconstruct(L))
+			if(IS_CULTIST(L) || iswizard(L) || isshade(L) || isconstruct(L))
 				if(L.health != L.maxHealth)
 					new /obj/effect/temp_visual/heal(get_turf(src), COLOR_HEALING_GREEN)
 
@@ -322,7 +322,7 @@ GLOBAL_LIST_INIT(blacklisted_pylon_turfs, typecacheof(list(
 
 /obj/structure/cult/functional/archives/Initialize(mapload)
 	. = ..()
-	icon_state = SSticker.cultdat?.archives_icon_state
+	icon_state = GET_CULT_DATA(archives_icon_state, "archives")
 
 /obj/effect/gateway
 	name = "gateway"
diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm
index 4dcc1b987c13..81b28b18e6be 100644
--- a/code/game/gamemodes/cult/ritual.dm
+++ b/code/game/gamemodes/cult/ritual.dm
@@ -22,19 +22,18 @@
 
 /obj/item/melee/cultblade/dagger/New()
 	..()
-	if(SSticker.mode)
-		icon_state = SSticker.cultdat.dagger_icon
-		item_state = SSticker.cultdat.dagger_icon
+	icon_state = GET_CULT_DATA(dagger_icon, "blood_dagger")
+	item_state = GET_CULT_DATA(dagger_icon, "blood_dagger")
 
 /obj/item/melee/cultblade/dagger/examine(mob/user)
 	. = ..()
-	if(iscultist(user) || user.stat == DEAD)
-		. += "<span class='cult'>A dagger gifted by [SSticker.cultdat.entity_title3]. Allows the scribing of runes and access to the knowledge archives of the cult of [SSticker.cultdat.entity_name].</span>"
+	if(IS_CULTIST(user) || user.stat == DEAD)
+		. += "<span class='cult'>A dagger gifted by [GET_CULT_DATA(entity_title3, "your god")]. Allows the scribing of runes and access to the knowledge archives of the cult of [GET_CULT_DATA(entity_name, "your god")].</span>"
 		. += "<span class='cultitalic'>Striking another cultist with it will purge holy water from them.</span>"
 		. += "<span class='cultitalic'>Striking a noncultist will tear their flesh, additionally, if you recently downed them with cult magic it will stun them completely.</span>"
 
 /obj/item/melee/cultblade/dagger/attack(mob/living/M, mob/living/user)
-	if(iscultist(M))
+	if(IS_CULTIST(M))
 		if(M.reagents && M.reagents.has_reagent("holywater")) //allows cultists to be rescued from the clutches of ordained religion
 			if(M == user) // Targeting yourself
 				to_chat(user, "<span class='warning'>You can't remove holy water from yourself!</span>")
@@ -51,7 +50,7 @@
 	. = ..()
 
 /obj/item/melee/cultblade/dagger/attack_self(mob/user)
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		to_chat(user, "<span class='warning'>[src] is covered in unintelligible shapes and markings.</span>")
 		return
 	scribe_rune(user)
@@ -59,26 +58,26 @@
 /obj/item/melee/cultblade/dagger/proc/narsie_rune_check(mob/living/user, area/A)
 	var/datum/game_mode/gamemode = SSticker.mode
 
-	if(gamemode.cult_objs.cult_status < NARSIE_NEEDS_SUMMONING)
-		to_chat(user, "<span class='cultitalic'><b>[SSticker.cultdat.entity_name]</b> is not ready to be summoned yet!</span>")
+	if(gamemode.cult_team.cult_status < NARSIE_NEEDS_SUMMONING)
+		to_chat(user, "<span class='cultitalic'><b>[GET_CULT_DATA(entity_name, "Your god")]</b> is not ready to be summoned yet!</span>")
 		return FALSE
-	if(gamemode.cult_objs.cult_status == NARSIE_HAS_RISEN)
+	if(gamemode.cult_team.cult_status == NARSIE_HAS_RISEN)
 		to_chat(user, "<span class='cultlarge'>\"I am already here. There is no need to try to summon me now.\"</span>")
 		return FALSE
 
-	var/list/summon_areas = gamemode.cult_objs.obj_summon.summon_spots
+	var/list/summon_areas = gamemode.cult_team.obj_summon.summon_spots
 	if(!(A in summon_areas))
-		to_chat(user, "<span class='cultlarge'>[SSticker.cultdat.entity_name] can only be summoned where the veil is weak - in [english_list(summon_areas)]!</span>")
+		to_chat(user, "<span class='cultlarge'>[GET_CULT_DATA(entity_name, "Your god")] can only be summoned where the veil is weak - in [english_list(summon_areas)]!</span>")
 		return FALSE
 	var/confirm_final = tgui_alert(user, "This is the FINAL step to summon your deities power, it is a long, painful ritual and the crew will be alerted to your presence AND your location!",
-	"Are you prepared for the final battle?", list("My life for [SSticker.cultdat.entity_name]!", "No"))
+	"Are you prepared for the final battle?", list("My life for [GET_CULT_DATA(entity_name, "the cult")]!", "No"))
 	if(user)
 		if(confirm_final == "No" || confirm_final == null)
 			to_chat(user, "<span class='cultitalic'><b>You decide to prepare further before scribing the rune.</b></span>")
 			return FALSE
 		else
 			if(locate(/obj/effect/rune) in range(1, user))
-				to_chat(user, "<span class='cultlarge'>You need a space cleared of runes before you can summon [SSticker.cultdat.entity_title1]!</span>")
+				to_chat(user, "<span class='cultlarge'>You need a space cleared of runes before you can summon [GET_CULT_DATA(entity_title1, "your god")]!</span>")
 				return FALSE
 			else
 				return TRUE
@@ -150,7 +149,7 @@
 	if(narsie_rune)
 		if(!narsie_rune_check(user, A))
 			return // don't do shit
-		var/list/summon_areas = gamemode.cult_objs.obj_summon.summon_spots
+		var/list/summon_areas = gamemode.cult_team.obj_summon.summon_spots
 		if(!(A in summon_areas)) // Check again to make sure they didn't move
 			to_chat(user, "<span class='cultlarge'>The ritual can only begin where the veil is weak - in [english_list(summon_areas)]!</span>")
 			return
@@ -170,7 +169,7 @@
 	else
 		others_message = "<span class='biggerdanger'>[user] cuts [user.p_their()] body and begins writing something particularly ominous in [user.p_their()] own blood!</span>"
 	user.visible_message(others_message,
-		"<span class='cultitalic'>You slice open your body and begin drawing a sigil of [SSticker.cultdat.entity_title3].</span>")
+		"<span class='cultitalic'>You slice open your body and begin drawing a sigil of [GET_CULT_DATA(entity_title3, "your god")].</span>")
 
 	drawing_rune = TRUE // Only one at a time
 	var/scribe_successful = do_after(user, initial(rune.scribe_delay) * scribe_multiplier, target = runeturf)
@@ -184,7 +183,7 @@
 		return
 
 	user.visible_message("<span class='warning'>[user] creates a strange circle in [user.p_their()] own blood.</span>",
-						"<span class='cultitalic'>You finish drawing the arcane markings of [SSticker.cultdat.entity_title3].</span>")
+						"<span class='cultitalic'>You finish drawing the arcane markings of [GET_CULT_DATA(entity_title3, "your god")].</span>")
 
 	var/obj/effect/rune/R = new rune(runeturf, keyword)
 	if(narsie_rune)
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index 653a30a58a2d..bc9a46fe2e3e 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -68,7 +68,7 @@ To draw a rune, use a ritual dagger.
 
 /obj/effect/rune/examine(mob/user)
 	. = ..()
-	if(iscultist(user) || user.stat == DEAD) //If they're a cultist or a ghost, tell them the effects
+	if(IS_CULTIST(user) || isobserver(user)) //If they're a cultist or a ghost, tell them the effects
 		. += "<b>Name:</b> [cultist_name]"
 		. += "<b>Effects:</b> [capitalize(cultist_desc)]"
 		. += "<b>Required Acolytes:</b> [req_cultists]"
@@ -76,7 +76,7 @@ To draw a rune, use a ritual dagger.
 			. += "<b>Keyword:</b> <span class='cultitalic'>[keyword]</span>"
 
 /obj/effect/rune/attackby(obj/I, mob/user, params)
-	if(istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user))
+	if(istype(I, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user))
 		// Telerunes with portals open
 		if(istype(src, /obj/effect/rune/teleport))
 			var/obj/effect/rune/teleport/T = src // Can't erase telerunes if they have a portal open
@@ -92,7 +92,7 @@ To draw a rune, use a ritual dagger.
 			qdel(src)
 		return
 	if(istype(I, /obj/item/nullrod))
-		if(iscultist(user))//cultist..what are doing..cultist..staph...
+		if(IS_CULTIST(user))//cultist..what are doing..cultist..staph...
 			user.drop_item()
 			user.visible_message("<span class='warning'>[I] suddenly glows with a white light, forcing [user] to drop it in pain!</span>", \
 			"<span class='danger'>[I] suddenly glows with a white light that sears your hand, forcing you to drop it!</span>") // TODO: Make this actually burn your hand
@@ -104,7 +104,7 @@ To draw a rune, use a ritual dagger.
 
 /obj/effect/rune/attack_hand(mob/living/user)
 	user.Move_Pulled(src) // So that you can still drag things onto runes
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		to_chat(user, "<span class='warning'>You aren't able to understand the words of [src].</span>")
 		return
 	var/list/invokers = can_invoke(user)
@@ -115,7 +115,7 @@ To draw a rune, use a ritual dagger.
 
 /obj/effect/rune/attack_animal(mob/living/simple_animal/M)
 	if(isshade(M) || isconstruct(M))
-		if(construct_invoke || !iscultist(M)) //if you're not a cult construct we want the normal fail message
+		if(construct_invoke || !IS_CULTIST(M)) //if you're not a cult construct we want the normal fail message
 			attack_hand(M)
 		else
 			to_chat(M, "<span class='warning'>You are unable to invoke the rune!</span>")
@@ -164,7 +164,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	// Get anyone nearby
 	if(req_cultists > 1 || allow_excess_invokers)
 		for(var/mob/living/L in range(1, src))
-			if(iscultist(L))
+			if(IS_CULTIST(L))
 				if(L == user)
 					continue
 				if(L.stat)
@@ -202,7 +202,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 			L.apply_damage(invoke_damage, BRUTE)
 			to_chat(L, "<span class='cultitalic'>[src] saps your strength!</span>")
 	do_invoke_glow()
-	SSblackbox.record_feedback("nested tally", "runes_invoked", 1, list("[initial(cultist_name)]", "[length(SSticker.mode.cult)]")) // the name of the rune, and the number of cultists in the cult when it was invoked
+	SSblackbox.record_feedback("nested tally", "runes_invoked", 1, list("[initial(cultist_name)]", "[length(SSticker.mode.cult_team.members)]")) // the name of the rune, and the number of cultists in the cult when it was invoked
 	if(ghost_invokers)
 		SSblackbox.record_feedback("nested tally", "runes_invoked_with_ghost", 1, list("[initial(cultist_name)]", "[ghost_invokers]")) //the name of the rune and the number of ghosts used to invoke it.
 
@@ -214,7 +214,7 @@ structure_check() searches for nearby cultist structures required for the invoca
   * * location - Location to teleport from
   * * target - Location to teleport to
   */
-/obj/effect/rune/proc/teleport_effect(mob/living/user, turf/location, target)
+/obj/effect/rune/proc/teleport_effect(mob/living/user, turf/location, turf/target)
 	new /obj/effect/temp_visual/dir_setting/cult/phase/out(location, user.dir)
 	new /obj/effect/temp_visual/dir_setting/cult/phase(target, user.dir)
 	// So that the mob only appears after the effect is finished
@@ -236,15 +236,6 @@ structure_check() searches for nearby cultist structures required for the invoca
 	animate(src, color = rgb(255, 0, 0), time = 0)
 	animate(src, color = rune_blood_color, time = 5)
 
-
-/obj/effect/rune/proc/check_icon()
-	if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way
-		var/bits = make_bit_triplet()
-		icon = get_rune(bits)
-	else
-		icon = get_rune_cult(invocation)
-
-
 //Malformed Rune: This forms if a rune is not drawn correctly. Invoking it does nothing but hurt the user.
 /obj/effect/rune/malformed
 	cultist_name = "Malformed"
@@ -256,7 +247,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	..()
 	for(var/M in invokers)
 		var/mob/living/L = M
-		to_chat(L, "<span class='cultitalic'><b>You feel your life force draining. [SSticker.cultdat.entity_title3] is displeased.</b></span>")
+		to_chat(L, "<span class='cultitalic'><b>You feel your life force draining. [GET_CULT_DATA(entity_title3, "Your god")] is displeased.</b></span>")
 	qdel(src)
 
 /mob/proc/null_rod_check() //The null rod, if equipped, will protect the holder from the effects of most runes
@@ -282,7 +273,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	var/list/offer_targets = list()
 	var/turf/T = get_turf(src)
 	for(var/mob/living/M in T)
-		if(!iscultist(M) || (M.mind && is_sacrifice_target(M.mind)))
+		if(!IS_CULTIST(M) || (M.mind && IS_SACRIFICE_TARGET(M.mind)))
 			if(isconstruct(M)) // No offering constructs please
 				continue
 			offer_targets += M
@@ -300,7 +291,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 			var/obj/item/organ/internal/brain/brain = O
 			b_mob = brain.brainmob
 
-		if(b_mob && b_mob.mind && (!iscultist(b_mob) || is_sacrifice_target(b_mob.mind)))
+		if(b_mob && b_mob.mind && (!IS_CULTIST(b_mob) || IS_SACRIFICE_TARGET(b_mob.mind)))
 			offer_targets += b_mob
 
 	if(!length(offer_targets))
@@ -318,7 +309,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 		rune_in_use = FALSE
 		return
 
-	if(L.stat != DEAD && is_convertable_to_cult(L.mind))
+	if(L.stat != DEAD && SSticker.mode.cult_team.is_convertable_to_cult(L.mind))
 		..()
 		do_convert(L, invokers)
 	else
@@ -333,50 +324,49 @@ structure_check() searches for nearby cultist structures required for the invoca
 		for(var/I in invokers)
 			to_chat(I, "<span class='warning'>You need at least two invokers to convert!</span>")
 		return
-	else
-		convertee.visible_message("<span class='warning'>[convertee] writhes in pain as the markings below them glow a bloody red!</span>", \
-								"<span class='cultlarge'><i>AAAAAAAAAAAAAA-</i></span>")
-		SSticker.mode.add_cultist(convertee.mind)
-		convertee.mind.special_role = "Cultist"
-		to_chat(convertee, "<span class='cultitalic'><b>Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible, truth. The veil of reality has been ripped away \
+
+	convertee.visible_message("<span class='warning'>[convertee] writhes in pain as the markings below them glow a bloody red!</span>", \
+							"<span class='cultlarge'><i>AAAAAAAAAAAAAA-</i></span>")
+	convertee.mind.add_antag_datum(/datum/antagonist/cultist)
+	to_chat(convertee, "<span class='cultitalic'><b>Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible, truth. The veil of reality has been ripped away \
 		and something evil takes root.</b></span>")
-		to_chat(convertee, "<span class='cultitalic'><b>Assist your new compatriots in their dark dealings. Your goal is theirs, and theirs is yours. You serve [SSticker.cultdat.entity_title3] above all else. Bring it back.\
+	to_chat(convertee, "<span class='cultitalic'><b>Assist your new compatriots in their dark dealings. Your goal is theirs, and theirs is yours. You serve [GET_CULT_DATA(entity_title3, "your god")] above all else. Bring it back.\
 		</b></span>")
 
-		if(ishuman(convertee))
-			var/mob/living/carbon/human/H = convertee
-			var/brutedamage = convertee.getBruteLoss()
-			var/burndamage = convertee.getFireLoss()
-			if(brutedamage || burndamage) // If the convertee is injured
-				// Heal 90% of all damage, including robotic limbs
-				H.adjustBruteLoss(-(brutedamage * 0.9), robotic = TRUE)
-				H.adjustFireLoss(-(burndamage * 0.9), robotic = TRUE)
-				if(ismachineperson(H))
-					H.visible_message("<span class='warning'>A dark force repairs [convertee]!</span>",
-					"<span class='cultitalic'>Your damage has been repaired. Now spread the blood to others.</span>")
-				else
-					H.visible_message("<span class='warning'>[convertee]'s wounds heal and close!</span>",
-					"<span class='cultitalic'>Your wounds have been healed. Now spread the blood to others.</span>")
-					for(var/obj/item/organ/external/E in H.bodyparts)
-						E.mend_fracture()
-						E.fix_internal_bleeding()
-						E.fix_burn_wound()
-					for(var/datum/disease/critical/crit in H.viruses) // cure all crit conditions
-						crit.cure()
-
-			H.uncuff()
-			H.Silence(6 SECONDS) //Prevent "HALP MAINT CULT" before you realise you're converted
-
-			var/obj/item/melee/cultblade/dagger/D = new(get_turf(src))
-			if(H.equip_to_slot_if_possible(D, SLOT_HUD_IN_BACKPACK, FALSE, TRUE))
-				to_chat(H, "<span class='cultlarge'>You have a dagger in your backpack. Use it to do [SSticker.cultdat.entity_title1]'s bidding.</span>")
+	if(ishuman(convertee))
+		var/mob/living/carbon/human/H = convertee
+		var/brutedamage = convertee.getBruteLoss()
+		var/burndamage = convertee.getFireLoss()
+		if(brutedamage || burndamage) // If the convertee is injured
+			// Heal 90% of all damage, including robotic limbs
+			H.adjustBruteLoss(-(brutedamage * 0.9), robotic = TRUE)
+			H.adjustFireLoss(-(burndamage * 0.9), robotic = TRUE)
+			if(ismachineperson(H))
+				H.visible_message("<span class='warning'>A dark force repairs [convertee]!</span>",
+				"<span class='cultitalic'>Your damage has been repaired. Now spread the blood to others.</span>")
 			else
-				to_chat(H, "<span class='cultlarge'>There is a dagger on the floor. Use it to do [SSticker.cultdat.entity_title1]'s bidding.</span>")
+				H.visible_message("<span class='warning'>[convertee]'s wounds heal and close!</span>",
+				"<span class='cultitalic'>Your wounds have been healed. Now spread the blood to others.</span>")
+				for(var/obj/item/organ/external/E in H.bodyparts)
+					E.mend_fracture()
+					E.fix_internal_bleeding()
+					E.fix_burn_wound()
+				for(var/datum/disease/critical/crit in H.viruses) // cure all crit conditions
+					crit.cure()
+
+		H.uncuff()
+		H.Silence(6 SECONDS) //Prevent "HALP MAINT CULT" before you realise you're converted
+
+		var/obj/item/melee/cultblade/dagger/D = new(get_turf(src))
+		if(H.equip_to_slot_if_possible(D, SLOT_HUD_IN_BACKPACK, FALSE, TRUE))
+			to_chat(H, "<span class='cultlarge'>You have a dagger in your backpack. Use it to do [GET_CULT_DATA(entity_title1, "your god")]'s bidding.</span>")
+		else
+			to_chat(H, "<span class='cultlarge'>There is a dagger on the floor. Use it to do [GET_CULT_DATA(entity_title1, "your god")]'s bidding.</span>")
 
 /obj/effect/rune/convert/proc/do_sacrifice(mob/living/offering, list/invokers)
 	var/mob/living/user = invokers[1] //the first invoker is always the user
 
-	if(offering.stat != DEAD || (offering.mind && is_sacrifice_target(offering.mind))) //Requires three people to sacrifice living targets/sacrifice objective
+	if(offering.stat != DEAD || (offering.mind && IS_SACRIFICE_TARGET(offering.mind))) //Requires three people to sacrifice living targets/sacrifice objective
 		if(length(invokers) < 3)
 			for(var/M in invokers)
 				to_chat(M, "<span class='cultitalic'>[offering] is too greatly linked to the world! You need three acolytes!</span>")
@@ -386,7 +376,6 @@ structure_check() searches for nearby cultist structures required for the invoca
 
 	var/sacrifice_fulfilled
 	var/worthless = FALSE
-	var/datum/game_mode/gamemode = SSticker.mode
 
 	if(isliving(offering) && !isbrain(offering))
 		var/mob/living/L = offering
@@ -398,7 +387,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 
 	if(offering.mind)
 		GLOB.sacrificed += offering.mind
-		if(is_sacrifice_target(offering.mind))
+		if(IS_SACRIFICE_TARGET(offering.mind))
 			sacrifice_fulfilled = TRUE
 	else
 		GLOB.sacrificed += offering
@@ -407,9 +396,9 @@ structure_check() searches for nearby cultist structures required for the invoca
 	for(var/M in invokers)
 		if(sacrifice_fulfilled)
 			to_chat(M, "<span class='cultlarge'>\"Yes! This is the one I desire! You have done well.\"</span>")
-			if(!SSticker.cultdat.mirror_shields_active) // Only show once
+			if(!SSticker.mode.cult_team.mirror_shields_active) // Only show once
 				to_chat(M, "<span class='cultitalic'>You are now able to construct mirror shields inside the daemon forge.</span>")
-				SSticker.cultdat.mirror_shields_active = TRUE
+				SSticker.mode.cult_team.mirror_shields_active = TRUE
 		else
 			if(ishuman(offering) && offering.mind?.offstation_role && offering.mind.special_role != SPECIAL_ROLE_ERT) //If you try it on a ghost role, you get nothing
 				to_chat(M, "<span class='cultlarge'>\"This soul is of no use to either of us.\"</span>")
@@ -435,7 +424,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 			offering.gib()
 		playsound(offering, 'sound/magic/disintegrate.ogg', 100, TRUE, SOUND_RANGE_SET(10))
 	if(sacrifice_fulfilled)
-		gamemode.cult_objs.succesful_sacrifice()
+		SSticker.mode.cult_team.successful_sacrifice()
 	return TRUE
 
 /obj/effect/rune/teleport
@@ -592,7 +581,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 
 /obj/effect/rune/raise_dead/examine(mob/user)
 	. = ..()
-	if(iscultist(user) || user.stat == DEAD)
+	if(IS_CULTIST(user) || user.stat == DEAD)
 		. += "<b>Sacrifices unrewarded:</b><span class='cultitalic'> [length(GLOB.sacrificed) - sacrifices_used]</span>"
 		. += "<b>Sacrifice cost per ressurection:</b><span class='cultitalic> [SOULS_TO_REVIVE]</span>"
 
@@ -625,7 +614,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 		fail_invoke()
 		return
 	for(var/mob/living/M in T.contents)
-		if(!iscultist(M))
+		if(!IS_CULTIST(M))
 			continue
 		potential_revive_mobs |= M
 	if(!length(potential_revive_mobs))
@@ -704,7 +693,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	..()
 	rune_in_use = FALSE
 	for(var/mob/living/M in range(0, src))
-		if(iscultist(M) && M.stat == DEAD)
+		if(IS_CULTIST(M) && M.stat == DEAD)
 			M.visible_message("<span class='warning'>[M] twitches.</span>")
 
 //Rite of the Corporeal Shield: When invoked, becomes solid and cannot be passed. Invoke again to undo.
@@ -753,7 +742,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	var/mob/living/user = invokers[1]
 	var/list/cultists = list()
 
-	for(var/datum/mind/M in SSticker.mode.cult)
+	for(var/datum/mind/M in SSticker.mode.cult_team.members)
 		if(!(M.current in invokers) && M.current && M.current.stat != DEAD)
 			cultists[M.current.real_name] = M.current
 	var/input = tgui_input_list(user, "Who do you wish to call to [src]?", "Acolytes", cultists)
@@ -774,8 +763,8 @@ structure_check() searches for nearby cultist structures required for the invoca
 		fail_invoke()
 		log_game("Summon Cultist rune failed - target restrained")
 		return
-	if(!iscultist(cultist_to_summon))
-		to_chat(user, "<span class='cultitalic'>[cultist_to_summon] is not a follower of the [SSticker.cultdat.entity_title3]!</span>")
+	if(!IS_CULTIST(cultist_to_summon))
+		to_chat(user, "<span class='cultitalic'>[cultist_to_summon] is not a follower of [GET_CULT_DATA(entity_title3, "our god")]!</span>")
 		fail_invoke()
 		log_game("Summon Cultist rune failed - target was deconverted")
 		return
@@ -788,7 +777,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	cultist_to_summon.visible_message("<span class='warning'>[cultist_to_summon] suddenly disappears in a flash of red light!</span>", \
 									"<span class='cultitalic'><b>Overwhelming vertigo consumes you as you are hurled through the air!</b></span>")
 	..()
-	INVOKE_ASYNC(src, PROC_REF(teleport_effect), cultist_to_summon, get_turf(cultist_to_summon), src)
+	INVOKE_ASYNC(src, PROC_REF(teleport_effect), cultist_to_summon, get_turf(cultist_to_summon), get_turf(src))
 	visible_message("<span class='warning'>[src] begins to bubble and rises into the form of [cultist_to_summon]!</span>")
 	cultist_to_summon.forceMove(get_turf(src))
 	qdel(src)
@@ -822,7 +811,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	var/turf/T = get_turf(src)
 	var/list/targets = list()
 	for(var/mob/living/L in viewers(T))
-		if(!iscultist(L) && L.blood_volume && !ismachineperson(L))
+		if(!IS_CULTIST(L) && L.blood_volume && !ismachineperson(L))
 			var/atom/I = L.null_rod_check()
 			if(I)
 				if(isitem(I))
@@ -860,7 +849,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	var/multiplier = iteration / 2 // Iteration 1 = 0.5, Iteration 2 = 1, etc.
 	set_light(6, 1 * iteration, color)
 	for(var/mob/living/L in viewers(T))
-		if(!iscultist(L) && L.blood_volume && !ismachineperson(L))
+		if(!IS_CULTIST(L) && L.blood_volume && !ismachineperson(L))
 			if(L.null_rod_check())
 				continue
 			L.take_overall_damage(0, tick_damage * multiplier)
@@ -876,7 +865,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 		return FALSE
 	var/list/cultists = list()
 	for(var/mob/living/M in range(1, src)) // Get all cultists currently in range
-		if(iscultist(M) && !M.incapacitated())
+		if(IS_CULTIST(M) && !M.incapacitated())
 			cultists += M
 
 	if(length(cultists) < req_cultists) // Stop the rune there's not enough invokers
@@ -896,9 +885,9 @@ structure_check() searches for nearby cultist structures required for the invoca
 
 /obj/effect/rune/manifest/examine(mob/user)
 	. = ..()
-	if(iscultist(user) || user.stat == DEAD)
+	if(IS_CULTIST(user) || user.stat == DEAD)
 		. += "<b>Amount of ghosts summoned:</b><span class='cultitalic'> [ghosts]</span>"
-		. += "<b>Maximum amount of ghosts:</b><span class='cultitalic'> [clamp(default_ghost_limit - SSticker.mode.cult_objs.sacrifices_done, minimum_ghost_limit, default_ghost_limit)]</span>"
+		. += "<b>Maximum amount of ghosts:</b><span class='cultitalic'> [clamp(default_ghost_limit - SSticker.mode.cult_team.sacrifices_done, minimum_ghost_limit, default_ghost_limit)]</span>"
 		. += "Lowers to a minimum of [minimum_ghost_limit] for each objective accomplished."
 
 /obj/effect/rune/manifest/invoke(list/invokers)
@@ -928,7 +917,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 			fail_invoke()
 			log_game("Manifest rune failed - not enough health")
 			return list()
-		if(ghosts >= clamp(default_ghost_limit - SSticker.mode.cult_objs.sacrifices_done, minimum_ghost_limit, default_ghost_limit))
+		if(ghosts >= clamp(default_ghost_limit - SSticker.mode.cult_team.sacrifices_done, minimum_ghost_limit, default_ghost_limit))
 			to_chat(user, "<span class='cultitalic'>You are sustaining too many ghosts to summon more!</span>")
 			fail_invoke()
 			log_game("Manifest rune failed - too many summoned ghosts")
@@ -945,7 +934,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	for(var/mob/dead/observer/O in T)
 		if(!O.client)
 			continue
-		if(iscultist(O) || jobban_isbanned(O, ROLE_CULTIST))
+		if(IS_CULTIST(O) || jobban_isbanned(O, ROLE_CULTIST))
 			continue
 		if(!HAS_TRAIT(O, TRAIT_RESPAWNABLE) || QDELETED(src) || QDELETED(O))
 			continue
@@ -974,8 +963,8 @@ structure_check() searches for nearby cultist structures required for the invoca
 						"<span class='cultitalic'>Your blood begins flowing into [src]. You must remain in place and conscious to maintain the forms of those summoned. This will hurt you slowly but surely...</span>")
 
 	var/obj/machinery/shield/cult/weak/shield = new(T)
-	SSticker.mode.add_cultist(new_human.mind, 0)
-	to_chat(new_human, "<span class='cultlarge'>You are a servant of the [SSticker.cultdat.entity_title3]. You have been made semi-corporeal by the cult of [SSticker.cultdat.entity_name], and you are to serve them at all costs.</span>")
+	new_human.mind.add_antag_datum(/datum/antagonist/cultist)
+	to_chat(new_human, "<span class='cultlarge'>You are a servant of [GET_CULT_DATA(entity_title3, "the cult")]. You have been made semi-corporeal by the cult of [GET_CULT_DATA(entity_name, "your god")], and you are to serve them at all costs.</span>")
 
 	while(!QDELETED(src) && !QDELETED(user) && !QDELETED(new_human) && (user in T))
 		if(new_human.InCritical())
@@ -999,7 +988,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 								"<span class='cultlarge'>Your link to the world fades. Your form breaks apart.</span>")
 		for(var/obj/item/I in new_human.get_all_slots())
 			new_human.unEquip(I)
-		SSticker.mode.remove_cultist(new_human.mind, FALSE)
+		new_human.mind.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
 		new_human.dust()
 
 /obj/effect/rune/manifest/proc/ghostify(mob/living/user, turf/T)
@@ -1054,11 +1043,8 @@ structure_check() searches for nearby cultist structures required for the invoca
 
 /obj/effect/rune/narsie/New()
 	..()
-	cultist_name = "Summon [SSticker.cultdat ? SSticker.cultdat.entity_name : "your god"]"
-	cultist_desc = "tears apart dimensional barriers, calling forth [SSticker.cultdat ? SSticker.cultdat.entity_title3 : "your god"]."
-
-/obj/effect/rune/narsie/check_icon()
-	return
+	cultist_name = "Summon [GET_CULT_DATA(entity_name, "your god")]"
+	cultist_desc = "tears apart dimensional barriers, calling forth [GET_CULT_DATA(entity_title3, "your god")]."
 
 /obj/effect/rune/narsie/cult_conceal() //can't hide this, and you wouldn't want to
 	return
@@ -1070,19 +1056,18 @@ structure_check() searches for nearby cultist structures required for the invoca
 	if(used)
 		return
 	var/mob/living/user = invokers[1]
-	var/datum/game_mode/gamemode = SSticker.mode
 	if(!is_station_level(user.z))
 		message_admins("[key_name_admin(user)] tried to summon an eldritch horror off station")
 		log_game("Summon Nar'Sie rune failed - off station Z level")
 		return
-	if(gamemode.cult_objs.cult_status == NARSIE_HAS_RISEN)
+	if(SSticker.mode.cult_team.cult_status == NARSIE_HAS_RISEN)
 		for(var/M in invokers)
 			to_chat(M, "<span class='cultlarge'>\"I am already here. There is no need to try to summon me now.\"</span>")
 		log_game("Summon god rune failed - already summoned")
 		return
 
 	//BEGIN THE SUMMONING
-	gamemode.cult_objs.succesful_summon()
+	SSticker.mode.cult_team.successful_summon()
 	used = TRUE
 	color = COLOR_RED
 	..()
@@ -1098,7 +1083,7 @@ structure_check() searches for nearby cultist structures required for the invoca
 	new /obj/singularity/narsie/large(T) //Causes Nar'Sie to spawn even if the rune has been removed
 
 /obj/effect/rune/narsie/attackby(obj/I, mob/user, params)	//Since the narsie rune takes a long time to make, add logging to removal.
-	if((istype(I, /obj/item/melee/cultblade/dagger) && iscultist(user)))
+	if((istype(I, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user)))
 		log_game("Summon Narsie rune erased by [key_name(user)] with a cult dagger")
 		message_admins("[key_name_admin(user)] erased a Narsie rune with a cult dagger")
 	if(istype(I, /obj/item/nullrod))	//Begone foul magiks. You cannot hinder me.
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 82d3a6fa07b1..b1f0fb320bea 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -20,7 +20,6 @@
 	var/probability = 0
 	var/station_was_nuked = FALSE //see nuclearbomb.dm and malfunction.dm
 	var/explosion_in_progress = FALSE //sit back and relax
-	var/list/datum/mind/modePlayer = new
 	var/list/restricted_jobs = list()	// Jobs it doesn't make sense to be.  I.E chaplain or AI cultist
 	var/list/secondary_restricted_jobs = list() // Same as above, but for secondary antagonists
 	var/list/protected_jobs = list()	// Jobs that can't be traitors
diff --git a/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm b/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm
index 11a5d6500d18..bb77d5dd2b89 100644
--- a/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm	
+++ b/code/game/gamemodes/miniantags/demons/slaughter demon/slaughter.dm	
@@ -117,7 +117,7 @@
 	return new /datum/spell_targeting/alive_mob_list
 
 /obj/effect/proc_holder/spell/sense_victims/valid_target(mob/living/target, user)
-	return target.stat == CONSCIOUS && target.key && !iscultist(target) // Only conscious, non cultist players
+	return target.stat == CONSCIOUS && target.key && !IS_CULTIST(target) // Only conscious, non cultist players
 
 /obj/effect/proc_holder/spell/sense_victims/cast(list/targets, mob/user)
 	var/mob/living/victim = targets[1]
@@ -152,7 +152,7 @@
 		S.mind.assigned_role = "Harbinger of the Slaughter"
 		S.mind.special_role = "Harbinger of the Slaughter"
 		to_chat(S, playstyle_string)
-		SSticker.mode.add_cultist(S.mind)
+		S.mind.add_antag_datum(/datum/antagonist/cultist)
 		var/obj/effect/proc_holder/spell/sense_victims/SV = new
 		AddSpell(SV)
 
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index 94d6590b82a3..93c6eaec86d5 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -1,6 +1,3 @@
-// then call SSticker.mode.add_revolutionary(_THE_PLAYERS_MIND_)
-// nothing else needs to be done, as that proc will check if they are a valid target.
-// Just make sure the converter is a head before you call it!
 // To remove a rev (from brainwashing or w/e), call SSticker.mode.remove_revolutionary(_THE_PLAYERS_MIND_),
 // this will also check they're not a head, so it can just be called freely
 
@@ -41,7 +38,7 @@
 	if(GLOB.configuration.gamemode.prevent_mindshield_antags)
 		restricted_jobs |= protected_jobs
 
-	for(var/i = 1 to REVOLUTION_MAX_HEADREVS)
+	for(var/i in 1 to REVOLUTION_MAX_HEADREVS)
 		if(!length(possible_revolutionaries))
 			break
 		var/datum/mind/new_headrev = pick_n_take(possible_revolutionaries)
@@ -56,14 +53,13 @@
 
 /datum/game_mode/revolution/post_setup()
 
-	rev_team = new /datum/team/revolution()
+	get_rev_team()
 
 	for(var/i in 1 to rev_team.need_another_headrev(1)) // yes this is a ONE, not a true
 		if(!length(pre_revolutionaries))
 			break
 		var/datum/mind/new_headrev = pick_n_take(pre_revolutionaries)
 		new_headrev.add_antag_datum(/datum/antagonist/rev/head)
-		rev_team.add_member(new_headrev)
 
 	..()
 
@@ -81,7 +77,7 @@
 
 /datum/game_mode/proc/get_rev_team()
 	if(!rev_team)
-		rev_team = new /datum/team/revolution()
+		new /datum/team/revolution() // assignment happens in create_team()
 	return rev_team
 
 //////////////////////////////////////
@@ -109,25 +105,6 @@
 		return TRUE
 	return ..()
 
-///////////////////////////////////////////////////
-//Deals with converting players to the revolution//
-///////////////////////////////////////////////////
-/datum/game_mode/proc/add_revolutionary(datum/mind/rev_mind)
-	var/mob/living/carbon/human/conversion_target = rev_mind.current
-	if(rev_mind.assigned_role in GLOB.command_positions)
-		return FALSE
-	if(ismindshielded(conversion_target))
-		return FALSE
-	if(rev_mind.has_antag_datum(/datum/antagonist/rev))
-		return FALSE
-	if(!conversion_target)
-		return FALSE
-	rev_team.add_member(rev_mind)
-
-	conversion_target.Silence(10 SECONDS)
-	conversion_target.Stun(10 SECONDS)
-	return TRUE
-
 //////////////////////////////////////////////////////////////////////////////
 //Deals with players being converted from the revolution (Not a rev anymore)//  // Modified to handle borged MMIs.  Accepts another var if the target is being borged at the time  -- Polymorph.
 //////////////////////////////////////////////////////////////////////////////
@@ -136,9 +113,8 @@
 	var/remove_head = (beingborged && rev_mind.has_antag_datum(/datum/antagonist/rev/head))
 
 	if(rev_mind.has_antag_datum(/datum/antagonist/rev, FALSE) || remove_head)
-		var/datum/antagonist/rev = rev_mind.has_antag_datum(/datum/antagonist/rev)
-		rev.silent = TRUE // We have some custom text, lets make the removal silent
-		rev_team.remove_member(rev_mind)
+		// We have some custom text, lets make the removal silent
+		rev_mind.remove_antag_datum(/datum/antagonist/rev, silent_removal = TRUE)
 
 		if(beingborged)
 			revolutionary.visible_message(
diff --git a/code/game/gamemodes/setupgame.dm b/code/game/gamemodes/setupgame.dm
index 21f6e1eced41..66cd614b9660 100644
--- a/code/game/gamemodes/setupgame.dm
+++ b/code/game/gamemodes/setupgame.dm
@@ -130,17 +130,3 @@
 				GLOB.assigned_mutation_blocks[block] = mutation
 
 	//testing("DNA2: [numsToAssign.len] blocks are unused: [english_list(numsToAssign)]")
-
-/proc/setupcult()
-	var/static/datum/cult_info/picked_cult // Only needs to get picked once
-
-	if(picked_cult)
-		return picked_cult
-
-	var/random_cult = pick(typesof(/datum/cult_info))
-	picked_cult = new random_cult()
-
-	if(!picked_cult)
-		stack_trace("Cult datum creation failed")
-	//todo:add adminonly datum var, check for said var here...
-	return picked_cult
diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm
index e983c7ed57fc..c13da60ffc9a 100644
--- a/code/game/gamemodes/wizard/soulstone.dm
+++ b/code/game/gamemodes/wizard/soulstone.dm
@@ -35,10 +35,10 @@
 	held_body = null
 
 /obj/item/soulstone/proc/can_use(mob/living/user)
-	if(iscultist(user) && purified && !iswizard(user))
+	if(IS_CULTIST(user) && purified && !iswizard(user))
 		return FALSE
 
-	if(iscultist(user) || iswizard(user) || (HAS_MIND_TRAIT(user, TRAIT_HOLY) && purified) || usability)
+	if(IS_CULTIST(user) || iswizard(user) || (HAS_MIND_TRAIT(user, TRAIT_HOLY) && purified) || usability)
 		return TRUE
 
 	return FALSE
@@ -66,7 +66,7 @@
 
 /obj/item/soulstone/pickup(mob/living/user)
 	. = ..()
-	if(iscultist(user) && purified && !iswizard(user))
+	if(IS_CULTIST(user) && purified && !iswizard(user))
 		to_chat(user, "<span class='danger'>[src] reeks of holy magic. You will need to cleanse it with a ritual dagger before anything can be done with it.</span>")
 		return
 	if(HAS_MIND_TRAIT(user, TRAIT_HOLY))
@@ -120,7 +120,7 @@
 		to_chat(user, "<span class='warning'>A mysterious force prevents you from trapping this being's soul.</span>")
 		return ..()
 
-	if(iscultist(user) && iscultist(M))
+	if(IS_CULTIST(user) && IS_CULTIST(M))
 		to_chat(user, "<span class='cultlarge'>\"Come now, do not capture your fellow's soul.\"</span>")
 		return ..()
 
@@ -178,7 +178,7 @@
 	return
 
 /obj/item/soulstone/attackby(obj/item/O, mob/user)
-	if(istype(O, /obj/item/storage/bible) && !iscultist(user) && HAS_MIND_TRAIT(user, TRAIT_HOLY))
+	if(istype(O, /obj/item/storage/bible) && !IS_CULTIST(user) && HAS_MIND_TRAIT(user, TRAIT_HOLY))
 		if(purified)
 			return
 		to_chat(user, "<span class='notice'>You begin to exorcise [src].</span>")
@@ -193,9 +193,9 @@
 			for(var/mob/M in contents)
 				if(M.mind)
 					icon_state = "purified_soulstone2"
-					if(iscultist(M))
-						SSticker.mode.remove_cultist(M.mind, FALSE)
-						to_chat(M, "<span class='userdanger'>An unfamiliar white light flashes through your mind, cleansing the taint of [SSticker.cultdat ? SSticker.cultdat.entity_title1 : "Nar'Sie"] \
+					if(IS_CULTIST(M))
+						M.mind.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
+						to_chat(M, "<span class='userdanger'>An unfamiliar white light flashes through your mind, cleansing the taint of [GET_CULT_DATA(entity_title1, "Nar'Sie")] \
 									and the memories of your time as their servant with it.</span>")
 						to_chat(M, "<span class='danger'>Assist [user], your saviour, and get vengeance on those who enslaved you!</span>")
 					else
@@ -206,7 +206,7 @@
 				EX.icon_state = "shade_angelic"
 			user.visible_message("<span class='notice'>[user] purifies [src]!</span>", "<span class='notice'>You purify [src]!</span>")
 
-	else if(istype(O, /obj/item/melee/cultblade/dagger) && iscultist(user))
+	else if(istype(O, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user))
 		if(!purified)
 			return
 		to_chat(user, "<span class='notice'>You begin to cleanse [src] of holy magic.</span>")
@@ -220,11 +220,11 @@
 			for(var/mob/M in contents)
 				if(M.mind)
 					icon_state = "soulstone2"
-					SSticker.mode.add_cultist(M.mind)
+					M.mind.add_antag_datum(/datum/antagonist/cultist)
 					to_chat(M, "<span class='cult'>Your shard has been cleansed of holy magic, and you are now bound to the cult's will. Obey them and assist in their goals.</span>")
 			for(var/mob/living/simple_animal/shade/EX in src)
 				EX.holy = FALSE
-				EX.icon_state = SSticker.cultdat?.shade_icon_state
+				EX.icon_state = GET_CULT_DATA(shade_icon_state, "shade")
 			to_chat(user, "<span class='notice'>You have cleansed [src] of holy magic.</span>")
 	else
 		..()
@@ -254,7 +254,7 @@
 		B.brainmob.key = S.key
 	S.cancel_camera()
 	held_body.forceMove(get_turf(src))
-	SSticker.mode.add_cult_immunity(held_body)
+	SSticker.mode?.cult_team?.add_cult_immunity(held_body)
 	remove_held_body()
 	new /obj/effect/temp_visual/cult/sparks(get_turf(src))
 	playsound(src, 'sound/effects/pylon_shatter.ogg', 40, TRUE)
@@ -269,7 +269,7 @@
 		else
 			icon_state = "soulstone"
 		name = initial(name)
-		if(iscultist(A))
+		if(IS_CULTIST(A))
 			to_chat(A, "<span class='userdanger'>You have been released from your prison, but you are still bound to the cult's will. Help them succeed in their goals at all costs.</span>")
 		else
 			to_chat(A, "<span class='userdanger'>You have been released from your prison, but you are still bound to your [purified ? "saviour" : "creator"]'s will.</span>")
@@ -288,7 +288,7 @@
 
 /obj/structure/constructshell/examine(mob/user)
 	. = ..()
-	if(in_range(user, src) && (iscultist(user) || iswizard(user) || user.stat == DEAD))
+	if(in_range(user, src) && (IS_CULTIST(user) || iswizard(user) || user.stat == DEAD))
 		. += "<span class='cult'>A construct shell, used to house bound souls from a soulstone.</span>"
 		. += "<span class='cult'>Placing a soulstone with a soul into this shell allows you to produce your choice of the following:</span>"
 		. += "<span class='cultitalic'>An <b>Artificer</b>, which can produce <b>more shells and soulstones</b>, as well as fortifications.</span>"
@@ -349,7 +349,7 @@
 				return
 			if(T.stat == DEAD)
 				to_chat(user, "<span class='danger'>Capture failed!</span> The shade has already been banished!")
-			if((iscultist(T) && purified) || (T.holy && !purified))
+			if((IS_CULTIST(T) && purified) || (T.holy && !purified))
 				to_chat(user, "<span class='danger'>Capture failed!</span> The shade recoils away from [src]!")
 			else
 				if(locate(/mob/living/simple_animal/shade) in contents)
@@ -370,9 +370,9 @@
 											"Wraith" = /mob/living/simple_animal/hostile/construct/wraith,
 											"Artificer" = /mob/living/simple_animal/hostile/construct/builder)
 			/// Custom construct icons for different cults
-			var/list/construct_icons = list("Juggernaut" = image(icon = 'icons/mob/cult.dmi', icon_state = SSticker.cultdat.get_icon("juggernaut")),
-											"Wraith" = image(icon = 'icons/mob/cult.dmi', icon_state = SSticker.cultdat.get_icon("wraith")),
-											"Artificer" = image(icon = 'icons/mob/cult.dmi', icon_state = SSticker.cultdat.get_icon("builder")))
+			var/list/construct_icons = list("Juggernaut" = image(icon = 'icons/mob/cult.dmi', icon_state = GET_CULT_DATA(get_icon("juggernaut"), "behemoth")),
+											"Wraith" = image(icon = 'icons/mob/cult.dmi', icon_state = GET_CULT_DATA(get_icon("wraith"), "floating")),
+											"Artificer" = image(icon = 'icons/mob/cult.dmi', icon_state = GET_CULT_DATA(get_icon("builder"), "artificer")))
 
 			if(shade)
 				var/construct_choice = show_radial_menu(user, shell, construct_icons, custom_check = CALLBACK(src, PROC_REF(radial_check), user), require_near = TRUE)
@@ -404,12 +404,12 @@
 			RemoveSpell(/obj/effect/proc_holder/spell/aoe/conjure/build/soulstone)
 			AddSpell(new /obj/effect/proc_holder/spell/aoe/conjure/build/soulstone/holy)
 
-	else if(iscultist(src)) // Re-grant cult actions, lost in the transfer
+	else if(mind.has_antag_datum(/datum/antagonist/cultist)) // Re-grant cult actions, lost in the transfer
 		var/datum/action/innate/cult/comm/CC = new
 		var/datum/action/innate/cult/check_progress/D = new
 		CC.Grant(src)
 		D.Grant(src)
-		SSticker.mode.cult_objs.study(src) // Display objectives again
+		SSticker.mode.cult_team.study_objectives(src) // Display objectives again
 		to_chat(src, "<span class='userdanger'>You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.</span>")
 	else
 		to_chat(src, "<span class='userdanger'>You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.</span>")
@@ -429,10 +429,9 @@
 		new /obj/effect/particle_effect/smoke/sleeping(target.loc)
 	C.faction |= "\ref[user]"
 	C.key = target.key
-	if(user && iscultist(user) || cult_override)
-		SSticker.mode.add_cultist(C.mind)
-		SSticker.mode.update_cult_icons_added(C.mind)
-	if(user && iscultist(user))
+	if(user && IS_CULTIST(user) || cult_override)
+		C.mind.add_antag_datum(/datum/antagonist/cultist)
+	if(user && IS_CULTIST(user))
 		to_chat(C, "<B>You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.</B>")
 	else
 		to_chat(C, "<B>You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.</B>")
@@ -454,10 +453,8 @@
 		if(iswizard(user))
 			SSticker.mode.update_wiz_icons_added(S.mind)
 			S.mind.special_role = SPECIAL_ROLE_WIZARD_APPRENTICE
-		if(iscultist(user))
-			SSticker.mode.add_cultist(S.mind)
-			S.mind.special_role = SPECIAL_ROLE_CULTIST
-			S.mind.store_memory("<b>Serve the cult's will.</b>")
+		if(IS_CULTIST(user))
+			S.mind.add_antag_datum(/datum/antagonist/cultist)
 			to_chat(S, "<span class='userdanger'>Your soul has been captured! You are now bound to the cult's will. Help them succeed in their goals at all costs.</span>")
 		else
 			S.mind.store_memory("<b>Serve [user.real_name], your creator.</b>")
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index ec1c1a186603..66e13e9fb79b 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -28,7 +28,6 @@
 	var/datum/mind/wizard = pick(possible_wizards)
 
 	wizards += wizard
-	modePlayer += wizard
 	wizard.assigned_role = SPECIAL_ROLE_WIZARD //So they aren't chosen for other jobs.
 	wizard.special_role = SPECIAL_ROLE_WIZARD
 	wizard.set_original_mob(wizard.current)
diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm
index d30d3479d9ad..2a07b6634ae6 100644
--- a/code/game/jobs/job/job.dm
+++ b/code/game/jobs/job/job.dm
@@ -76,6 +76,7 @@
 	SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_SPAWN, src, H)
 
 /datum/job/proc/announce(mob/living/carbon/human/H)
+	return
 
 /datum/job/proc/equip(mob/living/carbon/human/H, visualsOnly = FALSE, announce = TRUE)
 	if(!H)
diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm
index d72467b78479..f8898a07b65c 100644
--- a/code/game/machinery/computer/ai_core.dm
+++ b/code/game/machinery/computer/ai_core.dm
@@ -174,7 +174,7 @@
 					GLOB.empty_playable_ai_cores += D
 			else
 				if(brain.brainmob.mind)
-					SSticker.mode.remove_cultist(brain.brainmob.mind, 1)
+					brain.brainmob.mind.remove_antag_datum(/datum/antagonist/cultist)
 					SSticker.mode.remove_revolutionary(brain.brainmob.mind, 1)
 
 				var/mob/living/silicon/ai/A = new /mob/living/silicon/ai(loc, laws, brain)
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index bd691273d8a0..be54c452d939 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -354,9 +354,8 @@
 			I.forceMove(loc)
 
 	// Find a new sacrifice target if needed, if unable allow summoning
-	if(is_sacrifice_target(occupant.mind))
-		if(!SSticker.mode.cult_objs.find_new_sacrifice_target())
-			SSticker.mode.cult_objs.ready_to_summon()
+	if(IS_SACRIFICE_TARGET(occupant.mind))
+		SSticker.mode.cult_team.find_new_sacrifice_target()
 
 	//Update any existing objectives involving this mob.
 	if(occupant.mind)
diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm
index 407fd2bcf879..c2c47c9c9fee 100644
--- a/code/game/machinery/doors/airlock_types.dm
+++ b/code/game/machinery/doors/airlock_types.dm
@@ -505,18 +505,18 @@
 
 /obj/machinery/door/airlock/cult/Initialize()
 	. = ..()
-	icon = SSticker.cultdat?.airlock_runed_icon_file
-	overlays_file = SSticker.cultdat?.airlock_runed_overlays_file
+	icon = GET_CULT_DATA(airlock_runed_icon_file, initial(icon))
+	overlays_file = GET_CULT_DATA(airlock_runed_overlays_file, initial(overlays_file))
 	update_icon()
 	new openingoverlaytype(loc)
 
 /obj/machinery/door/airlock/cult/canAIControl(mob/user)
-	return (iscultist(user) && !isAllPowerLoss())
+	return (IS_CULTIST(user) && !isAllPowerLoss())
 
 /obj/machinery/door/airlock/cult/allowed(mob/living/L)
 	if(!density)
 		return TRUE
-	if(friendly || iscultist(L) || isshade(L)|| isconstruct(L))
+	if(friendly || IS_CULTIST(L) || isshade(L) || isconstruct(L))
 		if(!stealthy)
 			new openingoverlaytype(loc)
 		return TRUE
@@ -545,8 +545,8 @@
 	update_icon()
 
 /obj/machinery/door/airlock/cult/cult_reveal()
-	icon = SSticker.cultdat?.airlock_runed_icon_file
-	overlays_file = SSticker.cultdat?.airlock_runed_overlays_file
+	icon = GET_CULT_DATA(airlock_runed_icon_file, initial(icon))
+	overlays_file = GET_CULT_DATA(airlock_runed_overlays_file, initial(overlays_file))
 	opacity = initial(opacity)
 	glass = initial(glass)
 	airlock_material = initial(airlock_material)
@@ -583,8 +583,8 @@
 
 /obj/machinery/door/airlock/cult/unruned/Initialize()
 	. = ..()
-	icon = SSticker.cultdat?.airlock_unruned_icon_file
-	overlays_file = SSticker.cultdat?.airlock_unruned_overlays_file
+	icon = GET_CULT_DATA(airlock_unruned_icon_file, initial(icon))
+	overlays_file = GET_CULT_DATA(airlock_unruned_overlays_file, initial(overlays_file))
 	update_icon()
 
 /obj/machinery/door/airlock/cult/unruned/friendly
diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm
index 109e3e82dd14..afc9e630994b 100644
--- a/code/game/machinery/shieldgen.dm
+++ b/code/game/machinery/shieldgen.dm
@@ -97,7 +97,7 @@
 	parent_rune.attack_hand(user)
 
 /obj/machinery/shield/cult/barrier/attack_animal(mob/living/simple_animal/user)
-	if(iscultist(user))
+	if(IS_CULTIST(user))
 		parent_rune.attack_animal(user)
 	else
 		..()
diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm
index 54ca83df7328..7b8b9d835196 100644
--- a/code/game/machinery/syndicatebomb.dm
+++ b/code/game/machinery/syndicatebomb.dm
@@ -344,6 +344,7 @@
 	qdel(src)
 
 /obj/item/bombcore/proc/defuse()
+	return
 //Note: 	Because of how var/defused is used you shouldn't override this UNLESS you intend to set the var to 0 or
 //			otherwise remove the core/reset the wires before the end of defuse(). It will repeatedly be called otherwise.
 
diff --git a/code/game/magic/Uristrunes.dm b/code/game/magic/Uristrunes.dm
deleted file mode 100644
index 11ca14679183..000000000000
--- a/code/game/magic/Uristrunes.dm
+++ /dev/null
@@ -1,95 +0,0 @@
-/proc/get_rune_cult(word)
-	var/animated
-
-	if(word && !(SSticker.cultdat.theme == "fire" || SSticker.cultdat.theme == "death"))
-		animated = 1
-	else
-		animated = 0
-
-	var/bits = make_bit_triplet()
-
-	return get_rune(bits, animated)
-
-
-GLOBAL_LIST_EMPTY(cult_rune_cache)
-GLOBAL_VAR_INIT(cult_rune_style, "rune") // Style of run the cult is using (fire, death, regular, etc)
-
-/proc/get_rune(symbol_bits, animated = 0)
-	var/lookup = "[symbol_bits]-[animated]"
-
-
-	if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way
-		GLOB.cult_rune_style = "rune"
-	else if(SSticker.cultdat.theme == "fire")
-		GLOB.cult_rune_style = "fire-rune"
-	else if(SSticker.cultdat.theme == "death")
-		GLOB.cult_rune_style = "death-rune"
-
-
-	if(lookup in GLOB.cult_rune_cache)
-		return GLOB.cult_rune_cache[lookup]
-
-	var/icon/I = icon('icons/effects/uristrunes.dmi', "[GLOB.cult_rune_style]-179")
-
-	for(var/i = 0, i < 10, i++)
-		if(symbol_bits & (1 << i))
-			I.Blend(icon('icons/effects/uristrunes.dmi', "[GLOB.cult_rune_style]-[1 << i]"), ICON_OVERLAY)
-
-
-	I.SwapColor(rgb(0, 0, 0, 100), rgb(100, 0, 0, 200))//TO DO COMMENT:NEED TO ADJUST FOR DIFFRNET CULTS
-	I.SwapColor(rgb(0, 0, 0, 50), rgb(150, 0, 0, 200))
-
-	for(var/x = 1, x <= 32, x++)
-		for(var/y = 1, y <= 32, y++)
-			var/p = I.GetPixel(x, y)
-
-			if(p == null)
-				var/n = I.GetPixel(x, y + 1)
-				var/s = I.GetPixel(x, y - 1)
-				var/e = I.GetPixel(x + 1, y)
-				var/w = I.GetPixel(x - 1, y)
-
-				if(n == "#000000" || s == "#000000" || e == "#000000" || w == "#000000")
-					I.DrawBox(rgb(200, 0, 0, 200), x, y)
-
-				else
-					var/ne = I.GetPixel(x + 1, y + 1)
-					var/se = I.GetPixel(x + 1, y - 1)
-					var/nw = I.GetPixel(x - 1, y + 1)
-					var/sw = I.GetPixel(x - 1, y - 1)
-
-					if(ne == "#000000" || se == "#000000" || nw == "#000000" || sw == "#000000")
-						I.DrawBox(rgb(200, 0, 0, 100), x, y)
-
-	var/icon/result = icon(I, "")
-
-	result.Insert(I,  "", frame = 1, delay = 10)
-
-	if(animated == 1)
-		var/icon/I2 = icon(I, "")
-		I2.MapColors(rgb(0xff,0x0c,0,0), rgb(0,0,0,0), rgb(0,0,0,0), rgb(0,0,0,0xff))
-		I2.SetIntensity(1.04)
-
-		var/icon/I3 = icon(I, "")
-		I3.MapColors(rgb(0xff,0x18,0,0), rgb(0,0,0,0), rgb(0,0,0,0), rgb(0,0,0,0xff))
-		I3.SetIntensity(1.08)
-
-		var/icon/I4 = icon(I, "")
-		I4.MapColors(rgb(0xff,0x24,0,0), rgb(0,0,0,0), rgb(0,0,0,0), rgb(0,0,0,0xff))
-		I4.SetIntensity(1.12)
-
-		var/icon/I5 = icon(I, "")
-		I5.MapColors(rgb(0xff,0x30,0,0), rgb(0,0,0,0), rgb(0,0,0,0), rgb(0,0,0,0xff))
-		I5.SetIntensity(1.16)
-
-		result.Insert(I2, "", frame = 2, delay = 4)
-		result.Insert(I3, "", frame = 3, delay = 3)
-		result.Insert(I4, "", frame = 4, delay = 2)
-		result.Insert(I5, "", frame = 5, delay = 6)
-		result.Insert(I4, "", frame = 6, delay = 2)
-		result.Insert(I3, "", frame = 7, delay = 2)
-		result.Insert(I2, "", frame = 8, delay = 2)
-
-	GLOB.cult_rune_cache[lookup] = result
-
-	return result
diff --git a/code/game/objects/!objs.dm b/code/game/objects/!objs.dm
index 5947d66a8010..e4e4745c4d2e 100644
--- a/code/game/objects/!objs.dm
+++ b/code/game/objects/!objs.dm
@@ -80,7 +80,7 @@
 	host.add_fingerprint(user)
 
 /obj/proc/CouldNotUseTopic(mob/user)
-	// Nada
+	return
 
 /obj/Destroy()
 	if(!ismachinery(src))
diff --git a/code/game/objects/effects/temporary_visuals/misc_visuals.dm b/code/game/objects/effects/temporary_visuals/misc_visuals.dm
index aec954ae0855..736590114a21 100644
--- a/code/game/objects/effects/temporary_visuals/misc_visuals.dm
+++ b/code/game/objects/effects/temporary_visuals/misc_visuals.dm
@@ -80,14 +80,14 @@
 
 /obj/effect/temp_visual/dir_setting/wraith/Initialize(mapload)
 	. = ..()
-	icon_state = SSticker.cultdat?.wraith_jaunt_in_animation
+	icon_state = GET_CULT_DATA(wraith_jaunt_in_animation, initial(icon_state))
 
 /obj/effect/temp_visual/dir_setting/wraith/out
 	icon_state = "phase_shift"
 
 /obj/effect/temp_visual/dir_setting/wraith/out/Initialize(mapload)
 	. = ..()
-	icon_state = SSticker.cultdat?.wraith_jaunt_out_animation
+	icon_state = GET_CULT_DATA(wraith_jaunt_out_animation, initial(icon_state))
 
 /obj/effect/temp_visual/dir_setting/tailsweep
 	icon_state = "tailsweep"
diff --git a/code/game/objects/empulse.dm b/code/game/objects/empulse.dm
index e2bc73d59fa8..f8519f205b30 100644
--- a/code/game/objects/empulse.dm
+++ b/code/game/objects/empulse.dm
@@ -31,7 +31,7 @@
 	for(var/mob/M in range(heavy_range, epicenter))
 		SEND_SOUND(M, emp_sound)
 	for(var/atom/T in range(light_range, epicenter))
-		if(cause == "cult" && iscultist(T))
+		if(cause == "cult" && IS_CULTIST(T))
 			continue
 		var/distance = get_dist(epicenter, T)
 		var/will_affect = FALSE
diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm
index f5a857f86c5e..5faa9203f8ca 100644
--- a/code/game/objects/items/devices/flash.dm
+++ b/code/game/objects/items/devices/flash.dm
@@ -180,11 +180,27 @@
 		return
 	if(M.stat != CONSCIOUS)
 		to_chat(user, "<span class='warning'>They must be conscious before you can convert [M.p_them()]!</span>")
-	else if(SSticker.mode.add_revolutionary(M.mind))
+	else if(add_revolutionary(M.mind))
 		times_used-- //Flashes less likely to burn out for headrevs when used for conversion
 	else
 		to_chat(user, "<span class='warning'>This mind seems resistant to [src]!</span>")
 
+/obj/item/flash/proc/add_revolutionary(datum/mind/converting_mind)
+	var/mob/living/carbon/human/conversion_target = converting_mind.current
+	if(converting_mind.assigned_role in GLOB.command_positions)
+		return FALSE
+	if(!istype(conversion_target))
+		return FALSE
+	if(ismindshielded(conversion_target))
+		return FALSE
+	if(converting_mind.has_antag_datum(/datum/antagonist/rev))
+		return FALSE
+	converting_mind.add_antag_datum(/datum/antagonist/rev)
+
+	conversion_target.Silence(10 SECONDS)
+	conversion_target.Stun(10 SECONDS)
+	return TRUE
+
 /obj/item/flash/cyborg
 	origin_tech = null
 
diff --git a/code/game/objects/items/devices/radio/encryptionkey.dm b/code/game/objects/items/devices/radio/encryptionkey.dm
index 14a3daf131d2..490ee42151d5 100644
--- a/code/game/objects/items/devices/radio/encryptionkey.dm
+++ b/code/game/objects/items/devices/radio/encryptionkey.dm
@@ -13,9 +13,6 @@
 	var/change_voice = FALSE
 	var/list/channels = list()
 
-
-/obj/item/encryptionkey/attackby(obj/item/W as obj, mob/user as mob, params)
-
 /obj/item/encryptionkey/syndicate
 	name = "syndicate encryption key"
 	icon_state = "syn_cypherkey"
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index ceccaf3c74cf..ba66cfd4ce47 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -493,11 +493,11 @@ GLOBAL_LIST_INIT(cult_recipes, list (
 
 /obj/item/stack/sheet/runed_metal/New()
 	. = ..()
-	icon_state = SSticker.cultdat?.runed_metal_icon_state
-	item_state = SSticker.cultdat?.runed_metal_item_state
+	icon_state = GET_CULT_DATA(runed_metal_icon_state, initial(icon_state))
+	item_state = GET_CULT_DATA(runed_metal_item_state, initial(item_state))
 
 /obj/item/stack/sheet/runed_metal/attack_self(mob/living/user)
-	if(!iscultist(user))
+	if(!IS_CULTIST(user))
 		to_chat(user, "<span class='warning'>Only one with forbidden knowledge could hope to work this metal...</span>")
 		return
 	if(usr.holy_check())
diff --git a/code/game/objects/items/weapons/AI_modules.dm b/code/game/objects/items/weapons/AI_modules.dm
index 244d9cd9555b..a1b8c5ea69fd 100644
--- a/code/game/objects/items/weapons/AI_modules.dm
+++ b/code/game/objects/items/weapons/AI_modules.dm
@@ -104,6 +104,7 @@ AI MODULES
 	log_and_message_admins("used [src.name] on [target.name]([target.key])")
 
 /obj/item/aiModule/proc/addAdditionalLaws(mob/living/silicon/ai/target, mob/sender)
+	return
 
 
 /******************** Safeguard ********************/
diff --git a/code/game/objects/items/weapons/bio_chips/bio_chip_mindshield.dm b/code/game/objects/items/weapons/bio_chips/bio_chip_mindshield.dm
index 0eef4a4100cc..23ca69c16db0 100644
--- a/code/game/objects/items/weapons/bio_chips/bio_chip_mindshield.dm
+++ b/code/game/objects/items/weapons/bio_chips/bio_chip_mindshield.dm
@@ -19,7 +19,7 @@
 	if(target.mind)
 		if(target.mind.has_antag_datum(/datum/antagonist/rev))
 			SSticker.mode.remove_revolutionary(target.mind)
-		if(target.mind in SSticker.mode.cult)
+		if(IS_CULTIST(target))
 			to_chat(target, "<span class='warning'>You feel the corporate tendrils of Nanotrasen try to invade your mind!</span>")
 		return TRUE
 
diff --git a/code/game/objects/items/weapons/grenades/grenade.dm b/code/game/objects/items/weapons/grenades/grenade.dm
index bbb2eac75f46..37350f866cce 100644
--- a/code/game/objects/items/weapons/grenades/grenade.dm
+++ b/code/game/objects/items/weapons/grenades/grenade.dm
@@ -82,6 +82,7 @@
 
 
 /obj/item/grenade/proc/prime()
+	return
 
 /obj/item/grenade/proc/update_mob()
 	if(ismob(loc))
diff --git a/code/game/objects/items/weapons/holy_weapons.dm b/code/game/objects/items/weapons/holy_weapons.dm
index 25f511ce2b83..e29a0f02735a 100644
--- a/code/game/objects/items/weapons/holy_weapons.dm
+++ b/code/game/objects/items/weapons/holy_weapons.dm
@@ -561,8 +561,10 @@
 			var/mob/living/carbon/human/target = M
 
 			if(target.mind)
-				if(iscultist(target))
-					SSticker.mode.remove_cultist(target.mind, TRUE, TRUE) // This proc will handle message generation.
+				if(IS_CULTIST(target))
+					var/datum/antagonist/cultist/cultist = IS_CULTIST(target)
+					cultist.remove_gear_on_removal = TRUE
+					target.mind.remove_antag_datum(/datum/antagonist/cultist)
 					praying = FALSE
 					return
 				var/datum/antagonist/vampire/V = M.mind?.has_antag_datum(/datum/antagonist/vampire)
diff --git a/code/game/objects/items/weapons/melee/melee_misc.dm b/code/game/objects/items/weapons/melee/melee_misc.dm
index 6a04d96cd7ce..d5fe980d3398 100644
--- a/code/game/objects/items/weapons/melee/melee_misc.dm
+++ b/code/game/objects/items/weapons/melee/melee_misc.dm
@@ -156,7 +156,7 @@
 
 /obj/item/melee/spellblade/examine(mob/user)
 	. = ..()
-	if(enchant && (iswizard(user) || iscultist(user))) // only wizards and cultists understand runes
+	if(enchant && (iswizard(user) || IS_CULTIST(user))) // only wizards and cultists understand runes
 		. += "The runes along the side read; [enchant.desc]."
 
 
diff --git a/code/game/objects/items/weapons/storage/lockbox.dm b/code/game/objects/items/weapons/storage/lockbox.dm
index 503f92cafc2c..f085fafaae11 100644
--- a/code/game/objects/items/weapons/storage/lockbox.dm
+++ b/code/game/objects/items/weapons/storage/lockbox.dm
@@ -82,8 +82,10 @@
 		return
 
 /obj/item/storage/lockbox/hear_talk(mob/living/M as mob, list/message_pieces)
+	return
 
 /obj/item/storage/lockbox/hear_message(mob/living/M as mob, msg)
+	return
 
 /obj/item/storage/lockbox/mindshield
 	name = "Lockbox (Mindshield Implants)"
diff --git a/code/game/objects/structures/door_assembly_types.dm b/code/game/objects/structures/door_assembly_types.dm
index 0bbd26b89972..fe0786990311 100644
--- a/code/game/objects/structures/door_assembly_types.dm
+++ b/code/game/objects/structures/door_assembly_types.dm
@@ -170,8 +170,8 @@
 
 /obj/structure/door_assembly/door_assembly_cult/Initialize(mapload)
 	. = ..()
-	icon = SSticker.cultdat?.airlock_runed_icon_file
-	overlays_file = SSticker.cultdat?.airlock_runed_overlays_file
+	icon = GET_CULT_DATA(airlock_runed_icon_file, initial(icon))
+	overlays_file = GET_CULT_DATA(airlock_runed_overlays_file, initial(overlays_file))
 	update_icon()
 
 /obj/structure/door_assembly/door_assembly_cult/unruned
@@ -182,8 +182,8 @@
 
 /obj/structure/door_assembly/door_assembly_cult/unruned/Initialize(mapload)
 	. = ..()
-	icon = SSticker.cultdat?.airlock_unruned_icon_file
-	overlays_file = SSticker.cultdat?.airlock_unruned_overlays_file
+	icon = GET_CULT_DATA(airlock_unruned_icon_file, initial(icon))
+	overlays_file = GET_CULT_DATA(airlock_unruned_overlays_file, initial(overlays_file))
 	update_icon()
 
 /obj/structure/door_assembly/door_assembly_centcom
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index 0ffb9b51f240..d38b69822c90 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -454,11 +454,11 @@
 
 /obj/structure/girder/cult/Initialize(mapload)
 	. = ..()
-	icon_state = SSticker.cultdat?.cult_girder_icon_state
+	icon_state = GET_CULT_DATA(cult_girder_icon_state, initial(icon_state))
 
 /obj/structure/girder/cult/attackby(obj/item/W, mob/user, params)
 	add_fingerprint(user)
-	if(istype(W, /obj/item/melee/cultblade/dagger) && iscultist(user)) //Cultists can demolish cult girders instantly with their dagger
+	if(istype(W, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user)) //Cultists can demolish cult girders instantly with their dagger
 		user.visible_message("<span class='warning'>[user] strikes [src] with [W]!</span>", "<span class='notice'>You demolish [src].</span>")
 		refundMetal(metalUsed)
 		qdel(src)
diff --git a/code/game/turfs/simulated.dm b/code/game/turfs/simulated.dm
index 32ad5312a51e..8ff12079448f 100644
--- a/code/game/turfs/simulated.dm
+++ b/code/game/turfs/simulated.dm
@@ -142,5 +142,6 @@
 	QUEUE_SMOOTH_NEIGHBORS(src)
 
 /turf/simulated/proc/is_shielded()
+	return
 
 #undef WATER_WEAKEN_TIME
diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm
index dd1b5d8a86bb..2672ed9e7348 100644
--- a/code/game/turfs/simulated/floor/plating.dm
+++ b/code/game/turfs/simulated/floor/plating.dm
@@ -235,8 +235,7 @@
 
 /turf/simulated/floor/engine/cult/Initialize(mapload)
 	. = ..()
-	if(SSticker.mode)//only do this if the round is going..otherwise..fucking asteroid..
-		icon_state = SSticker.cultdat.cult_floor_icon_state
+	icon_state = GET_CULT_DATA(cult_floor_icon_state, initial(icon_state))
 
 /turf/simulated/floor/engine/cult/Entered(atom/A, atom/OL, ignoreRest)
 	. = ..()
diff --git a/code/game/turfs/simulated/walls_misc.dm b/code/game/turfs/simulated/walls_misc.dm
index 8198158ec813..db827ec7c183 100644
--- a/code/game/turfs/simulated/walls_misc.dm
+++ b/code/game/turfs/simulated/walls_misc.dm
@@ -15,7 +15,7 @@
 	. = ..()
 	if(SSticker.mode)//game hasn't started officially don't do shit..
 		new /obj/effect/temp_visual/cult/turf(src)
-		icon_state = SSticker.cultdat.cult_wall_icon_state
+		icon_state = GET_CULT_DATA(cult_wall_icon_state, initial(icon_state))
 
 /turf/simulated/wall/cult/bullet_act(obj/item/projectile/Proj)
 	. = ..()
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 56700a89a948..533b510719e9 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -23,6 +23,9 @@ GLOBAL_PROTECT(href_token)
 	/// Our currently linked marked datum
 	var/datum/marked_datum
 
+	/// Our index into GLOB.antagonist_teams, so that admins can have pretty tabs in the Check Teams menu.
+	var/team_switch_tab_index = 1
+
 /datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey)
 	if(IsAdminAdvancedProcCall())
 		to_chat(usr, "<span class='boldannounceooc'>Admin rank creation blocked: Advanced ProcCall detected.</span>")
diff --git a/code/modules/admin/misc_admin_procs.dm b/code/modules/admin/misc_admin_procs.dm
index 909ee5f5624d..81de0ed78b39 100644
--- a/code/modules/admin/misc_admin_procs.dm
+++ b/code/modules/admin/misc_admin_procs.dm
@@ -643,7 +643,7 @@ GLOBAL_VAR_INIT(nologevent, 0)
 		antag_list += "Head Rev"
 	if(M.mind.has_antag_datum(/datum/antagonist/rev, FALSE))
 		antag_list += "Revolutionary"
-	if(M.mind in SSticker.mode.cult)
+	if(IS_CULTIST(M))
 		antag_list += "Cultist"
 	if(M.mind in SSticker.mode.syndicates)
 		antag_list += "Nuclear Operative"
@@ -989,4 +989,29 @@ GLOBAL_VAR_INIT(gamma_ship_location, 1) // 0 = station , 1 = space
 		result[1]++
 	return result
 
+/**
+ * Allows admins to safely pick from SSticker.minds for objectives
+ * - caller, mob to ask for results
+ * - blacklist, optional list of targets that are not available
+ * - default_target, the target to show in the list as default
+ */
+/proc/get_admin_objective_targets(mob/caller, list/blacklist, mob/default_target)
+	if(!islist(blacklist))
+		blacklist = list(blacklist)
+
+	var/list/possible_targets = list()
+	for(var/datum/mind/possible_target in SSticker.minds)
+		if(!(possible_target in blacklist) && ishuman(possible_target.current))
+			possible_targets += possible_target.current // Allows for admins to pick off station roles
+
+	if(!length(possible_targets))
+		to_chat(caller, "<span class='warning'>No possible target found.</span>")
+		return
+
+	possible_targets = sortAtom(possible_targets)
+
+	var/mob/new_target = input(caller, "Select target:", "Objective target", default_target) as null|anything in possible_targets
+	if(!QDELETED(new_target))
+		return new_target.mind
+
 #undef PLAYER_NOTES_ENTRIES_PER_PAGE
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index 00d9aa49cf54..98cfdce2f7d6 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -429,34 +429,9 @@
 		/*if(ticker.mode.ninjas.len)
 			dat += check_role_table("Ninjas", ticker.mode.ninjas)*/
 
-		if(SSticker.mode.cult.len)
-			var/datum/game_mode/gamemode = SSticker.mode
-			var/datum/objective/current_sac_obj = gamemode.cult_objs.current_sac_objective()
-			dat += check_role_table("Cultists", SSticker.mode.cult)
-			if(current_sac_obj)
-				dat += "<br>Current cult objective: <br>[current_sac_obj.explanation_text]"
-			else if(gamemode.cult_objs.cult_status == NARSIE_NEEDS_SUMMONING)
-				dat += "<br>Current cult objective: Summon [SSticker.cultdat ? SSticker.cultdat.entity_name : "Nar'Sie"]"
-			else if(gamemode.cult_objs.cult_status == NARSIE_HAS_RISEN)
-				dat += "<br>Current cult objective: Feed [SSticker.cultdat ? SSticker.cultdat.entity_name : "Nar'Sie"]"
-			else if(gamemode.cult_objs.cult_status == NARSIE_HAS_FALLEN)
-				dat += "<br>Current cult objective: Kill all non-cultists"
-			else
-				dat += "<br>Current cult objective: None! (This is most likely a bug, or var editing gone wrong.)"
-			dat += "<br>Sacrifice objectives completed: [gamemode.cult_objs.sacrifices_done]"
-			dat += "<br>Sacrifice objectives needed for summoning: [gamemode.cult_objs.sacrifices_required]"
-			dat += "<br>Summoning locations: [english_list(gamemode.cult_objs.obj_summon.summon_spots)]"
-			dat += "<br><a href='?src=[UID()];cult_mindspeak=[UID()]'>Cult Mindspeak</a>"
-
-			if(gamemode.cult_objs.cult_status == NARSIE_DEMANDS_SACRIFICE)
-				dat += "<br><a href='?src=[UID()];cult_adjustsacnumber=[UID()]'>Modify amount of sacrifices required</a>"
-				dat += "<br><a href='?src=[UID()];cult_newtarget=[UID()]'>Reroll sacrifice target</a>"
-			else
-				dat += "<br>Modify amount of sacrifices required (Summon available!)</a>"
-				dat += "<br>Reroll sacrifice target (Summon available!)</a>"
-
-			dat += "<br><a href='?src=[UID()];cult_newsummonlocations=[UID()]'>Reroll summoning locations</a>"
-			dat += "<br><a href='?src=[UID()];cult_unlocknarsie=[UID()]'>Unlock Nar'Sie summoning</a>"
+		if(SSticker.mode.cult_team)
+			dat += check_role_table("Cultists", SSticker.mode.cult_team.members)
+			dat += "<a href='?src=[UID()];check_teams=1'>View Cult Team & Controls</a><br>"
 
 		if(SSticker.mode.traitors.len)
 			dat += check_role_table("Traitors", SSticker.mode.traitors)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index d64212e4fec8..da81e704edcf 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1616,12 +1616,30 @@
 	else if(href_list["team_command"])
 		if(!check_rights(R_ADMIN))
 			return
+		if(href_list["team_command"] == "reload") // reload the panel
+			check_teams()
+			return
+
 		var/datum/team/team
 		if(href_list["team_command"] == "new_custom_team") // this needs to be handled before all the other stuff, as the team doesn't exist yet
-			message_admins("[key_name_admin(usr)] created a new custom team.")
-			log_admin("[key_name(usr)] created a new custom team.")
-			team = new()
-			team.admin_rename_team(usr)
+			var/list/possible_teams = list()
+			for(var/datum/team/team_path as anything in typesof(/datum/team))
+				possible_teams[initial(team_path.name)] = team_path
+
+			var/chosen_team_name = input("Select a team type: (Creating a duplicate of a non-generic team may produce runtimes!)", "Team Type") as null|anything in possible_teams
+			if(!chosen_team_name)
+				return
+
+			var/chosen_team_path = possible_teams[chosen_team_name]
+			team = new chosen_team_path()
+			log_and_message_admins("created a new team '[team]' ([chosen_team_path]).")
+			if(chosen_team_path == /datum/team)
+				team.admin_rename_team(usr) // this has to come after, because the admin log could be delayed indefinitely.
+			check_teams()
+			return
+
+		if(href_list["team_command"] == "switch_team_tab")
+			team_switch_tab_index = clamp(text2num(href_list["team_index"]), 1, length(GLOB.antagonist_teams))
 			check_teams()
 			return
 
@@ -1692,65 +1710,6 @@
 		else
 			SStickets.convert_to_other_ticket(indexNum)
 
-	else if(href_list["cult_mindspeak"])
-		var/input = stripped_input(usr, "Communicate to all the cultists with the voice of [SSticker.cultdat.entity_name]", "Voice of [SSticker.cultdat.entity_name]")
-		if(!input)
-			return
-
-		for(var/datum/mind/H in SSticker.mode.cult)
-			if(H.current)
-				to_chat(H.current, "<span class='cult'>[SSticker.cultdat.entity_name] murmurs,</span> <span class='cultlarge'>\"[input]\"</span>")
-
-		for(var/mob/dead/observer/O in GLOB.player_list)
-			to_chat(O, "<span class='cult'>[SSticker.cultdat.entity_name] murmurs,</span> <span class='cultlarge'>\"[input]\"</span>")
-
-		message_admins("Admin [key_name_admin(usr)] has talked with the Voice of [SSticker.cultdat.entity_name].")
-		log_admin("[key_name(usr)] Voice of [SSticker.cultdat.entity_name]: [input]")
-
-	else if(href_list["cult_adjustsacnumber"])
-		var/amount = input("Adjust the amount of sacrifices required before summoning Nar'Sie", "Sacrifice Adjustment", 2) as null | num
-		if(amount > 0)
-			var/datum/game_mode/gamemode = SSticker.mode
-			var/old = gamemode.cult_objs.sacrifices_required
-			gamemode.cult_objs.sacrifices_required = amount
-			message_admins("Admin [key_name_admin(usr)] has modified the amount of cult sacrifices required before summoning from [old] to [amount]")
-			log_admin("Admin [key_name_admin(usr)] has modified the amount of cult sacrifices required before summoning from [old] to [amount]")
-
-	else if(href_list["cult_newtarget"])
-		if(alert(usr, "Reroll the cult's sacrifice target?", "Cult Debug", "Yes", "No") != "Yes")
-			return
-
-		var/datum/game_mode/gamemode = SSticker.mode
-		if(!gamemode.cult_objs.find_new_sacrifice_target())
-			gamemode.cult_objs.ready_to_summon()
-
-		message_admins("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.")
-		log_admin("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.")
-
-	else if(href_list["cult_newsummonlocations"])
-		if(alert(usr, "Reroll the cult's summoning locations?", "Cult Debug", "Yes", "No") != "Yes")
-			return
-
-		var/datum/game_mode/gamemode = SSticker.mode
-		gamemode.cult_objs.obj_summon.find_summon_locations(TRUE)
-		if(gamemode.cult_objs.cult_status == NARSIE_NEEDS_SUMMONING) //Only update cultists if they are already have the summon goal since they arent aware of summon spots till then
-			for(var/datum/mind/cult_mind in gamemode.cult)
-				if(cult_mind && cult_mind.current)
-					to_chat(cult_mind.current, "<span class='cult'>The veil has shifted! Our summoning will need to take place elsewhere.</span>")
-					to_chat(cult_mind.current, "<span class='cult'>Current goal : [gamemode.cult_objs.obj_summon.explanation_text]</span>")
-
-		message_admins("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.")
-		log_admin("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.")
-
-	else if(href_list["cult_unlocknarsie"])
-		if(alert(usr, "Unlock the ability to summon Nar'Sie?", "Cult Debug", "Yes", "No") != "Yes")
-			return
-
-		var/datum/game_mode/gamemode = SSticker.mode
-		gamemode.cult_objs.ready_to_summon()
-		message_admins("Admin [key_name_admin(usr)] has unlocked the Cult's ability to summon Nar'Sie.")
-		log_admin("Admin [key_name_admin(usr)] has unlocked the Cult's ability to summon Nar'Sie.")
-
 	else if(href_list["adminplayerobservecoodjump"])
 		var/client/C = usr.client
 		if(!isobserver(usr))
diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm
index 89ba5e45f27e..af3031c68058 100644
--- a/code/modules/admin/verbs/one_click_antag.dm
+++ b/code/modules/admin/verbs/one_click_antag.dm
@@ -162,7 +162,7 @@
 	var/list/mob/living/carbon/human/candidates = list()
 	var/mob/living/carbon/human/H = null
 	var/antnum = input(owner, "How many cultists do you want to create? Enter 0 to cancel.", "Amount:", 0) as num
-	if(!antnum || antnum <= 0) // 5 because cultist can really screw balance over if spawned in high amount.
+	if(!antnum || antnum <= 0)
 		return
 	log_admin("[key_name(owner)] tried making a Cult with One-Click-Antag")
 	message_admins("[key_name_admin(owner)] tried making a Cult with One-Click-Antag")
@@ -171,17 +171,17 @@
 		if(CandCheck(ROLE_CULTIST, applicant, temp))
 			candidates += applicant
 
-	if(length(candidates))
-		var/numCultists = min(length(candidates), antnum)
+	if(!length(candidates))
+		return FALSE
 
-		for(var/I in 1 to numCultists)
-			H = pick(candidates)
-			to_chat(H, CULT_GREETING)
-			SSticker.mode.add_cultist(H.mind)
-			SSticker.mode.equip_cultist(H)
-			candidates.Remove(H)
-		return TRUE
-	return FALSE
+	for(var/I in 1 to antnum)
+		if(!length(candidates))
+			return
+		H = pick_n_take(candidates)
+
+		var/datum/antagonist/cultist/cultist = H.mind.add_antag_datum(/datum/antagonist/cultist)
+		cultist.equip_roundstart_cultist(H)
+	return TRUE
 
 //Abductors
 /datum/admins/proc/makeAbductorTeam()
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index 788cb305374d..aceb94764138 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -23,11 +23,11 @@
 		cross = image('icons/obj/storage.dmi',"kingyellow")
 		font_color = "blue"
 		prayer_type = "CHAPLAIN PRAYER"
-	else if(iscultist(usr))
+	else if(IS_CULTIST(usr))
 		cross = image('icons/obj/storage.dmi',"tome")
 		font_color = "red"
 		prayer_type = "CULTIST PRAYER"
-		deity = SSticker.cultdat.entity_name
+		deity = GET_CULT_DATA(entity_name, "Cult God")
 
 	log_say("(PRAYER) [msg]", usr)
 	msg = "<span class='notice'>[bicon(cross)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""][mind && HAS_MIND_TRAIT(usr, TRAIT_HOLY) ? " (blessings: [mind.num_blessed])" : ""]:</font> [key_name(src, 1)] ([ADMIN_QUE(src,"?")]) ([ADMIN_PP(src,"PP")]) ([ADMIN_VV(src,"VV")]) ([ADMIN_TP(src,"TP")]) ([ADMIN_SM(src,"SM")]) ([admin_jump_link(src)]) ([ADMIN_SC(src,"SC")]) (<A HREF='?_src_=holder;Bless=[UID()]'>BLESS</A>) (<A HREF='?_src_=holder;Smite=[UID()]'>SMITE</A>):</b> [msg]</span>"
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 108f4ede4254..bd83d5d72643 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -1,5 +1,7 @@
 GLOBAL_LIST_EMPTY(antagonists)
 
+#define SUCCESSFUL_DETACH "dont touch this string numbnuts"
+
 /datum/antagonist
 	/// The name of the antagonist.
 	var/name = "Antagonist"
@@ -33,6 +35,8 @@ GLOBAL_LIST_EMPTY(antagonists)
 	var/clown_gain_text = "You are no longer clumsy."
 	/// If the owner is a clown, this text will be displayed to them when they lose this datum.
 	var/clown_removal_text = "You are clumsy again."
+	/// The spawn class to use for gain/removal clown text
+	var/clown_text_span_class = "boldnotice"
 	/// The url page name for this antagonist, appended to the end of the wiki url in the form of: [GLOB.configuration.url.wiki_url]/index.php/[wiki_page_name]
 	var/wiki_page_name
 
@@ -56,8 +60,8 @@ GLOBAL_LIST_EMPTY(antagonists)
 /datum/antagonist/Destroy(force, ...)
 	qdel(objective_holder)
 	GLOB.antagonists -= src
-	if(!QDELETED(owner))
-		detach_from_owner()
+	if(!QDELETED(owner) && detach_from_owner() != SUCCESSFUL_DETACH)
+		stack_trace("[src] ([type]) failed to detach from owner! This is very bad!")
 
 	return ..()
 
@@ -79,6 +83,7 @@ GLOBAL_LIST_EMPTY(antagonists)
 	LAZYREMOVE(owner.antag_datums, src)
 	restore_last_hud_and_role()
 	owner = null
+	return SUCCESSFUL_DETACH
 
 /**
  * Adds the owner to their respective gamemode's list. For example `SSticker.mode.traitors |= owner`.
@@ -149,16 +154,16 @@ GLOBAL_LIST_EMPTY(antagonists)
  */
 /datum/antagonist/proc/remove_innate_effects(mob/living/mob_override)
 	SHOULD_CALL_PARENT(TRUE)
-
-	var/mob/living/remove_effects_from = mob_override || owner?.current
-	if(!remove_effects_from)
+	// SS220 EDIT START - null safe var access
+	var/mob/living/L = mob_override || owner?.current
+	if(!L)
 		return
-
+	// SS220 EDIT END
 	if(antag_hud_type && antag_hud_name)
-		remove_antag_hud(remove_effects_from)
+		remove_antag_hud(L)
 	// If `mob_override` exists it means we're only transferring this datum, we don't need to show the clown any text.
-	handle_clown_mutation(remove_effects_from, mob_override ? null : clown_removal_text)
-	return remove_effects_from
+	handle_clown_mutation(L, mob_override ? null : clown_removal_text)
+	return L
 
 /**
  * Adds this datum's antag hud to `antag_mob`.
@@ -412,3 +417,5 @@ GLOBAL_LIST_EMPTY(antagonists)
 /// This is the custom blurb message used on login for an antagonist.
 /datum/antagonist/proc/custom_blurb()
 	return FALSE
+
+#undef SUCCESSFUL_DETACH
diff --git a/code/modules/antagonists/_common/antag_team.dm b/code/modules/antagonists/_common/antag_team.dm
index 43d0e90313d3..ba35e1de1eb6 100644
--- a/code/modules/antagonists/_common/antag_team.dm
+++ b/code/modules/antagonists/_common/antag_team.dm
@@ -1,6 +1,6 @@
 GLOBAL_LIST_EMPTY(antagonist_teams)
 
-#define DEFAULT_TEAM_NAME "Generic Team Name"
+#define DEFAULT_TEAM_NAME "Generic/Custom Team"
 
 /**
  * # Antagonist Team
@@ -11,7 +11,7 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
 	/// The name of the team.
 	var/name = DEFAULT_TEAM_NAME
 	/// A list of [minds][/datum/mind] who belong to this team.
-	var/list/datum/mind/members
+	var/list/datum/mind/members = list()
 	/// A list of objectives which all team members share.
 	var/datum/objective_holder/objective_holder
 	/// Type of antag datum members of this team have. Also given to new members added by admins.
@@ -19,46 +19,100 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
 	/// The name to save objective successes under in the blackboxes. Saves nothing if blank.
 	var/blackbox_save_name
 
-/datum/team/New(list/starting_members, add_antag_datum = TRUE)
+/datum/team/New(list/starting_members, add_antag_datum = TRUE) // SS220 EDIT - add_antag_datum arg
 	..()
-	members = list()
+	if(!can_create_team())
+		QDEL_IN(src, 0 SECONDS) // Give us time to crash so we can get the full call stack
+		CRASH("[src] ([type]) is not allowed to be created, this may be a duplicate team. Deleting...")
+	// Assign the team before member assignment to prevent duplicate teams
+	assign_team()
+	if(!create_team(starting_members, add_antag_datum)) // SS220 EDIT - add_antag_datum arg
+		CRASH("[src] ([type]) somehow failed to create a team!")
+
+/datum/team/proc/create_team(list/starting_members, add_antag_datum = TRUE) // SS220 EDIT - add_antag_datum arg
+	PROTECTED_PROC(TRUE)
 	objective_holder = new(src)
-
 	if(starting_members && !islist(starting_members))
 		starting_members = list(starting_members)
 	for(var/datum/mind/M as anything in starting_members)
-		add_member(M, add_antag_datum)
+		add_member(M, add_antag_datum = add_antag_datum) // SS220 EDIT - add_antag_datum arg
 	GLOB.antagonist_teams += src
+	return TRUE
 
 /datum/team/Destroy(force = FALSE, ...)
 	for(var/datum/mind/member as anything in members)
 		remove_member(member)
+	clear_team_reference() // Team reference must come AFTER removing all members, otherwise antag datums will not get removed
 	qdel(objective_holder)
 	members.Cut()
 	GLOB.antagonist_teams -= src
 	return ..()
 
+/datum/team/proc/can_create_team()
+	return TRUE
+
+/datum/team/proc/assign_team()
+	return
+
+/datum/team/proc/clear_team_reference()
+	return
+
 /**
  * Adds `new_member` to this team.
  *
- * Generally this should ONLY be called by `add_antag_datum()` to ensure proper order of operations.
+ * This is an interface proc, to prevent handle_removing_member from being called multiple times.
+ * It is better if this is only called from `add_antag_datum()`, but it is not required.
  */
-/datum/team/proc/add_member(datum/mind/new_member, add_antag_datum = TRUE)
-	SHOULD_CALL_PARENT(TRUE)
+/datum/team/proc/add_member(datum/mind/new_member, force = FALSE, add_antag_datum = TRUE) // SS220 EDIT - add_antag_datum arg
+	SHOULD_NOT_OVERRIDE(TRUE)
+	if(!force && (new_member in members))
+		return FALSE
 	members |= new_member
-
+	// SS220 EDIT START
+	. = TRUE
 	if(add_antag_datum && antag_datum_type)
 		var/datum/antagonist/antag = get_antag_datum_from_member(new_member) // make sure they have the antag datum
 		// If no matching antag datum was found, give them one.
 		if(!antag)
-			return new_member.add_antag_datum(antag_datum_type, src)
+			. = new_member.add_antag_datum(antag_datum_type, src)
+	handle_adding_member(new_member)
+	// SS220 EDIT END
 
 /**
- * Removes `member` from this team.
+ * An internal proc to allow teams to handle custom parts of adding a member.
+ * This should ONLY be called by `add_member()` to ensure proper order of operations.
  */
-/datum/team/proc/remove_member(datum/mind/member)
+/datum/team/proc/handle_adding_member(datum/mind/new_member)
+	PROTECTED_PROC(TRUE)
 	SHOULD_CALL_PARENT(TRUE)
+
+	// SS220 EDIT START - Commented for #840
+	// var/datum/antagonist/antag = get_antag_datum_from_member(new_member) // make sure they have the antag datum
+	// if(!antag) // this team has no antag role, we'll add it directly to their mind team
+		// LAZYDISTINCTADD(new_member.teams, src)
+	return
+	// SS220 EDIT END
+
+/**
+ * Removes `member` from this team.
+ * This is an interface proc, to prevent handle_removing_member from being called multiple times.
+ */
+/datum/team/proc/remove_member(datum/mind/member, force = FALSE)
+	SHOULD_NOT_OVERRIDE(TRUE)
+	if(!force && !(member in members))
+		return FALSE
 	members -= member
+	handle_removing_member(member)
+	return TRUE
+
+/**
+ * An internal proc for teams to remove a member.
+ */
+/datum/team/proc/handle_removing_member(datum/mind/member, force = FALSE)
+	PROTECTED_PROC(TRUE)
+	SHOULD_CALL_PARENT(TRUE)
+
+	// LAZYREMOVE(member.teams, src) // SS220 EDIT - Commented for #840
 	var/datum/antagonist/antag = get_antag_datum_from_member(member)
 	if(!QDELETED(antag))
 		qdel(antag)
@@ -73,13 +127,17 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
 			continue
 		valid_minds[H.real_name] = H.mind
 
+	if(!length(valid_minds))
+		to_chat(user, "<span class='warning'>No suitable humanoid targets found!</span>")
+		return
 	var/name = input(user, "Choose a player to add to this team", "Add Team Member") as null|anything in valid_minds
 	if(!name)
-		to_chat(user, "<span class='warning'>No suitable humanoid targets found!</span>")
 		return
 
 	var/datum/mind/new_member = valid_minds[name]
-	add_member(new_member)
+	add_member(new_member, TRUE)
+	log_admin("[key_name(usr)] added [key_name(new_member)] to the team '[src]'.")
+	message_admins("[key_name_admin(usr)] added [key_name(new_member)] to the team '[src]'.")
 
 /**
  * Adds a team objective to each member's matching antag datum.
@@ -106,6 +164,9 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
 		if(A.get_team() != src)
 			continue
 		return A
+	// If no matching antag datum was found, give them one.
+	if(antag_datum_type)
+		return member.add_antag_datum(antag_datum_type, src)
 
 /**
  * Special overrides for teams for target exclusion from objectives.
@@ -178,21 +239,61 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
 	message_admins("Team Message: [key_name(user)] -> '[name]' team. Message: [message]")
 	log_admin("Team Message: [key_name(user)] -> '[name]' team. Message: [message]")
 
+#define SEPERATOR "---"
 /**
  * Allows admins to add a team objective.
+ * Minimize overriding this proc please.
  */
 /datum/team/proc/admin_add_objective(mob/user)
-	var/selected = input("Select an objective type:", "Objective Type") as null|anything in GLOB.admin_objective_list
-	if(!selected)
+	SHOULD_CALL_PARENT(TRUE)
+
+	// available_objectives is assoc, `objective name` = `objective_path`
+	var/list/available_objectives = get_admin_priority_objectives()
+	if(length(available_objectives))
+		available_objectives[SEPERATOR] = "Whatever, we never read this"
+	available_objectives += GLOB.admin_objective_list
+	var/selected = input("Select an objective type:", "Objective Type") as null|anything in available_objectives
+	if(!selected || selected == SEPERATOR)
 		return
 
-	var/objective_type = GLOB.admin_objective_list[selected]
+	var/objective_type = available_objectives[selected]
+	var/return_value = handle_adding_admin_objective(user, objective_type)
+
+	if(istype(return_value, /datum/objective)) // handle_adding_admin_objective can return TRUE if its handled
+		add_team_objective(return_value)
+
+	else
+		if(return_value & TEAM_ADMIN_ADD_OBJ_PURPOSEFUL_CANCEL)
+			return
+		if(!(return_value & TEAM_ADMIN_ADD_OBJ_SUCCESS))
+			to_chat(user, "<span class='warning'>[src] team failed to properly handle your selected objective, if you believe this was an error, tell a coder.</span>")
+			return
+		if(return_value & TEAM_ADMIN_ADD_OBJ_CANCEL_LOG) // Logs are being handled elsewhere
+			return
+
+	message_admins("[key_name_admin(user)] added objective [objective_type] to the team '[name]'.")
+	log_admin("[key_name(user)] added objective [objective_type] to the team '[name]'.")
+
+#undef SEPERATOR
+
+/**
+ * Overridable logic for handling how the adding of objectives works works
+ * Can return an objective datum, or a boolean.
+ * Returns a boolean if its already added to the team objectives in a custom way
+ */
+/datum/team/proc/handle_adding_admin_objective(mob/user, objective_type)
+	PROTECTED_PROC(TRUE)
 	var/datum/objective/O = new objective_type(team_to_join = src)
 	O.find_target(get_target_excludes()) // Blacklist any team members from being the target.
-	add_team_objective(O)
+	return O
+
 
-	message_admins("[key_name_admin(user)] added objective [O.type] to the team '[name]'.")
-	log_admin("[key_name(user)] added objective [O.type] to the team '[name]'.")
+/**
+ * Returns an associated list of priority objectives for admins to add to the team, this is like
+ * Must return in the form `objective name` = `objective_path`.
+ */
+/datum/team/proc/get_admin_priority_objectives()
+	return list()
 
 /**
  * Allows admins to announce objectives to all team members.
@@ -239,7 +340,7 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
 /datum/team/proc/admin_remove_member(mob/user, datum/mind/M)
 	message_admins("[key_name_admin(user)] removed [key_name_admin(M)] from the team '[name]'.")
 	log_admin("[key_name(user)] removed [key_name(M)] from the team '[name]'.")
-	remove_member(M)
+	remove_member(M, TRUE)
 
 // Used for running team specific admin commands.
 /datum/team/Topic(href, href_list)
@@ -251,8 +352,35 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
 		if(href_list["command"] == admin_command)
 			var/datum/callback/C = commands[admin_command]
 			C.Invoke(usr)
+			usr.client.holder.check_teams()
 			return
 
+/datum/team/proc/get_admin_html()
+	var/list/content = list()
+	content += "<h3>[name] - [type]</h3>"
+	content += "<a href='?_src_=holder;team_command=rename_team;team=[UID()]'>Rename Team</a>"
+	content += "<a href='?_src_=holder;team_command=delete_team;team=[UID()]'>Delete Team</a>"
+	content += "<a href='?_src_=holder;team_command=communicate;team=[UID()]'>OOC Message Team</a>"
+	content += ADMIN_VV(src, "View Variables")
+	for(var/command in get_admin_commands())
+		// src is UID() so it points to `/datum/team/Topic` instead of `/datum/admins/Topic`.
+		content += "<a href='?src=[UID()];command=[command]'>[command]</a>"
+	content += "<br><br>Objectives:<br><ol>"
+	for(var/datum/objective/O as anything in objective_holder.get_objectives())
+		if(!istype(O))
+			stack_trace("Non-objective found in [type]'s objective_holder.get_objectives()")
+			continue
+		content += "<li>[O.explanation_text] - <a href='?_src_=holder;team_command=remove_objective;team=[UID()];objective=[O.UID()]'>Remove</a></li>"
+	content += "</ol><a href='?_src_=holder;team_command=add_objective;team=[UID()]'>Add Objective</a><br>"
+	if(objective_holder.has_objectives())
+		content += "</ol><a href='?_src_=holder;team_command=announce_objectives;team=[UID()]'>Announce Objectives to All Members</a><br><br>"
+	content += "Members: <br><ol>"
+	for(var/datum/mind/M as anything in members)
+		content += "<li>[M.name] - <a href='?_src_=holder;team_command=view_member;team=[UID()];member=[M.UID()]'>Show Player Panel</a>"
+		content += "<a href='?_src_=holder;team_command=remove_member;team=[UID()];member=[M.UID()]'>Remove Member</a></li>"
+	content += "</ol><a href='?_src_=holder;team_command=admin_add_member;team=[UID()]'>Add Member</a>"
+	return content
+
 /**
  * A list of team-specific admin commands for this team. Should be in the form of `"command" = CALLBACK(x, PROC_REF(some_proc))`.
  */
@@ -279,26 +407,22 @@ GLOBAL_LIST_EMPTY(antagonist_teams)
 	if(!length(GLOB.antagonist_teams))
 		content += "There are currently no antag teams.<br/>"
 	content += "<a href='?_src_=holder;team_command=new_custom_team;'>Create new Team</a>"
-	for(var/datum/team/T as anything in GLOB.antagonist_teams) // with multiple teams, this is going to get messy. It should probably be turned into a tabs-like system
-		content += "<h3>[T.name] - [T.type]</h3>"
-		content += "<a href='?_src_=holder;team_command=rename_team;team=[T.UID()]'>Rename Team</a>"
-		content += "<a href='?_src_=holder;team_command=delete_team;team=[T.UID()]'>Delete Team</a>"
-		content += "<a href='?_src_=holder;team_command=communicate;team=[T.UID()]'>Message Team</a>"
-		content += ADMIN_VV(T, "View Variables")
-		for(var/command in T.get_admin_commands())
-			// _src_ is T.UID() so it points to `/datum/team/Topic` instead of `/datum/admins/Topic`.
-			content += "<a href='?_src_=[T.UID()];command=[command]'>[command]</a>"
-		content += "<br><br>Objectives:<br><ol>"
-		for(var/datum/objective/O as anything in T.objective_holder.get_objectives())
-			content += "<li>[O.explanation_text] - <a href='?_src_=holder;team_command=remove_objective;team=[T.UID()];objective=[O.UID()]'>Remove</a></li>"
-		content += "</ol><a href='?_src_=holder;team_command=add_objective;team=[T.UID()]'>Add Objective</a><br>"
-		if(T.objective_holder.has_objectives())
-			content += "</ol><a href='?_src_=holder;team_command=announce_objectives;team=[T.UID()]'>Announce Objectives to All Members</a><br><br>"
-		content += "Members: <br><ol>"
-		for(var/datum/mind/M as anything in T.members)
-			content += "<li>[M.name] - <a href='?_src_=holder;team_command=view_member;team=[T.UID()];member=[M.UID()]'>Show Player Panel</a>"
-			content += "<a href='?_src_=holder;team_command=remove_member;team=[T.UID()];member=[M.UID()]'>Remove Member</a></li>"
-		content += "</ol><a href='?_src_=holder;team_command=admin_add_member;team=[T.UID()]'>Add Member</a><hr>"
+	content += "<a href='?_src_=holder;team_command=reload;'>Reload Menu</a><br>"
+	if(length(GLOB.antagonist_teams) > 1)
+		var/index = 1
+		for(var/datum/team/T as anything in GLOB.antagonist_teams)
+			content += "<a href='?_src_=holder;team_command=switch_team_tab;team_index=[index]'>[T.name]</a>"
+			index++
+	else
+		team_switch_tab_index = 1
+
+	if(length(GLOB.antagonist_teams))
+		content += "<hr>"
+		team_switch_tab_index = clamp(team_switch_tab_index, 1, length(GLOB.antagonist_teams))
+		var/datum/team/T = GLOB.antagonist_teams[team_switch_tab_index]
+		if(istype(T))
+			var/list/stringy_list = T.get_admin_html()
+			content += stringy_list.Join()
 	return content.Join()
 
 #undef DEFAULT_TEAM_NAME
diff --git a/code/modules/antagonists/cult/datum_cultist.dm b/code/modules/antagonists/cult/datum_cultist.dm
new file mode 100644
index 000000000000..41f3e898ea00
--- /dev/null
+++ b/code/modules/antagonists/cult/datum_cultist.dm
@@ -0,0 +1,142 @@
+/datum/antagonist/cultist
+	name = "Cultist"
+	job_rank = ROLE_CULTIST
+	special_role = SPECIAL_ROLE_CULTIST
+	give_objectives = FALSE
+	antag_hud_name = "hudcultist"
+	antag_hud_type = ANTAG_HUD_CULT
+	clown_gain_text = "A dark power has allowed you to overcome your clownish nature, letting you wield weapons without harming yourself."
+	clown_removal_text = "You are free of the dark power suppressing your clownish nature. You are clumsy again! Honk!"
+	clown_text_span_class = "cultitalic"
+	wiki_page_name = "Cultist"
+	var/remove_gear_on_removal = FALSE
+
+/datum/antagonist/cultist/on_gain()
+	create_team() // make sure theres a global cult team
+	..()
+	owner.current.faction |= "cult"
+	add_cult_actions()
+	SEND_SOUND(owner.current, sound('sound/ambience/antag/bloodcult.ogg'))
+	owner.current.create_log(CONVERSION_LOG, "Converted to the cult")
+	owner.current.create_attack_log("<span class='danger'>Has been converted to the cult!</span>")
+
+	var/datum/team/cult/cult = get_team()
+	ASSERT(cult)
+	if(cult.cult_risen)
+		rise()
+	if(cult.cult_ascendant)
+		ascend()
+	cult.study_objectives(owner.current)
+
+/datum/antagonist/cultist/detach_from_owner()
+	if(!owner.current)
+		return ..()
+	owner.current.faction -= "cult"
+	owner.current.create_log(CONVERSION_LOG, "Deconverted from the cult") // yes, this is its own log, instead of the default MISC_LOG
+	for(var/datum/action/innate/cult/C in owner.current.actions)
+		qdel(C)
+
+	if(!ishuman(owner.current))
+		return ..()
+	var/mob/living/carbon/human/H = owner.current
+	REMOVE_TRAIT(H, CULT_EYES, null)
+	H.change_eye_color(H.original_eye_color, FALSE)
+	H.update_eyes()
+	H.remove_overlay(HALO_LAYER)
+	H.update_body()
+
+	if(remove_gear_on_removal)
+		for(var/I in H.contents)
+			if(is_type_in_list(I, CULT_CLOTHING))
+				H.unEquip(I)
+	return ..()
+
+
+/datum/antagonist/cultist/greet()
+	return "<span class='cultlarge'>You catch a glimpse of the Realm of [GET_CULT_DATA(entity_name, "this is a bug at this point")], [GET_CULT_DATA(entity_title3, "I dont know what else to write")]. \
+						You now see how flimsy the world is, you see that it should be open to the knowledge of [GET_CULT_DATA(entity_name, "making a bug report")].</span>"
+
+/datum/antagonist/cultist/farewell()
+	if(owner && owner.current)
+		owner.current.visible_message("<span class='cult'>[owner.current] looks like [owner.current.p_they()] just reverted to [owner.current.p_their()] old faith!</span>",
+			"<span class='userdanger'>An unfamiliar white light flashes through your mind, cleansing the taint of [GET_CULT_DATA(entity_title1, "Nar'Sie")] and the memories of your time as their servant with it.</span>")
+
+/datum/antagonist/cultist/create_team(team)
+	return SSticker.mode.get_cult_team()
+
+/datum/antagonist/cultist/get_team()
+	return SSticker.mode.cult_team
+
+/datum/antagonist/cultist/on_body_transfer(old_body, new_body)
+	var/datum/team/cult/cult = get_team()
+	cult.cult_body_transfer(old_body, new_body)
+	add_cult_actions()
+
+/datum/antagonist/cultist/proc/rise()
+	if(!ishuman(owner.current))
+		return
+	var/mob/living/carbon/human/H = owner.current
+	if(!H.original_eye_color)
+		H.original_eye_color = H.get_eye_color()
+	H.change_eye_color(BLOODCULT_EYE, FALSE)
+	ADD_TRAIT(H, CULT_EYES, CULT_TRAIT)
+	H.update_eyes()
+	H.update_body()
+
+/datum/antagonist/cultist/proc/ascend()
+	if(!ishuman(owner.current))
+		return
+	var/mob/living/carbon/human/H = owner.current
+	new /obj/effect/temp_visual/cult/sparks(get_turf(H), H.dir)
+	H.update_halo_layer()
+
+/datum/antagonist/cultist/proc/descend()
+	if(!ishuman(owner.current))
+		return
+	var/mob/living/carbon/human/H = owner.current
+	new /obj/effect/temp_visual/cult/sparks(get_turf(H), H.dir)
+	H.update_halo_layer()
+	to_chat(H, "<span class='userdanger'>The halo above your head shatters!</span>")
+	playsound(H, "shatter", 50, TRUE)
+
+/datum/antagonist/cultist/proc/add_cult_actions()
+	if(!owner.current)
+		return
+	var/datum/action/innate/cult/comm/communicate_spell = new
+	var/datum/action/innate/cult/check_progress/progress_report = new
+	communicate_spell.Grant(owner.current)
+	progress_report.Grant(owner.current)
+	if(ishuman(owner.current))
+		var/datum/action/innate/cult/blood_magic/magic = new
+		var/datum/action/innate/cult/use_dagger/dagger = new
+		magic.Grant(owner.current)
+		dagger.Grant(owner.current)
+
+	owner.current.update_action_buttons(TRUE)
+
+/datum/antagonist/cultist/proc/equip_roundstart_cultist()
+	if(!ishuman(owner.current))
+		return FALSE
+	. |= cult_give_item(/obj/item/melee/cultblade/dagger)
+	. |= cult_give_item(/obj/item/stack/sheet/runed_metal/ten)
+	to_chat(owner.current, "<span class='cult'>These will help you start the cult on this station. Use them well, and remember - you are not the only one.</span>")
+
+/datum/antagonist/cultist/proc/cult_give_item(obj/item/item_path)
+	if(!ishuman(owner.current))
+		return
+	var/mob/living/carbon/human/H = owner.current
+	var/list/slots = list(
+		"backpack" = SLOT_HUD_IN_BACKPACK,
+		"left pocket" = SLOT_HUD_LEFT_STORE,
+		"right pocket" = SLOT_HUD_RIGHT_STORE
+	)
+
+	var/where = H.equip_in_one_of_slots(new item_path(H), slots)
+	if(where)
+		to_chat(H, "<span class='danger'>You have \a [initial(item_path.name)] in your [where].</span>")
+		if(H.s_active) // Update whatever inventory they have open
+			H.s_active.orient2hud(H)
+			H.s_active.show_to(H)
+		return TRUE
+	to_chat(H, "<span class='userdanger'>Unfortunately, you weren't able to get \a [initial(item_path.name)]. This is very bad and you should adminhelp immediately (press F1).</span>")
+	return FALSE
diff --git a/code/modules/antagonists/cult/team_cult.dm b/code/modules/antagonists/cult/team_cult.dm
new file mode 100644
index 000000000000..8f21d8569613
--- /dev/null
+++ b/code/modules/antagonists/cult/team_cult.dm
@@ -0,0 +1,569 @@
+/datum/team/cult
+	name = "Cult"
+	antag_datum_type = /datum/antagonist/cultist
+
+	/// Does the cult have glowing eyes
+	var/cult_risen = FALSE
+	/// Does the cult have halos
+	var/cult_ascendant = FALSE
+	/// How many crew need to be converted to rise
+	var/rise_number
+	/// How many crew need to be converted to ascend
+	var/ascend_number
+	/// Used for the CentComm announcement at ascension
+	var/ascend_percent
+	/// Variable used for tracking the progress of the cult's sacrifices & god summonings
+	var/cult_status = NARSIE_IS_ASLEEP
+
+	/// God summon objective added when ready_to_summon() is called
+	var/datum/objective/eldergod/obj_summon
+	var/sacrifices_done = 0
+	var/sacrifices_required = 2
+
+	/// Are cultist mirror shields active yet?
+	var/mirror_shields_active = FALSE
+
+	// Disables the station-wide announcements, unused except for admin editing.
+	var/no_announcements = FALSE
+
+/datum/team/cult/create_team(list/starting_members)
+	cult_threshold_check() // Set this ALWAYS before any check_cult_size check, or
+	. = ..()
+
+	objective_holder.add_objective(/datum/objective/servecult)
+
+	addtimer(CALLBACK(src, PROC_REF(cult_threshold_check)), 2 MINUTES) // Check again in 2 minutes for latejoiners
+
+	cult_status = NARSIE_DEMANDS_SACRIFICE
+
+	create_next_sacrifice()
+
+	for(var/datum/mind/M as anything in starting_members)
+		var/datum/antagonist/cultist/cultist = M.has_antag_datum(/datum/antagonist/cultist)
+		cultist.equip_roundstart_cultist()
+
+/datum/team/cult/can_create_team()
+	return isnull(SSticker.mode.cult_team)
+
+/datum/team/cult/assign_team()
+	SSticker.mode.cult_team = src
+
+/datum/team/cult/clear_team_reference()
+	if(SSticker.mode.cult_team == src)
+		SSticker.mode.cult_team = null
+	else
+		CRASH("[src] ([type]) attempted to clear a team reference that wasn't itself!")
+
+/datum/team/cult/handle_adding_member(datum/mind/new_member)
+	. = ..()
+	check_cult_size()
+	RegisterSignal(new_member.current, COMSIG_MOB_STATCHANGE, PROC_REF(cultist_stat_change))
+	RegisterSignal(new_member.current, COMSIG_PARENT_QDELETING, PROC_REF(cultist_deleting))
+
+/datum/team/cult/handle_removing_member(datum/mind/member)
+	. = ..()
+	UnregisterSignal(member.current, COMSIG_MOB_STATCHANGE)
+	UnregisterSignal(member.current, COMSIG_PARENT_QDELETING)
+	check_cult_size()
+
+/datum/team/cult/on_round_end()
+	var/list/endtext = list()
+	endtext += "<br><b>The cultists' objectives were:</b>"
+	for(var/datum/objective/obj in objective_holder.get_objectives())
+		endtext += "<br>[obj.explanation_text] - "
+		if(!obj.check_completion())
+			endtext += "<font color='red'>Fail.</font>"
+		else
+			endtext += "<font color='green'><B>Success!</B></font>"
+
+	to_chat(world, endtext.Join(""))
+
+/datum/team/cult/proc/add_cult_immunity(mob/living/target)
+	ADD_TRAIT(target, TRAIT_CULT_IMMUNITY, CULT_TRAIT)
+	addtimer(CALLBACK(src, PROC_REF(remove_cult_immunity), target), 1 MINUTES)
+
+/datum/team/cult/proc/remove_cult_immunity(mob/living/target)
+	REMOVE_TRAIT(target, TRAIT_CULT_IMMUNITY, CULT_TRAIT)
+
+/**
+ * Makes sure that the signal stays on the correct body when a cultist changes bodies
+ */
+/datum/team/cult/proc/cult_body_transfer(old_body, new_body)
+	UnregisterSignal(old_body, COMSIG_MOB_STATCHANGE)
+	UnregisterSignal(old_body, COMSIG_PARENT_QDELETING)
+	RegisterSignal(new_body, COMSIG_MOB_STATCHANGE, PROC_REF(cultist_stat_change))
+	RegisterSignal(new_body, COMSIG_PARENT_QDELETING, PROC_REF(cultist_deleting))
+
+/**
+  * Returns the current number of cultists and constructs.
+  *
+  * Returns the number of cultists and constructs in a list ([1] = Cultists, [2] = Constructs), or as one combined number.
+  *
+  * * separate - Should the number be returned as a list with two separate values (Humans and Constructs) or as one number.
+  */
+/datum/team/cult/proc/get_cultists(separate = FALSE)
+	var/cultists = 0
+	var/constructs = 0
+	var/list/minds_to_remove = list()
+	for(var/datum/mind/M as anything in members)
+		if(isnull(M))
+			stack_trace("Found a null mind in /datum/team/cult's members. Removing...")
+			minds_to_remove |= M // I don't really want to remove them while iterating, as I'm not sure how byond would handle that while iterating over members
+			continue
+		if(isnull(M.current))
+			stack_trace("Found a mind with no body in /datum/team/cult's members. Removing...")
+			minds_to_remove |= M // I don't really want to remove them while iterating, as I'm not sure how byond would handle that while iterating over members
+			continue
+		if(QDELETED(M) || M.current.stat == DEAD)
+			continue
+		if(ishuman(M.current) && !M.current.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST))
+			cultists++
+		else if(isconstruct(M.current))
+			constructs++
+
+	if(length(minds_to_remove))
+		for(var/datum/mind/M as anything in minds_to_remove)
+			remove_member(M)
+
+	if(separate)
+		return list(cultists, constructs)
+	return cultists + constructs
+
+/datum/team/cult/proc/cultist_stat_change(mob/target_cultist, new_stat, old_stat)
+	SIGNAL_HANDLER
+	if(new_stat == old_stat) // huh, how? whatever, we ignore it
+		return
+	if(new_stat != DEAD && old_stat != DEAD)
+		return // switching between alive and unconcious
+	// switching between dead and alive/unconcious
+	INVOKE_ASYNC(src, PROC_REF(check_cult_size))
+
+/datum/team/cult/proc/cultist_deleting(mob/deleting_cultist)
+	SIGNAL_HANDLER
+	INVOKE_ASYNC(src, PROC_REF(remove_member), deleting_cultist.mind)
+
+/datum/team/cult/proc/check_cult_size()
+	if(!ascend_percent)
+		stack_trace("[src]'s check_cult_size was called before cult_threshold_check, which leads to weird logic! This should be fixed ASAP.")
+		cult_threshold_check()
+
+	var/cult_players = get_cultists()
+
+	if(cult_ascendant)
+		// The cult only falls if below 1/2 of the rising, usually pretty low. e.g. 5% on highpop, 10% on lowpop
+		if(cult_players < (rise_number / 2))
+			cult_fall()
+		return
+
+	if((cult_players >= rise_number) && !cult_risen)
+		cult_rise()
+		return
+
+	if(cult_players >= ascend_number)
+		cult_ascend()
+
+/datum/team/cult/proc/cult_rise()
+	cult_risen = TRUE
+	for(var/datum/mind/M in members)
+		if(!ishuman(M.current))
+			continue
+		SEND_SOUND(M.current, sound('sound/hallucinations/i_see_you2.ogg'))
+		to_chat(M.current, "<span class='cultlarge'>The veil weakens as your cult grows, your eyes begin to glow...</span>")
+
+	addtimer(CALLBACK(src, PROC_REF(all_members_timer), TYPE_PROC_REF(/datum/antagonist/cultist, rise)), 20 SECONDS)
+
+/datum/team/cult/proc/cult_ascend()
+	cult_ascendant = TRUE
+	for(var/datum/mind/M in members)
+		if(!ishuman(M.current))
+			continue
+		SEND_SOUND(M.current, sound('sound/hallucinations/im_here1.ogg'))
+		to_chat(M.current, "<span class='cultlarge'>Your cult is ascendant and the red harvest approaches - you cannot hide your true nature for much longer!</span>")
+
+	addtimer(CALLBACK(src, PROC_REF(all_members_timer), TYPE_PROC_REF(/datum/antagonist/cultist, ascend)), 20 SECONDS)
+	if(!no_announcements)
+		GLOB.major_announcement.Announce("Picking up extradimensional activity related to the Cult of [GET_CULT_DATA(entity_name, "Nar'Sie")] from your station. Data suggests that about [ascend_percent * 100]% of the station has been converted. Security staff are authorized to use lethal force freely against cultists. Non-security staff should be prepared to defend themselves and their work areas from hostile cultists. Self defense permits non-security staff to use lethal force as a last resort, but non-security staff should be defending their work areas, not hunting down cultists. Dead crewmembers must be revived and deconverted once the situation is under control.", "Central Command Higher Dimensional Affairs", 'sound/AI/commandreport.ogg')
+
+/datum/team/cult/proc/cult_fall()
+	cult_ascendant = FALSE
+	for(var/datum/mind/M in members)
+		if(!ishuman(M.current))
+			continue
+		SEND_SOUND(M.current, sound('sound/hallucinations/wail.ogg'))
+		to_chat(M.current, "<span class='cultlarge'>The veil repairs itself, your power grows weaker...</span>")
+
+	addtimer(CALLBACK(src, PROC_REF(all_members_timer), TYPE_PROC_REF(/datum/antagonist/cultist, descend)), 20 SECONDS)
+	if(!no_announcements)
+		GLOB.major_announcement.Announce("Paranormal activity has returned to minimal levels. \
+									Security staff should minimize lethal force against cultists, using non-lethals where possible. \
+									All dead cultists should be taken to medbay or robotics for immediate revival and deconversion. \
+									Non-security staff may defend themselves, but should prioritize leaving any areas with cultists and reporting the cultists to security. \
+									Self defense permits non-security staff to use lethal force as a last resort. Hunting down cultists may make you liable for a manslaughter charge. \
+									Any access granted in response to the paranormal threat should be reset. \
+									Any and all security gear that was handed out should be returned. Finally, all weapons (including improvised) should be removed from the crew.",
+									"Central Command Higher Dimensional Affairs", 'sound/AI/commandreport.ogg')
+/**
+ * This is a magic fuckin proc that takes a proc_ref, and calls it on all the human cultists.
+ * Created so that we don't make 1000 timers, and I'm too lazy to make a proc for all of these.
+ * Used in callbacks for some *magic bullshit*.
+ */
+/datum/team/cult/proc/all_members_timer(proc_ref_to_call)
+	for(var/datum/mind/M in members)
+		if(!ishuman(M.current))
+			continue
+		var/datum/antagonist/cultist/cultist = M.has_antag_datum(/datum/antagonist/cultist)
+		if(cultist)
+			call(cultist, proc_ref_to_call)() // yes this is a type proc ref passed by a callback, i know its deranged
+
+/datum/team/cult/proc/is_convertable_to_cult(datum/mind/mind)
+	if(!mind)
+		return FALSE
+	if(!mind.current)
+		return FALSE
+	if(IS_SACRIFICE_TARGET(mind))
+		return FALSE
+	if(mind.has_antag_datum(/datum/antagonist/cultist))
+		return TRUE //If they're already in the cult, assume they are convertable
+	if(HAS_MIND_TRAIT(mind.current, TRAIT_HOLY))
+		return FALSE
+	if(ishuman(mind.current))
+		var/mob/living/carbon/human/H = mind.current
+		if(ismindshielded(H)) //mindshield protects against conversions unless removed
+			return FALSE
+	if(mind.offstation_role)
+		return FALSE
+	if(issilicon(mind.current))
+		return FALSE //can't convert machines, that's ratvar's thing
+	if(isguardian(mind.current))
+		var/mob/living/simple_animal/hostile/guardian/G = mind.current
+		if(IS_CULTIST(G.summoner))
+			return TRUE //can't convert it unless the owner is converted
+	if(isgolem(mind.current))
+		return FALSE
+	if(isanimal(mind.current))
+		return FALSE
+	return TRUE
+
+/**
+  * Decides at the start of the round how many conversions are needed to rise/ascend.
+  *
+  * The number is decided by (Percentage * (Players - Cultists)), so for example at 110 players it would be 11 conversions for rise. (0.1 * (110 - 4))
+  * These values change based on population because 20 cultists are MUCH more powerful if there's only 50 players, compared to 120.
+  *
+  * Below 100 players, [CULT_RISEN_LOW] and [CULT_ASCENDANT_LOW] are used.
+  * Above 100 players, [CULT_RISEN_HIGH] and [CULT_ASCENDANT_HIGH] are used.
+  */
+/datum/team/cult/proc/cult_threshold_check()
+	var/list/living_players = get_living_players(exclude_nonhuman = TRUE, exclude_offstation = TRUE)
+	var/players = length(living_players)
+	var/cultists = get_cultists() // Don't count the starting cultists towards the number of needed conversions
+	if(players >= CULT_POPULATION_THRESHOLD)
+		// Highpop
+		ascend_percent = CULT_ASCENDANT_HIGH
+		rise_number = round(CULT_RISEN_HIGH * (players - cultists))
+		ascend_number = round(CULT_ASCENDANT_HIGH * (players - cultists))
+	else
+		// Lowpop
+		ascend_percent = CULT_ASCENDANT_LOW
+		rise_number = round(CULT_RISEN_LOW * (players - cultists))
+		ascend_number = round(CULT_ASCENDANT_LOW * (players - cultists))
+
+/datum/team/cult/proc/speak_to_all_alive_cultists(...)
+	var/message_to_sent = args.Join("<br>")
+	for(var/datum/mind/cult_mind in members)
+		if(cult_mind?.current)
+			to_chat(cult_mind.current, message_to_sent)
+
+/datum/team/cult/get_admin_priority_objectives()
+	. = list()
+	.["Sacrifice"] = /datum/objective/sacrifice
+	.["Summon God"] = /datum/objective/eldergod
+
+/datum/team/cult/handle_adding_admin_objective(mob/user, objective_type)
+	if(objective_type == /datum/objective/sacrifice)
+		if(obj_summon)
+			if(confirm_remove_eldergod_obj(user))
+				return TEAM_ADMIN_ADD_OBJ_SUCCESS
+
+		if(current_sac_objective())
+			var/alert_result = alert(user, "There is already a current sacrifice, reroll the cult's sacrifice target?", "Cult Debug", "Reroll", "Add new sacrifice", "Cancel")
+			if(alert_result == "Reroll")
+				admin_reroll_sac_target(user)
+				return TEAM_ADMIN_ADD_OBJ_SUCCESS | TEAM_ADMIN_ADD_OBJ_CANCEL_LOG
+			else if(alert_result == "Add new sacrifice")
+				return ..()
+			else
+				return TEAM_ADMIN_ADD_OBJ_PURPOSEFUL_CANCEL
+
+		return ..()
+
+
+	else if(objective_type == /datum/objective/eldergod)
+		if(confirm_add_eldergod_obj())
+			return TEAM_ADMIN_ADD_OBJ_SUCCESS
+		return TEAM_ADMIN_ADD_OBJ_PURPOSEFUL_CANCEL
+
+	return ..()
+
+/datum/team/cult/admin_remove_objective(mob/user, datum/objective/O)
+	if(istype(O, /datum/objective/eldergod))
+		confirm_remove_eldergod_obj(user)
+		return
+	. = ..()
+
+/datum/team/cult/proc/confirm_add_eldergod_obj(admin_caller, alert_text = "Unlock the ability to summon Nar'Sie?")
+	if(alert(admin_caller, alert_text, "Cult Debug", "Yes", "No") != "Yes")
+		return FALSE
+
+	ready_to_summon()
+
+	message_admins("Admin [key_name_admin(admin_caller)] has unlocked the Cult's ability to summon Nar'Sie.")
+	log_admin("Admin [key_name_admin(admin_caller)] has unlocked the Cult's ability to summon Nar'Sie.")
+	return TRUE
+
+/datum/team/cult/proc/confirm_remove_eldergod_obj(admin_caller)
+	if(alert(admin_caller, "Revert to pre-summon stage of Cult?", "Cult Debug", "Yes", "No") != "Yes")
+		return FALSE
+
+	sacrifices_required = max(sacrifices_done + 1, sacrifices_required) // make sure we're at least one above the required amount
+	objective_holder.remove_objective(obj_summon) // qdel's the objective too
+	obj_summon = null
+	current_sac_objective() // Create an objective only if needed
+	cult_status = NARSIE_DEMANDS_SACRIFICE
+
+	message_admins("Admin [key_name_admin(admin_caller)] has removed the Cult's ability to summon Nar'Sie.")
+	log_admin("Admin [key_name_admin(admin_caller)] has removed the Cult's ability to summon Nar'Sie.")
+	return TRUE
+
+/datum/team/cult/proc/study_objectives(mob/living/M, display_members = FALSE) //Called by cultists/cult constructs checking their objectives
+	if(!M)
+		return FALSE
+
+	switch(cult_status)
+		if(NARSIE_IS_ASLEEP)
+			to_chat(M, "<span class='cult'>[GET_CULT_DATA(entity_name, "The Dark One")] is asleep. This is probably a bug.</span>")
+		if(NARSIE_DEMANDS_SACRIFICE)
+			var/list/all_objectives = objective_holder.get_objectives()
+			if(!length(all_objectives))
+				to_chat(M, "<span class='danger'>Error: No objectives. Something went wrong, adminhelp with F1.</span>")
+			else
+				var/datum/objective/sacrifice/current_obj = all_objectives[length(all_objectives)] //get the last obj in the list, ie the current one
+				to_chat(M, "<span class='cult'>The Veil needs to be weakened before we are able to summon [GET_CULT_DATA(entity_title1, "The Dark One")].</span>")
+				to_chat(M, "<span class='cult'>Current goal: [current_obj.explanation_text]</span>")
+		if(NARSIE_NEEDS_SUMMONING)
+			to_chat(M, "<span class='cult'>The Veil is weak! We can summon [GET_CULT_DATA(entity_title3, "The Dark One")]!</span>")
+			to_chat(M, "<span class='cult'>Current goal: [obj_summon.explanation_text]</span>")
+		if(NARSIE_HAS_RISEN)
+			to_chat(M, "<span class='cultlarge'>\"I am here.\"</span>")
+			to_chat(M, "<span class='cult'>Current goal:</span> <span class='cultlarge'>\"Feed me.\"</span>")
+		if(NARSIE_HAS_FALLEN)
+			to_chat(M, "<span class='cultlarge'>[GET_CULT_DATA(entity_name, "The Dark One")] has been banished!</span>")
+			to_chat(M, "<span class='cult'>Current goal: Slaughter the unbelievers!</span>")
+		else
+			to_chat(M, "<span class='danger'>Error: Cult objective status currently unknown. Something went wrong, adminhelp with F1.</span>")
+
+	if(!display_members)
+		return
+	var/list/cult = get_cultists(separate = TRUE)
+	var/total_cult = cult[1] + cult[2]
+
+	var/overview = "<span class='cultitalic'><br><b>Current cult members: [total_cult]"
+	if(!cult_ascendant)
+		var/rise = rise_number - total_cult
+		var/ascend = ascend_number - total_cult
+		if(rise > 0)
+			overview += " | Conversions until Rise: [rise]"
+		else if(ascend > 0)
+			overview += " | Conversions until Ascension: [ascend]"
+	to_chat(M, "[overview]</b></span>")
+
+	if(cult[2]) // If there are any constructs, separate them out
+		to_chat(M, "<span class='cultitalic'><b>Cultists:</b> [cult[1]]")
+		to_chat(M, "<span class='cultitalic'><b>Constructs:</b> [cult[2]]")
+
+/datum/team/cult/proc/create_next_sacrifice()
+	var/datum/objective/sacrifice/obj_sac = objective_holder.add_objective(/datum/objective/sacrifice)
+	if(!obj_sac.target)
+		objective_holder.remove_objective(obj_sac)
+		ready_to_summon()
+		return
+	return obj_sac
+
+/// Return the current sacrifice objective datum, if any
+/datum/team/cult/proc/current_sac_objective()
+	var/list/presummon_objs = objective_holder.get_objectives()
+	if(cult_status == NARSIE_DEMANDS_SACRIFICE && length(presummon_objs))
+		var/datum/objective/sacrifice/current_obj = presummon_objs[length(presummon_objs)]
+		if(current_obj.sacced)
+			return create_next_sacrifice()
+		if(istype(current_obj))
+			return current_obj
+
+/datum/team/cult/proc/is_sac_target(datum/mind/mind)
+	var/datum/objective/sacrifice/current_obj = current_sac_objective()
+	return istype(current_obj) && current_obj.target == mind
+
+/datum/team/cult/proc/find_new_sacrifice_target()
+	var/datum/objective/sacrifice/current_obj = current_sac_objective()
+	if(!current_obj)
+		return FALSE
+	if(!current_obj.find_target(list(current_obj.target)))
+		objective_holder.remove_objective(current_obj)
+		ready_to_summon()
+		return FALSE
+	speak_to_all_alive_cultists("<span class='danger'>[GET_CULT_DATA(entity_name, "Your god")]</span> murmurs, <span class='cultlarge'>Our goal is beyond your reach. Sacrifice [current_obj.target] instead...</span>")
+	return TRUE
+
+/datum/team/cult/proc/successful_sacrifice()
+	var/datum/objective/sacrifice/current_obj = current_sac_objective()
+	if(!istype(current_obj))
+		return
+	current_obj.sacced = TRUE
+	sacrifices_done++
+	if(sacrifices_done >= sacrifices_required)
+		ready_to_summon()
+		return
+
+	var/datum/objective/sacrifice/obj_sac = create_next_sacrifice()
+	if(!obj_sac)
+		return
+
+	speak_to_all_alive_cultists(
+		"<span class='cult'>You and your acolytes have made progress, but there is more to do still before [GET_CULT_DATA(entity_title1, "The Dark One")] can be summoned!</span>",
+		"<span class='cult'>Current goal: [obj_sac.explanation_text]</span>"
+	)
+
+/datum/team/cult/proc/ready_to_summon()
+	if(!obj_summon)
+		obj_summon = objective_holder.add_objective(/datum/objective/eldergod)
+
+	cult_status = NARSIE_NEEDS_SUMMONING
+	speak_to_all_alive_cultists(
+		"<span class='cult'>You and your acolytes have succeeded in preparing the station for the ultimate ritual!</span>",
+		"<span class='cult'>Current goal: [obj_summon.explanation_text]</span>"
+	)
+
+/datum/team/cult/proc/successful_summon()
+	cult_status = NARSIE_HAS_RISEN
+	obj_summon.summoned = TRUE
+
+/datum/team/cult/proc/narsie_death()
+	cult_status = NARSIE_HAS_FALLEN
+	obj_summon.killed = TRUE
+	speak_to_all_alive_cultists(
+		"<span class='cultlarge'>RETRIBUTION!</span>",
+		"<span class='cult'>Current goal: Slaughter the heretics!</span>"
+	)
+
+/datum/team/cult/proc/get_cult_status_as_string()
+	var/list/define_to_string = list(
+		"[NARSIE_IS_ASLEEP]" = "NARSIE_IS_ASLEEP",
+		"[NARSIE_DEMANDS_SACRIFICE]" = "NARSIE_DEMANDS_SACRIFICE",
+		"[NARSIE_NEEDS_SUMMONING]" = "NARSIE_NEEDS_SUMMONING",
+		"[NARSIE_HAS_RISEN]" = "NARSIE_HAS_RISEN",
+		"[NARSIE_HAS_FALLEN]" = "NARSIE_HAS_FALLEN",
+	)
+	return define_to_string["[cult_status]"]
+
+/**
+ * ADMIN STUFF DOWN YONDER
+ */
+
+/datum/team/cult/get_admin_commands()
+	return list(
+		"Cult Mindspeak" = CALLBACK(src, PROC_REF(cult_mindspeak))
+		)
+
+/datum/team/cult/proc/cult_mindspeak(admin_caller)
+	var/input = stripped_input(admin_caller, "Communicate to all the cultists with the voice of [GET_CULT_DATA(entity_name, "a cult god")]", "Voice of [GET_CULT_DATA(entity_name, "Cult God")]")
+	if(!input)
+		return
+
+	speak_to_all_alive_cultists("<span class='cult'>[GET_CULT_DATA(entity_name, "Your god")] murmurs,</span> <span class='cultlarge'>\"[input]\"</span>")
+
+	for(var/mob/dead/observer/O in GLOB.player_list)
+		to_chat(O, "<span class='cult'>[GET_CULT_DATA(entity_name, "Your god")] murmurs,</span> <span class='cultlarge'>\"[input]\"</span>")
+
+	message_admins("Admin [key_name_admin(admin_caller)] has talked with the Voice of [GET_CULT_DATA(entity_name, "Cult God")].")
+	log_admin("[key_name(admin_caller)] Voice of [GET_CULT_DATA(entity_name, "Cult God")]: [input]")
+
+/datum/team/cult/proc/admin_reroll_sac_target(mob/user)
+	var/datum/objective/sacrifice/current_obj = current_sac_objective()
+
+	var/choice = alert(usr, "How would you like to reroll the cult sacrifice?", "Pick objective", "Pick target", "Random reroll", "Cancel")
+	if(choice == "Pick target")
+		var/new_target = get_admin_objective_targets(user, get_target_excludes(), current_obj.target.current)
+		if(new_target)
+			current_obj.target = new_target
+			current_obj.update_explanation_text()
+	else if(choice == "Random reroll")
+		find_new_sacrifice_target()
+	else
+		return
+
+	message_admins("Admin [key_name_admin(user)] has rerolled the Cult's sacrifice target.")
+	log_admin("Admin [key_name_admin(user)] has rerolled the Cult's sacrifice target.")
+	user.client.holder.check_teams()
+
+/datum/team/cult/Topic(href, href_list)
+	. = ..()
+
+	if(!check_rights(R_ADMIN))
+		return
+
+	// manually cramming some shit in here, because it only conditonally pops up
+
+	switch(href_list["cult_command"])
+		if("cult_adjustsacnumber")
+			var/amount = input("Adjust the amount of sacrifices required before summoning Nar'Sie", "Sacrifice Adjustment", 2) as null | num
+			if(amount > 0)
+				var/old = sacrifices_required
+				sacrifices_required = amount
+				message_admins("Admin [key_name_admin(usr)] has modified the amount of cult sacrifices required before summoning from [old] to [amount]")
+				log_admin("Admin [key_name_admin(usr)] has modified the amount of cult sacrifices required before summoning from [old] to [amount]")
+				if(sacrifices_done >= sacrifices_required)
+					confirm_add_eldergod_obj(usr, "Would you also like to unlock the summoning of Nar'sie?")
+			usr.client.holder.check_teams()
+
+		if("cult_newtarget")
+			if(alert(usr, "Reroll the cult's sacrifice target?", "Cult Debug", "Yes", "No") != "Yes")
+				return
+			admin_reroll_sac_target(usr)
+
+		if("cult_newsummonlocations")
+			if(!obj_summon)
+				to_chat(usr, "<span class='danger'>The cult has NO summon objective yet.</span>")
+				return
+			if(alert(usr, "Reroll the cult's summoning locations?", "Cult Debug", "Yes", "No") != "Yes")
+				return
+
+			obj_summon.find_summon_locations(TRUE)
+			if(cult_status == NARSIE_NEEDS_SUMMONING) //Only update cultists if they are already have the summon goal since they arent aware of summon spots till then
+				speak_to_all_alive_cultists(
+					"<span class='cult'>The veil has shifted! Our summoning will need to take place elsewhere.</span>",
+					"<span class='cult'>Current goal: [obj_summon.explanation_text]</span>"
+				)
+
+			message_admins("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.")
+			log_admin("Admin [key_name_admin(usr)] has rerolled the Cult's sacrifice target.")
+			usr.client.holder.check_teams()
+
+/datum/team/cult/get_admin_html()
+	var/list/content = ..()
+	content += "<br><br>Cult Controls:<br>"
+	content += "<br>Cult Status: [get_cult_status_as_string()]"
+	content += "<br>Sacrifices completed: [sacrifices_done]"
+	content += "<br>Sacrifice required for summoning: [sacrifices_required]<br>"
+	if(obj_summon)
+		content += "<br>Summoning locations: [english_list(obj_summon.summon_spots)]"
+		content += "<br><a href='?src=[UID()];cult_command=cult_newsummonlocations'>Reroll summoning locations</a>"
+	else
+		content += "<br>Summoning locations: None, Cult has not yet reached the summoning stage."
+	content += "<br>"
+	if(cult_status == NARSIE_DEMANDS_SACRIFICE)
+		content += "<br><a href='?src=[UID()];cult_command=cult_adjustsacnumber'>Modify amount of sacrifices required</a>"
+		content += "<br><a href='?src=[UID()];cult_command=cult_newtarget'>Reroll sacrifice target</a>"
+	else
+		content += "<br>Cannot modify amount of sacrifices required (Summon available!)"
+		content += "<br>Cannot reroll sacrifice target (Summon available!)"
+	return content
diff --git a/code/modules/antagonists/revolutionary/datum_headrev.dm b/code/modules/antagonists/revolutionary/datum_headrev.dm
index 9e56f012b762..5f98e11c7b44 100644
--- a/code/modules/antagonists/revolutionary/datum_headrev.dm
+++ b/code/modules/antagonists/revolutionary/datum_headrev.dm
@@ -57,8 +57,7 @@
 
 /datum/antagonist/rev/head/proc/demote()
 	var/datum/mind/old_owner = owner
-	silent = TRUE
-	owner.remove_antag_datum(/datum/antagonist/rev/head)
+	owner.remove_antag_datum(/datum/antagonist/rev/head, silent_removal = TRUE)
 
 	var/datum/antagonist/rev/demoted = new()
 	demoted.silent = TRUE
diff --git a/code/modules/antagonists/revolutionary/datum_revolutionary.dm b/code/modules/antagonists/revolutionary/datum_revolutionary.dm
index ed3be5c22cdf..9f9ffd95e0f3 100644
--- a/code/modules/antagonists/revolutionary/datum_revolutionary.dm
+++ b/code/modules/antagonists/revolutionary/datum_revolutionary.dm
@@ -38,7 +38,7 @@
 	return SSticker.mode.get_rev_team()
 
 /datum/antagonist/rev/get_team()
-	return SSticker.mode.get_rev_team()
+	return SSticker.mode.rev_team
 
 /datum/antagonist/rev/give_objectives()
 	var/datum/team/revolution/revolting = get_team()
@@ -46,8 +46,7 @@
 
 /datum/antagonist/rev/proc/promote()
 	var/datum/mind/old_owner = owner
-	silent = TRUE
-	owner.remove_antag_datum(/datum/antagonist/rev, FALSE)
+	owner.remove_antag_datum(/datum/antagonist/rev, FALSE, silent_removal = TRUE)
 
 	var/datum/antagonist/rev/head/new_revhead = new()
 	new_revhead.silent = TRUE
diff --git a/code/modules/antagonists/revolutionary/team_revolution.dm b/code/modules/antagonists/revolutionary/team_revolution.dm
index 8ede26271ce6..87a9bb3deb1e 100644
--- a/code/modules/antagonists/revolutionary/team_revolution.dm
+++ b/code/modules/antagonists/revolutionary/team_revolution.dm
@@ -4,26 +4,29 @@
 	var/max_headrevs = REVOLUTION_MAX_HEADREVS // adminbus is possible
 	var/have_we_won = FALSE
 
-/datum/team/revolution/New()
-	..()
+/datum/team/revolution/create_team()
+	. = ..()
 	update_team_objectives()
 	SSshuttle.registerHostileEnvironment(src)
 
 /datum/team/revolution/Destroy(force, ...)
-	SSticker.mode.rev_team = null
 	SSshuttle.clearHostileEnvironment(src)
 	return ..()
 
+/datum/team/revolution/can_create_team()
+	return isnull(SSticker.mode.rev_team)
 
-/datum/team/revolution/get_target_excludes()
-	return ..() + get_targetted_head_minds()
+/datum/team/revolution/assign_team()
+	SSticker.mode.rev_team = src
 
+/datum/team/revolution/clear_team_reference()
+	if(SSticker.mode.rev_team == src)
+		SSticker.mode.rev_team = null
+	else
+		CRASH("[src] ([type]) attempted to clear a team reference that wasn't itself!")
 
-/datum/team/revolution/remove_member(datum/mind/member)
-	. = ..()
-	var/datum/antagonist/rev/revolting = member.has_antag_datum(/datum/antagonist/rev) // maybe this should be get_antag_datum_from_member(member)
-	if(!QDELETED(revolting))
-		member.remove_antag_datum(/datum/antagonist/rev)
+/datum/team/revolution/get_target_excludes()
+	return ..() + get_targetted_head_minds()
 
 /datum/team/revolution/admin_add_objective(mob/user)
 	sanitize_objectives()
@@ -32,6 +35,10 @@
 		message_admins("[key_name_admin(user)] added a mutiny objective to the team '[name]', and no target was found, removing.")
 		log_admin("[key_name_admin(user)] added a mutiny objective to the team '[name]', and no target was found, removing.")
 
+/datum/team/revolution/get_admin_priority_objectives()
+	. = list()
+	.["Mutiny"] = /datum/objective/mutiny
+
 /datum/team/revolution/on_round_end()
 	return // for now... show nothing. Add this in when revs is added to midround/dynamic. Not showing it currently because its dependent on rev gamemode
 
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
index fa4a608c1e18..b0e22bc88748 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
@@ -44,3 +44,4 @@
 	return connected_device.portableConnectorReturnAir()
 
 /obj/proc/portableConnectorReturnAir()
+	return
diff --git a/code/modules/holiday/holiday.dm b/code/modules/holiday/holiday.dm
index 628b1f01828e..0f712f6b1e8c 100644
--- a/code/modules/holiday/holiday.dm
+++ b/code/modules/holiday/holiday.dm
@@ -10,6 +10,7 @@
 
 // This proc gets run before the game starts when the holiday is activated. Do festive shit here.
 /datum/holiday/proc/celebrate()
+	return
 
 // When the round starts, this proc is ran to get a text message to display to everyone to wish them a happy holiday
 /datum/holiday/proc/greet()
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index c77dab0c9fa7..dc43a12ccca7 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -269,12 +269,16 @@
 	return ..()
 
 /obj/item/crusher_trophy/proc/on_melee_hit(mob/living/target, mob/living/user) //the target and the user
+	return
 
 /obj/item/crusher_trophy/proc/on_projectile_fire(obj/item/projectile/destabilizer/marker, mob/living/user) //the projectile fired and the user
+	return
 
 /obj/item/crusher_trophy/proc/on_mark_application(mob/living/target, datum/status_effect/crusher_mark/mark, had_mark) //the target, the mark applied, and if the target had a mark before
+	return
 
 /obj/item/crusher_trophy/proc/on_mark_detonation(mob/living/target, mob/living/user) //the target and the user
+	return
 
 //goliath
 /obj/item/crusher_trophy/goliath_tentacle
diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm
index 555e05928f3a..715f18cccc57 100644
--- a/code/modules/mob/dead/observer/orbit.dm
+++ b/code/modules/mob/dead/observer/orbit.dm
@@ -111,7 +111,6 @@
 					if(SSticker && SSticker.mode)
 						other_antags += list(
 							"Blob" = (mind.special_role == SPECIAL_ROLE_BLOB),
-							"Cultist" = (mind in SSticker.mode.cult),
 							"Wizard" = (mind in SSticker.mode.wizards),
 							"Wizard's Apprentice" = (mind in SSticker.mode.apprentices),
 							"Nuclear Operative" = (mind in SSticker.mode.syndicates),
diff --git a/code/modules/mob/living/carbon/human/human_examine.dm b/code/modules/mob/living/carbon/human/human_examine.dm
index 61a9b1f54463..b8ecb090471a 100644
--- a/code/modules/mob/living/carbon/human/human_examine.dm
+++ b/code/modules/mob/living/carbon/human/human_examine.dm
@@ -31,10 +31,10 @@
 				return "<span class='warning'>[p_they(TRUE)] [p_have()] [hand_blood_color != "#030303" ? "blood-stained":"oil-stained"] hands!</span>\n"
 		if("eyes")
 			if(HAS_TRAIT(src, SCRYING))
-				if(iscultist(src) && HAS_TRAIT(src, CULT_EYES))
+				if(IS_CULTIST(src) && HAS_TRAIT(src, CULT_EYES))
 					return "<span class='boldwarning'>[p_their(TRUE)] glowing red eyes are glazed over!</span>\n"
 				return "<span class='boldwarning'>[p_their(TRUE)] eyes are glazed over.</span>\n"
-			if(iscultist(src) && HAS_TRAIT(src, CULT_EYES))
+			if(IS_CULTIST(src) && HAS_TRAIT(src, CULT_EYES))
 				return "<span class='boldwarning'>[p_their(TRUE)] eyes are glowing an unnatural red!</span>\n"
 
 	return msg
diff --git a/code/modules/mob/living/carbon/human/human_mob.dm b/code/modules/mob/living/carbon/human/human_mob.dm
index 62253f123b72..84d9d965d526 100644
--- a/code/modules/mob/living/carbon/human/human_mob.dm
+++ b/code/modules/mob/living/carbon/human/human_mob.dm
@@ -1676,7 +1676,7 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
 	rad_act(current_size * 3)
 
 /mob/living/carbon/human/narsie_act()
-	if(iswizard(src) && iscultist(src)) //Wizard cultists are immune to narsie because it would prematurely end the wiz round that's about to end by the automated shuttle call anyway
+	if(iswizard(src) && IS_CULTIST(src)) //Wizard cultists are immune to narsie because it would prematurely end the wiz round that's about to end by the automated shuttle call anyway
 		return
 	..()
 
diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm
index ea8e856288c7..f24d67e83466 100644
--- a/code/modules/mob/living/carbon/human/human_update_icons.dm
+++ b/code/modules/mob/living/carbon/human/human_update_icons.dm
@@ -1343,7 +1343,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
 /mob/living/carbon/human/proc/update_halo_layer()
 	remove_overlay(HALO_LAYER)
 
-	if(iscultist(src) && SSticker.mode.cult_ascendant)
+	if(IS_CULTIST(src) && SSticker.mode.cult_team.cult_ascendant)
 		var/istate = pick("halo1", "halo2", "halo3", "halo4", "halo5", "halo6")
 		var/mutable_appearance/new_halo_overlay = mutable_appearance('icons/effects/32x64.dmi', istate, -HALO_LAYER)
 		overlays_standing[HALO_LAYER] = new_halo_overlay
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 4f3933fffb2f..4fc6391cee5e 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -393,6 +393,7 @@
 
 
 /mob/proc/get_contents()
+	return
 
 
 //Recursive function to find everything a mob is holding.
@@ -482,11 +483,6 @@
 			C.reagents.clear_reagents()
 			QDEL_LIST_CONTENTS(C.reagents.addiction_list)
 			C.reagents.addiction_threshold_accumulated.Cut()
-		if(iscultist(src))
-			if(SSticker.mode.cult_risen)
-				SSticker.mode.rise(src)
-			if(SSticker.mode.cult_ascendant)
-				SSticker.mode.ascend(src)
 
 		QDEL_LIST_CONTENTS(C.processing_patches)
 
@@ -851,6 +847,7 @@
 	return 0
 
 /mob/living/proc/check_ear_prot()
+	return
 
 /**
  * Returns the name override, if any, for the slot somebody is trying to strip
diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm
index 995ac5c9e0a9..47b33f22361a 100644
--- a/code/modules/mob/living/silicon/pai/recruit.dm
+++ b/code/modules/mob/living/silicon/pai/recruit.dm
@@ -39,8 +39,6 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo
 			card.setPersonality(pai)
 			card.looking_for_personality = 0
 
-			SSticker.mode.update_cult_icons_removed(card.pai.mind)
-
 			pai_candidates -= candidate
 			usr << browse(null, "window=findPai")
 		return
diff --git a/code/modules/mob/living/silicon/silicon_login.dm b/code/modules/mob/living/silicon/silicon_login.dm
index 0e13a23e00b2..6592a4d6a331 100644
--- a/code/modules/mob/living/silicon/silicon_login.dm
+++ b/code/modules/mob/living/silicon/silicon_login.dm
@@ -2,7 +2,7 @@
 	SetSleeping(0)
 	if(mind && SSticker && SSticker.mode)
 		SSticker.mode.remove_revolutionary(mind, 1)
-		SSticker.mode.remove_cultist(mind, 1)
+		mind.remove_antag_datum(/datum/antagonist/cultist)
 		SSticker.mode.remove_wizard(mind)
 		mind.remove_antag_datum(/datum/antagonist/changeling)
 		mind.remove_antag_datum(/datum/antagonist/vampire)
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index cd5bc149f7d7..0889c60fb818 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -36,30 +36,26 @@
 
 /mob/living/simple_animal/hostile/construct/Initialize(mapload)
 	. = ..()
-	if(!SSticker.mode)//work around for maps with runes and cultdat is not loaded all the way
-		name = "[construct_type] ([rand(1, 1000)])"
-		real_name = construct_type
-		icon_living = construct_type
-		icon_state = construct_type
-	else
-		name = "[SSticker.cultdat.get_name(construct_type)] ([rand(1, 1000)])"
-		real_name = SSticker.cultdat.get_name(construct_type)
-		icon_living = SSticker.cultdat.get_icon(construct_type)
-		icon_state = SSticker.cultdat.get_icon(construct_type)
+	name = "[GET_CULT_DATA(get_name(construct_type), construct_type)] ([rand(1, 1000)])"
+	real_name = GET_CULT_DATA(get_name(construct_type), construct_type)
+	icon_living = GET_CULT_DATA(get_icon(construct_type), construct_type)
+	icon_state = GET_CULT_DATA(get_icon(construct_type), construct_type)
 
 	for(var/spell in construct_spells)
 		AddSpell(new spell(null))
 
-	set_light(2, 3, l_color = SSticker.cultdat ? SSticker.cultdat.construct_glow : LIGHT_COLOR_BLOOD_MAGIC)
+	set_light(2, 3, l_color = GET_CULT_DATA(construct_glow, LIGHT_COLOR_BLOOD_MAGIC))
 
 /mob/living/simple_animal/hostile/construct/Destroy()
+	mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
 	remove_held_body()
 	return ..()
 
 /mob/living/simple_animal/hostile/construct/death(gibbed)
+	mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
 	if(held_body) // Null check for empty bodies
 		held_body.forceMove(get_turf(src))
-		SSticker.mode.add_cult_immunity(held_body)
+		SSticker.mode?.cult_team?.add_cult_immunity(held_body)
 		if(ismob(held_body)) // Check if the held_body is a mob
 			held_body.key = key
 		else if(istype(held_body, /obj/item/organ/internal/brain)) // Check if the held_body is a brain
@@ -72,10 +68,6 @@
 	playsound(src, 'sound/effects/pylon_shatter.ogg', 40, TRUE)
 	return ..()
 
-/mob/living/simple_animal/hostile/construct/Destroy()
-	SSticker.mode.remove_cultist(show_message = FALSE, target_mob = src)
-	return ..()
-
 /mob/living/simple_animal/hostile/construct/proc/add_held_body(atom/movable/body)
 	held_body = body
 	RegisterSignal(body, COMSIG_PARENT_QDELETING, PROC_REF(remove_held_body))
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index 3a95cf1d7121..5483c9390c97 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -373,7 +373,7 @@ Difficulty: Hard
 
 /mob/living/simple_animal/hostile/megafauna/bubblegum/proc/hit_up_narsi()
 	SetRecoveryTime(20)
-	visible_message("<span class='colossus'><b>[pick("[SSticker.cultdat.entity_name], I call on YOU for one of MY favours you owe me!", "[SSticker.cultdat.entity_title1], I call on you for some support...", "Let us see how you like the minions of [SSticker.cultdat.entity_title2]!", "Oh, [SSticker.cultdat.entity_title3] join me in RENDING THIS WHELP APART!")]</b></span>")
+	visible_message("<span class='colossus'><b>[pick("[GET_CULT_DATA(entity_name, "Nar'sie")], I call on YOU for one of MY favours you owe me!", "[GET_CULT_DATA(entity_title1, "Nar'sie")], I call on you for some support...", "Let us see how you like the minions of [GET_CULT_DATA(entity_title2, "Nar'sie")]!", "Oh, [GET_CULT_DATA(entity_title3, "Nar'sie")] join me in RENDING THIS WHELP APART!")]</b></span>")
 	var/list/turfs = list()
 	var/constructs = 0
 	for(var/turf/T in view(6, target))
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining/elites/herald.dm
index 527ad1e96c18..bf637c1fa3e4 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/elites/herald.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/elites/herald.dm
@@ -284,7 +284,7 @@
 			continue
 		if(T.z != usr.z) //No crossing zlvls
 			continue
-		if(istype(i, /obj/item/shield/mirror) && !iscultist(usr)) //No teleporting to cult bases
+		if(istype(i, /obj/item/shield/mirror) && !IS_CULTIST(usr)) //No teleporting to cult bases
 			continue
 		if(istype(i, /obj/structure/mirror))
 			var/obj/structure/mirror/B = i
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index 7b49bb26964a..17e52b478a89 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -34,12 +34,8 @@
 	deathmessage = "lets out a contented sigh as their form unwinds."
 	var/holy = FALSE
 
-/mob/living/simple_animal/shade/cult/Initialize(mapload)
-	. = ..()
-	icon_state = SSticker.cultdat?.shade_icon_state
-
 /mob/living/simple_animal/shade/Destroy()
-	SSticker.mode.remove_cultist(show_message = FALSE, target_mob = src)
+	mind?.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
 	return ..()
 
 /mob/living/simple_animal/shade/attackby(obj/item/O, mob/user)  //Marker -Agouri
@@ -56,6 +52,12 @@
 	holy = TRUE
 	icon_state = "shade_angelic"
 
+/mob/living/simple_animal/shade/cult
+
+/mob/living/simple_animal/shade/cult/Initialize(mapload)
+	. = ..()
+	icon_state = GET_CULT_DATA(shade_icon_state, initial(icon_state))
+
 /mob/living/simple_animal/shade/sword
 	faction = list("neutral")
 	a_intent = INTENT_HARM // scuffed sword mechanics bad
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index f407ff4da6d6..235b5b26710a 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1486,12 +1486,11 @@ GLOBAL_LIST_INIT(holy_areas, typecacheof(list(
 		return FALSE
 
 	//Allows cult to bypass holy areas once they summon
-	var/datum/game_mode/gamemode = SSticker.mode
-	if(iscultist(src) && gamemode.cult_objs.cult_status == NARSIE_HAS_RISEN)
+	if(mind.has_antag_datum(/datum/antagonist/cultist) && SSticker.mode.cult_team.cult_status == NARSIE_HAS_RISEN)
 		return FALSE
 
 	//Execption for Holy Constructs
-	if(isconstruct(src) && !iscultist(src))
+	if(isconstruct(src) && !mind.has_antag_datum(/datum/antagonist/cultist))
 		return FALSE
 
 	to_chat(src, "<span class='warning'>Your powers are useless on this holy ground.</span>")
diff --git a/code/modules/mob/mob_misc_procs.dm b/code/modules/mob/mob_misc_procs.dm
index aeb97a0b355b..fb452bffb237 100644
--- a/code/modules/mob/mob_misc_procs.dm
+++ b/code/modules/mob/mob_misc_procs.dm
@@ -796,9 +796,6 @@
 		if(DEAD)
 			return "dead"
 
-/mob/proc/attempt_listen_to_deadsay()
-
-
 /mob/proc/is_roundstart_observer()
 	return (ckey in GLOB.roundstart_observer_keys)
 
diff --git a/code/modules/pda/messenger_plugins.dm b/code/modules/pda/messenger_plugins.dm
index 74b1aabdb577..74f4f3a51ca0 100644
--- a/code/modules/pda/messenger_plugins.dm
+++ b/code/modules/pda/messenger_plugins.dm
@@ -2,6 +2,7 @@
 	var/datum/data/pda/app/messenger/messenger
 
 /datum/data/pda/messenger_plugin/proc/user_act(mob/user as mob, obj/item/pda/P)
+	return
 
 
 /datum/data/pda/messenger_plugin/virus
diff --git a/code/modules/power/engines/singularity/narsie.dm b/code/modules/power/engines/singularity/narsie.dm
index 47676b99e59d..c6cb5db24c4e 100644
--- a/code/modules/power/engines/singularity/narsie.dm
+++ b/code/modules/power/engines/singularity/narsie.dm
@@ -28,8 +28,8 @@
 
 /obj/singularity/narsie/large/Initialize(mapload, starting_energy)
 	. = ..()
-	icon_state = SSticker.cultdat?.entity_icon_state
-	name = SSticker.cultdat?.entity_name
+	icon_state = GET_CULT_DATA(entity_icon_state, initial(icon_state))
+	name = GET_CULT_DATA(entity_name, initial(name))
 
 	var/sound/cry = sound('modular_ss220/aesthetics_sounds/sound/narsie/narsie_risen.ogg') //SS220 EDIT
 
@@ -40,9 +40,7 @@
 		to_chat(player, "<font size='15' color='red'><b> [uppertext(name)] HAS RISEN</b></font>")
 		SEND_SOUND(player, cry)
 
-	var/datum/game_mode/gamemode = SSticker.mode
-	if(gamemode)
-		gamemode.cult_objs.succesful_summon()
+	SSticker.mode?.cult_team?.successful_summon()
 
 	var/area/A = get_area(src)
 	if(A)
@@ -59,13 +57,7 @@
 /obj/singularity/narsie/large/Destroy()
 	to_chat(world, "<font size='15' color='red'><b> [uppertext(name)] HAS FALLEN</b></font>")
 	SEND_SOUND(world, sound('sound/hallucinations/wail.ogg'))
-	var/datum/game_mode/gamemode = SSticker.mode
-	if(gamemode)
-		gamemode.cult_objs.narsie_death()
-		for(var/datum/mind/cult_mind in SSticker.mode.cult)
-			if(cult_mind && cult_mind.current)
-				to_chat(cult_mind.current, "<span class='cultlarge'>RETRIBUTION!</span>")
-				to_chat(cult_mind.current, "<span class='cult'>Current goal: Slaughter the heretics!</span>")
+	SSticker.mode?.cult_team?.narsie_death()
 	..()
 
 /obj/singularity/narsie/large/attack_ghost(mob/dead/observer/user as mob)
@@ -102,7 +94,7 @@
 /obj/singularity/narsie/mezzer()
 	for(var/mob/living/carbon/M in oviewers(8, src))
 		if(M.stat == CONSCIOUS)
-			if(!iscultist(M))
+			if(!IS_CULTIST(M))
 				to_chat(M, "<span class='warning'>You feel your sanity crumble away in an instant as you gaze upon [src.name]...</span>")
 				M.Stun(6 SECONDS)
 
@@ -129,7 +121,7 @@
 		if(pos.z != src.z)
 			continue
 
-		if(iscultist(food))
+		if(IS_CULTIST(food))
 			cultists += food
 		else
 			noncultists += food
@@ -160,12 +152,12 @@
 		return
 	if(!target)
 		return
-	to_chat(target, "<span class='cultlarge'>[uppertext(SSticker.cultdat.entity_name)] HAS LOST INTEREST IN YOU</span>")
+	to_chat(target, "<span class='cultlarge'>[uppertext(GET_CULT_DATA(entity_name, name))] HAS LOST INTEREST IN YOU</span>")
 	target = food
 	if(ishuman(target))
-		to_chat(target, "<span class ='cultlarge'>[uppertext(SSticker.cultdat.entity_name)] HUNGERS FOR YOUR SOUL</span>")
+		to_chat(target, "<span class ='cultlarge'>[uppertext(GET_CULT_DATA(entity_name, name))] HUNGERS FOR YOUR SOUL</span>")
 	else
-		to_chat(target, "<span class ='cultlarge'>[uppertext(SSticker.cultdat.entity_name)] HAS CHOSEN YOU TO LEAD HER TO HER NEXT MEAL</span>")
+		to_chat(target, "<span class ='cultlarge'>[uppertext(GET_CULT_DATA(entity_name, name))] HAS CHOSEN YOU TO LEAD HER TO HER NEXT MEAL</span>")
 
 //Wizard narsie
 /obj/singularity/narsie/wizard
@@ -182,7 +174,7 @@
 	icon = 'icons/obj/narsie_spawn_anim.dmi'
 	dir = SOUTH
 	move_self = FALSE
-	flick(SSticker.cultdat?.entity_spawn_animation, src)
+	flick(GET_CULT_DATA(entity_spawn_animation, "narsie_spawn_anim"), src)
 	sleep(11)
 	move_self = TRUE
 	icon = initial(icon)
diff --git a/code/modules/power/engines/singularity/singularity.dm b/code/modules/power/engines/singularity/singularity.dm
index cd63e924f61b..9fbc5c13cd2b 100644
--- a/code/modules/power/engines/singularity/singularity.dm
+++ b/code/modules/power/engines/singularity/singularity.dm
@@ -314,10 +314,10 @@
 		set_light(10)
 	if(istype(A, /obj/singularity/narsie))
 		if(current_size == STAGE_SIX)
-			visible_message("<span class='userdanger'>[SSticker.cultdat?.entity_name] is consumed by [src]!</span>")
+			visible_message("<span class='userdanger'>[GET_CULT_DATA(entity_name, A.name)] is consumed by [src]!</span>")
 			qdel(A)
 		else
-			visible_message("<span class='userdanger'>[SSticker.cultdat?.entity_name] strikes down [src]!</span>")
+			visible_message("<span class='userdanger'>[GET_CULT_DATA(entity_name, A.name)] strikes down [src]!</span>")
 			investigate_log("has been destroyed by Nar'Sie","singulo")
 			qdel(src)
 
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 5562950fb090..607f01ab5148 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -332,13 +332,19 @@
 	return ..()
 
 /obj/item/borg/upgrade/modkit/proc/modify_projectile(obj/item/projectile/kinetic/K)
+	return
 
 //use this one for effects you want to trigger before any damage is done at all and before damage is decreased by pressure
 /obj/item/borg/upgrade/modkit/proc/projectile_prehit(obj/item/projectile/kinetic/K, atom/target, obj/item/gun/energy/kinetic_accelerator/KA)
+	return
+
 //use this one for effects you want to trigger before mods that do damage
 /obj/item/borg/upgrade/modkit/proc/projectile_strike_predamage(obj/item/projectile/kinetic/K, turf/target_turf, atom/target, obj/item/gun/energy/kinetic_accelerator/KA)
+	return
+
 //and this one for things that don't need to trigger before other damage-dealing mods
 /obj/item/borg/upgrade/modkit/proc/projectile_strike(obj/item/projectile/kinetic/K, turf/target_turf, atom/target, obj/item/gun/energy/kinetic_accelerator/KA)
+	return
 
 //Range
 /obj/item/borg/upgrade/modkit/range
diff --git a/code/modules/reagents/chemistry/reagents/water.dm b/code/modules/reagents/chemistry/reagents/water.dm
index 451cc4fa829e..269012732637 100644
--- a/code/modules/reagents/chemistry/reagents/water.dm
+++ b/code/modules/reagents/chemistry/reagents/water.dm
@@ -260,7 +260,7 @@
 	if(current_cycle >= 30)		// 12 units, 60 seconds @ metabolism 0.4 units & tick rate 2.0 sec
 		M.AdjustStuttering(8 SECONDS, bound_lower = 0, bound_upper = 40 SECONDS)
 		M.Dizzy(10 SECONDS)
-		if(iscultist(M))
+		if(IS_CULTIST(M))
 			for(var/datum/action/innate/cult/blood_magic/BM in M.actions)
 				for(var/datum/action/innate/cult/blood_spell/BS in BM.spells)
 					to_chat(M, "<span class='cultlarge'>Your blood rites falter as holy water scours your body!</span>")
@@ -278,14 +278,18 @@
 		M.AdjustConfused(6 SECONDS)
 		if(isvampirethrall(M))
 			M.mind.remove_antag_datum(/datum/antagonist/mindslave/thrall)
+
 			holder.remove_reagent(id, volume)
 			M.visible_message("<span class='biggerdanger'>[M] recoils, their skin flushes with colour, regaining their sense of control!</span>")
 			M.SetJitter(0)
 			M.SetStuttering(0)
 			M.SetConfused(0)
 			return
-		if(iscultist(M))
-			SSticker.mode.remove_cultist(M.mind, TRUE, TRUE)
+		if(IS_CULTIST(M))
+			var/datum/antagonist/cultist/cultist = IS_CULTIST(M)
+			cultist.remove_gear_on_removal = TRUE
+			M.mind.remove_antag_datum(/datum/antagonist/cultist)
+
 			holder.remove_reagent(id, volume)	// maybe this is a little too perfect and a max() cap on the statuses would be better??
 			M.SetJitter(0)
 			M.SetStuttering(0)
@@ -374,7 +378,7 @@
 
 /datum/reagent/fuel/unholywater/on_mob_life(mob/living/M)
 	var/update_flags = STATUS_UPDATE_NONE
-	if(iscultist(M))
+	if(IS_CULTIST(M))
 		M.AdjustDrowsy(-10 SECONDS)
 		M.AdjustParalysis(-2 SECONDS)
 		M.AdjustStunned(-4 SECONDS)
diff --git a/code/modules/redis/callbacks/server_messages_callback.dm b/code/modules/redis/callbacks/server_messages_callback.dm
index ea263a223f00..59a285369384 100644
--- a/code/modules/redis/callbacks/server_messages_callback.dm
+++ b/code/modules/redis/callbacks/server_messages_callback.dm
@@ -8,3 +8,4 @@
 	// And fire
 	SSinstancing.execute_command(data["src"], data["cmd"], data["args"])
 	#endif
+	return // we need this so that the proc isnt empty when on non-multi-instance
diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm
index b7b2d0f74191..af6950b9911f 100644
--- a/code/modules/surgery/organs/vocal_cords.dm
+++ b/code/modules/surgery/organs/vocal_cords.dm
@@ -150,7 +150,7 @@ GLOBAL_DATUM_INIT(multispin_words, /regex, regex("like a record baby"))
 
 /obj/item/organ/internal/vocal_cords/colossus/handle_speech(message)
 	spans = "colossus yell" //reset spans, just in case someone gets deculted or the cords change owner
-	if(iscultist(owner))
+	if(IS_CULTIST(owner))
 		spans += "narsiesmall"
 	return "<span class=\"[spans]\">[uppertext(message)]</span>"
 
@@ -186,7 +186,7 @@ GLOBAL_DATUM_INIT(multispin_words, /regex, regex("like a record baby"))
 			power_multiplier *= 0.5
 
 	//Cultists are closer to their gods and are more powerful, but they'll give themselves away
-	if(iscultist(owner))
+	if(IS_CULTIST(owner))
 		power_multiplier *= 2
 
 	//It's magic, they are a wizard.
diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm
index a1c03c154a43..53937b57bb66 100644
--- a/code/modules/tgui/external.dm
+++ b/code/modules/tgui/external.dm
@@ -149,6 +149,7 @@
  * client/verb/uiclose(), which closes the ui window
  */
 /datum/proc/ui_close(mob/user)
+	return
 
 /**
  * verb
diff --git a/icons/effects/uristrunes.dmi b/icons/effects/uristrunes.dmi
deleted file mode 100644
index ee2063ea9aea..000000000000
Binary files a/icons/effects/uristrunes.dmi and /dev/null differ
diff --git a/modular_ss220/antagonists/code/blood_brothers/blood_brothers_datum.dm b/modular_ss220/antagonists/code/blood_brothers/blood_brothers_datum.dm
index b145dabbbd17..7432787643f0 100644
--- a/modular_ss220/antagonists/code/blood_brothers/blood_brothers_datum.dm
+++ b/modular_ss220/antagonists/code/blood_brothers/blood_brothers_datum.dm
@@ -9,7 +9,7 @@
 	antag_hud_name = "hudbloodbrother"
 	antag_hud_type = ANTAG_HUD_BLOOD_BROTHER
 	clown_gain_text = {"Ты очень много тренировался, чтобы наконец-то вступить в Синдикат, даже твоя клоунская натура не сможет помешать.
-						Ты уверенно владеешь всем оружием."}
+Ты уверенно владеешь всем оружием."}
 	clown_removal_text = "Все тренировки пошли насмарку - ты как был клоуном, так и остался."
 	wiki_page_name = "Blood_Brothers"
 	var/datum/team/blood_brothers_team/brothers_team = null
@@ -24,9 +24,11 @@
 	. = ..()
 	SEND_SOUND(owner.current, sound('modular_ss220/antagonists/sound/ambience/antag/blood_brothers_intro.ogg'))
 
-	. += {"Вы ненавидите Нанотрейзен, корпорация дала вам достаточно поводов для этого.
-			Лучшую возможность бороться с ней предоставляет Синдикат, так что вы со своим напарником, разделяющим подобные чувства, связались с ними, чтобы вступить в их ряды.
-			Теперь вы кровные братья и вы готовы сделать все ради общей цели."}
+	. += trim({"
+Вы ненавидите Нанотрейзен, корпорация дала вам достаточно поводов для этого.
+Лучшую возможность бороться с ней предоставляет Синдикат, так что вы со своим напарником, разделяющим подобные чувства, связались с ними, чтобы вступить в их ряды.
+Теперь вы кровные братья и вы готовы сделать все ради общей цели.
+"})
 
 	var/brother_names = get_brother_names_text()
 	if(brother_names)
diff --git a/modular_ss220/antagonists/code/blood_brothers/blood_brothers_team.dm b/modular_ss220/antagonists/code/blood_brothers/blood_brothers_team.dm
index 5f3640ecd464..79f156d7a1d4 100644
--- a/modular_ss220/antagonists/code/blood_brothers/blood_brothers_team.dm
+++ b/modular_ss220/antagonists/code/blood_brothers/blood_brothers_team.dm
@@ -44,11 +44,11 @@
 	pick_meeting_area()
 	forge_objectives()
 
-/datum/team/blood_brothers_team/add_member(datum/mind/new_member, add_antag_datum)
+/datum/team/blood_brothers_team/handle_adding_member(datum/mind/new_member)
 	. = ..()
 	update_name()
 
-/datum/team/blood_brothers_team/remove_member(datum/mind/member)
+/datum/team/blood_brothers_team/handle_removing_member(datum/mind/member, force)
 	. = ..()
 	update_name()
 
diff --git a/modular_ss220/clothing/code/mask.dm b/modular_ss220/clothing/code/mask.dm
index 16f632d60b54..53f8378b0142 100644
--- a/modular_ss220/clothing/code/mask.dm
+++ b/modular_ss220/clothing/code/mask.dm
@@ -3,6 +3,7 @@
 
 /obj/item/clothing/mask/proc/handle_speech(datum/source, list/speech_args)
 	SIGNAL_HANDLER
+	return
 
 /obj/item/clothing/mask/equipped(mob/M, slot)
 	. = ..()
diff --git a/modular_ss220/credits/code/credits.dm b/modular_ss220/credits/code/credits.dm
index d7a71e3fdec8..49e040935603 100644
--- a/modular_ss220/credits/code/credits.dm
+++ b/modular_ss220/credits/code/credits.dm
@@ -28,6 +28,7 @@
 
 
 /datum/credits/proc/fill_credits()
+	return
 
 /datum/credits/proc/count_time()
 	playing_time += delay_time
diff --git a/paradise.dme b/paradise.dme
index fb8b0c4c52ae..d66b58c052d3 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -888,7 +888,6 @@
 #include "code\game\machinery\vendors\tilt_crits.dm"
 #include "code\game\machinery\vendors\vending.dm"
 #include "code\game\machinery\vendors\wardrobe_vendors.dm"
-#include "code\game\magic\Uristrunes.dm"
 #include "code\game\mecha\mech_bay.dm"
 #include "code\game\mecha\mech_fabricator.dm"
 #include "code\game\mecha\mecha.dm"
@@ -1437,6 +1436,8 @@
 #include "code\modules\antagonists\changeling\powers\swap_form.dm"
 #include "code\modules\antagonists\changeling\powers\tiny_prick.dm"
 #include "code\modules\antagonists\changeling\powers\transform.dm"
+#include "code\modules\antagonists\cult\datum_cultist.dm"
+#include "code\modules\antagonists\cult\team_cult.dm"
 #include "code\modules\antagonists\revolutionary\datum_headrev.dm"
 #include "code\modules\antagonists\revolutionary\datum_revolutionary.dm"
 #include "code\modules\antagonists\revolutionary\team_revolution.dm"
diff --git a/tgui/yarn.lock b/tgui/yarn.lock
index 9d1006f47d16..141e5941de85 100644
--- a/tgui/yarn.lock
+++ b/tgui/yarn.lock
@@ -4446,12 +4446,12 @@ __metadata:
   linkType: hard
 
 "follow-redirects@npm:^1.15.4":
-  version: 1.15.4
-  resolution: "follow-redirects@npm:1.15.4"
+  version: 1.15.6
+  resolution: "follow-redirects@npm:1.15.6"
   peerDependenciesMeta:
     debug:
       optional: true
-  checksum: 2e8f5f259a6b02dfa8dc199e08431848a7c3beed32eb4c19945966164a52c89f07b86c3afcc32ebe4279cf0a960520e45a63013d6350309c5ec90133c5d9351a
+  checksum: 70c7612c4cab18e546e36b991bbf8009a1a41cf85354afe04b113d1117569abf760269409cb3eb842d9f7b03d62826687086b081c566ea7b1e6613cf29030bf7
   languageName: node
   linkType: hard
 
diff --git a/tools/bootstrap/python b/tools/bootstrap/python
old mode 100644
new mode 100755
index d5e031b8aaf2..e679f2c7a6a9
--- a/tools/bootstrap/python
+++ b/tools/bootstrap/python
@@ -23,6 +23,7 @@ cd "$OldPWD"
 PythonVersion="$PYTHON_VERSION"
 PythonDir="$Cache/python-$PythonVersion"
 PythonExe="$PythonDir/python.exe"
+PythonArg=""
 Log="$Cache/last-command.log"
 
 # If a portable Python for Windows is not present, search on $PATH.
@@ -38,7 +39,8 @@ if [ "$(uname)" = "Linux" ] || [ ! -f "$PythonExe" ]; then
 	elif command -v python >/dev/null 2>&1; then
 		PythonExe=python
 	elif command -v py >/dev/null 2>&1; then
-		PythonExe="py -3"
+		PythonExe="py"
+		PythonArg="-3"
 	else
 		echo
 		if command -v apt-get >/dev/null 2>&1; then
@@ -62,12 +64,17 @@ if [ "$(uname)" = "Linux" ] || [ ! -f "$PythonExe" ]; then
 	PythonDir="$Cache/venv"
 	if [ ! -d "$PythonDir" ]; then
 		echo "Creating virtualenv..."
-		"$PythonExe" -m venv "$PythonDir"
+		"$PythonExe" $PythonArg -m venv "$PythonDir"
 	fi
 	if [ -f "$PythonDir/bin/python" ]; then
 		PythonExe="$PythonDir/bin/python"
+		PythonArg=""
 	elif [ -f "$PythonDir/scripts/python3.exe" ]; then
 		PythonExe="$PythonDir/scripts/python3.exe";
+		PythonArg=""
+	elif [ -f "$PythonDir/scripts/python.exe" ]; then
+		PythonExe="$PythonDir/scripts/python.exe";
+		PythonArg=""
 	else
 		echo "bootstrap/python failed to find the python executable inside its virtualenv"
 		exit 1
@@ -77,8 +84,9 @@ fi
 # Use pip to install our requirements
 if [ ! -f "$PythonDir/requirements.txt" ] || [ "$(b2sum < "$Sdk/requirements.txt")" != "$(b2sum < "$PythonDir/requirements.txt")" ]; then
 	echo "Updating dependencies..."
-	"$PythonExe" -m pip install -U wheel
-	"$PythonExe" -m pip install -U pip -r "$Sdk/requirements.txt"
+	"$PythonExe"  $PythonArg -m ensurepip || echo "ensurepip failed, continuing anyway..."
+	"$PythonExe" $PythonArg -m pip install -U wheel
+	"$PythonExe" $PythonArg -m pip install -U pip -r "$Sdk/requirements.txt"
 	cp "$Sdk/requirements.txt" "$PythonDir/requirements.txt"
 	echo "---"
 fi
@@ -107,6 +115,11 @@ if ! command -v tee >/dev/null 2>&1; then
 	}
 fi
 
+if [ "$(uname -o)" = "Msys" ]; then
+	# replace /c/ with c:/ for Windows
+	Sdk=$(echo "$Sdk" | sed 's|^\(/.\)/|\1:/|')
+fi
+
 # Invoke python with all command-line arguments
 export PYTHONPATH="$Sdk$PATHSEP${PYTHONPATH:-}"
 mkdir -p "$Cache"
diff --git a/tools/ci/lints.dm b/tools/ci/lints.dm
index 39c14e1bbc95..8f7bdc1f68d8 100644
--- a/tools/ci/lints.dm
+++ b/tools/ci/lints.dm
@@ -16,12 +16,19 @@
 #pragma PointlessParentCall error
 #pragma PointlessBuiltinCall error
 #pragma SuspiciousMatrixCall error
+#pragma FallbackBuiltinArgument error
+#pragma PointlessScopeOperator error
 #pragma MalformedRange error
 #pragma InvalidRange error
 #pragma InvalidSetStatement error
 #pragma InvalidOverride error
 #pragma DanglingVarType error
 #pragma MissingInterpolatedExpression error
+#pragma AmbiguousResourcePath error
+#pragma SuspiciousSwitchCase error
 
 //3000-3999
 #pragma EmptyBlock error
+#pragma EmptyProc error
+#pragma UnsafeClientAccess disabled
+#pragma AssignmentInConditional error